Skip to content

Commit

Permalink
CMY constraint and semantic colors
Browse files Browse the repository at this point in the history
  • Loading branch information
gywn committed Nov 27, 2017
1 parent fe991ce commit 201c5a6
Show file tree
Hide file tree
Showing 13 changed files with 422 additions and 297 deletions.
29 changes: 26 additions & 3 deletions include/color.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ struct LAB {
};
using LAB = struct LAB;

/** A color in LCH colorspace */
struct LCH {
double l;
double c;
double h;
};
using LCH = struct LCH;

/** A color in CIEXYZ colorspace */
struct XYZ {
double x;
Expand All @@ -33,6 +41,7 @@ struct RGB {
};
using RGB = struct RGB;

/** A color in CMY colorspace */
struct CMY {
double c;
double m;
Expand All @@ -59,17 +68,31 @@ using CMY = struct CMY;
*/
double CIEDE2000(const LAB &lab1, const LAB &lab2);

constexpr double deg2Rad(const double deg);
constexpr double rad2Deg(const double rad);
constexpr double deg2Rad(const double deg) { return (deg * (M_PI / 180.0)); }
constexpr double rad2Deg(const double rad) { return ((180.0 / M_PI) * rad); }

RGB XYZtoRGB(const XYZ &xyz);
XYZ LABtoXYZ(const LAB &lab);
RGB LABtoRGB(const LAB &lab);
CMY RGBtoCMY(const RGB &rgb);

inline RGB LABtoRGB(const LAB &lab) { return XYZtoRGB(LABtoXYZ(lab)); }

inline LCH LABtoLCH(const LAB &lab) {
double theta = atan2(lab.b, lab.a);
if (theta < 0)
theta += 2 * M_PI;
return LCH{lab.l, sqrt(lab.a * lab.a + lab.b * lab.b), color::rad2Deg(theta)};
}

inline LAB LCHtoLAB(const LCH &lch) {
const double theta = color::deg2Rad(lch.h);
return LAB{lch.l, lch.c * cos(theta), lch.c * sin(theta)};
}

} // namespace color

std::ostream &operator<<(std::ostream &s, const color::LAB &lab);
std::ostream &operator<<(std::ostream &s, const color::LCH &lch);
std::ostream &operator<<(std::ostream &s, const color::XYZ &xyz);
std::ostream &operator<<(std::ostream &s, const color::RGB &rgb);
std::ostream &operator<<(std::ostream &s, const color::CMY &rgb);
12 changes: 6 additions & 6 deletions include/constant.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ const double A31 = -0.03269638;
const double A32 = -0.18259425;
const double A33 = 1.4716528;

static const double r2 = 0.2; // cyan's absorbance of green
static const double r3 = 0.1; // cyan's absorbance of blue
static const double g1 = 0.0; // magenta's absorbance of red
static const double g3 = 0.1; // magenta's absorbance of blue
static const double b1 = 0; // yellow's absorbance of red
static const double b2 = 0.1; // yellow's absorbance of green
static const double r2 = 0.2; // cyan's absorbance of green
static const double r3 = 0.1; // cyan's absorbance of blue
static const double g1 = 0.0; // magenta's absorbance of red
static const double g3 = 0.1; // magenta's absorbance of blue
static const double b1 = 0; // yellow's absorbance of red
static const double b2 = 0.1; // yellow's absorbance of green

/* (1 - RGB) -> CMY */
const double B11 = 1 - b2 * g3;
Expand Down
8 changes: 6 additions & 2 deletions include/fitness.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
inline double offRange(double x, double a, double b);

double offRGB(const color::LAB &lab);
double offRGB(const color::LCH &lch);

double offChroma(const color::LAB &lab, double C);
double offChroma(const color::LCH &lch, double C);

/**
* @param lab a vector of color::LAB, inter-distances of lab[freeM:] are ignored
Expand All @@ -20,15 +22,15 @@ double offChroma(const color::LAB &lab, double C);
LexiProduct<double> fitnessFunc(const std::vector<color::LAB> &lab,
size_t M = 0, double maxC = -1.);

class PerceptionResult {
public:
struct PerceptionResult {
unsigned long flags;
double L;
double maxC;
std::vector<color::LAB> lab;
std::vector<color::RGB> rgb;
LexiProduct<double> fitness;
};
using PerceptionResult = struct PerceptionResult;

std::ostream &operator<<(std::ostream &os, const PerceptionResult &res);

Expand All @@ -43,3 +45,5 @@ std::ostream &operator<<(std::ostream &os, const PerceptionResult &res);
PerceptionResult perception(size_t M, double L = -1, double maxC = -1,
std::vector<color::LAB> const *fixed = nullptr,
bool quiet = false);

color::LCH maxChroma(const color::LCH &lch, double maxC = -1);
112 changes: 42 additions & 70 deletions python/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from argparse import ArgumentParser
import getpass
import os
from .native import perception, LABtoRGB
from .native import LABtoRGB, LABtoLCH, LCHtoLAB, maxChroma, perception
from .version import __version__
from .pystache_tree import recursive_render

Expand All @@ -14,13 +14,12 @@ def rgb_hex(rgb):
return ''.join([hex(x) for x in rgb])


def nearest(rgbs, pivot):
new = sorted(
rgbs,
key=
lambda rgb: (rgb[0] - pivot[0])**2 + (rgb[1] - pivot[1])**2 + (rgb[2] - pivot[2])**2
)
return new[0]
def interpolate2D(x1, y1, z1, x2, y2, z2, x3, y3, z3):
d = x2 * y1 - x3 * y1 - x1 * y2 + x3 * y2 + x1 * y3 - x2 * y3
c = x3 * y2 * z1 - x2 * y3 * z1 - x3 * y1 * z2 + x1 * y3 * z2 + x2 * y1 * z3 - x1 * y2 * z3
b = x3 * z1 + x1 * z2 - x3 * z2 - x1 * z3 + x2 * z3 - x2 * z1
a = y2 * z1 - y3 * z1 - y1 * z2 + y3 * z2 + y1 * z3 - y2 * z3
return lambda x, y: (c - y * b - x * a) / d


def argparser():
Expand Down Expand Up @@ -53,17 +52,15 @@ def argparser():
'--maxC',
type=float,
default=-1,
help='maximal chroma for main-palette, negative value for unconstrainted')
help=
'maximal chroma for main-palette, negative value for unconstrainted')
ps.add_argument(
'--sub-maxC',
type=float,
default=20,
help='maximal chroma for sub-palette, negative value for unconstrainted')
ps.add_argument(
'--mu',
type=float,
default=1.5,
help='mu')
help='maximal chroma for sub-palette, negative value for unconstrainted'
)
ps.add_argument('--mu', type=float, default=1.5, help='mu')
ps.add_argument('-D', '--debug', action='store_true', default=False)

return ps
Expand All @@ -72,85 +69,60 @@ def argparser():
def main():
args = argparser().parse_args()
if args.background < 0:
args.background = 10
if args.foreground < 0:
if args.foreground < 0:
args.foreground = 93
args.background = 100 - args.foreground
elif args.foreground < 0:
args.foreground = 100 - args.background
# if args.background < 50:
# args.foreground = min(100, args.background + 90)
# else:
# args.foreground = max(0, args.background - 90)
sgn = 1 if args.foreground > args.background else -1
if args.L < 0:
if args.foreground > 50:
args.L = (args.foreground * 2 + 50) / (2 + 1)
else:
args.L = (args.foreground * 0.1 + 50) / (0.1 + 1)
if args.maxC < 0:
pass
# x = args.L - args.background
# x1 = -50
# y1 = -70
# x2 = 80
# y2 = 50
# args.maxC = (x * (y1 - y2) - x2 * y1 + x1 * y2) / (x1 - x2)
if (args.debug):
from pprint import pprint
pprint(args)
L_main = args.L
L_sub = args.background + sgn * 5

# L3: text color, very close to foreground
L3 = interpolate2D(0, 100, 45, 100, 0, 80, 60, 60, 60)(
args.foreground, args.background) if args.L < 0 else args.L
# L2: UI line/background, large inter-distance, close to foreground
L2 = interpolate2D(0, 100, 50, 100, 0, 70, 60, 60, 60)(args.foreground,
args.background)
# L1: indicator color, large inter-distance, close to background
L1 = interpolate2D(0, 100, 70, 100, 0, 55, 60, 60, 60)(args.foreground,
args.background)
# L0: background color, very close to background
L0 = interpolate2D(0, 100, 90, 100, 0, 15, 60, 60, 60)(args.foreground,
args.background)

semantic_hue = {'red': 30, 'yellow': 80, 'green': 120, 'blue': 260}
palette_main = perception(
7,
L=L_main,
maxC=args.maxC,
fixed=[(L_main, 0, 0)],
quiet=not args.debug)
palette_sub = perception(
7,
L=L_sub,
maxC=args.sub_maxC,
fixed=[(L_sub, 0, 0)],
quiet=not args.debug)
7, L=L3, maxC=args.maxC, fixed=[(L3, 0, 0)], quiet=not args.debug)
context = {
'name': args.name,
'version': __version__,
'user': getpass.getuser(),
'ui-theme': 'vs-dark' if args.background < 50 else 'vs'
}

# legacy
for i, rgb in enumerate(palette_main['rgb']):
context[f'base{i + 8:02X}-hex'] = rgb_hex(rgb)
for i in range(7):
context[f'base{i:02X}-hex'] = rgb_hex(
LABtoRGB((args.background + i * sgn * 5, 0, 0)))
context['base07-hex'] = rgb_hex(LABtoRGB((args.foreground, 0, 0)))

sgn = 1 if args.foreground > args.background else -1
context['fg-hex'] = rgb_hex(LABtoRGB((args.foreground, 0, 0)))
# context['bg-hex'] = rgb_hex(LABtoRGB((args.background, 0, 0)))
for i, delta in enumerate([0, 5, 10]):
for i, delta in enumerate([0, 5, 12]):
context[f'bg-{i}-hex'] = rgb_hex(
LABtoRGB((args.background + sgn * delta, 0, 0)))
for i, delta in enumerate([15, 25, 50]):
for i, delta in enumerate([15, 25, 60]):
context[f'line-{i}-hex'] = rgb_hex(
LABtoRGB((args.background + sgn * delta, 0, 0)))
for i, rgb in enumerate(palette_main['rgb']):
context[f'main-{i}-hex'] = rgb_hex(rgb)
for i, rgb in enumerate(palette_sub['rgb']):
context[f'sub-{i}-hex'] = rgb_hex(rgb)
for name, rgb in [('red-hex', (1, 0, 0)), ('green-hex', (0, 0.5, 0)),
('blue-hex', (0, 0, 1)), ('yellow-hex', (0.5, 0.5, 0))]:
context['main-' + name] = rgb_hex(nearest(palette_main['rgb'], rgb))
context['sub-' + name] = rgb_hex(nearest(palette_sub['rgb'], rgb))
for name, hue in semantic_hue.items():
for level, L in [(0, L0), (1, L1), (2, L2)]:
context[f'{name}-{level}-hex'] = rgb_hex(
LABtoRGB(LCHtoLAB(maxChroma([L, 0, hue], maxC=args.maxC))))

if (args.debug):
from pprint import pprint
palette_main["fitness"] = palette_main["fitness"][0];
palette_sub["fitness"] = palette_sub["fitness"][0];
palette_main["fitness"] = palette_main["fitness"][0]
# palette_sub["fitness"] = palette_sub["fitness"][0];
pprint(palette_main)
pprint(palette_sub)
# pprint(palette_sub)
pprint(context)

recursive_render(
os.path.join(os.path.dirname(__file__), 'data/vscode'),
'/Users/kinker/.vscode/extensions', context)
os.path.expanduser('~/.vscode/extensions'), context)
Loading

0 comments on commit 201c5a6

Please sign in to comment.