diff --git a/coloraide/__meta__.py b/coloraide/__meta__.py index ed3806033..f475c5a47 100644 --- a/coloraide/__meta__.py +++ b/coloraide/__meta__.py @@ -188,5 +188,5 @@ def parse_version(ver): return Version(major, minor, micro, release, pre, post, dev) -__version_info__ = Version(0, 1, 0, "alpha", 7) +__version_info__ = Version(0, 1, 0, "alpha", 8) __version__ = __version_info__._get_canonical() diff --git a/coloraide/colors/__init__.py b/coloraide/colors/__init__.py index 1112f94f8..8edba4c66 100644 --- a/coloraide/colors/__init__.py +++ b/coloraide/colors/__init__.py @@ -67,8 +67,8 @@ def __eq__(self, other): return ( other.space() == self.space() and - other.coords() == self.coords() and - other.alpha == self.alpha + util.cmp_coords(other.coords(), self.coords()) and + util.cmp_coords(other.alpha, self.alpha) ) def is_nan(self, name): diff --git a/coloraide/colors/_interpolate.py b/coloraide/colors/_interpolate.py index c80b9b4f1..19178cabf 100644 --- a/coloraide/colors/_interpolate.py +++ b/coloraide/colors/_interpolate.py @@ -19,18 +19,21 @@ from . _range import Angle -def overlay(c1, c2, a1, a2, a0): +def overlay(c1, c2, a1, a2, a0, angle=False): """Overlay one color channel over the other.""" if util.is_nan(c1) and util.is_nan(c2): return 0.0 elif util.is_nan(c1): - return c2 * a2 + return c2 if angle else c2 * a2 elif util.is_nan(c2): - return c1 * a1 + return c1 if angle else c1 * a1 - c0 = c1 * a1 + c2 * a2 * (1 - a1) - return c0 / a0 if a0 else c0 + if angle: + return c1 + (c2 - c1) * (1 - a1) + else: + c0 = c1 * a1 + c2 * a2 * (1 - a1) + return c0 / a0 if a0 else c0 def interpolate(p, coords1, coords2, create, progress, outspace, premultiplied): @@ -188,28 +191,26 @@ def overlay(self, background, *, space=None, in_place=False): if this is None: raise ValueError('Invalid colorspace value: {}'.format(space)) - # Some spaces, like those that are cylindrical, will not work well, - # so a space can specify a rectangular space that is better suited. - if this.ALPHA_COMPOSITE is not None: - this = this.convert(this.ALPHA_COMPOSITE, fit=True) - background = background.convert(background.ALPHA_COMPOSITE, fit=True) - if this.space() != background.space(): # pragma: no cover - # Catch the rare event that two spaces request incompatible spaces (maybe some weird - # derived class instance). - raise ValueError('Cannot overlay space {} onto space {}'.format(this.space(), background.space())) - # Get the coordinates and indexes of valid hues prepare_coords(this) prepare_coords(background) + # Adjust hues if we have two valid hues + if isinstance(this, Cylindrical): + adjust_hues(this, background, util.DEF_HUE_ADJ) + coords1 = this.coords() coords2 = background.coords() a1 = this.alpha a2 = background.alpha a0 = a1 + a2 * (1.0 - a1) + gamut = this._range coords = [] + # Avoid multiplying angles and don't mix them the same as non-angles for i, value in enumerate(coords1): - coords.append(overlay(coords1[i], coords2[i], a1, a2, a0)) + g = gamut[i][0] + is_angle = isinstance(g, Angle) + coords.append(overlay(coords1[i], coords2[i], a1, a2, a0, angle=is_angle)) this._coords = coords this.alpha = a0 else: diff --git a/coloraide/colors/_space.py b/coloraide/colors/_space.py index bb139149d..35254c7dc 100644 --- a/coloraide/colors/_space.py +++ b/coloraide/colors/_space.py @@ -77,8 +77,6 @@ class Space(contrast.Contrast, interpolate.Interpolate, distance.Distance, gamut GAMUT = None # White point WHITE = convert.WHITES["D50"] - # When doing alpha composition, select an alternative space to perform the compositing in. - ALPHA_COMPOSITE = None def __init__(self, color=None): """Initialize.""" diff --git a/coloraide/colors/hsl.py b/coloraide/colors/hsl.py index f6c32e330..2e860372b 100644 --- a/coloraide/colors/hsl.py +++ b/coloraide/colors/hsl.py @@ -63,7 +63,6 @@ class HSL(Cylindrical, Space): DEF_VALUE = "color(hsl 0 0 0 / 1)" CHANNEL_NAMES = frozenset(["hue", "saturation", "lightness", "alpha"]) DEFAULT_MATCH = re.compile(RE_DEFAULT_MATCH.format(color_space=SPACE)) - ALPHA_COMPOSITE = "srgb" WHITE = convert.WHITES["D65"] _range = ( diff --git a/coloraide/colors/hsv.py b/coloraide/colors/hsv.py index aca2e3d05..4d675daf8 100644 --- a/coloraide/colors/hsv.py +++ b/coloraide/colors/hsv.py @@ -58,7 +58,6 @@ class HSV(Cylindrical, Space): CHANNEL_NAMES = frozenset(["hue", "saturation", "value", "alpha"]) DEFAULT_MATCH = re.compile(RE_DEFAULT_MATCH.format(color_space=SPACE)) GAMUT = "hsl" - ALPHA_COMPOSITE = "srgb" WHITE = convert.WHITES["D65"] _range = ( diff --git a/coloraide/colors/hwb.py b/coloraide/colors/hwb.py index 7e4c8fe4e..538f11a1e 100644 --- a/coloraide/colors/hwb.py +++ b/coloraide/colors/hwb.py @@ -43,7 +43,6 @@ class HWB(Cylindrical, Space): CHANNEL_NAMES = frozenset(["hue", "blackness", "whiteness", "alpha"]) DEFAULT_MATCH = re.compile(RE_DEFAULT_MATCH.format(color_space=SPACE)) GAMUT = "hsl" - ALPHA_COMPOSITE = "srgb" WHITE = convert.WHITES["D65"] _range = ( diff --git a/coloraide/colors/lch.py b/coloraide/colors/lch.py index 08a08ab38..a1599e364 100644 --- a/coloraide/colors/lch.py +++ b/coloraide/colors/lch.py @@ -59,7 +59,6 @@ class LCH(Cylindrical, Space): DEF_VALUE = "color(lch 0 0 0 / 1)" CHANNEL_NAMES = frozenset(["lightness", "chroma", "hue", "alpha"]) DEFAULT_MATCH = re.compile(RE_DEFAULT_MATCH.format(color_space=SPACE)) - ALPHA_COMPOSITE = "lab" WHITE = convert.WHITES["D50"] _range = ( diff --git a/coloraide/util.py b/coloraide/util.py index c0e5ccfee..10f1a9f2f 100644 --- a/coloraide/util.py +++ b/coloraide/util.py @@ -39,6 +39,15 @@ def no_nan(value): return [(0.0 if is_nan(x) else x) for x in value] +def cmp_coords(c1, c2): + """Compare coordinates.""" + + if is_number(c1): + return (math.isnan(c1) and math.isnan(c2)) or c1 == c2 + else: + return all(map(lambda a, b: (math.isnan(a) and math.isnan(b)) or a == b, c1, c2)) + + def dot(a, b): """Get dot product of simple numbers, vectors, and 2D matrices and/or numbers.""" diff --git a/docs/src/markdown/about/changelog.md b/docs/src/markdown/about/changelog.md index bc717f04e..e7c64ba36 100644 --- a/docs/src/markdown/about/changelog.md +++ b/docs/src/markdown/about/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 0.1.0a8 + +- **NEW**: Remove workaround to force cylindrical colors to overlay in non-cylindrical spaces. Allow colors to be + overlaid in any color space. Original issues related to allowing cylindrical spaces as been fixed. Overlaying in + cylindrical spaces may not make sense, but it is no longer prohibited. +- **FIX**: Ensure color comparison will yield true if two channels have `NaN`. + ## 0.1.0a7 - **FIX**: Fix issue with translation of an input that is compressed hex with a specified alpha. diff --git a/docs/src/markdown/color.md b/docs/src/markdown/color.md index 8bd63c741..d22453654 100644 --- a/docs/src/markdown/color.md +++ b/docs/src/markdown/color.md @@ -140,7 +140,7 @@ or color names that are part of color variables (`#!css var(--color-red)`). import re from coloraide import Color -RE_COLOR_START = re.compile(r"(?i)(?:\b(?