diff --git a/.gitignore b/.gitignore
index c749d68..5f9d397 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,11 @@
*.ipynb
*.o
*.so
+*.py
+*.sh
+*.txt
.vscode/
build/
-dist/
\ No newline at end of file
+dist/
+draft/
+perception-default/
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 62c9f9d..4666830 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,7 +5,7 @@ project(perception)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wconversion -Wno-unused-private-field -Wno-unused-variable -Wno-unused-parameter -fno-omit-frame-pointer -fsanitize=address")
set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_STATIC_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wconversion -Wno-unused-private-field -Wno-unused-variable -Wno-unused-parameter")
+# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wconversion -Wno-unused-private-field -Wno-unused-variable -Wno-unused-parameter")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(LIB_SOURCES src/colorspace.cpp src/CIEDE2000.cpp src/fitness.cpp)
diff --git a/python/cli.py b/python/cli.py
new file mode 100644
index 0000000..1282a3c
--- /dev/null
+++ b/python/cli.py
@@ -0,0 +1,111 @@
+from argparse import ArgumentParser, RawDescriptionHelpFormatter
+from getpass import getuser
+from textwrap import dedent
+from .profile import parse
+from .theme import update_context
+from .version import __version__
+
+
+def argparser():
+ ps = ArgumentParser(
+ prog='perception',
+ description=dedent("""
+ Generate optimized discernible color palette and themes settings
+
+ Example:
+ perception --temperature -1 --profile demo
+ perception --background 97 --profile vscode
+ """),
+ formatter_class=RawDescriptionHelpFormatter)
+
+ ps.add_argument(
+ '-n',
+ '--name',
+ default='default',
+ type=str,
+ help='theme name, default: \'default\'')
+ ps.add_argument(
+ '-f',
+ '--foreground',
+ '--Lf',
+ default=-1,
+ dest='Lf',
+ metavar='Lf',
+ type=float,
+ help='foreground luminocity between 0 < Lf < 100')
+ ps.add_argument(
+ '-b',
+ '--background',
+ '--Lb',
+ default=-1,
+ dest='Lb',
+ metavar='Lb',
+ type=float,
+ help='background luminocity 0 < Lb < 100')
+ ps.add_argument(
+ '-L',
+ '--palette-luminocity',
+ '--L3',
+ default=-1,
+ dest='L3',
+ metavar='L3',
+ type=float,
+ help='main palette luminocity 0 < L3 < 100')
+ ps.add_argument(
+ '--maxC',
+ default=-1,
+ metavar='maxC',
+ type=float,
+ help='maximal chroma > 0')
+ ps.add_argument(
+ '-T',
+ '--temperature',
+ default=0,
+ dest='dK',
+ metavar='dK',
+ type=float,
+ help='temperature hint, -1(warm) < dK < 1(cold)')
+ ps.add_argument(
+ '-p',
+ '--profile',
+ action='append',
+ default=[],
+ dest='profiles',
+ metavar='PROFILE',
+ help='profiles to be parsed')
+ ps.add_argument(
+ '--L2',
+ default=-1,
+ dest='L2',
+ metavar='L2',
+ type=float,
+ help='(advanced) 0 < L2 < 100')
+ ps.add_argument(
+ '--L1',
+ default=-1,
+ dest='L1',
+ metavar='L1',
+ type=float,
+ help='(advanced) 0 < L1 < 100')
+ ps.add_argument(
+ '--L0',
+ default=-1,
+ dest='L0',
+ metavar='L0',
+ type=float,
+ help='(advanced) 0 < L0 < 100')
+ ps.add_argument('-v', '--verbose', action='store_true', default=False)
+
+ return ps
+
+
+def main():
+ args = argparser().parse_args()
+ ctx = {
+ 'name': args.name,
+ 'version': __version__,
+ 'user': getuser(),
+ }
+
+ update_context(args, ctx)
+ parse(args, ctx)
diff --git a/python/data/demo/context.json.mustache b/python/data/demo/context.json.mustache
new file mode 100644
index 0000000..fcc8924
--- /dev/null
+++ b/python/data/demo/context.json.mustache
@@ -0,0 +1,46 @@
+{
+ "name": "{{name}}",
+ "version": "{{version}}",
+ "user": "{{user}}",
+ "ui-theme": "{{ui-theme}}",
+
+ // Foreground
+ "fg-hex": "{{fg-hex}}",
+
+ // Foreground Palette
+ "main-0-hex": "{{main-0-hex}}",
+ "main-1-hex": "{{main-1-hex}}",
+ "main-2-hex": "{{main-2-hex}}",
+ "main-3-hex": "{{main-3-hex}}",
+ "main-4-hex": "{{main-4-hex}}",
+ "main-5-hex": "{{main-5-hex}}",
+ "main-6-hex": "{{main-6-hex}}",
+
+ // Lines
+ "line-2-hex": "{{line-2-hex}}",
+ "line-1-hex": "{{line-1-hex}}",
+ "line-0-hex": "{{line-0-hex}}",
+
+ // Semantic Foregrounds
+ "red-2-hex": "{{red-2-hex}}",
+ "yellow-2-hex": "{{yellow-2-hex}}",
+ "green-2-hex": "{{green-2-hex}}",
+ "blue-2-hex": "{{blue-2-hex}}",
+
+ // Semantic Inversed Backgrounds
+ "red-1-hex": "{{red-1-hex}}",
+ "yellow-1-hex": "{{yellow-1-hex}}",
+ "green-1-hex": "{{green-1-hex}}",
+ "blue-1-hex": "{{blue-1-hex}}",
+
+ // Semantic Backgrounds/Inversed Foregrounds
+ "red-0-hex": "{{red-0-hex}}",
+ "yellow-0-hex": "{{yellow-0-hex}}",
+ "green-0-hex": "{{green-0-hex}}",
+ "blue-0-hex": "{{blue-0-hex}}",
+
+ // Backgrounds
+ "bg-2-hex": "{{bg-2-hex}}",
+ "bg-1-hex": "{{bg-1-hex}}",
+ "bg-0-hex": "{{bg-0-hex}}"
+}
\ No newline at end of file
diff --git a/python/data/demo/perception-{{name}}-demo.html.mustache b/python/data/demo/perception-{{name}}-demo.html.mustache
new file mode 100644
index 0000000..53e462d
--- /dev/null
+++ b/python/data/demo/perception-{{name}}-demo.html.mustache
@@ -0,0 +1,130 @@
+
+
+
+
+

+
+
+Foreground:
+fg-hex {{fg-hex}}
+
+Lines:
+line-2-hex {{line-2-hex}}
+line-1-hex {{line-1-hex}}
+line-0-hex {{line-0-hex}}
+
+Foreground Palette:
+main-0-hex {{main-0-hex}}
+main-1-hex {{main-1-hex}}
+main-2-hex {{main-2-hex}}
+main-3-hex {{main-3-hex}}
+main-4-hex {{main-4-hex}}
+main-5-hex {{main-5-hex}}
+main-6-hex {{main-6-hex}}
+
+Semantic Foregrounds:
+red-2-hex {{red-2-hex}}
+yellow-2-hex {{yellow-2-hex}}
+green-2-hex {{green-2-hex}}
+blue-2-hex {{blue-2-hex}}
+
+
+Semantic Inversed Backgrounds:
+red-1-hex {{red-1-hex}}
+yellow-1-hex {{yellow-1-hex}}
+green-1-hex {{green-1-hex}}
+blue-1-hex {{blue-1-hex}}
+
+Semantic Backgrounds/Inversed Foregrounds:
+red-0-hex {{red-0-hex}}
+yellow-0-hex {{yellow-0-hex}}
+green-0-hex {{green-0-hex}}
+blue-0-hex {{blue-0-hex}}
+
+Backgrounds:
+bg-2-hex {{bg-2-hex}}
+bg-1-hex {{bg-1-hex}}
+bg-0-hex {{bg-0-hex}}
+
+
+
+
\ No newline at end of file
diff --git a/python/data/demo/perception-{{name}}-thumb.svg.mustache b/python/data/demo/perception-{{name}}-thumb.svg.mustache
new file mode 100644
index 0000000..225a381
--- /dev/null
+++ b/python/data/demo/perception-{{name}}-thumb.svg.mustache
@@ -0,0 +1,70 @@
+
+
+
+
diff --git a/python/data/nvim/colors/perception-{{name}}.vim.mustache b/python/data/vim/colors/perception-{{name}}.vim.mustache
similarity index 100%
rename from python/data/nvim/colors/perception-{{name}}.vim.mustache
rename to python/data/vim/colors/perception-{{name}}.vim.mustache
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 bb13f42..f97068f 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
@@ -213,9 +213,9 @@
// Notification dialog colors
"notification.background": "#{{bg-1-hex}}",
"notification.foreground": "#{{line-2-hex}}",
- "notification.buttonBackground": "#{{blue-1-hex}}",
- "notification.buttonHoverBackground": "#{{blue-1-hex}}cc",
- "notification.buttonForeground": "#{{fg-hex}}",
+ "notification.buttonBackground": "#{{blue-2-hex}}",
+ "notification.buttonHoverBackground": "#{{blue-2-hex}}cc",
+ "notification.buttonForeground": "#{{bg-0-hex}}",
"notification.infoBackground": "#{{blue-2-hex}}",
"notification.infoForeground": "#{{blue-0-hex}}",
"notification.warningBackground": "#{{yellow-2-hex}}",
@@ -239,10 +239,10 @@
"terminal.ansiBrightBlack": "#{{fg-hex}}",
"terminal.ansiRed": "#{{main-0-hex}}",
"terminal.ansiBrightRed": "#{{main-0-hex}}",
- "terminal.ansiYellow": "#{{main-1-hex}}",
- "terminal.ansiBrightYellow": "#{{main-1-hex}}",
- "terminal.ansiGreen": "#{{main-2-hex}}",
- "terminal.ansiBrightGreen": "#{{main-2-hex}}",
+ "terminal.ansiYellow": "#{{main-2-hex}}",
+ "terminal.ansiBrightYellow": "#{{main-2-hex}}",
+ "terminal.ansiGreen": "#{{main-3-hex}}",
+ "terminal.ansiBrightGreen": "#{{main-3-hex}}",
"terminal.ansiCyan": "#{{main-4-hex}}",
"terminal.ansiBrightCyan": "#{{main-4-hex}}",
"terminal.ansiBlue": "#{{main-5-hex}}",
diff --git a/python/pystache_tree.py b/python/profile.py
similarity index 57%
rename from python/pystache_tree.py
rename to python/profile.py
index 54194da..fc8b24c 100644
--- a/python/pystache_tree.py
+++ b/python/profile.py
@@ -32,3 +32,30 @@ def recursive_render(src_path,
shutil.copyfile(src_path, dest_path)
if debug:
print(f'Copy file {dest_path}')
+
+
+# @param args.profiles
+# @param args.verbose
+def parse(args, ctx):
+ srcs = ['.', os.path.join(os.path.dirname(__file__), 'data')]
+ dest = f'./perception-{args.name}'
+ if not args.profiles:
+ args.profiles = ['demo']
+ print('Available builtin profiles:')
+ for d in srcs[1:]:
+ if not os.path.isdir(d):
+ continue
+ for n in os.listdir(d):
+ print(f' {n}')
+ for profile in args.profiles:
+ profile_srcs = [os.path.join(d, profile) for d in srcs]
+ found = False
+ for profile_src in profile_srcs:
+ if os.path.exists(profile_src):
+ found = True
+ break
+ if not found:
+ raise OSError(f'Cannot find profile \'{profile}\'')
+ profile_dest = os.path.join(dest, profile)
+ print(f'Parse profile \'{profile}\' into {profile_dest}')
+ recursive_render(profile_src, profile_dest, ctx, debug=args.verbose)
diff --git a/python/theme.py b/python/theme.py
index 7dcf909..d2c4319 100644
--- a/python/theme.py
+++ b/python/theme.py
@@ -1,10 +1,5 @@
-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
@@ -31,82 +26,13 @@ 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')),
- ('nvim', os.path.expanduser('~/.vim')),
- ('nvim', os.path.expanduser('~/.config/nvim')),
- ]
- 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()
-
+# @param args.Lf
+# @param args.Lb
+# @param args.L
+# @param args.maxC
+# @param args.dK
+# @param args.verbose
+def update_context(args, ctx):
if args.Lb < 0:
if args.Lf < 0:
args.Lf = 93
@@ -114,43 +40,45 @@ def main():
elif args.Lf < 0:
args.Lf = 100 - args.Lb
+ ctx['fg-hex'] = rgb_hex(LABtoRGB((tempered_gray(args.Lf, args.dK))))
+ ctx['ui-theme'] = 'vs-dark' if args.Lb < 50 else 'vs'
+
# 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)
+ if args.L3 < 0:
+ args.L3 = interpolate2D(0, 100, 45, 100, 0, 80, 60, 60, 60)(args.Lf,
+ args.Lb)
# 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)
+ if args.L2 < 0:
+ args.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)
+ if args.L1 < 0:
+ args.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.L0 < 0:
+ args.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}')
+ f'Init parameters: Lf={args.Lf} L3={args.L3} args.L2={args.L2} args.L1={args.L1} '
+ + f'args.L0={args.L0} Lb={args.Lb} dK={args.dK}')
sgn = 1 if args.Lf > args.Lb else -1
palette3 = perception(
7,
- L=L3,
+ L=args.L3,
maxC=args.maxC,
- fixed=[tempered_gray(L3, args.dK)],
+ fixed=[tempered_gray(args.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])):
+ for (name, hue), (level,
+ L) in product(SEMANTIC_HUE.items(),
+ enumerate([args.L0, args.L1, args.L2])):
ctx[f'{name}-{level}-hex'] = rgb_hex(
LABtoRGB(LCHtoLAB(maxChroma([L, 0, hue], maxC=args.maxC))))
@@ -161,8 +89,3 @@ def main():
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 1b2b064..6f284ba 100644
--- a/setup.py
+++ b/setup.py
@@ -1,13 +1,16 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
+from distutils.version import LooseVersion
+from setuptools import setup, Extension
+from setuptools.command.build_ext import build_ext
import glob
import multiprocessing
import os
+import platform
+import re
import subprocess
import sys
-from setuptools import setup, Extension
-from setuptools.command.build_ext import build_ext
class CMakeExtension(Extension):
@@ -17,6 +20,23 @@ def __init__(self, name, sourcedir=''):
class CMakeBuild(build_ext):
+ def run(self):
+ try:
+ out = subprocess.check_output(['cmake', '--version'])
+ except OSError:
+ raise RuntimeError(
+ "CMake must be installed to build the following extensions: " +
+ ", ".join(e.name for e in self.extensions))
+
+ if platform.system() == "Windows":
+ cmake_version = LooseVersion(
+ re.search(r'version\s*([\d.]+)', out.decode()).group(1))
+ if cmake_version < '3.1.0':
+ raise RuntimeError("CMake >= 3.1.0 is required on Windows")
+
+ for ext in self.extensions:
+ self.build_extension(ext)
+
def build_extension(self, ext):
extdir = os.path.abspath(
os.path.dirname(self.get_ext_fullpath(ext.name)))
@@ -28,8 +48,17 @@ def build_extension(self, ext):
cfg = 'Debug' if self.debug else 'Release'
build_args = ['--config', cfg]
- cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
- build_args += ['--', '-j{0}'.format(multiprocessing.cpu_count())]
+ if platform.system() == "Windows":
+ cmake_args += [
+ '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(
+ cfg.upper(), extdir)
+ ]
+ if sys.maxsize > 2**32:
+ cmake_args += ['-A', 'x64']
+ build_args += ['--', '/m']
+ else:
+ cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
+ build_args += ['--', '-j{0}'.format(multiprocessing.cpu_count())]
if not os.path.exists(self.build_temp):
os.makedirs(self.build_temp)
@@ -59,9 +88,7 @@ def build_extension(self, ext):
for p in glob.glob('python/data/**', recursive=True)
]
},
- entry_points={
- 'console_scripts': [
- 'perception-theme = perception.theme:main',
- ]
- },
+ entry_points={'console_scripts': [
+ 'perception = perception.cli:main',
+ ]},
cmdclass=dict(build_ext=CMakeBuild))