From fe991ce5220e98867885e84e596d5a234bbd2150 Mon Sep 17 00:00:00 2001 From: Guangyang Wen Date: Sun, 26 Nov 2017 17:55:48 +0100 Subject: [PATCH] vscode support --- .gitignore | 4 +- CMakeLists.txt | 15 +- include/color.h | 9 + include/constant.h | 18 + include/fitness.h | 12 +- python/__init__.py | 156 ++++++++ .../package.json.mustache | 17 + .../themes/perception-theme.json.mustache | 349 ++++++++++++++++++ python/pystache_tree.py | 25 ++ python/version.py | 1 + setup.py | 22 +- src/colorspace.cpp | 15 + src/fitness.cpp | 45 ++- src/python.cpp | 36 +- test/testfitness2.cpp | 6 +- test/testperception.py | 6 + 16 files changed, 692 insertions(+), 44 deletions(-) create mode 100644 python/__init__.py create mode 100644 python/data/vscode/perception-theme-{{name}}/package.json.mustache create mode 100644 python/data/vscode/perception-theme-{{name}}/themes/perception-theme.json.mustache create mode 100644 python/pystache_tree.py create mode 100644 python/version.py create mode 100644 test/testperception.py diff --git a/.gitignore b/.gitignore index cb1161f..c749d68 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,10 @@ *.dat *.dll *.dylib +*.egg-info/ *.ipynb *.o *.so .vscode/ -build/ \ No newline at end of file +build/ +dist/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 1540452..62c9f9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,15 +12,14 @@ set(LIB_SOURCES src/colorspace.cpp src/CIEDE2000.cpp src/fitness.cpp) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") find_package(PythonLibsNew REQUIRED) # -DPYTHON_EXECUTABLE should be set -if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") - add_library(perception MODULE ${LIB_SOURCES} src/python.cpp) - target_link_libraries(perception ${PYTHON_LIBRARIES}) - target_include_directories(perception PRIVATE include ${PYTHON_INCLUDE_DIR}) - set_target_properties(perception PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}") - set_target_properties(perception PROPERTIES SUFFIX "${PYTHON_MODULE_EXTENSION}") +add_library(native MODULE ${LIB_SOURCES} src/python.cpp) +target_link_libraries(native ${PYTHON_LIBRARIES}) +target_include_directories(native PRIVATE include ${PYTHON_INCLUDE_DIR}) +set_target_properties(native PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}") +set_target_properties(native PROPERTIES SUFFIX "${PYTHON_MODULE_EXTENSION}") -else(NOT CMAKE_BUILD_TYPE MATCHES "Debug") +if(CMAKE_BUILD_TYPE MATCHES "Debug") add_library(perception SHARED ${LIB_SOURCES}) target_include_directories(perception PUBLIC include) @@ -42,4 +41,4 @@ else(NOT CMAKE_BUILD_TYPE MATCHES "Debug") add_executable(testfitness2 test/testfitness2.cpp) target_link_libraries(testfitness2 perception) -endif(NOT CMAKE_BUILD_TYPE MATCHES "Debug") +endif(CMAKE_BUILD_TYPE MATCHES "Debug") diff --git a/include/color.h b/include/color.h index daddc60..fdcbb32 100644 --- a/include/color.h +++ b/include/color.h @@ -33,6 +33,13 @@ struct RGB { }; using RGB = struct RGB; +struct CMY { + double c; + double m; + double y; +}; +using CMY = struct CMY; + /** * @brief * Obtain Delta-E 2000 value. @@ -58,9 +65,11 @@ constexpr double rad2Deg(const double rad); RGB XYZtoRGB(const XYZ &xyz); XYZ LABtoXYZ(const LAB &lab); RGB LABtoRGB(const LAB &lab); +CMY RGBtoCMY(const RGB &rgb); } // namespace color std::ostream &operator<<(std::ostream &s, const color::LAB &lab); 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); diff --git a/include/constant.h b/include/constant.h index 76c944a..454d9ed 100644 --- a/include/constant.h +++ b/include/constant.h @@ -16,4 +16,22 @@ const double A23 = -0.03294208; 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 + +/* (1 - RGB) -> CMY */ +const double B11 = 1 - b2 * g3; +const double B12 = -g1 + b1 * g3; +const double B13 = -b1 + b2 * g1; +const double B21 = -r2 + b2 * r3; +const double B22 = 1 - b1 * r3; +const double B23 = -b2 + b1 * r2; +const double B31 = g3 * r2 - r3; +const double B32 = -g3 + g1 * r3; +const double B33 = 1 - g1 * r2; } // namespace color \ No newline at end of file diff --git a/include/fitness.h b/include/fitness.h index c9fd8f7..c4f6ca4 100644 --- a/include/fitness.h +++ b/include/fitness.h @@ -9,18 +9,23 @@ inline double offRange(double x, double a, double b); double offRGB(const color::LAB &lab); +double offChroma(const color::LAB &lab, double C); + /** * @param lab a vector of color::LAB, inter-distances of lab[freeM:] are ignored * @param M 0 < M <= lab.size(), set to lab.size() if 0 + * @param maxC maximal chroma, < 0 to be ignored * @return a lexicographical product of distances */ LexiProduct fitnessFunc(const std::vector &lab, - size_t M = 0); + size_t M = 0, double maxC = -1.); class PerceptionResult { public: unsigned long flags; double L; + double maxC; + std::vector lab; std::vector rgb; LexiProduct fitness; }; @@ -29,11 +34,12 @@ std::ostream &operator<<(std::ostream &os, const PerceptionResult &res); /* * @param M numbers of free colors - * @param L luminocity, < 0 to be ignored + * @param L luminocity constraint, < 0 to be ignored + * @param maxC maximal chroma, < 0 to be ignored * @param fixed vector of fixed colors, optional * @param quiet don't write info to stdout * @return PerceptionResult */ -PerceptionResult perception(size_t M, double L = -1, +PerceptionResult perception(size_t M, double L = -1, double maxC = -1, std::vector const *fixed = nullptr, bool quiet = false); diff --git a/python/__init__.py b/python/__init__.py new file mode 100644 index 0000000..98304b2 --- /dev/null +++ b/python/__init__.py @@ -0,0 +1,156 @@ +from argparse import ArgumentParser +import getpass +import os +from .native import perception, LABtoRGB +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 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 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: + args.background = 10 + if 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 + + 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) + 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))) + + 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]): + context[f'bg-{i}-hex'] = rgb_hex( + LABtoRGB((args.background + sgn * delta, 0, 0))) + for i, delta in enumerate([15, 25, 50]): + 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)) + + 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'), + '/Users/kinker/.vscode/extensions', context) diff --git a/python/data/vscode/perception-theme-{{name}}/package.json.mustache b/python/data/vscode/perception-theme-{{name}}/package.json.mustache new file mode 100644 index 0000000..4f402c1 --- /dev/null +++ b/python/data/vscode/perception-theme-{{name}}/package.json.mustache @@ -0,0 +1,17 @@ +{ + "name": "perception-theme-{{name}}", + "version": "{{version}}", + "engines": { + "vscode": "^0.9.0" + }, + "publisher": "{{user}}", + "contributes": { + "themes": [ + { + "label": "Perception Colors Theme {{name}}", + "uiTheme": "{{ui-theme}}", + "path": "./themes/perception-theme.json" + } + ] + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..1902a56 --- /dev/null +++ b/python/data/vscode/perception-theme-{{name}}/themes/perception-theme.json.mustache @@ -0,0 +1,349 @@ +{ + "name": "Perception Colors Theme", + "type": "dark", + "colors": { + // Contrast colors + // "contrastActiveBorder": "#f00", //7 + // "contrastBorder": "#f00", //6 + + // Base colors + // "focusBorder": "#f00", //7 + "foreground": "#{{fg-hex}}", //5 + "widget.shadow": "#00000033", //0 + "selection.background": "#{{bg-1-hex}}", //2 + "errorForeground": "#{{main-0-hex}}", //8 + + // Button control + "button.background": "#{{main-1-hex}}", //1 + "button.foreground": "#{{main-5-hex}}", //5 + "button.hoverBackground": "#{{main-3-hex}}", //3 + + // Dropdown control + "dropdown.background": "#{{bg-0-hex}}", //0 + // "dropdown.border": "#f00", //2 + "dropdown.foreground": "#{{main-5-hex}}", //5 + + // Input control + "input.background": "#{{bg-0-hex}}", //0 + // "input.border": "#f00", //0 + "input.foreground": "#{{fg-hex}}", //5 + "input.placeholderForeground": "#{{line-2-hex}}", //3 + "inputOption.activeBorder": "#{{base09-hex}}", //9 + "inputValidation.errorBackground": "#{{main-0-hex}}", //8 + "inputValidation.errorBorder": "#{{main-0-hex}}", //8 + "inputValidation.infoBackground": "#{{main-5-hex}}", //D + "inputValidation.infoBorder": "#{{main-5-hex}}", //D + "inputValidation.warningBackground": "#{{main-2-hex}}", //A + "inputValidation.warningBorder": "#{{main-2-hex}}", //A + + // Scroll bar control + "scrollbar.shadow": "#00000033", //1 + "scrollbarSlider.activeBackground": "#{{main-4-hex}}44", //4 + "scrollbarSlider.background": "#{{fg-hex}}11", //2 + "scrollbarSlider.hoverBackground": "#{{main-4-hex}}22", //3 + + // Badge + "badge.background": "#{{main-2-hex}}", //0 + "badge.foreground": "#{{main-5-hex}}", //5 + + // Progress bar + "progressBar.background": "#{{main-3-hex}}", //3 + + // Lists and trees + "list.activeSelectionBackground": "#{{bg-2-hex}}", //2 + "list.activeSelectionForeground": "#{{main-5-hex}}", //5 + "list.dropBackground": "#{{sub-6-hex}}", //7 + "list.focusBackground": "#{{main-1-hex}}", //2 + "list.focusForeground": "#{{main-5-hex}}", //5 + "list.highlightForeground": "#{{main-3-hex}}", //7 + "list.hoverBackground": "#{{bg-2-hex}}", //3 + "list.hoverForeground": "#{{main-5-hex}}", //5 + "list.inactiveSelectionBackground": "#{{bg-2-hex}}", //2 + "list.inactiveSelectionForeground": "#{{main-5-hex}}", //5 + + // Activity bar + "activityBar.background": "#{{bg-0-hex}}", //0 + "activityBar.border": "#{{line-0-hex}}", //0 + "activityBar.dropBackground": "#{{sub-6-hex}}", //7 + "activityBar.foreground": "#{{fg-hex}}", //5 + "activityBarBadge.background": "#{{main-5-hex}}", //D + "activityBarBadge.foreground": "#{{bg-0-hex}}", //7 + + // Side bar + "sideBar.background": "#{{bg-1-hex}}", //1 + "sideBar.border": "#{{line-0-hex}}", //1 + "sideBar.foreground": "#{{line-2-hex}}", //5 + "sideBarSectionHeader.background": "#{{bg-0-hex}}", //3 + "sideBarSectionHeader.foreground": "#{{line-2-hex}}", //5 + "sideBarTitle.foreground": "#{{line-2-hex}}", //5 + + // Editor groups and tabs + "editorGroup.background": "#{{main-4-hex}}", //0 + // "editorGroup.border": "#f00", + "editorGroup.dropBackground": "#{{bg-1-hex}}", //2 + "editorGroupHeader.noTabsBackground": "#{{bg-1-hex}}", //1 + "editorGroupHeader.tabsBackground": "#{{bg-1-hex}}", //1 + // "editorGroupHeader.tabsBorder": "#f00", + "tab.activeBackground": "#{{bg-0-hex}}", //0 + "tab.activeForeground": "#{{fg-hex}}", //5 + "tab.border": "#{{line-0-hex}}", + "tab.inactiveBackground": "#{{bg-1-hex}}", //1 + "tab.inactiveForeground": "#{{line-2-hex}}", + "tab.unfocusedActiveForeground": "#{{main-4-hex}}", //4 + "tab.unfocusedInactiveForeground": "#{{main-4-hex}}", //4 + + // Editor colors + "editor.background": "#{{bg-0-hex}}", //0 + "editor.foreground": "#{{fg-hex}}", //5 + "editorLineNumber.foreground": "#{{line-1-hex}}", //3 + "editorCursor.foreground": "#{{main-5-hex}}", //5 + "editor.selectionBackground": "#{{sub-6-hex}}", //2 + "editor.selectionHighlightBackground": "#{{main-2-hex}}", //1 + "editor.inactiveSelectionBackground": "#{{bg-1-hex}}", //2 + "editor.wordHighlightBackground": "#{{main-2-hex}}", //2 + "editor.wordHighlightStrongBackground": "#{{main-3-hex}}", //3 + "editor.findMatchBackground": "#{{main-2-hex}}6f", //A + "editor.findMatchHighlightBackground": "#{{sub-2-hex}}", //9 + "editor.findRangeHighlightBackground": "#f0f", + "editor.hoverHighlightBackground": "#{{main-2-hex}}", //2 + "editor.lineHighlightBackground": "#{{bg-1-hex}}", //1 + // "editor.lineHighlightBorder": "#f00", + "editorLink.activeForeground": "#{{main-5-hex}}", //D + "editor.rangeHighlightBackground": "#{{sub-2-hex}}", //1 + "editorWhitespace.foreground": "#{{main-3-hex}}", //3 + "editorIndentGuide.background": "#{{main-3-hex}}", //3 + "editorRuler.foreground": "#{{main-3-hex}}", //3 + "editorCodeLens.foreground": "#{{main-2-hex}}", //2 + "editorBracketMatch.background": "#{{sub-4-hex}}", //2 + // "editorBracketMatch.border": "#f00", + // "editorOverviewRuler.border": "#f00", + // "editorError.border": "#f0f", + "editorError.foreground": "#{{main-0-hex}}", //8 + "editorWarning.foreground": "#{{main-0-hex}}", //8 + // "editorWarning.border": "#f00", + "editorGutter.addedBackground": "#{{sub-green-hex}}", //B + "editorGutter.background": "#{{bg-0-hex}}", //0 + "editorGutter.deletedBackground": "#{{sub-red-hex}}", //8 + "editorGutter.modifiedBackground": "#{{sub-yellow-hex}}", //E + + // Diff editor colors + "diffEditor.insertedTextBackground": "#{{base0B-hex}}20", + // "diffEditor.insertedTextBorder": "#f00", + "diffEditor.removedTextBackground": "#{{main-0-hex}}20", + // "diffEditor.removedTextBorder": "#f00", + + // Editor widget colors + "editorWidget.background": "#{{main-3-hex}}", //0 + // "editorWidget.border": "#f00", + "editorSuggestWidget.background": "#{{main-4-hex}}", //1 + // "editorSuggestWidget.border": "#f00", + "editorSuggestWidget.foreground": "#{{main-5-hex}}", //5 + "editorSuggestWidget.highlightForeground": "#f0f", + "editorSuggestWidget.selectedBackground": "#{{main-2-hex}}", //2 + "editorHoverWidget.background": "#{{main-0-hex}}", //1 + // "editorHoverWidget.border": "#f00", + "debugExceptionWidget.background": "#{{main-5-hex}}", //1 + // "debugExceptionWidget.border": "#f00", + "editorMarkerNavigation.background": "#{{main-6-hex}}", //1 + "editorMarkerNavigationError.background": "#{{main-0-hex}}", //8 + "editorMarkerNavigationWarning.background": "#{{main-2-hex}}", //A + + // Peek view colors + // "peekView.border": "#f00", + "peekViewEditor.background": "#{{main-3-hex}}", //1 + "peekViewEditor.matchHighlightBackground": "#{{base09-hex}}6f", //9 + "peekViewEditorGutter.background": "#{{main-0-hex}}", //1 + "peekViewResult.background": "#{{main-1-hex}}", //0 + "peekViewResult.fileForeground": "#{{main-5-hex}}", //5 + "peekViewResult.lineForeground": "#{{main-3-hex}}", //3 + "peekViewResult.matchHighlightBackground": "#{{base09-hex}}6f", //9 + "peekViewResult.selectionBackground": "#{{main-2-hex}}", //2 + "peekViewResult.selectionForeground": "#{{main-5-hex}}", //5 + "peekViewTitle.background": "#{{main-2-hex}}", //2 + "peekViewTitleDescription.foreground": "#{{main-3-hex}}", //3 + "peekViewTitleLabel.foreground": "#{{main-5-hex}}", //5 + + // Merge conflicts + // "merge.border": "#f00", + "merge.currentContentBackground": "#{{main-5-hex}}40", //D + "merge.currentHeaderBackground": "#{{main-5-hex}}40", //D + "merge.incomingContentBackground": "#{{base0B-hex}}60", //B + "merge.incomingHeaderBackground": "#{{base0B-hex}}60", //B + "editorOverviewRuler.currentContentForeground": "#{{main-5-hex}}", //D + "editorOverviewRuler.incomingContentForeground": "#{{base0B-hex}}", //B + + // Panel colors + "panel.background": "#{{bg-0-hex}}", //0 + "panel.border": "#{{line-0-hex}}", + "panelTitle.activeBorder": "#{{main-5-hex}}", + "panelTitle.activeForeground": "#{{main-5-hex}}", //5 + "panelTitle.inactiveForeground": "#{{line-2-hex}}", //3 + + // Status bar colors + "statusBar.background": "#{{bg-2-hex}}", //D + // "statusBar.border": "#{{line-0-hex}}", + "statusBar.debuggingBackground": "#{{main-2-hex}}", //9 + "statusBar.debuggingForeground": "#{{bg-0-hex}}", //7 + "statusBar.foreground": "#{{fg-hex}}", //7 + "statusBar.noFolderBackground": "#{{main-6-hex}}", //E + "statusBar.noFolderForeground": "#{{main-3-hex}}", //7 + "statusBarItem.activeBackground": "#{{main-3-hex}}", //3 + "statusBarItem.hoverBackground": "#{{line-0-hex}}", //2 + "statusBarItem.prominentBackground": "#f0f", + "statusBarItem.prominentHoverBackground": "#f00", + + // Title bar colors (macOS) + "titleBar.activeBackground": "#{{bg-2-hex}}", //0 + "titleBar.activeForelround": "#{{line-2-hex}}", //5 + "titleBar.inactiveBackground": "#{{bg-2-hex}}", //1 + "titleBar.inactiveForeground": "#{{line-0-hex}}", //3 + + // Notification dialog colors + "notification.background": "#{{main-2-hex}}", //2 + "notification.foreground": "#{{main-5-hex}}", //5 + + // Extensions + "extensionButton.prominentBackground": "#{{base0B-hex}}", //B + "extensionButton.prominentForeground": "#{{main-3-hex}}", //7 + "extensionButton.prominentHoverBackground": "#{{main-2-hex}}", //2 + + // Quick picker + // "pickerGroup.border": "#f00", + "pickerGroup.foreground": "#{{main-3-hex}}", //3 + + // Integrated terminal colors + "terminal.background": "#{{main-4-hex}}", //0 + "terminal.foreground": "#{{main-5-hex}}", //5 + "terminal.ansiBlack": "#{{main-2-hex}}", //2 + "terminal.ansiBrightBlack": "#{{main-3-hex}}", //3 + "terminal.ansiRed": "#{{main-0-hex}}", //8 + "terminal.ansiBrightRed": "#{{main-0-hex}}", //8 + "terminal.ansiYellow": "#{{base09-hex}}", //9 + "terminal.ansiBrightYellow": "#{{main-2-hex}}", //A + "terminal.ansiGreen": "#{{base0B-hex}}", //B + "terminal.ansiBrightGreen": "#{{base0B-hex}}", //B + "terminal.ansiCyan": "#{{base0C-hex}}", //C + "terminal.ansiBrightCyan": "#{{base0C-hex}}", //C + "terminal.ansiBlue": "#{{main-5-hex}}", //D + "terminal.ansiBrightBlue": "#{{main-5-hex}}", //D + "terminal.ansiMagenta": "#{{main-6-hex}}", //E + "terminal.ansiBrightMagenta": "#{{main-6-hex}}", //E + "terminal.ansiWhite": "#{{main-6-hex}}", //6 + "terminal.ansiBrightWhite": "#{{main-3-hex}}", //7 + + // Debug + "debugToolBar.background": "#{{main-1-hex}}", //1 + + // Welcome page + "welcomePage.buttonBackground": "#{{main-1-hex}}", //1 + "welcomePage.buttonHoverBackground": "#{{main-2-hex}}", //2 + "walkThrough.embeddedEditorBackground": "#{{main-5-hex}}", //0 + + // Unknown + "descriptionForeground": "#{{main-3-hex}}", //3 + "textBlockQuote.background": "#{{main-1-hex}}", //1 + "textBlockQuote.border": "#{{main-5-hex}}", //D + "textCodeBlock.background": "#{{main-6-hex}}", //0 + "textLink.activeForeground": "#{{base0C-hex}}", //C + "textLink.foreground": "#{{main-5-hex}}", //D + "textPreformat.foreground": "#{{main-5-hex}}", //D + "textSeparator.foreground": "#f0f" + }, + "tokenColors": [ + { + "name": "Section", + "scope": [ + "punctuation.section", + "punctuation.separator" + ], + "settings": { + "foreground": "#{{main-4-hex}}" //3 + } + }, + { + "name": "Keyword", + "scope": [ + "keyword" + ], + "settings": { + "foreground": "#{{main-5-hex}}" //3 + } + }, + { + "name": "Constant", + "scope": [ + "constant", + "support.constant" + ], + "settings": { + "foreground": "#{{main-2-hex}}" //3 + } + }, + { + "name": "String", + "scope": [ + "string", + "punctuation.definition.string" + ], + "settings": { + "foreground": "#{{main-3-hex}}" //3 + } + }, + { + "name": "Entity", + "scope": [ + "entity", + "support.function", + "meta.function", + "meta.at-rule", + "meta.definition" + ], + "settings": { + "foreground": "#{{main-0-hex}}" //3 + } + }, + { + "name": "Variable", + "scope": [ + "variable", + "support.variable" + ], + "settings": { + "foreground": "#{{main-1-hex}}" //3 + } + }, + { + "name": "Storage & Operator", + "scope": [ + "keyword.operator", // different from parent rule 'keyword' + "storage", + "support.type", + "support.class", + "meta.type" + ], + "settings": { + "foreground": "#{{main-6-hex}}" //3 + } + }, + { + "name": "Comment", + "scope": [ + "comment", + "punctuation.definition.comment" + ], + "settings": { + "fontStyle": "italic", + "foreground": "#{{line-1-hex}}" //3 + } + }, + { + "name": "Invalid", + "scope": [ + "invalid" + ], + "settings": { + "foreground": "#{{main-0-hex}}" //3 + } + } + ] +} diff --git a/python/pystache_tree.py b/python/pystache_tree.py new file mode 100644 index 0000000..9c9fca3 --- /dev/null +++ b/python/pystache_tree.py @@ -0,0 +1,25 @@ +import os +import shutil +from pystache import Renderer +from pystache.defaults import TEMPLATE_EXTENSION + +TEMPLATE_SUFFIX = os.extsep + TEMPLATE_EXTENSION +TEMPLATE_SUFFIX_LEN = len(TEMPLATE_SUFFIX) + + +def recursive_render(src_path, dest_path, context, renderer=Renderer(missing_tags='strict')): + if (os.path.isdir(src_path)): + os.makedirs(dest_path, exist_ok=True) + 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) + 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}') + else: + shutil.copyfile(src_path, dest_path) + print(f'Copy file {dest_path}') diff --git a/python/version.py b/python/version.py new file mode 100644 index 0000000..b8023d8 --- /dev/null +++ b/python/version.py @@ -0,0 +1 @@ +__version__ = '0.0.1' diff --git a/setup.py b/setup.py index e0b822e..52d2418 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import glob import multiprocessing import os import subprocess @@ -41,9 +42,26 @@ def build_extension(self, ext): ['cmake', '--build', '.'] + build_args, cwd=self.build_temp) +with open('python/version.py') as f: + exec(f.read()) + setup( name='perception', - version='0.0.1', + version=__version__, # noqa: F821 description='Perception colors model', - ext_modules=[CMakeExtension('perception')], + install_requires=['pystache'], + packages=['perception'], + package_dir={'perception': 'python'}, + ext_modules=[CMakeExtension('perception.native')], + package_data={ + 'perception': [ + os.path.relpath(p, 'python') + for p in glob.glob('python/data/**', recursive=True) + ] + }, + entry_points={ + 'console_scripts': [ + 'perception-theme = perception:main', + ] + }, cmdclass=dict(build_ext=CMakeBuild)) diff --git a/src/colorspace.cpp b/src/colorspace.cpp index ed37572..3d16240 100644 --- a/src/colorspace.cpp +++ b/src/colorspace.cpp @@ -42,6 +42,17 @@ XYZ LABtoXYZ(const LAB &lab) { RGB LABtoRGB(const LAB &lab) { return XYZtoRGB(LABtoXYZ(lab)); } +CMY RGBtoCMY(const RGB &rgb) { + const double cr = 1 - rgb.r; + const double cg = 1 - rgb.g; + const double cb = 1 - rgb.b; + double c = cr * B11 + cg * B12 + cb * B13; + double m = cr * B21 + cg * B22 + cb * B23; + double y = cr * B31 + cg * B32 + cb * B33; + + return CMY{c, m, y}; +} + } // namespace color std::ostream &operator<<(std::ostream &s, const color::LAB &lab) { @@ -55,3 +66,7 @@ std::ostream &operator<<(std::ostream &s, const color::XYZ &xyz) { std::ostream &operator<<(std::ostream &s, const color::RGB &rgb) { return (s << "RGB(" << rgb.r << "," << rgb.g << "," << rgb.b << ")"); } + +std::ostream &operator<<(std::ostream &s, const color::CMY &cmy) { + return (s << "CMY(" << cmy.c << "," << cmy.m << "," << cmy.y << ")"); +} diff --git a/src/fitness.cpp b/src/fitness.cpp index 42c9bee..9bf4a14 100644 --- a/src/fitness.cpp +++ b/src/fitness.cpp @@ -15,13 +15,23 @@ inline double offRange(double x, double a, double b) { return 0; } -double offRGB(const color::LAB &lab) { +inline double offRGB(const color::LAB &lab) { const color::RGB rgb{color::LABtoRGB(lab)}; const auto dr = offRange(rgb.r, 0, 1); const auto dg = offRange(rgb.g, 0, 1); const auto db = offRange(rgb.b, 0, 1); + const color::CMY cmy{color::RGBtoCMY(rgb)}; + const auto dc = offRange(cmy.c, 0, std::numeric_limits::max()); + const auto dm = offRange(cmy.m, 0, std::numeric_limits::max()); + const auto dy = offRange(cmy.y, 0, std::numeric_limits::max()); - return std::sqrt(dr * dr + dg * dg + db * db); + return std::sqrt(dr * dr + dg * dg + db * db + dc * dc + dm * dm + dy * dy); +} + +inline double offChroma(const color::LAB &lab, double C) { + if (C < 0) + return 0; + return offRange(std::sqrt(lab.a * lab.a + lab.b * lab.b), 0, C); } class Combination { @@ -30,14 +40,15 @@ class Combination { size_t j; }; -LexiProduct fitnessFunc(const std::vector &lab, size_t M) { +LexiProduct fitnessFunc(const std::vector &lab, size_t M, + double maxC) { size_t totalM = lab.size(); std::vector penalty(totalM); if (M == 0) M = totalM; for (size_t i = 0; i < M; ++i) // 300 is the approx. diameter of ab-plane - penalty[i] = offRGB(lab[i]) * 300 * totalM; + penalty[i] = (offRGB(lab[i]) * 300 + offChroma(lab[i], maxC)) * totalM; // number of combinations between free/fixed colors size_t K = (M * (2 * totalM - M - 1)) / 2; @@ -67,10 +78,11 @@ LexiProduct fitnessFunc(const std::vector &lab, size_t M) { for (size_t i = 0; i < K; ++i) { const auto ref = cached_ref[i]; const auto &combi = ref_combi[ref]; + const auto DE = color::CIEDE2000(lab[combi.i], lab[combi.j]); + // const double penaltyDE = (maxDE > 0 && DE > maxDE ? totalM * (DE - maxDE) + // : 0); fitness_ref[i] = std::pair( - penalty[combi.i] + penalty[combi.j] - - color::CIEDE2000(lab[combi.i], lab[combi.j]), - ref); + penalty[combi.i] + penalty[combi.j] /* + penaltyDE */ - DE, ref); } std::sort( fitness_ref.begin(), fitness_ref.end(), @@ -102,7 +114,7 @@ std::ostream &operator<<(std::ostream &os, const PerceptionResult &res) { return os; } -PerceptionResult perception(size_t M, double L, +PerceptionResult perception(size_t M, double L, double maxC, std::vector const *fixed, bool quiet) { bool freeL = L < 0; bool noFixed = fixed == nullptr || fixed->size() == 0; @@ -120,7 +132,7 @@ PerceptionResult perception(size_t M, double L, return accu + c.l; }) / (double)fixedM; - std::vector stddev(N, std::min(100. - initL, initL)); + std::vector stddev(N, std::max(std::min(100. - initL, initL), 1.)); if (freeL) { xstart[2 * M] = initL; stddev[2 * M] = 100.; @@ -128,7 +140,7 @@ PerceptionResult perception(size_t M, double L, if (!quiet) std::cout << "freeM=" << M << ", fixedM=" << fixedM << ", initL=" << initL - << std::endl; + << ", maxC=" << maxC << std::endl; Parameters> parameters; parameters.lambda = (int)(300. * log(N)); /* 100x default */ @@ -150,7 +162,7 @@ PerceptionResult perception(size_t M, double L, pop = evo.samplePopulation(); // Do not change content of pop for (size_t i = 0; i < parameters.lambda; ++i) { fill_lab(pop[i].x, freeL ? pop[i].x[2 * M] : L, lab, M); - populationFitness[i] = fitnessFunc(lab, M); + populationFitness[i] = fitnessFunc(lab, M, maxC); } evo.updateDistribution(populationFitness); } @@ -169,9 +181,14 @@ PerceptionResult perception(size_t M, double L, std::cout << "Stop: flags " << flags << std::endl << evo.getStopMessage(); std::vector rgb(M); - for (size_t i = 0; i < M; ++i) + double finalMaxC = 0; + for (size_t i = 0; i < M; ++i) { + finalMaxC = + std::max(finalMaxC, sqrt(lab[i].a * lab[i].a + lab[i].b * lab[i].b)); rgb[i] = color::LABtoRGB(lab[i]); + } - return PerceptionResult{flags, freeL ? xfinal[2 * M] : L, std::move(rgb), - std::move(fitness)}; + return PerceptionResult{flags, freeL ? xfinal[2 * M] : L, + finalMaxC, std::move(lab), + std::move(rgb), std::move(fitness)}; } \ No newline at end of file diff --git a/src/python.cpp b/src/python.cpp index 37dbec5..04cbc69 100644 --- a/src/python.cpp +++ b/src/python.cpp @@ -39,15 +39,17 @@ static PyObject *pyperception(PyObject *self, PyObject *args, PyObject *kw) { // color::LAB fg, bg; unsigned long M; double L = -1; + double maxC = -1; PyObject *fixed_pyo = nullptr; bool quiet = false; static char kw1[] = "M"; static char kw2[] = "L"; - static char kw3[] = "fixed"; - static char kw4[] = "quiet"; - static char *kwlist[] = {kw1, kw2, kw3, kw4}; - if (!PyArg_ParseTupleAndKeywords(args, kw, "k|dOp", kwlist, &M, &L, + static char kw3[] = "maxC"; + static char kw4[] = "fixed"; + static char kw5[] = "quiet"; + static char *kwlist[] = {kw1, kw2, kw3, kw4, kw5, nullptr}; + if (!PyArg_ParseTupleAndKeywords(args, kw, "k|ddOp", kwlist, &M, &L, &maxC, &fixed_pyo, &quiet)) return nullptr; @@ -76,13 +78,17 @@ static PyObject *pyperception(PyObject *self, PyObject *args, PyObject *kw) { return nullptr; } } - if (!quiet) { - for (const auto &lab : fixed) - std::cout << lab << " "; - std::cout << std::endl; - } - const auto result = perception(M, L, &fixed, quiet); + // if (!quiet) { + // for (const auto &lab : fixed) + // std::cout << lab << " "; + // std::cout << std::endl; + // } + + const auto result = perception(M, L, maxC, &fixed, quiet); + PyObj lab = PyObj(PyList_New(0)); + for (const auto &c : result.lab) + PyList_Append(lab.ptr, PyObj(Py_BuildValue("(ddd)", c.l, c.a, c.b)).ptr); PyObj rgb = PyObj(PyList_New(0)); for (const auto &c : result.rgb) PyList_Append(rgb.ptr, PyObj(Py_BuildValue("(ddd)", c.r, c.g, c.b)).ptr); @@ -90,8 +96,9 @@ static PyObject *pyperception(PyObject *self, PyObject *args, PyObject *kw) { for (const auto d : result.fitness.prd) PyList_Append(fitness.ptr, PyObj(PyFloat_FromDouble(d)).ptr); - return Py_BuildValue("{sksdsOsO}", "flags", result.flags, "L", result.L, - "rgb", rgb.ptr, "fitness", fitness.ptr); + return Py_BuildValue("{sksdsdsOsOsO}", "flags", result.flags, "L", result.L, + "maxC", result.maxC, "lab", lab.ptr, "rgb", rgb.ptr, + "fitness", fitness.ptr); } static PyMethodDef methods[] = { @@ -100,7 +107,8 @@ static PyMethodDef methods[] = { "--------------------------------------\n" "\n" "@param M number of colors\n" - "@param L=-1 luminocity, < 0 for optimized value\n" + "@param L=-1 luminocity, < 0 for no constraint\n" + "@param maxC=-1 maximal distance, < 0 for no constraint\n" "@param fixed=None iterable of (l, a, b)\n" "@param quiet=False print messages to stdout"}, {"LABtoRGB", LABtoRGB, METH_VARARGS, @@ -114,4 +122,4 @@ static struct PyModuleDef module = { PyModuleDef_HEAD_INIT, "perception", "Python interface for perception colors library", -1, methods}; -PyMODINIT_FUNC PyInit_perception(void) { return PyModule_Create(&module); } \ No newline at end of file +PyMODINIT_FUNC PyInit_native(void) { return PyModule_Create(&module); } \ No newline at end of file diff --git a/test/testfitness2.cpp b/test/testfitness2.cpp index a685889..7c3ab5c 100644 --- a/test/testfitness2.cpp +++ b/test/testfitness2.cpp @@ -15,8 +15,10 @@ unsigned compress(double x, unsigned max = 256) { int main(int argc, char *argv[]) { const size_t M = 7; const std::vector fixed{{0, 0, 0}, {90, 0, 0}}; - const auto result = perception(M, -1, &fixed); - std::cout << result << std::endl; + for (const auto L : std::vector{-1, 30, 60}) { + const auto result = perception(M, L, 100, &fixed); + std::cout << result << std::endl; + } // Write a HTML demo // const color::RGB frgb(color::LABtoRGB(foreground)); diff --git a/test/testperception.py b/test/testperception.py new file mode 100644 index 0000000..07da10c --- /dev/null +++ b/test/testperception.py @@ -0,0 +1,6 @@ +#! /usr/bin/env python3 + +from perception import perception + +perception(7, L=90) +perception(7, L=80) \ No newline at end of file