aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/colors.rst2
-rw-r--r--docs/index.rst4
-rwxr-xr-xtests/test_text.py19
-rwxr-xr-xthcolor/_builtin/_css.py5
-rwxr-xr-xthcolor/_builtin/_default.py38
-rwxr-xr-xthcolor/_color.py220
-rwxr-xr-xthcolor/_sys.py40
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.
# ---