diff --git a/coloraide/__meta__.py b/coloraide/__meta__.py index b234edd22..13ab05874 100644 --- a/coloraide/__meta__.py +++ b/coloraide/__meta__.py @@ -204,5 +204,5 @@ def parse_version(ver: str) -> Version: return Version(major, minor, micro, release, pre, post, dev) -__version_info__ = Version(8, 8, 1, "final") +__version_info__ = Version(8, 9, 0, "final") __version__ = __version_info__._get_canonical() diff --git a/coloraide/interpolate/__init__.py b/coloraide/interpolate/__init__.py index 1f82d35d2..2bcfd3896 100644 --- a/coloraide/interpolate/__init__.py +++ b/coloraide/interpolate/__init__.py @@ -829,6 +829,9 @@ def carryforward_convert(color: Color, space: str, hue_index: int, powerless: bo 'L': False, 'V': False, 'a': False, 'b': False } + # Analogous components: all undefined + all_undefined = all(math.isnan(c) for c in color[:-1]) + # Gather undefined channels if isinstance(cs1, RGBish): for i, name in zip(cs1.indexes(), ('R', 'G', 'B')): @@ -838,18 +841,34 @@ def carryforward_convert(color: Color, space: str, hue_index: int, powerless: bo for i, name in zip(cs1.indexes(), ('L', 'C', 'H')): if math.isnan(color[i]): channels[name] = True + # Analogous components: chroma and hue + if channels['C'] and channels['H']: + channels['a'] = True + channels['b'] = True elif isinstance(cs1, Labish): for i, name in zip(cs1.indexes(), ('L', 'a', 'b')): if math.isnan(color[i]): channels[name] = True + # Analogous components: chroma and hue + if channels['a'] and channels['b']: + channels['C'] = True + channels['H'] = True elif isinstance(cs1, HSLish): for i, name in zip(cs1.indexes(), ('H', 'C', 'L')): if math.isnan(color[i]): channels[name] = True + # Analogous components: chroma and hue + if channels['C'] and channels['H']: + channels['a'] = True + channels['b'] = True elif isinstance(cs1, HSVish): for i, name in zip(cs1.indexes(), ('H', 'C', 'V')): if math.isnan(color[i]): channels[name] = True + # Analogous components: chroma and hue + if channels['C'] and channels['H']: + channels['a'] = True + channels['b'] = True elif cs1.is_polar(): if math.isnan(color[cs1.hue_index()]): # type: ignore[attr-defined] channels['H'] = True @@ -859,7 +878,9 @@ def carryforward_convert(color: Color, space: str, hue_index: int, powerless: bo carry.append(-1) # Channels that need to be carried forward - if isinstance(cs2, RGBish): + if all_undefined: + carry.extend(cs2.indexes()) + elif isinstance(cs2, RGBish): indexes = cs2.indexes() for e, name in enumerate(('R', 'G', 'B')): if channels[name]: diff --git a/docs/src/markdown/about/changelog.md b/docs/src/markdown/about/changelog.md index 38d1c5ec5..713a6f104 100644 --- a/docs/src/markdown/about/changelog.md +++ b/docs/src/markdown/about/changelog.md @@ -3,6 +3,10 @@ icon: lucide/scroll-text --- # Changelog +## 8.9 + +- **NEW**: Add support for CSS analogous component sets in interpolation and mixing. + ## 8.8.1 - **ENHANCE**: Minor speed improvements related to chromatic adaptation. diff --git a/docs/src/markdown/examples/3d_models.html b/docs/src/markdown/examples/3d_models.html index 9dd98c2d7..ea2677ed2 100644 --- a/docs/src/markdown/examples/3d_models.html +++ b/docs/src/markdown/examples/3d_models.html @@ -1241,7 +1241,7 @@

ColorAide Color Space Models

let colorSpaces = null let colorGamuts = null let lastModel = null -let package = 'coloraide-8.8.1-py3-none-any.whl' +let package = 'coloraide-8.9-py3-none-any.whl' const defaultSpace = 'lab' const defaultGamut = 'srgb' const exceptions = new Set(['hwb', 'ryb', 'ryb-biased']) diff --git a/docs/src/markdown/examples/colorpicker.html b/docs/src/markdown/examples/colorpicker.html index adf588b2c..14c74afaa 100644 --- a/docs/src/markdown/examples/colorpicker.html +++ b/docs/src/markdown/examples/colorpicker.html @@ -421,7 +421,7 @@

ColorAide Color Picker

let pyodide = null let webspace = '' let initial = 'oklab(0.69 0.13 -0.1 / 0.85)' - let package = 'coloraide-8.8.1-py3-none-any.whl' + let package = 'coloraide-8.9-py3-none-any.whl' const base = `${window.location.origin}/${window.location.pathname.split('/')[1]}/playground/` package = base + package diff --git a/docs/src/markdown/interpolation.md b/docs/src/markdown/interpolation.md index d48deb4c0..27c05ec64 100644 --- a/docs/src/markdown/interpolation.md +++ b/docs/src/markdown/interpolation.md @@ -1323,8 +1323,8 @@ derived from `RGBish`, so `x`, `y`, and `z` is treated like super saturated `r`, Space\ Type | Channel\ Equivalents ------------ | -------- `RGBish` | `r`, `g`, `b` -`LABish` | `l` -`LCHish` | `l`, `c`, `h` +`Labish` | `l` +`LChish` | `l`, `c`, `h` `HSLish` | `h`, `s`, `l` `HSVish` | `h`, `s`, `v` `Cylindrical`| `h` @@ -1343,6 +1343,11 @@ Opponent a | `a` Opponent b | `b` Value | `v` +Additionally, `carryforward` is applied to analogous sets as well. This means that a combination of components are +considered equivalent to another set of components. For instance, if an `LChish` space has an undefined chroma and hue, +when converting to a `Labish` space, `a` and `b` would be considered undefined (and vice versa). If all color components +are undefined, then when converted, all components in the new space would also be undefined. + ## Powerless Hues > [!example] Experimental diff --git a/docs/src/zensical.yml b/docs/src/zensical.yml index 94a3c76f4..0fd03aeb8 100644 --- a/docs/src/zensical.yml +++ b/docs/src/zensical.yml @@ -354,7 +354,7 @@ extra_javascript: - assets/pymdownx-extras/extra-loader-Ccztcqfq.js - https://cdn.jsdelivr.net/npm/ace-builds@1.43.0/src-min-noconflict/ace.js - https://cdn.jsdelivr.net/npm/mermaid@11.12.1/dist/mermaid.min.js - - playground-config-3ba60a2e.js + - playground-config-7363a2a9.js - https://cdn.jsdelivr.net/pyodide/v0.29.0/full/pyodide.js - assets/coloraide-extras/extra-notebook.js - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js diff --git a/docs/theme/playground-config-3ba60a2e.js b/docs/theme/playground-config-7363a2a9.js similarity index 71% rename from docs/theme/playground-config-3ba60a2e.js rename to docs/theme/playground-config-7363a2a9.js index 188089712..1dd2a7cdf 100644 --- a/docs/theme/playground-config-3ba60a2e.js +++ b/docs/theme/playground-config-7363a2a9.js @@ -1,5 +1,5 @@ var colorNotebook = { - "playgroundWheels": ['micropip', 'pygments-2.19.2-py3-none-any.whl', 'coloraide-8.8.1-py3-none-any.whl'], - "notebookWheels": ['pyyaml', 'markdown-3.10.1-py3-none-any.whl', 'pymdown_extensions-10.20-py3-none-any.whl', 'micropip', 'pygments-2.19.2-py3-none-any.whl', 'coloraide-8.8.1-py3-none-any.whl'], + "playgroundWheels": ['micropip', 'pygments-2.19.2-py3-none-any.whl', 'coloraide-8.9-py3-none-any.whl'], + "notebookWheels": ['pyyaml', 'markdown-3.10.1-py3-none-any.whl', 'pymdown_extensions-10.20-py3-none-any.whl', 'micropip', 'pygments-2.19.2-py3-none-any.whl', 'coloraide-8.9-py3-none-any.whl'], "defaultPlayground": "import coloraide\ncoloraide.__version__\nColor('red')" } diff --git a/tests/test_interpolation.py b/tests/test_interpolation.py index e10ec2d6a..f83b22d9c 100644 --- a/tests/test_interpolation.py +++ b/tests/test_interpolation.py @@ -2457,3 +2457,34 @@ def test_spectral_no_colors(self): with self.assertRaises(ValueError): Color.weighted_mix([], method='spectral') + + def test_carryforward_sets(self): + """Test analogous sets.""" + + result = Color.interpolate( + ['color(srgb none none none)', 'oklch(70% 0.1 240)'], + space='oklch', + carryforward=True + )(0.5) + self.assertColorEqual(result, Color('oklch(0.7 0.1 240)')) + + result = Color.interpolate( + ['oklab(40% none none)', 'oklch(70% 0.1 240)'], + space='oklch', + carryforward=True + )(0.5) + self.assertColorEqual(result, Color('oklch(0.55 0.1 240)')) + + result = Color.interpolate( + ['oklch(40% none none)', 'oklab(70% 0.1 -1.5)'], + space='oklab', + carryforward=True + )(0.5) + self.assertColorEqual(result, Color('oklab(0.55 0.1 -1.5)')) + + result = Color.interpolate( + ['hsl(none none 40%)', 'oklab(70% 0.1 -1.5)'], + space='oklab', + carryforward=True + )(0.5) + self.assertColorEqual(result, Color('oklab(0.60514 0.1 -1.5)')) diff --git a/zensical.yml b/zensical.yml index d24d65626..cf9403f25 100644 --- a/zensical.yml +++ b/zensical.yml @@ -354,7 +354,7 @@ extra_javascript: - assets/pymdownx-extras/extra-loader-Ccztcqfq.js - https://cdn.jsdelivr.net/npm/ace-builds@1.43.0/src-min-noconflict/ace.js - https://cdn.jsdelivr.net/npm/mermaid@11.12.1/dist/mermaid.min.js - - playground-config-3ba60a2e.js + - playground-config-7363a2a9.js - https://cdn.jsdelivr.net/pyodide/v0.29.0/full/pyodide.js - assets/coloraide-extras/extra-notebook-CU9d9Z53.js - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js