Coverage for /usr/lib/python3/dist-packages/colorzero/color.py: 18%
214 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-02-10 12:38 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-02-10 12:38 +0000
1# vim: set et sw=4 sts=4 fileencoding=utf-8:
2#
3# The colorzero color library
4# Copyright (c) 2016-2018 Dave Jones <dave@waveform.org.uk>
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above copyright
12# notice, this list of conditions and the following disclaimer in the
13# documentation and/or other materials provided with the distribution.
14# * Neither the name of the copyright holder nor the
15# names of its contributors may be used to endorse or promote products
16# derived from this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28# POSSIBILITY OF SUCH DAMAGE.
30"Defines the main :class:`Color` class of the package."
32from __future__ import (
33 unicode_literals,
34 print_function,
35 division,
36 absolute_import,
37)
39import re
41from . import conversions as cv, types, attr, deltae, tables, easings
43# Make Py2's str and range equivalent to Py3's
44str = type('') # pylint: disable=redefined-builtin,invalid-name
46# Lots of the methods below use single character parameter names (r for red, y
47# for luma, etc.); this is is normal and in keeping with most of the referenced
48# sources
49# pylint: disable=invalid-name
52class Color(types.RGB):
53 """
54 The Color class is a tuple which represents a color as linear red, green,
55 and blue components.
57 The class has a flexible constructor which allows you to create an instance
58 from any built-in color system. There are also explicit constructors for
59 every known system that can convert (directly or indirectly) to linear RGB.
60 For example, an instance of :class:`Color` can be constructed in any of the
61 following ways::
63 >>> Color('#f00')
64 <Color html='#ff0000' rgb=(1, 0, 0)>
65 >>> Color('green')
66 <Color html='#008000' rgb=(0.0, 0.501961, 0.0)>
67 >>> Color(0, 0, 1)
68 <Color html='#0000ff' rgb=(0, 0, 1)>
69 >>> Color(h=0, s=1, v=0.5)
70 <Color html='#800000' rgb=(0.5, 0, 0)>
71 >>> Color(y=0.4, u=-0.05, v=0.615)
72 <Color html='#ff104c' rgb=(1, 0.0626644, 0.298394)>
74 The specific forms that the default constructor will accept are enumerated
75 below:
77 +------------------------------+------------------------------------------+
78 | Style | Description |
79 +==============================+==========================================+
80 | Single scalar parameter | Equivalent to calling |
81 | | :meth:`Color.from_string`, or |
82 | | :meth:`Color.from_rgb24`. |
83 +------------------------------+------------------------------------------+
84 | Three positional parameters | Equivalent to calling |
85 | or a 3-tuple with no field | :meth:`Color.from_rgb` if all three |
86 | names | parameters are between 0.0 and 1.0, or |
87 | | :meth:`Color.from_rgb_bytes` otherwise. |
88 +------------------------------+ |
89 | Three named parameters, or a | |
90 | 3-tuple with fields | |
91 | "r", "g", "b" | |
92 +------------------------------+ |
93 | Three named parameters, or a | |
94 | 3-tuple with fields | |
95 | "red", "green", "blue" | |
96 +------------------------------+------------------------------------------+
97 | Three named parameters, or a | Equivalent to calling |
98 | 3-tuple with fields | :meth:`Color.from_yuv` if "y" is between |
99 | "y", "u", "v" | 0.0 and 1.0, "u" is between -0.436 and |
100 | | 0.436, and "v" is between -0.615 and |
101 | | 0.615, or :meth:`Color.from_yuv_bytes` |
102 | | otherwise. |
103 +------------------------------+------------------------------------------+
104 | Three named parameters, or a | Equivalent to calling |
105 | 3-tuple with fields | :meth:`Color.from_yiq`. |
106 | "y", "i", "q" | |
107 +------------------------------+------------------------------------------+
108 | Three named parameters, or a | Equivalent to calling |
109 | 3-tuple with fields | :meth:`Color.from_hls`. |
110 | "h", "l", "s" | |
111 +------------------------------+ |
112 | Three named parameters, or a | |
113 | 3-tuple with fields | |
114 | "hue", "lightness", | |
115 | "saturation" | |
116 +------------------------------+------------------------------------------+
117 | Three named parameters, or a | Equivalent to calling |
118 | 3-tuple with fields | :meth:`Color.from_hsv` |
119 | "h", "s", "v" | |
120 +------------------------------+ |
121 | Three named parameters, or a | |
122 | 3-tuple with fields | |
123 | "hue", "saturation", "value" | |
124 +------------------------------+------------------------------------------+
125 | Three named parameters, or a | Equivalent to calling |
126 | 3-tuple with fields | :meth:`Color.from_xyz` |
127 | "x", "y", "z" | |
128 +------------------------------+------------------------------------------+
129 | Three named parameters, or a | Equivalent to calling |
130 | 3-tuple with fields | :meth:`Color.from_lab` |
131 | "l", "a", "b" | |
132 +------------------------------+------------------------------------------+
133 | Three named parameters, or a | Equivalent to calling |
134 | 3-tuple with fields | :meth:`Color.from_luv` |
135 | "l", "u", "v" | |
136 +------------------------------+------------------------------------------+
138 If the constructor parameters do not conform to any of the variants in the
139 table above, a :exc:`ValueError` will be raised.
141 Internally, the color is *always* represented as 3 :class:`float` values
142 corresponding to the red, green, and blue components of the color. These
143 values take a value from 0.0 to 1.0 (least to full intensity). The class
144 provides several attributes which can be used to convert one color system
145 into another::
147 >>> Color('#f00').hls
148 HLS(h=0.0, l=0.5, s=1.0)
149 >>> Color.from_string('green').hue
150 Hue(deg=120.0)
151 >>> Color.from_rgb_bytes(0, 0, 255).yuv
152 YUV(y=0.114, u=0.436, v=-0.10001426533523537)
154 As :class:`Color` derives from tuple, instances are immutable. While this
155 provides the advantage that they can be used in a :class:`set` or as keys
156 of a :class:`dict`, it does mean that colors themselves cannot be
157 *directly* manipulated (e.g. by setting the red component).
159 However, several auxilliary classes in the module provide the ability to
160 perform simple transformations of colors via operators which produce a new
161 :class:`Color` instance. For example, you can add, subtract, and multiply
162 colors directly::
164 >>> Color('red') + Color('blue')
165 <Color html='#ff00ff' rgb=(1, 0, 1)>
166 >>> Color('magenta') - Color('red')
167 <Color html='#0000ff' rgb=(0, 0, 1)>
169 Values are clipped to ensure the resulting color is still valid::
171 >>> Color('#ff00ff') + Color('#ff0000')
172 <Color html='#ff00ff' rgb=(1, 0, 1)>
174 You can wrap numbers in constructors like :class:`Red` (or obtain elements
175 of existing colors), then add, subtract, or multiply them with a
176 :class:`Color`::
178 >>> Color('red') - Red(0.5)
179 <Color html='#800000' rgb=(0.5, 0, 0)>
180 >>> Color('green') + Color('grey').red
181 <Color html='#808000' rgb=(0.501961, 0.501961, 0)>
183 You can even manipulate non-primary attributes like hue, saturation, and
184 lightness with standard addition, subtraction or multiplication operators::
186 >>> Color.from_hls(0.5, 0.5, 1.0)
187 <Color html='#00ffff' rgb=(0, 1, 1)>
188 >>> Color.from_hls(0.5, 0.5, 1.0) * Lightness(0.8)
189 <Color html='#00cccc' rgb=(0, 0.8, 0.8)>
190 >>> (Color.from_hls(0.5, 0.5, 1.0) * Lightness(0.8)).hls
191 HLS(h=0.5, l=0.4, s=1.0)
193 In the last example above, a :class:`Color` instance is constructed from
194 HLS (hue, lightness, saturation) values with a lightness of 0.5. This is
195 multiplied by a :class:`Lightness` a value of 0.8 which constructs a new
196 :class:`Color` with the same hue and saturation, but a lightness of 0.4
197 (0.8 × 0.5).
199 If an instance is converted to a string (with :func:`str`) it will return a
200 string containing the 7-character HTML code for the color (e.g. "#ff0000"
201 for red). As can be seen in the examples above, a similar representation is
202 included for the output of :func:`repr`. The output of :func:`repr` can
203 be customized by assigning values to :attr:`Color.repr_style`.
205 .. _RGB: https://en.wikipedia.org/wiki/RGB_color_space
206 .. _Y'UV: https://en.wikipedia.org/wiki/YUV
207 .. _Y'IQ: https://en.wikipedia.org/wiki/YIQ
208 .. _HLS: https://en.wikipedia.org/wiki/HSL_and_HSV
209 .. _HSV: https://en.wikipedia.org/wiki/HSL_and_HSV
211 .. attribute:: red
213 Return the red value as a :class:`Red` instance
215 .. attribute:: green
217 Return the green value as a :class:`Green` instance
219 .. attribute:: blue
221 Return the blue value as a :class:`Blue` instance
223 .. attribute:: repr_style
225 Specifies the style of output returned when using :func:`repr` against
226 a :class:`Color` instance. This is an attribute of the class, not of
227 instances. For example::
229 >>> Color('#f00')
230 <Color html='#ff0000' rgb=(1, 0, 0)>
231 >>> Color.repr_style = 'html'
232 >>> Color('#f00')
233 Color('#ff0000')
235 The following values are valid:
237 * 'default' - The style shown above
238 * 'term16m' - Similar to the default style, but instead of the HTML
239 style being included, a swatch previewing the color is output. Note
240 that the terminal must support `24-bit color ANSI codes`_ for this to
241 work.
242 * 'term256' - Similar to 'termtrue', but uses the closest color that
243 can be found in the standard 256-color xterm palette. Note that the
244 terminal must support `8-bit color ANSI codes`_ for this to work.
245 * 'html' - Outputs a valid :class:`Color` constructor using the HTML
246 style, e.g. ``Color('#ff99bb')``
247 * 'rgb' - Outputs a valid :class:`Color` constructor using the floating
248 point RGB values, e.g. ``Color(1, 0.25, 0)``
249 """
250 # pylint: disable=too-many-public-methods
252 __slots__ = ()
254 repr_style = 'default'
256 def __new__(cls, *args, **kwargs):
257 def from_rgb(r, g, b):
258 "Determine whether bytes or floats are being passed for RGB"
259 if 0.0 <= r <= 1.0 and 0.0 <= g <= 1.0 and 0.0 <= b <= 1.0:
260 return cls.from_rgb(r, g, b)
261 else:
262 return cls.from_rgb_bytes(r, g, b)
264 def from_yuv(y, u, v):
265 "Determine whether bytes or floats are being passed for YUV"
266 if (
267 0.0 <= y <= 1.0 and
268 abs(u) <= cv.BT601.Umax and
269 abs(v) <= cv.BT601.Vmax):
270 return cls.from_yuv(y, u, v)
271 else:
272 return cls.from_yuv_bytes(y, u, v)
274 if kwargs:
275 try:
276 # Yes, lambdas are fine here
277 # pylint: disable=unnecessary-lambda
278 return {
279 frozenset('rgb'): from_rgb,
280 frozenset('yuv'): from_yuv,
281 frozenset('yiq'): cls.from_yiq,
282 frozenset('hls'): cls.from_hls,
283 frozenset('hsv'): cls.from_hsv,
284 frozenset('xyz'): cls.from_xyz,
285 frozenset('lab'): cls.from_lab,
286 frozenset('luv'): cls.from_luv,
287 frozenset('cmy'): cls.from_cmy,
288 frozenset('cmyk'): cls.from_cmyk,
289 frozenset(('red', 'green', 'blue')):
290 lambda red, green, blue:
291 from_rgb(red, green, blue),
292 frozenset(('cyan', 'magenta', 'yellow')):
293 lambda cyan, magenta, yellow:
294 cls.from_cmy(cyan, magenta, yellow),
295 frozenset(('cyan', 'magenta', 'yellow', 'black')):
296 lambda cyan, magenta, yellow, black:
297 cls.from_cmyk(cyan, magenta, yellow, black),
298 frozenset(('hue', 'lightness', 'saturation')):
299 lambda hue, lightness, saturation:
300 cls.from_hls(hue, lightness, saturation),
301 frozenset(('hue', 'saturation', 'value')):
302 lambda hue, saturation, value:
303 cls.from_hsv(hue, saturation, value),
304 }[frozenset(kwargs.keys())](**kwargs)
305 except KeyError:
306 pass
307 else:
308 if len(args) == 1:
309 if isinstance(args[0], bytes):
310 spec = args[0].decode('ascii')
311 else:
312 spec = args[0]
313 if isinstance(spec, str):
314 return cls.from_string(spec)
315 elif isinstance(spec, tuple):
316 try:
317 return cls(**spec._asdict())
318 except AttributeError:
319 if len(spec) == 3:
320 return from_rgb(*spec)
321 elif isinstance(spec, int):
322 return cls.from_rgb24(spec)
323 elif len(args) == 3:
324 r, g, b = args
325 return from_rgb(r, g, b)
326 raise ValueError('Unable to construct Color from provided arguments')
328 @classmethod
329 def from_string(cls, s):
330 """
331 Construct a :class:`Color` from a 4 or 7 character CSS-like
332 representation (e.g. "#f00" or "#ff0000" for red), or from one of the
333 named colors (e.g. "green" or "wheat") from the `CSS standard`_. Any
334 other string format will result in a :exc:`ValueError`.
336 .. _CSS standard: http://www.w3.org/TR/css3-color/#svg-color
337 """
338 if s[:1] != '#':
339 s = cv.name_to_html(s)
340 return cls.from_rgb_bytes(*cv.html_to_rgb_bytes(s))
342 @classmethod
343 def from_rgb(cls, r, g, b):
344 """
345 Construct a :class:`Color` from three linear `RGB`_ float values
346 between 0.0 and 1.0.
347 """
348 return super(Color, cls).__new__(cls,
349 cv.clamp_float(r),
350 cv.clamp_float(g),
351 cv.clamp_float(b))
353 @classmethod
354 def from_rgb24(cls, n):
355 """
356 Construct a :class:`Color` from an unsigned 24-bit integer number
357 of the form 0x00BBGGRR.
358 """
359 return cls.from_rgb_bytes(*cv.rgb24_to_rgb_bytes(n))
361 @classmethod
362 def from_rgb565(cls, n):
363 """
364 Construct a :class:`Color` from an unsigned 16-bit integer number
365 in RGB565 format.
366 """
367 return cls.from_rgb(*cv.rgb565_to_rgb(n))
369 @classmethod
370 def from_rgb_bytes(cls, r, g, b):
371 """
372 Construct a :class:`Color` from three `RGB`_ byte values between 0 and
373 255.
375 .. _RGB: https://en.wikipedia.org/wiki/RGB_color_space
376 """
377 return cls.from_rgb(*cv.rgb_bytes_to_rgb(r, g, b))
379 @classmethod
380 def from_yuv(cls, y, u, v):
381 """
382 Construct a :class:`Color` from three `Y'UV`_ float values. The Y value
383 may be between 0.0 and 1.0. U may be between -0.436 and 0.436, while
384 V may be between -0.615 and 0.615.
386 .. _Y'UV: https://en.wikipedia.org/wiki/YUV
387 """
388 return cls.from_rgb(*cv.yuv_to_rgb(y, u, v))
390 @classmethod
391 def from_yuv_bytes(cls, y, u, v):
392 """
393 Construct a :class:`Color` from three `Y'UV`_ byte values between 0 and
394 255. The U and V values are biased by 128 to prevent negative values as
395 is typical in video applications. The Y value is biased by 16 for the
396 same purpose.
398 .. _Y'UV: https://en.wikipedia.org/wiki/YUV
399 """
400 return cls.from_rgb_bytes(*cv.yuv_bytes_to_rgb_bytes(y, u, v))
402 @classmethod
403 def from_yiq(cls, y, i, q):
404 """
405 Construct a :class:`Color` from three `Y'IQ`_ float values. Y' can be
406 between 0.0 and 1.0, while I and Q can be between -1.0 and 1.0.
408 .. _Y'IQ: https://en.wikipedia.org/wiki/YIQ
409 """
410 return cls.from_rgb(*cv.yiq_to_rgb(y, i, q))
412 @classmethod
413 def from_hls(cls, h, l, s):
414 """
415 Construct a :class:`Color` from `HLS`_ (hue, lightness, saturation)
416 floats between 0.0 and 1.0.
418 .. _HLS: https://en.wikipedia.org/wiki/HSL_and_HSV
419 """
420 return cls.from_rgb(*cv.hls_to_rgb(h, l, s))
422 @classmethod
423 def from_hsv(cls, h, s, v):
424 """
425 Construct a :class:`Color` from `HSV`_ (hue, saturation, value) floats
426 between 0.0 and 1.0.
428 .. _HSV: https://en.wikipedia.org/wiki/HSL_and_HSV
429 """
430 return cls.from_rgb(*cv.hsv_to_rgb(h, s, v))
432 @classmethod
433 def from_cmy(cls, c, m, y):
434 """
435 Construct a :class:`Color` from `CMY`_ (cyan, magenta, yellow) floats
436 between 0.0 and 1.0.
438 .. note::
440 This conversion uses the basic subtractive method which is not
441 accurate for color reproduction on print devices. See the `Color
442 FAQ`_ for more information.
444 .. _Color FAQ: http://poynton.ca/notes/colour_and_gamma/ColorFAQ.html#RTFToC24
445 .. _CMY: https://en.wikipedia.org/wiki/CMYK_color_model
446 """
447 return cls.from_rgb(*cv.cmy_to_rgb(c, m, y))
449 @classmethod
450 def from_cmyk(cls, c, m, y, k):
451 """
452 Construct a :class:`Color` from `CMYK`_ (cyan, magenta, yellow, black)
453 floats between 0.0 and 1.0.
455 .. note::
457 This conversion uses the basic subtractive method which is not
458 accurate for color reproduction on print devices. See the `Color
459 FAQ`_ for more information.
461 .. _Color FAQ: http://poynton.ca/notes/colour_and_gamma/ColorFAQ.html#RTFToC24
462 .. _CMYK: https://en.wikipedia.org/wiki/CMYK_color_model
463 """
464 return cls.from_cmy(*cv.cmyk_to_cmy(c, m, y, k))
466 @classmethod
467 def from_xyz(cls, x, y, z):
468 """
469 Construct a :class:`Color` from (X, Y, Z) float values representing
470 a color in the `CIE 1931 color space`_. The conversion assumes the
471 sRGB working space with reference white D65.
473 .. _CIE 1931 color space: https://en.wikipedia.org/wiki/CIE_1931_color_space
474 """
475 return cls.from_rgb(*cv.xyz_to_rgb(x, y, z))
477 @classmethod
478 def from_lab(cls, l, a, b):
479 """
480 Construct a :class:`Color` from (L*, a*, b*) float values representing
481 a color in the `CIE Lab color space`_. The conversion assumes the
482 sRGB working space with reference white D65.
484 .. _CIE Lab color space: https://en.wikipedia.org/wiki/Lab_color_space
485 """
486 return cls.from_xyz(*cv.lab_to_xyz(l, a, b))
488 @classmethod
489 def from_luv(cls, l, u, v):
490 """
491 Construct a :class:`Color` from (L*, u*, v*) float values representing
492 a color in the `CIE Luv color space`_. The conversion assumes the sRGB
493 working space with reference white D65.
495 .. _CIE Luv color space: https://en.wikipedia.org/wiki/CIELUV
496 """
497 return cls.from_xyz(*cv.luv_to_xyz(l, u, v))
499 def __add__(self, other):
500 if isinstance(other, types.RGB):
501 return Color.from_rgb(self.r + other.r,
502 self.g + other.g,
503 self.b + other.b)
504 elif isinstance(other, (attr.Red, attr.Green, attr.Blue)):
505 r, g, b = self
506 return Color.from_rgb(
507 r + other if isinstance(other, attr.Red) else r,
508 g + other if isinstance(other, attr.Green) else g,
509 b + other if isinstance(other, attr.Blue) else b,
510 )
511 elif isinstance(other, (attr.Hue, attr.Lightness, attr.Saturation)):
512 h, l, s = self.hls
513 return Color.from_hls(
514 h + other if isinstance(other, attr.Hue) else h,
515 l + other if isinstance(other, attr.Lightness) else l,
516 s + other if isinstance(other, attr.Saturation) else s,
517 )
518 elif isinstance(other, attr.Luma):
519 y, u, v = self.yuv
520 return Color.from_yuv(y + other, u, v)
521 return NotImplemented
523 def __radd__(self, other):
524 # Addition is commutative
525 if isinstance(other, (types.RGB,
526 attr.Red, attr.Green, attr.Blue,
527 attr.Hue, attr.Lightness, attr.Saturation,
528 attr.Luma)):
529 return self.__add__(other)
530 return NotImplemented
532 def __sub__(self, other):
533 if isinstance(other, types.RGB):
534 return Color.from_rgb(self.r - other.r,
535 self.g - other.g,
536 self.b - other.b)
537 elif isinstance(other, (attr.Red, attr.Green, attr.Blue)):
538 r, g, b = self.rgb
539 return Color.from_rgb(
540 r - other if isinstance(other, attr.Red) else r,
541 g - other if isinstance(other, attr.Green) else g,
542 b - other if isinstance(other, attr.Blue) else b,
543 )
544 elif isinstance(other, (attr.Hue, attr.Lightness, attr.Saturation)):
545 h, l, s = self.hls
546 return Color.from_hls(
547 h - other if isinstance(other, attr.Hue) else h,
548 l - other if isinstance(other, attr.Lightness) else l,
549 s - other if isinstance(other, attr.Saturation) else s,
550 )
551 elif isinstance(other, attr.Luma):
552 y, u, v = self.yuv
553 return Color.from_yuv(y - other, u, v)
554 return NotImplemented
556 def __rsub__(self, other):
557 if isinstance(other, (attr.Red, attr.Green, attr.Blue)):
558 r, g, b = self.rgb
559 return Color.from_rgb(
560 other - r if isinstance(other, attr.Red) else 0.0,
561 other - g if isinstance(other, attr.Green) else 0.0,
562 other - b if isinstance(other, attr.Blue) else 0.0,
563 )
564 return NotImplemented
566 def __mul__(self, other):
567 if isinstance(other, types.RGB):
568 return Color.from_rgb(self.r * other.r,
569 self.g * other.g,
570 self.b * other.b)
571 elif isinstance(other, (attr.Red, attr.Green, attr.Blue)):
572 r, g, b = self
573 return Color.from_rgb(
574 r * other if isinstance(other, attr.Red) else r,
575 g * other if isinstance(other, attr.Green) else g,
576 b * other if isinstance(other, attr.Blue) else b,
577 )
578 elif isinstance(other, (attr.Hue, attr.Lightness, attr.Saturation)):
579 h, l, s = self.hls
580 return Color.from_hls(
581 h * other if isinstance(other, attr.Hue) else h,
582 l * other if isinstance(other, attr.Lightness) else l,
583 s * other if isinstance(other, attr.Saturation) else s,
584 )
585 elif isinstance(other, attr.Luma):
586 y, u, v = self.yuv
587 return Color.from_yuv(y * other, u, v)
588 return NotImplemented
590 def __rmul__(self, other):
591 # Multiplication is commutative
592 if isinstance(other, (types.RGB,
593 attr.Red, attr.Green, attr.Blue,
594 attr.Hue, attr.Lightness, attr.Saturation,
595 attr.Luma)):
596 return self.__mul__(other)
597 return NotImplemented
599 _format_re = re.compile(r'^(?P<back>[fb])?(?P<term>0|8|256|16[mM])?$')
600 def __format__(self, format_spec):
601 m = Color._format_re.match(format_spec)
602 if not m:
603 raise ValueError('Invalid format %r for Color' % format_spec)
604 back = m.group('back')
605 term = m.group('term')
606 if term == '0':
607 args = ({
608 None: 0,
609 'f': 39,
610 'b': 49,
611 }[back],)
612 elif term in (None, '8'):
613 table = tables.DOS_COLORS
614 if back == 'b':
615 code = 40
616 table = {
617 k: (bold, index)
618 for k, (bold, index) in table.items()
619 if not bold
620 }
621 else:
622 code = 30
623 try:
624 bold, index = table[self.rgb_bytes]
625 except KeyError:
626 bold, index = sorted(
627 (self.difference(Color.from_rgb_bytes(*color)), bold, index)
628 for color, (bold, index) in table.items()
629 )[0][1:]
630 args = (1,) if bold else ()
631 args += (code + index,)
632 elif term == '256':
633 code = 48 if back == 'b' else 38
634 try:
635 index = tables.XTERM_COLORS[self.rgb_bytes]
636 except KeyError:
637 index = sorted(
638 (self.difference(Color.from_rgb_bytes(*color)), index)
639 for color, index in tables.XTERM_COLORS.items()
640 )[0][1]
641 args = (48 if back == 'b' else 38, 5, index)
642 elif term.lower() == '16m':
643 args = (48 if back == 'b' else 38, 2) + self.rgb_bytes
644 else:
645 assert False # pragma: no cover
646 return '\x1b[' + ';'.join(str(i) for i in args) + 'm'
648 def __str__(self):
649 return self.html
651 def __repr__(self):
652 try:
653 return {
654 'default': lambda: '<Color html=%r rgb=(%g, %g, %g)>' % (self.html, self.r, self.g, self.b),
655 'term16m': lambda: '<Color {self:16m}###{self:0} rgb=({self.r:g}, {self.g:g}, {self.b:g})>'.format(self=self),
656 'term256': lambda: '<Color {self:256}###{self:0} rgb=({self.r:g}, {self.g:g}, {self.b:g})>'.format(self=self),
657 'html': lambda: 'Color(%r)' % self.html,
658 'rgb': lambda: 'Color(%g, %g, %g)' % self.rgb,
659 }[Color.repr_style]()
660 except KeyError:
661 raise ValueError('invalid repr_style value: %s' % Color.repr_style)
663 @property
664 def html(self):
665 """
666 Returns the color as a string in HTML #RRGGBB format.
667 """
668 return cv.rgb_bytes_to_html(*self.rgb_bytes)
670 @property
671 def rgb(self):
672 """
673 Return a simple 3-tuple of (r, g, b) float values in the range 0.0 <= n
674 <= 1.0.
676 .. note::
678 The :class:`Color` class can already be treated as such a 3-tuple
679 but for the cases where you want a straight
680 :func:`~collections.namedtuple` this property is available.
681 """
682 return types.RGB(*self)
684 @property
685 def rgb565(self):
686 """
687 Returns an unsigned 16-bit integer number representing the color in
688 the RGB565 encoding.
689 """
690 return cv.rgb_to_rgb565(*self)
692 @property
693 def rgb_bytes(self):
694 """
695 Returns a 3-tuple of (red, green, blue) byte values.
696 """
697 return cv.rgb_to_rgb_bytes(*self)
699 @property
700 def yuv(self):
701 """
702 Returns a 3-tuple of (y, u, v) float values; Y values can be between
703 0.0 and 1.0, U values are between -0.436 and 0.436, and V values are
704 between -0.615 and 0.615.
705 """
706 return cv.rgb_to_yuv(*self)
708 @property
709 def yuv_bytes(self):
710 """
711 Returns a 3-tuple of (y, u, v) byte values. Y values are biased by 16
712 in the result to prevent negatives. U and V values are biased by 128
713 for the same purpose.
714 """
715 return cv.rgb_bytes_to_yuv_bytes(*self.rgb_bytes)
717 @property
718 def yiq(self):
719 """
720 Returns a 3-tuple of (y, i, q) float values; y values can be between
721 0.0 and 1.0, whilst i and q values can be between -1.0 and 1.0.
722 """
723 return cv.rgb_to_yiq(*self)
725 @property
726 def xyz(self):
727 """
728 Returns a 3-tuple of (X, Y, Z) float values representing the color in
729 the `CIE 1931 color space`_. The conversion assumes the sRGB working
730 space, with reference white D65.
732 .. _CIE 1931 color space: https://en.wikipedia.org/wiki/CIE_1931_color_space
733 """
734 return cv.rgb_to_xyz(*self)
736 @property
737 def lab(self):
738 """
739 Returns a 3-tuple of (L*, a*, b*) float values representing the color
740 in the `CIE Lab color space`_ with the `D65 standard illuminant`_.
742 .. _CIE Lab color space: https://en.wikipedia.org/wiki/Lab_color_space
743 .. _D65 standard illuminant: https://en.wikipedia.org/wiki/Illuminant_D65
744 """
745 return cv.xyz_to_lab(*self.xyz)
747 @property
748 def luv(self):
749 """
750 Returns a 3-tuple of (L*, u*, v*) float values representing the color
751 in the `CIE Luv color space`_ with the `D65 standard illuminant`_.
753 .. _CIE Luv color space: https://en.wikipedia.org/wiki/CIELUV
754 """
755 return cv.xyz_to_luv(*self.xyz)
757 @property
758 def hls(self):
759 """
760 Returns a 3-tuple of (hue, lightness, saturation) float values (between
761 0.0 and 1.0).
762 """
763 return cv.rgb_to_hls(*self)
765 @property
766 def hsv(self):
767 """
768 Returns a 3-tuple of (hue, saturation, value) float values (between 0.0
769 and 1.0).
770 """
771 return cv.rgb_to_hsv(*self)
773 @property
774 def cmy(self):
775 """
776 Returns a 3-tuple of (cyan, magenta, yellow) float values (between 0.0
777 and 1.0).
779 .. note::
781 This conversion uses the basic subtractive method which is not
782 accurate for color reproduction on print devices. See the `Color
783 FAQ`_ for more information.
785 .. _Color FAQ: http://poynton.ca/notes/colour_and_gamma/ColorFAQ.html#RTFToC24
786 """
787 return cv.rgb_to_cmy(*self)
789 @property
790 def cmyk(self):
791 """
792 Returns a 4-tuple of (cyan, magenta, yellow, black) float values
793 (between 0.0 and 1.0).
795 .. note::
797 This conversion uses the basic subtractive method which is not
798 accurate for color reproduction on print devices. See the `Color
799 FAQ`_ for more information.
801 .. _Color FAQ: http://poynton.ca/notes/colour_and_gamma/ColorFAQ.html#RTFToC24
802 """
803 return cv.cmy_to_cmyk(*self.cmy)
805 @property
806 def hue(self):
807 """
808 Returns the hue of the color as a :class:`Hue` instance which can be
809 used in operations with other :class:`Color` instances.
810 """
811 return attr.Hue(self.hls[0])
813 @property
814 def lightness(self):
815 """
816 Returns the lightness of the color as a :class:`Lightness` instance
817 which can be used in operations with other :class:`Color` instances.
818 """
819 return attr.Lightness(self.hls[1])
821 @property
822 def saturation(self):
823 """
824 Returns the saturation of the color as a :class:`Saturation` instance
825 which can be used in operations with other :class:`Color` instances.
826 """
827 return attr.Saturation(self.hls[2])
829 @property
830 def luma(self):
831 """
832 Returns the `luma`_ of the color as a :class:`Luma` instance which can
833 be used in operations with other :class:`Color` instances.
835 .. _luma: https://en.wikipedia.org/wiki/Luma_(video)
836 """
837 return attr.Luma(self.yuv[0])
839 def difference(self, other, method='euclid'):
840 """
841 Determines the difference between this color and *other* using the
842 specified *method*.
844 :param Color other:
845 The color to compare this color to.
847 :param str method:
848 The algorithm to use in the comparison. Valid values are:
850 * 'euclid' - This is the default method. Calculate the `Euclidian
851 distance`_. This is by far the fastest method, but also the least
852 accurate in terms of human perception.
853 * 'cie1976' - Use the `CIE 1976`_ formula for calculating the
854 difference between two colors in CIE Lab space.
855 * 'cie1994g' - Use the `CIE 1994`_ formula with the "graphic arts"
856 bias for calculating the difference.
857 * 'cie1994t' - Use the `CIE 1994`_ forumula with the "textiles"
858 bias for calculating the difference.
859 * 'ciede2000' - Use the `CIEDE 2000`_ formula for calculating the
860 difference.
862 :returns:
863 A :class:`float` indicating how different the two colors are. Note
864 that the Euclidian distance will be significantly different to the
865 other calculations; effectively this just measures the distance
866 between the two colors by treating them as coordinates in a three
867 dimensional Euclidian space. All other methods are means of
868 calculating a `Delta E`_ value in which 2.3 is considered a
869 `just-noticeable difference`_ (JND).
871 For example::
873 >>> Color('red').difference(Color('red'))
874 0.0
875 >>> Color('red').difference(Color('red'), method='cie1976')
876 0.0
877 >>> Color('red').difference(Color('#900'))
878 0.4
879 >>> Color('red').difference(Color('#900'), method='cie1976')
880 40.17063087142142
881 >>> Color('red').difference(Color('#900'), method='ciede2000')
882 21.078146289272155
883 >>> Color('red').difference(Color('blue'))
884 1.4142135623730951
885 >>> Color('red').difference(Color('blue'), method='cie1976')
886 176.31403908880046
888 .. note::
890 Instead of using this method, you may wish to simply use the
891 various difference functions (:func:`euclid`, :func:`cie1976`,
892 etc.) directly.
894 .. _Delta E: https://en.wikipedia.org/wiki/Color_difference
895 .. _just-noticeable difference: https://en.wikipedia.org/wiki/Just-noticeable_difference
896 .. _Euclidian distance: https://en.wikipedia.org/wiki/Euclidean_distance
897 .. _CIE 1976: https://en.wikipedia.org/wiki/Color_difference#CIE76
898 .. _CIE 1994: https://en.wikipedia.org/wiki/Color_difference#CIE94
899 .. _CIEDE 2000: https://en.wikipedia.org/wiki/Color_difference#CIEDE2000
900 """
901 if isinstance(method, bytes):
902 method = method.decode('ascii')
903 try:
904 fn = getattr(deltae, method)
905 except AttributeError:
906 raise ValueError('invalid method: %s' % method)
907 else:
908 if method.startswith('cie'):
909 return fn(self.lab, other.lab)
910 else:
911 return fn(self, other)
913 def gradient(self, other, steps=10, easing=easings.linear):
914 """
915 Returns a generator which fades between this color and *other* in the
916 specified number of *steps*.
918 :param Color other:
919 The color that will end the gradient (with the color the method is
920 called upon starting the gradient)
922 :param int steps:
923 The unqiue number of colors to include in the generated gradient.
924 Defaults to 10 if unspecified.
926 :param callable easing:
927 A function which controls the speed of the progression. If
928 specified, if must be a function which takes a single parameter,
929 the number of *steps*, and yields a sequence of values between 0.0
930 (representing the start of the gradient) and 1.0 (representing the
931 end). The default is :func:`linear`.
933 :return:
934 A generator yielding *steps* :class:`Color` instances which fade
935 from this color to *other*.
937 For example::
939 >>> Color.repr_style = 'html'
940 >>> print('\\n'.join(
941 ... repr(c) for c in
942 ... Color('red').gradient(Color('green'))
943 ... ))
944 Color('#ff0000')
945 Color('#e30e00')
946 Color('#c61c00')
947 Color('#aa2b00')
948 Color('#8e3900')
949 Color('#714700')
950 Color('#555500')
951 Color('#396400')
952 Color('#1c7200')
953 Color('#008000')
955 .. versionadded:: 1.1
956 """
957 if steps < 2:
958 raise ValueError('steps must be >= 2')
959 # NOTE: Can't simply subtract self from other here, as the result will
960 # be clamped and we want the actual result.
961 delta = types.RGB(*(
962 other_i - self_i
963 for self_i, other_i in zip(self, other)
964 ))
965 for t in easing(steps):
966 yield self + types.RGB(*(delta_i * t for delta_i in delta))