diff --git a/python/__init__.py b/python/__init__.py index edb9b67..b0d2977 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -1,128 +1,2 @@ -from argparse import ArgumentParser -import getpass -import os from .native import LABtoRGB, LABtoLCH, LCHtoLAB, maxChroma, perception from .version import __version__ -from .pystache_tree import recursive_render - - -def hex(x): - return '{:02x}'.format(min(round(256 * max(0, x)), 255)).upper() - - -def rgb_hex(rgb): - return ''.join([hex(x) for x in rgb]) - - -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(): - ps = ArgumentParser() - - ps.add_argument( - '-n', - '--name', - type=str, - default='default', - help='theme name, default: \'default\'') - ps.add_argument( - '-f', - '--foreground', - type=float, - default=-1, - help='foreground luminocity between 0 and 100') - ps.add_argument( - '-b', - '--background', - type=float, - default=-1, - help='background luminocity between 0 and 100') - ps.add_argument( - '-L', - type=float, - default=-1, - help='main palette luminocity between 0 and 100') - ps.add_argument( - '--maxC', - type=float, - default=-1, - 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') - ps.add_argument('-D', '--debug', action='store_true', default=False) - - return ps - - -def main(): - args = argparser().parse_args() - if args.background < 0: - if args.foreground < 0: - args.foreground = 93 - args.background = 100 - args.foreground - elif args.foreground < 0: - args.foreground = 100 - args.background - if (args.debug): - from pprint import pprint - pprint(args) - # 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=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' - } - - sgn = 1 if args.foreground > args.background else -1 - context['fg-hex'] = rgb_hex(LABtoRGB((args.foreground, 0, 0))) - 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, 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 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]; - pprint(palette_main) - # pprint(palette_sub) - pprint(context) - - recursive_render( - os.path.join(os.path.dirname(__file__), 'data/vscode'), - os.path.expanduser('~/.vscode/extensions'), context) diff --git a/python/data/vscode/perception-theme-{{name}}/themes/perception-theme.json.mustache b/python/data/vscode/perception-theme-{{name}}/themes/perception-theme.json.mustache index e2cf059..bb13f42 100644 --- a/python/data/vscode/perception-theme-{{name}}/themes/perception-theme.json.mustache +++ b/python/data/vscode/perception-theme-{{name}}/themes/perception-theme.json.mustache @@ -230,7 +230,7 @@ // Quick picker // "pickerGroup.border": "#f00", - "pickerGroup.foreground": "#{{green-2-hex}}", + "pickerGroup.foreground": "#{{blue-2-hex}}", // Integrated terminal colors "terminal.background": "#{{bg-0-hex}}", diff --git a/python/pystache_tree.py b/python/pystache_tree.py index 91dbb6e..54194da 100644 --- a/python/pystache_tree.py +++ b/python/pystache_tree.py @@ -10,21 +10,25 @@ def recursive_render(src_path, dest_path, context, - renderer=Renderer(missing_tags='strict')): + renderer=Renderer(missing_tags='strict'), + debug=True): if (os.path.isdir(src_path)): os.makedirs(dest_path, exist_ok=True) - print(f'Enter directory {dest_path}') + if debug: + print(f'Enter directory {dest_path}') for name in os.listdir(src_path): child_src_path = os.path.join(src_path, name) child_dest_path = os.path.join(dest_path, renderer.render(name, context)) recursive_render(child_src_path, child_dest_path, context, - renderer) + renderer, debug) elif dest_path[-TEMPLATE_SUFFIX_LEN:] == TEMPLATE_SUFFIX: dest_path = dest_path[:-TEMPLATE_SUFFIX_LEN] with open(src_path, 'r') as src, open(dest_path, 'w') as dest: dest.write(renderer.render(src.read(), context)) - print(f'Parse file {dest_path}') + if debug: + print(f'Parse file {dest_path}') else: shutil.copyfile(src_path, dest_path) - print(f'Copy file {dest_path}') + if debug: + print(f'Copy file {dest_path}') diff --git a/python/theme.py b/python/theme.py new file mode 100644 index 0000000..c9fee28 --- /dev/null +++ b/python/theme.py @@ -0,0 +1,164 @@ +from argparse import ArgumentParser +from getpass import getuser +from itertools import product +import os +from .native import LABtoRGB, LCHtoLAB, maxChroma, perception +from .version import __version__ +from .pystache_tree import recursive_render + +D65_A = -1.65 / 100 # a component of D65 in LAB +D65_B = -19.33 / 100 # b component of D65 in LAB +SEMANTIC_HUE = {'red': 30, 'yellow': 80, 'green': 120, 'blue': 260} + + +def hex(x): + return '{:02x}'.format(min(round(256 * max(0, x)), 255)).upper() + + +def rgb_hex(rgb): + return ''.join([hex(x) for x in rgb]) + + +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 tempered_gray(L, dK): + return (L, L * D65_A * dK, L * D65_B * dK) + + +def argparser(): + ps = ArgumentParser() + + ps.add_argument( + '-n', + '--name', + default='default', + type=str, + help='theme name, default: \'default\'') + ps.add_argument( + '-f', + '--foreground', + default=-1, + dest='Lf', + metavar='Lf', + type=float, + help='foreground luminocity between 0 < Lf < 100') + ps.add_argument( + '-b', + '--background', + default=-1, + dest='Lb', + metavar='Lb', + type=float, + help='background luminocity 0 < Lb < 100') + ps.add_argument( + '-L', + default=-1, + type=float, + help='main palette luminocity 0 < L < 100') + ps.add_argument( + '--maxC', + default=-1, + dest='maxC', + type=float, + help='main palette maximal chroma > 0') + ps.add_argument( + '--temp', + default=0, + dest='dK', + metavar='dK', + type=float, + help='temperature hint, -1(warm) < dK < 1(cold)') + ps.add_argument( + '-i', + '--in-place', + action='store_true', + default=False, + dest='inplace', + help='update themes in place') + ps.add_argument('-v', '--verbose', action='store_true', default=False) + + return ps + + +def parse(args, ctx): + data_dir = os.path.join(os.path.dirname(__file__), 'data') + if args.inplace: + templates = [('vscode', os.path.expanduser('~/.vscode/extensions'))] + for profile, dest in templates: + print(f'Apply profile \'{profile}\'') + recursive_render( + os.path.join(data_dir, profile), dest, ctx, debug=args.verbose) + else: + dest = os.path.abspath(f'./perception-themes-{args.name}') + print(f'Generate themes in {dest}') + recursive_render(data_dir, dest, ctx, debug=args.verbose) + + +def main(): + args = argparser().parse_args() + + if args.Lb < 0: + if args.Lf < 0: + args.Lf = 93 + args.Lb = 100 - args.Lf + elif args.Lf < 0: + args.Lf = 100 - args.Lb + + # L3: text color, very close to Lf + L3 = (interpolate2D(0, 100, 45, 100, 0, 80, 60, 60, 60)(args.Lf, args.Lb) + if args.L < 0 else args.L) + # L2: UI line/background, large inter-distance, close to Lf + L2 = interpolate2D(0, 100, 50, 100, 0, 70, 60, 60, 60)(args.Lf, args.Lb) + # L1: indicator color, large inter-distance, close to Lb + L1 = interpolate2D(0, 100, 70, 100, 0, 55, 60, 60, 60)(args.Lf, args.Lb) + # L0: background color, very close to Lb + L0 = interpolate2D(0, 100, 90, 100, 0, 15, 60, 60, 60)(args.Lf, args.Lb) + + if (args.verbose): + print(f'Init parameters: Lf={args.Lf} L={L3} L2={L2} L1={L1} ' + + f'L0={L0} Lb={args.Lb} dK={args.dK}') + else: + print( + f'Init parameters: Lf={args.Lf} L={L3} Lb={args.Lb} dK={args.dK}') + + sgn = 1 if args.Lf > args.Lb else -1 + palette3 = perception( + 7, + L=L3, + maxC=args.maxC, + fixed=[tempered_gray(L3, args.dK)], + quiet=not args.verbose) + ctx = { + 'name': args.name, + 'version': __version__, + 'user': getuser(), + 'ui-theme': 'vs-dark' if args.Lb < 50 else 'vs', + 'fg-hex': rgb_hex(LABtoRGB((tempered_gray(args.Lf, args.dK)))) + } + + for i, rgb in enumerate(palette3['rgb']): + ctx[f'main-{i}-hex'] = rgb_hex(rgb) + + for (name, hue), (level, L) in product(SEMANTIC_HUE.items(), + enumerate([L0, L1, L2])): + ctx[f'{name}-{level}-hex'] = rgb_hex( + LABtoRGB(LCHtoLAB(maxChroma([L, 0, hue], maxC=args.maxC)))) + + for i, delta in enumerate([15, 25, 60]): + ctx[f'line-{i}-hex'] = rgb_hex( + LABtoRGB(tempered_gray(args.Lb + sgn * delta, args.dK))) + + for i, delta in enumerate([0, 5, 12]): + ctx[f'bg-{i}-hex'] = rgb_hex( + LABtoRGB(tempered_gray(args.Lb + sgn * delta, args.dK))) + + print('Generated palette: ' + ' '.join( + [f'#{rgb_hex(rgb)}' for rgb in palette3["rgb"]])) + + parse(args, ctx) diff --git a/setup.py b/setup.py index 52d2418..1b2b064 100644 --- a/setup.py +++ b/setup.py @@ -61,7 +61,7 @@ def build_extension(self, ext): }, entry_points={ 'console_scripts': [ - 'perception-theme = perception:main', + 'perception-theme = perception.theme:main', ] }, cmdclass=dict(build_ext=CMakeBuild))