diff --git a/monobiome/cli/scheme.py b/monobiome/cli/scheme.py index 4080eb9..7e3e652 100644 --- a/monobiome/cli/scheme.py +++ b/monobiome/cli/scheme.py @@ -54,6 +54,7 @@ def register_parser(subparsers: _SubparserType) -> None: # particularly good measure of perceptual distinction, so we'd prefer the # former. parser.add_argument( + "-l", "--l-base", type=int, default=20, @@ -82,7 +83,7 @@ def register_parser(subparsers: _SubparserType) -> None: parser.add_argument( "--term-fg-gap", type=int, - default=60, + default=65, help="Terminal foreground lightness gap (default: 60)", ) diff --git a/monobiome/constants.py b/monobiome/constants.py index 317b897..3742771 100644 --- a/monobiome/constants.py +++ b/monobiome/constants.py @@ -16,6 +16,8 @@ L_max: int = parameters.get("L_max", 98) L_step: int = parameters.get("L_step", 5) L_points: list[int] = list(range(L_min, L_max+1)) + +# L-space just affects accuracy of chroma max L_space = np.arange(0, 100 + L_step, L_step) monotone_C_map = parameters.get("monotone_C_map", {}) diff --git a/monobiome/scheme.py b/monobiome/scheme.py index 1a94174..068ba93 100644 --- a/monobiome/scheme.py +++ b/monobiome/scheme.py @@ -98,7 +98,7 @@ def generate_scheme_groups( metric_map = { "wcag": lambda mc,ac: ac.contrast(mc, method='wcag21'), - "oklch": lambda mc,ac: mc.distance(ac, space="oklch"), + "oklch": oklch_distance, "lightness": lambda mc,ac: abs(mc.coords()[0]-ac.coords()[0])*100, } @@ -131,6 +131,9 @@ def generate_scheme_groups( ("distance", distance), ("l_base", l_base), ("l_step", l_step), + ("fg_gap", fg_gap), + ("grey_gap", grey_gap), + ("term_fg_gap", term_fg_gap), ] # note how selection_bg steps up by `l_step`, selection_fg steps down by @@ -157,7 +160,7 @@ def generate_scheme_groups( accent_pairs = [ ("black", f"f{{{{{biome}.l{l_base}}}}}"), ("grey", f"f{{{{{biome}.l{l_base+grey_gap}}}}}"), - ("white", f"f{{{{{biome}.l{l_base+term_fg_gap-l_step}}}}}"), + ("white", f"f{{{{{biome}.l{l_base+term_fg_gap-2*l_step}}}}}"), ] for color_name, mb_accent in accent_color_map.items(): aL = int(100*accent_colors[mb_accent].coords()[0]) @@ -184,29 +187,42 @@ def generate_scheme( term_color_map: dict[str, str], vim_color_map: dict[str, str], ) -> str: + l_sys = l_base + l_app = l_base + l_step + + term_bright_offset = 10 + + # negate gaps if mode is light + if mode == "light": + l_step *= -1 + fg_gap *= -1 + grey_gap *= -1 + term_fg_gap *= -1 + term_bright_offset *= -1 + meta, _, mt, ac = generate_scheme_groups( mode, biome, metric, distance, - l_base, l_step, + l_sys, l_step, fg_gap, grey_gap, term_fg_gap, full_color_map ) _, term, _, term_norm_ac = generate_scheme_groups( mode, biome, metric, distance, - l_base + l_step, l_step, + l_app, l_step, fg_gap, grey_gap, term_fg_gap, term_color_map ) _, _, _, term_bright_ac = generate_scheme_groups( mode, biome, metric, distance, - l_base + l_step + 10, l_step, + l_app + term_bright_offset, l_step, fg_gap, grey_gap, term_fg_gap, term_color_map ) _, _, vim_mt, vim_ac = generate_scheme_groups( mode, biome, metric, distance, - l_base + l_step, l_step, + l_app, l_step, fg_gap, grey_gap, term_fg_gap, vim_color_map ) diff --git a/monobiome/util.py b/monobiome/util.py index 8fc70c1..9128649 100644 --- a/monobiome/util.py +++ b/monobiome/util.py @@ -1,3 +1,4 @@ +import math from types import GenericAlias from argparse import ArgumentParser, _SubParsersAction @@ -6,5 +7,29 @@ from coloraide import Color _SubParsersAction.__class_getitem__ = classmethod(GenericAlias) _SubparserType = _SubParsersAction[ArgumentParser] -def oklch_distance(mc: Color, ac: Color) -> float: - return mc.distance(ac, space="oklch") +def oklch_distance(xc: Color, yc: Color) -> float: + """ + Compute the distance between two colors in OKLCH space. + + Note: `xc` and `yc` are presumed to be OKLCH colors already, such that + `.coords()` yields an `(l, c, h)` triple directly rather than first + requiring conversion. When we can make this assumption, we save roughly an + order of magnitude in runtime. + + 1. `xc.distance(yc, space="oklch")`: 500k evals takes ~2s + 2. This method: 500k evals takes ~0.2s + """ + + l1, c1, h1 = xc.coords() + l2, c2, h2 = yc.coords() + + rad1 = h1 / 180 * math.pi + rad2 = h2 / 180 * math.pi + x1, y1 = c1 * math.cos(rad1), c1 * math.sin(rad1) + x2, y2 = c2 * math.cos(rad2), c2 * math.sin(rad2) + + dx = x1 - x2 + dy = y1 - y2 + dz = l1 - l2 + + return (dx**2 + dy**2 + dz**2)**0.5