diff options
-rwxr-xr-x | tests/test_text.py | 8 | ||||
-rwxr-xr-x | thcolor/_color.py | 36 | ||||
-rwxr-xr-x | thcolor/_sys.py | 116 |
3 files changed, 119 insertions, 41 deletions
diff --git a/tests/test_text.py b/tests/test_text.py index a923842..eb3d5c3 100755 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -23,9 +23,9 @@ def _deg(value): ('rgba(1,22,242,0.500)', ( 1, 22, 242, 0.50)), ('rbga(5, 7)', ( 5, 0, 7, 1.00)), ('hsl(0, 1,50.0%)', (255, 0, 0, 1.00)), - ('hls(0 / 1 0.5 , 0.2)', (255, 0, 0, 0.20)), + ('hls(0 / 1 0.5 , 0.2)', (255, 255, 255, 0.20)), ('hwb(0 0% 0)', (255, 0, 0, 1.00)), - ('hbw(127 .5)', (128, 255, 142, 1.00)), + ('hbw(127 .5)', ( 0, 128, 15, 1.00)), ('gray(100)', (100, 100, 100, 1.00)), ('gray(100 / 55 %)', (100, 100, 100, 0.55)), ('gray(red( #123456 )/0.2/)', ( 18, 18, 18, 0.20)), @@ -39,8 +39,8 @@ def test_rgba(test_input, expected): ('lighter(50%, hsl(0, 1, 60.0%))', (_deg( 0), 1.00, 1.00, 1.00)), ('saturate(10%, hls(0, 1, 85.0%))', (_deg( 0), 0.95, 1.00, 1.00)), ('desaturate(10%, hls(0, 1, 5%, 0.2))', (_deg( 0), 0.00, 1.00, 0.20)), - ('rgba(255, 0, 0, 20 %)', (_deg( 0), 0.50, 1.00, 0.20)), - ('Y40, 33%, 55%', (_deg(84), 0.15, 0.39, 1.00)), + ('rgba(255, 0, 0, 20 %)', (_deg( 0), 1.00, 0.50, 0.20)), + ('Y40, 33%, 55%', (_deg(84), 0.16, 0.39, 1.00)), )) def test_hsla(test_input, expected): assert Color.from_text(test_input).hsla() == expected diff --git a/thcolor/_color.py b/thcolor/_color.py index 63b35d5..7850ac4 100755 --- a/thcolor/_color.py +++ b/thcolor/_color.py @@ -383,13 +383,9 @@ class Color: if self._type == Color.Type.RGB: return (self._r, self._g, self._b) elif self._type == Color.Type.HSL: - r, g, b = map(lambda x: int(x * 255), - _hls_to_rgb(self._hue.turns % 1, self._lgt, self._sat)) - return (r, g, b) + return _hls_to_rgb(self._hue, self._lgt, self._sat) elif self._type == Color.Type.HWB: - r, g, b = map(lambda x: int(x * 255), - _hwb_to_rgb(self._hue.turns % 1, self._wht, self._blk)) - return (r, g, b) + return _hwb_to_rgb(self._hue, self._wht, self._blk) raise ValueError(f"color type {self._type} doesn't translate to rgb") @@ -397,13 +393,11 @@ class Color: """ Get the (hue, saturation, lightness) components of the color. """ if self._type == Color.Type.RGB: - h, l, s = _rgb_to_hls(self._r, self._g, self._b) - return (_Angle(_Angle.Type.TURN, h), s, l) + return _rgb_to_hls(self._r, self._g, self._b) elif self._type == Color.Type.HSL: return (self._hue, self._sat, self._lgt) elif self._type == Color.Type.HWB: - h, l, s = _hwb_to_hls(self._hue.turns % 1, self._wht, self._blk) - return (_Angle(_Angle.Type.TURN, h), s, l) + return _hwb_to_hls(self._hue, self._wht, self._blk) raise ValueError(f"color type {self._type} doesn't translate to hsl") @@ -411,11 +405,9 @@ class Color: """ Get the (hue, whiteness, blackness) components of the color. """ if self._type == Color.Type.RGB: - h, w, b = _rgb_to_hwb(self._r, self._g, self._b) - return (_Angle(_Angle.Type.TURN, h), w, b) + return _rgb_to_hwb(self._r, self._g, self._b) elif self._type == Color.Type.HSL: - h, w, b = _hls_to_hwb(self._hue.turns % 1, self._lgt, self._sat) - return (_Angle(_Angle.Type.TURN, h), w, b) + return _hls_to_hwb(self._hue, self._lgt, self._sat) elif self._type == Color.Type.HWB: return (self._hue, self._wht, self._blk) @@ -448,6 +440,14 @@ class Color: h, s, l, a = self.hsla() return (h, l, s, a) + def hwba(self): + """ Get the (hue, whiteness, blackness, alpha) components of + the color. """ + + h, w, b = self.hwb() + a = self._alpha + return (h, w, b, a) + def css(self): """ Get the CSS declarations (with compatibility management). """ @@ -471,17 +471,17 @@ class Color: l = round(self._lgt, 5) * 100 if a < 1.0: - yield f'hsla({self._hue}, {s}%, {l}%, {a})' + yield f'hsla({self._hue.degrees}deg, {s}%, {l}%, {a})' else: - yield f'hsl({self._hue}, {s}%, {l}%)' + yield f'hsl({self._hue.degrees}deg, {s}%, {l}%)' elif self._type == Type.HWB: w = round(self._wht, 5) * 100 b = round(self._blk, 5) * 100 if a < 1.0: - yield f'hwba({self._hue}, {w}%, {b}%, {a})' + yield f'hwba({self._hue.degrees}deg, {w}%, {b}%, {a})' else: - yield f'hwb({self._hue}, {w}%, {b}%)' + yield f'hwb({self._hue.degrees}deg, {w}%, {b}%)' return list(statements()) diff --git a/thcolor/_sys.py b/thcolor/_sys.py index e40d885..dbcf001 100755 --- a/thcolor/_sys.py +++ b/thcolor/_sys.py @@ -6,7 +6,8 @@ """ Conversions between color systems. """ from math import ceil as _ceil -from colorsys import hls_to_rgb + +from ._angle import Angle as _Angle __all__ = ["hls_to_hwb", "hwb_to_hls", "hls_to_rgb", "rgb_to_hls", "rgb_to_hwb", "hwb_to_rgb", "netscape_color"] @@ -15,41 +16,118 @@ __all__ = ["hls_to_hwb", "hwb_to_hls", "hls_to_rgb", "rgb_to_hls", # Color systems conversion utilities. # --- -def hls_to_hwb(hue, s, l): +def _rgb(r, g, b): + return tuple(map(lambda x: int(round(x * 255, 0)), (r, g, b))) +def _hls(hue, s, l): + return _Angle(_Angle.Type.DEG, round(hue, 2)), round(l, 2), round(s, 2) + +def hls_to_hwb(hue, l, s): """ Convert HWB to HSL. """ - # TODO - pass + _, w, b = rgb_to_hwb(*hls_to_rgb(hue, l, s)) + return (hue, w, b) -def hwb_to_hls(hue, l, s): +def hwb_to_hls(hue, w, b): """ Convert HWB to HLS. """ - # TODO - pass + _, l, s = rgb_to_hls(*hwb_to_rgb(hue, w, b)) + return (hue, l, s) + +def hls_to_rgb(hue, l, s): + """ Convert HLS to RGB. """ + + if s == 0: + # Achromatic color. + + return l, l, l + + def _hue_to_rgb(t1, t2, hue): + hue %= 6 + + if hue < 1: + return t1 + (t2 - t1) * hue + elif hue < 3: + return t2 + elif hue < 4: + return t1 + (t2 - t1) * (4 - hue) + return t1 + + hue = (hue.degrees % 360) / 60 + if l <= 0.5: + t2 = l * (s + 1) + else: + t2 = l + s - (l * s) -# `hls_to_rgb` is imported from the standard `colorsys` module. + t1 = l * 2 - t2 + + return _rgb(\ + _hue_to_rgb(t1, t2, hue + 2), + _hue_to_rgb(t1, t2, hue), + _hue_to_rgb(t1, t2, hue - 2)) + +def hwb_to_rgb(hue, w, bl): + """ Convert HWB to RGB color. + https://drafts.csswg.org/css-color/#hwb-to-rgb """ + + r, g, b = map(lambda x: x / 255, hls_to_rgb(hue, 0.5, 1.0)) + if w + bl > 1: + w, bl = map(lambda x: x / (w + bl), (w, bl)) + return _rgb(*map(lambda x: x * (1 - w - bl) + w, (r, g, b))) def rgb_to_hls(r, g, b): """ Convert RGB to HLS. """ - # TODO - pass + r, g, b = map(lambda x: x / 255, (r, g, b)) + + min_value = min((r, g, b)) + max_value = max((r, g, b)) + chroma = max_value - min_value + + if chroma == 0: + hue = 0 + elif r == max_value: + hue = (g - b) / chroma + elif g == max_value: + hue = (b - r) / chroma + 2 + else: + hue = (r - g) / chroma + 4 + + hue = hue * 60 + (hue < 0) * 360 + l = (min_value + max_value) / 2 + if min_value == max_value: + s = 0 + else: + s = max_value - min_value + if l < 0.5: + s /= max_value + min_value + else: + s /= 2 - max_value - min_value + + return _hls(hue, l, s) def rgb_to_hwb(r, g, b): """ Convert RGB to HWB. """ - # TODO - pass + r, g, b = map(lambda x: x / 255, (r, g, b)) -def hwb_to_rgb(hue, w, b): - """ Convert HWB to RGB color. - https://drafts.csswg.org/css-color/#hwb-to-rgb """ + max_value = max((r, g, b)) + min_value = min((r, g, b)) + chroma = max_value - min_value + + if chroma == 0: + hue = 0 + elif r == max_value: + hue = (g - b) / chroma + elif g == max_value: + hue = (b - r) / chroma + 2 + elif g == max_value: + hue = (r - g) / chroma + 4 - r, g, b = hls_to_rgb(hue, 0.5, 1.0) - f = lambda x: x * (1 - w - b) + w - r, g, b = f(r), f(g), f(b) + hue = (hue % 6) * 360 + w = min_value + b = max_value - return r, g, b + return _Angle(_Angle.Type.DEG, hue), w, b # --- # Other utilities. |