diff options
-rw-r--r-- | docs/colors.rst | 2 | ||||
-rw-r--r-- | docs/index.rst | 4 | ||||
-rwxr-xr-x | tests/test_text.py | 19 | ||||
-rwxr-xr-x | thcolor/_builtin/_css.py | 5 | ||||
-rwxr-xr-x | thcolor/_builtin/_default.py | 38 | ||||
-rwxr-xr-x | thcolor/_color.py | 220 | ||||
-rwxr-xr-x | thcolor/_sys.py | 40 |
7 files changed, 165 insertions, 163 deletions
diff --git a/docs/colors.rst b/docs/colors.rst index 5a657cd..e5d3b65 100644 --- a/docs/colors.rst +++ b/docs/colors.rst @@ -8,4 +8,4 @@ Colors can have one of the following types: Colors are represented in ``thcolor`` as instances of the following class: .. autoclass:: thcolor.Color - :members: type, rgb, rgba, hls, hlsa, hwb, hwba, css + :members: type, rgb, rgba, hls, hlsa, hwb, hwba, cmyk, cmyka, css diff --git a/docs/index.rst b/docs/index.rst index 0db7bf0..33e175d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,8 +16,8 @@ To install the module, use pip: For more information and links, consult `the official website`_. -Sommaire --------- +Table of contents +----------------- .. toctree:: :maxdepth: 2 diff --git a/tests/test_text.py b/tests/test_text.py index 055d815..45a39fe 100755 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -32,22 +32,29 @@ def _deg(value): ('gray(red( #123456 )/0.2/)', ( 18, 18, 18, 0.20)), ('B20 50% 32%', (137, 128, 173, 1.00)), ('ncol(B20 / 50% 32%)', (137, 128, 173, 1.00)), + ('cmyk(0% 37% 0.13 .78)', ( 56, 35, 49, 1.00)), )) def test_rgba(test_input, expected): assert Color.from_text(test_input).rgba() == expected @pytest.mark.parametrize('test_input,expected', ( - ('darker(10%, hsl(0, 1, 50.0%))', (_deg( 0), 1.00, 0.40, 1.00)), - ('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), 1.00, 0.50, 0.20)), - ('Y40, 33%, 55%', (_deg(84), 0.16, 0.39, 1.00)), + ('darker(10%, hsl(0, 1, 50.0%))', (_deg( 0 ), 1.00, 0.40, 1.00)), + ('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 ), 1.00, 0.50, 0.20)), + ('Y40, 33%, 55%', (_deg(83.23), 0.16, 0.39, 1.00)), )) def test_hsla(test_input, expected): assert Color.from_text(test_input).hsla() == expected @pytest.mark.parametrize('test_input,expected', ( + ('cmyk(0% 37% 0.13 .78)', (0.00, 0.37, 0.13, 0.78, 1.00)), +)) +def test_cmyka(test_input, expected): + assert Color.from_text(test_input).cmyka() == expected + +@pytest.mark.parametrize('test_input,expected', ( ('blue', ('#0000FF',)), (' rgb (1,22, 242 , 50.0% )', diff --git a/thcolor/_builtin/_css.py b/thcolor/_builtin/_css.py index 5cf012c..12bb1aa 100755 --- a/thcolor/_builtin/_css.py +++ b/thcolor/_builtin/_css.py @@ -420,9 +420,4 @@ class CSS4Reference(CSS3Reference): # TODO: lch raise NotImplementedError - def cmyk(self, c: percentage, m: percentage = percentage(0), - y: percentage = percentage(0), k = percentage(0)): - # TODO: cmyk - raise NotImplementedError - # End of file. diff --git a/thcolor/_builtin/_default.py b/thcolor/_builtin/_default.py index 05ac49e..bbbf8d1 100755 --- a/thcolor/_builtin/_default.py +++ b/thcolor/_builtin/_default.py @@ -100,6 +100,44 @@ class DefaultReference(_CSS4Reference): return self._hwb((h, w, b, alpha), (0, 2, 1)) # --- + # CMYK utilities and extensions. + # --- + + def cmyk(self, c: number | percentage, + m: number | percentage = percentage(0), + y: number | percentage = percentage(0), + k = number | percentage(0), + alpha: number | percentage = number(1.0)): + ci, mi, yi, ki = 0, 1, 2, 3 + + try: + c = c.to_factor() + except ValueError as e: + raise _InvalidArgumentValueError(ci, str(e)) + + try: + m = m.to_factor() + except ValueError as e: + raise _InvalidArgumentValueError(mi, str(e)) + + try: + y = y.to_factor() + except ValueError as e: + raise _InvalidArgumentValueError(yi, str(e)) + + try: + k = k.to_factor() + except ValueError as e: + raise _InvalidArgumentValueError(ki, str(e)) + + try: + alpha = alpha.to_factor() + except ValueError as e: + raise _InvalidArgumentValueError(4, str(e)) + + return _Reference.color(_Color(_Color.Type.CMYK, c, m, y, k, alpha)) + + # --- # Get the RGB components of a color. # --- diff --git a/thcolor/_color.py b/thcolor/_color.py index a26e929..828ab21 100755 --- a/thcolor/_color.py +++ b/thcolor/_color.py @@ -21,8 +21,8 @@ except ImportError: from ._ref import Reference as _Reference from ._angle import Angle as _Angle from ._sys import (hls_to_rgb as _hls_to_rgb, rgb_to_hls as _rgb_to_hls, - hls_to_hwb as _hls_to_hwb, hwb_to_hls as _hwb_to_hls, rgb_to_hwb as _rgb_to_hwb, hwb_to_rgb as _hwb_to_rgb, + rgb_to_cmyk as _rgb_to_cmyk, cmyk_to_rgb as _cmyk_to_rgb, netscape_color as _netscape_color) from ._exc import (\ ColorExpressionDecodingError as _ColorExpressionDecodingError, @@ -131,7 +131,16 @@ class Color: Create a color using its hue, whiteness and blackness components. The hue is represented by an :class:`Angle` object, and the - whiteness and light,ess are values going from 0.0 to 1.0. + whiteness and lightness are values going from 0.0 to 1.0. + + An alpha value going from 0.0 (invisible) to 1.0 (opaque) can be + appended to the base components. + + .. function:: Color(Color.Type.CMYK, cyan, magenta, yellow, """ \ + """black, alpha = 1.0) + + Create a color using its cyan, magenta, yellow and blackness + components, which are all values going from 0.0 to 1.0. An alpha value going from 0.0 (invisible) to 1.0 (opaque) can be appended to the base components. """ @@ -144,6 +153,7 @@ class Color: # `_hue`: hue for HSL and HWB notations. # `_sat`, `_lgt`: saturation and light for HSL. # `_wht`, `_blk`: whiteness and blackness for HWB. + # `_cy`, `_ma`, `_ye`, `_bl`: CMYK components. class Type(_Enum): """ Class representing the type of a color, or how it is expressed. @@ -168,6 +178,11 @@ class Color: A color expressed through its HWB components: hue, whiteness and blackness. + .. data:: CMYK + + A color expressed through its CMYK components: cyan, magenta, + yellow and black. + An alpha component can be added to every single one of these types, so it is not included in the type names. """ @@ -175,6 +190,7 @@ class Color: RGB = 1 HSL = 2 HWB = 3 + CMYK = 4 def __init__(self, *args, **kwargs): self._type = Color.Type.INVALID @@ -191,6 +207,9 @@ class Color: elif self._type == Color.Type.HWB: args += (('hue', repr(self._hue)), ('whiteness', repr(self._wht)), ('blackness', repr(self._blk))) + elif self._type == Color.Type.CMYK: + args += (('cyan', repr(self._cy)), ('magenta', repr(self._ma)), + ('yellow', repr(self._ye)), ('black', repr(self._bl))) args += (('alpha', self._alpha),) @@ -305,6 +324,14 @@ class Color: (('w', 'white', 'whiteness'), _percentage), (('b', 'black', 'blackness'), _percentage), (('a', 'alpha'), _percentage, 1.0)) + elif type == Color.Type.CMYK: + self._cy, self._ma, self._ye, self._bl, self._alpha = \ + _decode_varargs(\ + (('c', 'cyan'), _percentage), + (('m', 'magenta'), _percentage), + (('y', 'yellow'), _percentage), + (('b', 'black'), _percentage), + (('a', 'alpha'), _percentage, 1.0)) else: raise ValueError(f"invalid color type: {type}") @@ -324,128 +351,6 @@ class Color: return self._type - def alpha(self): - """ Get the alpha. """ - - return self._alpha - - @property - def red(self): - """ Get the red component. """ - - r, _, _ = self.rgb() - return r - - @property - def green(self): - """ Get the green component. """ - - _, g, _ = self.rgb() - return g - - @property - def blue(self): - """ Get the blue component. """ - - _, _, b = self.rgb() - return b - - @property - def hue(self): - """ Hue for the HSL or HWB. """ - - if self._type == Color.Type.HWB: - return self._hue - - h, _, _ = self.hsl() - return h - - @property - def saturation(self): - """ Saturation for the HSL notation. """ - - _, s, _ = self.hsl() - return s - - @property - def lightness(self): - """ Lightness for the HSL notation. """ - - _, _, l = self.hsl() - return l - - @property - def whiteness(self): - """ Whiteness for the HWB notation. """ - - _, w, _ = self.hwb() - return w - - @property - def blackness(self): - """ Blackness for the HWB notation. """ - - _, _, b = self.hwb() - return b - - # --- - # Short properties. - # --- - - @property - def a(self): - """ Alias for the `alpha` property. """ - - return self.alpha - - @property - def r(self): - """ Alias for the `red` property. """ - - return self.r - - @property - def g(self): - """ Alias for the `green` property. """ - - return self.green - - @property - def b(self): - """ Alias for the `blue` property. """ - - return self.blue - - @property - def h(self): - """ Alias for the `hue` property. """ - - return self.hue - - @property - def s(self): - """ Alias for the `saturation` property. """ - - return self.saturation - - @property - def l(self): - """ Alias for the `lightness` property. """ - - return self.lightness - - @property - def wh(self): - """ Alias for the `whiteness` property. """ - - return self.whiteness - - @property - def bl(self): - """ Alias for the `blackness` property. """ - - return self.blackness - # --- # Conversion methods. # --- @@ -466,6 +371,8 @@ class Color: return _hls_to_rgb(self._hue, self._lgt, self._sat) elif self._type == Color.Type.HWB: return _hwb_to_rgb(self._hue, self._wht, self._blk) + elif self._type == Color.Type.CMYK: + return _cmyk_to_rgb(self._cy, self._ma, self._ye, self._bl) raise ValueError(f"color type {self._type} doesn't translate to rgb") @@ -479,14 +386,16 @@ class Color: If the color is not represented as HSL internally, it will be converted. """ - if self._type == Color.Type.RGB: - return _rgb_to_hls(self._r, self._g, self._b) - elif self._type == Color.Type.HSL: + if self._type == Color.Type.HSL: return (self._hue, self._sat, self._lgt) - elif self._type == Color.Type.HWB: - return _hwb_to_hls(self._hue, self._wht, self._blk) - raise ValueError(f"color type {self._type} doesn't translate to hsl") + try: + rgb = self.rgb() + except ValueError: + raise ValueError(f"color type {self._type} doesn't translate " \ + "to hsl") from None + + return _rgb_to_hls(*rgb) def hwb(self): """ Get the HWB (hue, whiteness, blackness) components of the color. @@ -498,13 +407,38 @@ class Color: If the color is not represented as HSL internally, it will be converted. """ - if self._type == Color.Type.RGB: - return _rgb_to_hwb(self._r, self._g, self._b) - elif self._type == Color.Type.HSL: - return _hls_to_hwb(self._hue, self._lgt, self._sat) - elif self._type == Color.Type.HWB: + if self._type == Color.Type.HWB: return (self._hue, self._wht, self._blk) + try: + rgb = self.rgb() + except ValueError: + raise ValueError(f"color type {self._type} doesn't translate " \ + "to hwb") from None + + return _rgb_to_hwb(*rgb) + + def cmyk(self): + """ Get the CMYK (cyan, magenta, yellow, black) components of the + color. For example: + + >>> Color.from_text("cmyk(.1 .2 .3 .4)").cmyk() + ... (0.1, 0.2, 0.3, 0.4) + + If the color is not represented as CMYK internally, it will be + converted naively. """ + + if self._type == Color.Type.CMYK: + return (self._cy, self._ma, self._ye, self._bl) + + try: + rgb = self.rgb() + except ValueError: + raise ValueError(f"color type {self._type} doesn't translate " \ + "to cmyk") from None + + return _rgb_to_cmyk(*rgb) + def rgba(self): """ Get the sRGB (red, green, blue) and alpha components of the color. For example: @@ -561,8 +495,24 @@ class Color: h, w, b = self.hwb() a = self._alpha + return (h, w, b, a) + def cmyka(self): + """ Get the CMYK (cyan, magenta, yellow, black) and alpha components + of the color. For example: + + >>> Color.from_text("cmyk(.1 .2 .3 .4 / 10%)").cmyka() + ... (0.1, 0.2, 0.3, 0.4, 0.1) + + If the color is not represented as CMYK internally, it will be + converted naively. """ + + c, m, y, k = self.cmyk() + a = self._alpha + + return (c, m, y, k, a) + def css(self): """ Get the CSS color descriptions, with older CSS specifications compatibility, as a list of strings. diff --git a/thcolor/_sys.py b/thcolor/_sys.py index dbcf001..d0e0227 100755 --- a/thcolor/_sys.py +++ b/thcolor/_sys.py @@ -9,8 +9,8 @@ from math import ceil as _ceil 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"] +__all__ = ["hls_to_rgb", "rgb_to_hls", "rgb_to_hwb", "hwb_to_rgb", + "cmyk_to_rgb", "rgb_to_cmyk", "netscape_color"] # --- # Color systems conversion utilities. @@ -21,18 +21,6 @@ def _rgb(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. """ - - _, w, b = rgb_to_hwb(*hls_to_rgb(hue, l, s)) - return (hue, w, b) - -def hwb_to_hls(hue, w, b): - """ Convert HWB to HLS. """ - - _, 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. """ @@ -129,6 +117,30 @@ def rgb_to_hwb(r, g, b): return _Angle(_Angle.Type.DEG, hue), w, b +def cmyk_to_rgb(c, m, y, k): + """ Convert CMYK to RGB. """ + + r = 1 - min(1, c * (1 - k) + k) + g = 1 - min(1, m * (1 - k) + k) + b = 1 - min(1, y * (1 - k) + k) + + return _rgb(r, g, b) + +def rgb_to_cmyk(r, g, b): + """ Convert RGB to CMYK. """ + + r, g, b = map(lambda x: x / 255, (r, g, b)) + + k = 1 - max((r, g, b)) + if k == 1: + c, m, y = 0, 0, 0 + else: + c = (1 - r - k) / (1 - k) + m = (1 - g - k) / (1 - k) + y = (1 - b - k) / (1 - k) + + return (c, m, y, k) + # --- # Other utilities. # --- |