Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/colour.py: 64%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2"""Color Library
4.. :doctest:
6This module defines several color formats that can be converted to one or
7another.
9Formats
10-------
12HSL:
13 3-uple of Hue, Saturation, Lightness all between 0.0 and 1.0
15RGB:
16 3-uple of Red, Green, Blue all between 0.0 and 1.0
18HEX:
19 string object beginning with '#' and with red, green, blue value.
20 This format accept color in 3 or 6 value ex: '#fff' or '#ffffff'
22WEB:
23 string object that defaults to HEX representation or human if possible
25Usage
26-----
28Several function exists to convert from one format to another. But all
29function are not written. So the best way is to use the object Color.
31Please see the documentation of this object for more information.
33.. note:: Some constants are defined for convenience in HSL, RGB, HEX
35"""
37from __future__ import with_statement, print_function
39import hashlib
40import re
41import sys
44##
45## Some Constants
46##
48## Soften inequalities and some rounding issue based on float
49FLOAT_ERROR = 0.0000005
52RGB_TO_COLOR_NAMES = {
53 (0, 0, 0): ['Black'],
54 (0, 0, 128): ['Navy', 'NavyBlue'],
55 (0, 0, 139): ['DarkBlue'],
56 (0, 0, 205): ['MediumBlue'],
57 (0, 0, 255): ['Blue'],
58 (0, 100, 0): ['DarkGreen'],
59 (0, 128, 0): ['Green'],
60 (0, 139, 139): ['DarkCyan'],
61 (0, 191, 255): ['DeepSkyBlue'],
62 (0, 206, 209): ['DarkTurquoise'],
63 (0, 250, 154): ['MediumSpringGreen'],
64 (0, 255, 0): ['Lime'],
65 (0, 255, 127): ['SpringGreen'],
66 (0, 255, 255): ['Cyan', 'Aqua'],
67 (25, 25, 112): ['MidnightBlue'],
68 (30, 144, 255): ['DodgerBlue'],
69 (32, 178, 170): ['LightSeaGreen'],
70 (34, 139, 34): ['ForestGreen'],
71 (46, 139, 87): ['SeaGreen'],
72 (47, 79, 79): ['DarkSlateGray', 'DarkSlateGrey'],
73 (50, 205, 50): ['LimeGreen'],
74 (60, 179, 113): ['MediumSeaGreen'],
75 (64, 224, 208): ['Turquoise'],
76 (65, 105, 225): ['RoyalBlue'],
77 (70, 130, 180): ['SteelBlue'],
78 (72, 61, 139): ['DarkSlateBlue'],
79 (72, 209, 204): ['MediumTurquoise'],
80 (75, 0, 130): ['Indigo'],
81 (85, 107, 47): ['DarkOliveGreen'],
82 (95, 158, 160): ['CadetBlue'],
83 (100, 149, 237): ['CornflowerBlue'],
84 (102, 205, 170): ['MediumAquamarine'],
85 (105, 105, 105): ['DimGray', 'DimGrey'],
86 (106, 90, 205): ['SlateBlue'],
87 (107, 142, 35): ['OliveDrab'],
88 (112, 128, 144): ['SlateGray', 'SlateGrey'],
89 (119, 136, 153): ['LightSlateGray', 'LightSlateGrey'],
90 (123, 104, 238): ['MediumSlateBlue'],
91 (124, 252, 0): ['LawnGreen'],
92 (127, 255, 0): ['Chartreuse'],
93 (127, 255, 212): ['Aquamarine'],
94 (128, 0, 0): ['Maroon'],
95 (128, 0, 128): ['Purple'],
96 (128, 128, 0): ['Olive'],
97 (128, 128, 128): ['Gray', 'Grey'],
98 (132, 112, 255): ['LightSlateBlue'],
99 (135, 206, 235): ['SkyBlue'],
100 (135, 206, 250): ['LightSkyBlue'],
101 (138, 43, 226): ['BlueViolet'],
102 (139, 0, 0): ['DarkRed'],
103 (139, 0, 139): ['DarkMagenta'],
104 (139, 69, 19): ['SaddleBrown'],
105 (143, 188, 143): ['DarkSeaGreen'],
106 (144, 238, 144): ['LightGreen'],
107 (147, 112, 219): ['MediumPurple'],
108 (148, 0, 211): ['DarkViolet'],
109 (152, 251, 152): ['PaleGreen'],
110 (153, 50, 204): ['DarkOrchid'],
111 (154, 205, 50): ['YellowGreen'],
112 (160, 82, 45): ['Sienna'],
113 (165, 42, 42): ['Brown'],
114 (169, 169, 169): ['DarkGray', 'DarkGrey'],
115 (173, 216, 230): ['LightBlue'],
116 (173, 255, 47): ['GreenYellow'],
117 (175, 238, 238): ['PaleTurquoise'],
118 (176, 196, 222): ['LightSteelBlue'],
119 (176, 224, 230): ['PowderBlue'],
120 (178, 34, 34): ['Firebrick'],
121 (184, 134, 11): ['DarkGoldenrod'],
122 (186, 85, 211): ['MediumOrchid'],
123 (188, 143, 143): ['RosyBrown'],
124 (189, 183, 107): ['DarkKhaki'],
125 (192, 192, 192): ['Silver'],
126 (199, 21, 133): ['MediumVioletRed'],
127 (205, 92, 92): ['IndianRed'],
128 (205, 133, 63): ['Peru'],
129 (208, 32, 144): ['VioletRed'],
130 (210, 105, 30): ['Chocolate'],
131 (210, 180, 140): ['Tan'],
132 (211, 211, 211): ['LightGray', 'LightGrey'],
133 (216, 191, 216): ['Thistle'],
134 (218, 112, 214): ['Orchid'],
135 (218, 165, 32): ['Goldenrod'],
136 (219, 112, 147): ['PaleVioletRed'],
137 (220, 20, 60): ['Crimson'],
138 (220, 220, 220): ['Gainsboro'],
139 (221, 160, 221): ['Plum'],
140 (222, 184, 135): ['Burlywood'],
141 (224, 255, 255): ['LightCyan'],
142 (230, 230, 250): ['Lavender'],
143 (233, 150, 122): ['DarkSalmon'],
144 (238, 130, 238): ['Violet'],
145 (238, 221, 130): ['LightGoldenrod'],
146 (238, 232, 170): ['PaleGoldenrod'],
147 (240, 128, 128): ['LightCoral'],
148 (240, 230, 140): ['Khaki'],
149 (240, 248, 255): ['AliceBlue'],
150 (240, 255, 240): ['Honeydew'],
151 (240, 255, 255): ['Azure'],
152 (244, 164, 96): ['SandyBrown'],
153 (245, 222, 179): ['Wheat'],
154 (245, 245, 220): ['Beige'],
155 (245, 245, 245): ['WhiteSmoke'],
156 (245, 255, 250): ['MintCream'],
157 (248, 248, 255): ['GhostWhite'],
158 (250, 128, 114): ['Salmon'],
159 (250, 235, 215): ['AntiqueWhite'],
160 (250, 240, 230): ['Linen'],
161 (250, 250, 210): ['LightGoldenrodYellow'],
162 (253, 245, 230): ['OldLace'],
163 (255, 0, 0): ['Red'],
164 (255, 0, 255): ['Magenta', 'Fuchsia'],
165 (255, 20, 147): ['DeepPink'],
166 (255, 69, 0): ['OrangeRed'],
167 (255, 99, 71): ['Tomato'],
168 (255, 105, 180): ['HotPink'],
169 (255, 127, 80): ['Coral'],
170 (255, 140, 0): ['DarkOrange'],
171 (255, 160, 122): ['LightSalmon'],
172 (255, 165, 0): ['Orange'],
173 (255, 182, 193): ['LightPink'],
174 (255, 192, 203): ['Pink'],
175 (255, 215, 0): ['Gold'],
176 (255, 218, 185): ['PeachPuff'],
177 (255, 222, 173): ['NavajoWhite'],
178 (255, 228, 181): ['Moccasin'],
179 (255, 228, 196): ['Bisque'],
180 (255, 228, 225): ['MistyRose'],
181 (255, 235, 205): ['BlanchedAlmond'],
182 (255, 239, 213): ['PapayaWhip'],
183 (255, 240, 245): ['LavenderBlush'],
184 (255, 245, 238): ['Seashell'],
185 (255, 248, 220): ['Cornsilk'],
186 (255, 250, 205): ['LemonChiffon'],
187 (255, 250, 240): ['FloralWhite'],
188 (255, 250, 250): ['Snow'],
189 (255, 255, 0): ['Yellow'],
190 (255, 255, 224): ['LightYellow'],
191 (255, 255, 240): ['Ivory'],
192 (255, 255, 255): ['White']
193}
195## Building inverse relation
196COLOR_NAME_TO_RGB = dict(
197 (name.lower(), rgb)
198 for rgb, names in RGB_TO_COLOR_NAMES.items()
199 for name in names)
202LONG_HEX_COLOR = re.compile(r'^#[0-9a-fA-F]{6}$')
203SHORT_HEX_COLOR = re.compile(r'^#[0-9a-fA-F]{3}$')
206class C_HSL:
208 def __getattr__(self, value):
209 label = value.lower()
210 if label in COLOR_NAME_TO_RGB:
211 return rgb2hsl(tuple(v / 255. for v in COLOR_NAME_TO_RGB[label]))
212 raise AttributeError("%s instance has no attribute %r"
213 % (self.__class__, value))
216HSL = C_HSL()
219class C_RGB:
220 """RGB colors container
222 Provides a quick color access.
224 >>> from colour import RGB
226 >>> RGB.WHITE
227 (1.0, 1.0, 1.0)
228 >>> RGB.BLUE
229 (0.0, 0.0, 1.0)
231 >>> RGB.DONOTEXISTS # doctest: +ELLIPSIS
232 Traceback (most recent call last):
233 ...
234 AttributeError: ... has no attribute 'DONOTEXISTS'
236 """
238 def __getattr__(self, value):
239 return hsl2rgb(getattr(HSL, value))
242class C_HEX:
243 """RGB colors container
245 Provides a quick color access.
247 >>> from colour import HEX
249 >>> HEX.WHITE
250 '#fff'
251 >>> HEX.BLUE
252 '#00f'
254 >>> HEX.DONOTEXISTS # doctest: +ELLIPSIS
255 Traceback (most recent call last):
256 ...
257 AttributeError: ... has no attribute 'DONOTEXISTS'
259 """
261 def __getattr__(self, value):
262 return rgb2hex(getattr(RGB, value))
264RGB = C_RGB()
265HEX = C_HEX()
268##
269## Conversion function
270##
272def hsl2rgb(hsl):
273 """Convert HSL representation towards RGB
275 :param h: Hue, position around the chromatic circle (h=1 equiv h=0)
276 :param s: Saturation, color saturation (0=full gray, 1=full color)
277 :param l: Ligthness, Overhaul lightness (0=full black, 1=full white)
278 :rtype: 3-uple for RGB values in float between 0 and 1
280 Hue, Saturation, Range from Lightness is a float between 0 and 1
282 Note that Hue can be set to any value but as it is a rotation
283 around the chromatic circle, any value above 1 or below 0 can
284 be expressed by a value between 0 and 1 (Note that h=0 is equiv
285 to h=1).
287 This algorithm came from:
288 http://www.easyrgb.com/index.php?X=MATH&H=19#text19
290 Here are some quick notion of HSL to RGB conversion:
292 >>> from colour import hsl2rgb
294 With a lightness put at 0, RGB is always rgbblack
296 >>> hsl2rgb((0.0, 0.0, 0.0))
297 (0.0, 0.0, 0.0)
298 >>> hsl2rgb((0.5, 0.0, 0.0))
299 (0.0, 0.0, 0.0)
300 >>> hsl2rgb((0.5, 0.5, 0.0))
301 (0.0, 0.0, 0.0)
303 Same for lightness put at 1, RGB is always rgbwhite
305 >>> hsl2rgb((0.0, 0.0, 1.0))
306 (1.0, 1.0, 1.0)
307 >>> hsl2rgb((0.5, 0.0, 1.0))
308 (1.0, 1.0, 1.0)
309 >>> hsl2rgb((0.5, 0.5, 1.0))
310 (1.0, 1.0, 1.0)
312 With saturation put at 0, the RGB should be equal to Lightness:
314 >>> hsl2rgb((0.0, 0.0, 0.25))
315 (0.25, 0.25, 0.25)
316 >>> hsl2rgb((0.5, 0.0, 0.5))
317 (0.5, 0.5, 0.5)
318 >>> hsl2rgb((0.5, 0.0, 0.75))
319 (0.75, 0.75, 0.75)
321 With saturation put at 1, and lightness put to 0.5, we can find
322 normal full red, green, blue colors:
324 >>> hsl2rgb((0 , 1.0, 0.5))
325 (1.0, 0.0, 0.0)
326 >>> hsl2rgb((1 , 1.0, 0.5))
327 (1.0, 0.0, 0.0)
328 >>> hsl2rgb((1.0/3 , 1.0, 0.5))
329 (0.0, 1.0, 0.0)
330 >>> hsl2rgb((2.0/3 , 1.0, 0.5))
331 (0.0, 0.0, 1.0)
333 Of course:
334 >>> hsl2rgb((0.0, 2.0, 0.5)) # doctest: +ELLIPSIS
335 Traceback (most recent call last):
336 ...
337 ValueError: Saturation must be between 0 and 1.
339 And:
340 >>> hsl2rgb((0.0, 0.0, 1.5)) # doctest: +ELLIPSIS
341 Traceback (most recent call last):
342 ...
343 ValueError: Lightness must be between 0 and 1.
345 """
346 h, s, l = [float(v) for v in hsl]
348 if not (0.0 - FLOAT_ERROR <= s <= 1.0 + FLOAT_ERROR):
349 raise ValueError("Saturation must be between 0 and 1.")
350 if not (0.0 - FLOAT_ERROR <= l <= 1.0 + FLOAT_ERROR):
351 raise ValueError("Lightness must be between 0 and 1.")
353 if s == 0:
354 return l, l, l
356 if l < 0.5:
357 v2 = l * (1.0 + s)
358 else:
359 v2 = (l + s) - (s * l)
361 v1 = 2.0 * l - v2
363 r = _hue2rgb(v1, v2, h + (1.0 / 3))
364 g = _hue2rgb(v1, v2, h)
365 b = _hue2rgb(v1, v2, h - (1.0 / 3))
367 return r, g, b
370def rgb2hsl(rgb):
371 """Convert RGB representation towards HSL
373 :param r: Red amount (float between 0 and 1)
374 :param g: Green amount (float between 0 and 1)
375 :param b: Blue amount (float between 0 and 1)
376 :rtype: 3-uple for HSL values in float between 0 and 1
378 This algorithm came from:
379 http://www.easyrgb.com/index.php?X=MATH&H=19#text19
381 Here are some quick notion of RGB to HSL conversion:
383 >>> from colour import rgb2hsl
385 Note that if red amount is equal to green and blue, then you
386 should have a gray value (from black to white).
389 >>> rgb2hsl((1.0, 1.0, 1.0)) # doctest: +ELLIPSIS
390 (..., 0.0, 1.0)
391 >>> rgb2hsl((0.5, 0.5, 0.5)) # doctest: +ELLIPSIS
392 (..., 0.0, 0.5)
393 >>> rgb2hsl((0.0, 0.0, 0.0)) # doctest: +ELLIPSIS
394 (..., 0.0, 0.0)
396 If only one color is different from the others, it defines the
397 direct Hue:
399 >>> rgb2hsl((0.5, 0.5, 1.0)) # doctest: +ELLIPSIS
400 (0.66..., 1.0, 0.75)
401 >>> rgb2hsl((0.2, 0.1, 0.1)) # doctest: +ELLIPSIS
402 (0.0, 0.33..., 0.15...)
404 Having only one value set, you can check that:
406 >>> rgb2hsl((1.0, 0.0, 0.0))
407 (0.0, 1.0, 0.5)
408 >>> rgb2hsl((0.0, 1.0, 0.0)) # doctest: +ELLIPSIS
409 (0.33..., 1.0, 0.5)
410 >>> rgb2hsl((0.0, 0.0, 1.0)) # doctest: +ELLIPSIS
411 (0.66..., 1.0, 0.5)
413 Regression check upon very close values in every component of
414 red, green and blue:
416 >>> rgb2hsl((0.9999999999999999, 1.0, 0.9999999999999994))
417 (0.0, 0.0, 0.999...)
419 Of course:
421 >>> rgb2hsl((0.0, 2.0, 0.5)) # doctest: +ELLIPSIS
422 Traceback (most recent call last):
423 ...
424 ValueError: Green must be between 0 and 1. You provided 2.0.
426 And:
427 >>> rgb2hsl((0.0, 0.0, 1.5)) # doctest: +ELLIPSIS
428 Traceback (most recent call last):
429 ...
430 ValueError: Blue must be between 0 and 1. You provided 1.5.
432 """
433 r, g, b = [float(v) for v in rgb]
435 for name, v in {'Red': r, 'Green': g, 'Blue': b}.items():
436 if not (0 - FLOAT_ERROR <= v <= 1 + FLOAT_ERROR):
437 raise ValueError("%s must be between 0 and 1. You provided %r."
438 % (name, v))
440 vmin = min(r, g, b) ## Min. value of RGB
441 vmax = max(r, g, b) ## Max. value of RGB
442 diff = vmax - vmin ## Delta RGB value
444 vsum = vmin + vmax
446 l = vsum / 2
448 if diff < FLOAT_ERROR: ## This is a gray, no chroma...
449 return (0.0, 0.0, l)
451 ##
452 ## Chromatic data...
453 ##
455 ## Saturation
456 if l < 0.5:
457 s = diff / vsum
458 else:
459 s = diff / (2.0 - vsum)
461 dr = (((vmax - r) / 6) + (diff / 2)) / diff
462 dg = (((vmax - g) / 6) + (diff / 2)) / diff
463 db = (((vmax - b) / 6) + (diff / 2)) / diff
465 if r == vmax:
466 h = db - dg
467 elif g == vmax:
468 h = (1.0 / 3) + dr - db
469 elif b == vmax:
470 h = (2.0 / 3) + dg - dr
472 if h < 0: h += 1
473 if h > 1: h -= 1
475 return (h, s, l)
478def _hue2rgb(v1, v2, vH):
479 """Private helper function (Do not call directly)
481 :param vH: rotation around the chromatic circle (between 0..1)
483 """
485 while vH < 0: vH += 1
486 while vH > 1: vH -= 1
488 if 6 * vH < 1: return v1 + (v2 - v1) * 6 * vH
489 if 2 * vH < 1: return v2
490 if 3 * vH < 2: return v1 + (v2 - v1) * ((2.0 / 3) - vH) * 6
492 return v1
495def rgb2hex(rgb, force_long=False):
496 """Transform RGB tuple to hex RGB representation
498 :param rgb: RGB 3-uple of float between 0 and 1
499 :rtype: 3 hex char or 6 hex char string representation
501 Usage
502 -----
504 >>> from colour import rgb2hex
506 >>> rgb2hex((0.0,1.0,0.0))
507 '#0f0'
509 Rounding try to be as natural as possible:
511 >>> rgb2hex((0.0,0.999999,1.0))
512 '#0ff'
514 And if not possible, the 6 hex char representation is used:
516 >>> rgb2hex((0.23,1.0,1.0))
517 '#3bffff'
519 >>> rgb2hex((0.0,0.999999,1.0), force_long=True)
520 '#00ffff'
522 """
524 hx = ''.join(["%02x" % int(c * 255 + 0.5 - FLOAT_ERROR)
525 for c in rgb])
527 if not force_long and hx[0::2] == hx[1::2]:
528 hx = ''.join(hx[0::2])
530 return "#%s" % hx
533def hex2rgb(str_rgb):
534 """Transform hex RGB representation to RGB tuple
536 :param str_rgb: 3 hex char or 6 hex char string representation
537 :rtype: RGB 3-uple of float between 0 and 1
539 >>> from colour import hex2rgb
541 >>> hex2rgb('#00ff00')
542 (0.0, 1.0, 0.0)
544 >>> hex2rgb('#0f0')
545 (0.0, 1.0, 0.0)
547 >>> hex2rgb('#aaa') # doctest: +ELLIPSIS
548 (0.66..., 0.66..., 0.66...)
550 >>> hex2rgb('#aa') # doctest: +ELLIPSIS
551 Traceback (most recent call last):
552 ...
553 ValueError: Invalid value '#aa' provided for rgb color.
555 """
557 try:
558 rgb = str_rgb[1:]
560 if len(rgb) == 6:
561 r, g, b = rgb[0:2], rgb[2:4], rgb[4:6]
562 elif len(rgb) == 3:
563 r, g, b = rgb[0] * 2, rgb[1] * 2, rgb[2] * 2
564 else:
565 raise ValueError()
566 except:
567 raise ValueError("Invalid value %r provided for rgb color."
568 % str_rgb)
570 return tuple([float(int(v, 16)) / 255 for v in (r, g, b)])
573def hex2web(hex):
574 """Converts HEX representation to WEB
576 :param rgb: 3 hex char or 6 hex char string representation
577 :rtype: web string representation (human readable if possible)
579 WEB representation uses X11 rgb.txt to define conversion
580 between RGB and english color names.
582 Usage
583 =====
585 >>> from colour import hex2web
587 >>> hex2web('#ff0000')
588 'red'
590 >>> hex2web('#aaaaaa')
591 '#aaa'
593 >>> hex2web('#abc')
594 '#abc'
596 >>> hex2web('#acacac')
597 '#acacac'
599 """
600 dec_rgb = tuple(int(v * 255) for v in hex2rgb(hex))
601 if dec_rgb in RGB_TO_COLOR_NAMES:
602 ## take the first one
603 color_name = RGB_TO_COLOR_NAMES[dec_rgb][0]
604 ## Enforce full lowercase for single worded color name.
605 return color_name if len(re.sub(r"[^A-Z]", "", color_name)) > 1 \
606 else color_name.lower()
608 # Hex format is verified by hex2rgb function. And should be 3 or 6 digit
609 if len(hex) == 7:
610 if hex[1] == hex[2] and \
611 hex[3] == hex[4] and \
612 hex[5] == hex[6]:
613 return '#' + hex[1] + hex[3] + hex[5]
614 return hex
617def web2hex(web, force_long=False):
618 """Converts WEB representation to HEX
620 :param rgb: web string representation (human readable if possible)
621 :rtype: 3 hex char or 6 hex char string representation
623 WEB representation uses X11 rgb.txt to define conversion
624 between RGB and english color names.
626 Usage
627 =====
629 >>> from colour import web2hex
631 >>> web2hex('red')
632 '#f00'
634 >>> web2hex('#aaa')
635 '#aaa'
637 >>> web2hex('#foo') # doctest: +ELLIPSIS
638 Traceback (most recent call last):
639 ...
640 AttributeError: '#foo' is not in web format. Need 3 or 6 hex digit.
642 >>> web2hex('#aaa', force_long=True)
643 '#aaaaaa'
645 >>> web2hex('#aaaaaa')
646 '#aaaaaa'
648 >>> web2hex('#aaaa') # doctest: +ELLIPSIS
649 Traceback (most recent call last):
650 ...
651 AttributeError: '#aaaa' is not in web format. Need 3 or 6 hex digit.
653 >>> web2hex('pinky') # doctest: +ELLIPSIS
654 Traceback (most recent call last):
655 ...
656 ValueError: 'pinky' is not a recognized color.
658 And color names are case insensitive:
660 >>> Color('RED')
661 <Color red>
663 """
664 if web.startswith('#'):
665 if (LONG_HEX_COLOR.match(web) or
666 (not force_long and SHORT_HEX_COLOR.match(web))):
667 return web.lower()
668 elif SHORT_HEX_COLOR.match(web) and force_long:
669 return '#' + ''.join([("%s" % (t, )) * 2 for t in web[1:]])
670 raise AttributeError(
671 "%r is not in web format. Need 3 or 6 hex digit." % web)
673 web = web.lower()
674 if web not in COLOR_NAME_TO_RGB:
675 raise ValueError("%r is not a recognized color." % web)
677 ## convert dec to hex:
679 return rgb2hex([float(int(v)) / 255 for v in COLOR_NAME_TO_RGB[web]],
680 force_long)
683## Missing functions conversion
685hsl2hex = lambda x: rgb2hex(hsl2rgb(x))
686hex2hsl = lambda x: rgb2hsl(hex2rgb(x))
687rgb2web = lambda x: hex2web(rgb2hex(x))
688web2rgb = lambda x: hex2rgb(web2hex(x))
689web2hsl = lambda x: rgb2hsl(web2rgb(x))
690hsl2web = lambda x: rgb2web(hsl2rgb(x))
693def color_scale(begin_hsl, end_hsl, nb):
694 """Returns a list of nb color HSL tuples between begin_hsl and end_hsl
696 >>> from colour import color_scale
698 >>> [rgb2hex(hsl2rgb(hsl)) for hsl in color_scale((0, 1, 0.5),
699 ... (1, 1, 0.5), 3)]
700 ['#f00', '#0f0', '#00f', '#f00']
702 >>> [rgb2hex(hsl2rgb(hsl))
703 ... for hsl in color_scale((0, 0, 0),
704 ... (0, 0, 1),
705 ... 15)] # doctest: +ELLIPSIS
706 ['#000', '#111', '#222', ..., '#ccc', '#ddd', '#eee', '#fff']
708 Of course, asking for negative values is not supported:
710 >>> color_scale((0, 1, 0.5), (1, 1, 0.5), -2)
711 Traceback (most recent call last):
712 ...
713 ValueError: Unsupported negative number of colors (nb=-2).
715 """
717 if nb < 0:
718 raise ValueError(
719 "Unsupported negative number of colors (nb=%r)." % nb)
721 step = tuple([float(end_hsl[i] - begin_hsl[i]) / nb for i in range(0, 3)]) \
722 if nb > 0 else (0, 0, 0)
724 def mul(step, value):
725 return tuple([v * value for v in step])
727 def add_v(step, step2):
728 return tuple([v + step2[i] for i, v in enumerate(step)])
730 return [add_v(begin_hsl, mul(step, r)) for r in range(0, nb + 1)]
733##
734## Color Pickers
735##
737def RGB_color_picker(obj):
738 """Build a color representation from the string representation of an object
740 This allows to quickly get a color from some data, with the
741 additional benefit that the color will be the same as long as the
742 (string representation of the) data is the same::
744 >>> from colour import RGB_color_picker, Color
746 Same inputs produce the same result::
748 >>> RGB_color_picker("Something") == RGB_color_picker("Something")
749 True
751 ... but different inputs produce different colors::
753 >>> RGB_color_picker("Something") != RGB_color_picker("Something else")
754 True
756 In any case, we still get a ``Color`` object::
758 >>> isinstance(RGB_color_picker("Something"), Color)
759 True
761 """
763 ## Turn the input into a by 3-dividable string. SHA-384 is good because it
764 ## divides into 3 components of the same size, which will be used to
765 ## represent the RGB values of the color.
766 digest = hashlib.sha384(str(obj).encode('utf-8')).hexdigest()
768 ## Split the digest into 3 sub-strings of equivalent size.
769 subsize = int(len(digest) / 3)
770 splitted_digest = [digest[i * subsize: (i + 1) * subsize]
771 for i in range(3)]
773 ## Convert those hexadecimal sub-strings into integer and scale them down
774 ## to the 0..1 range.
775 max_value = float(int("f" * subsize, 16))
776 components = (
777 int(d, 16) ## Make a number from a list with hex digits
778 / max_value ## Scale it down to [0.0, 1.0]
779 for d in splitted_digest)
781 return Color(rgb2hex(components)) ## Profit!
784def hash_or_str(obj):
785 try:
786 return hash((type(obj).__name__, obj))
787 except TypeError:
788 ## Adds the type name to make sure two object of different type but
789 ## identical string representation get distinguished.
790 return type(obj).__name__ + str(obj)
793##
794## All purpose object
795##
797class Color(object):
798 """Abstraction of a color object
800 Color object keeps information of a color. It can input/output to different
801 format (HSL, RGB, HEX, WEB) and their partial representation.
803 >>> from colour import Color, HSL
805 >>> b = Color()
806 >>> b.hsl = HSL.BLUE
808 Access values
809 -------------
811 >>> b.hue # doctest: +ELLIPSIS
812 0.66...
813 >>> b.saturation
814 1.0
815 >>> b.luminance
816 0.5
818 >>> b.red
819 0.0
820 >>> b.blue
821 1.0
822 >>> b.green
823 0.0
825 >>> b.rgb
826 (0.0, 0.0, 1.0)
827 >>> b.hsl # doctest: +ELLIPSIS
828 (0.66..., 1.0, 0.5)
829 >>> b.hex
830 '#00f'
832 Change values
833 -------------
835 Let's change Hue toward red tint:
837 >>> b.hue = 0.0
838 >>> b.hex
839 '#f00'
841 >>> b.hue = 2.0/3
842 >>> b.hex
843 '#00f'
845 In the other way round:
847 >>> b.hex = '#f00'
848 >>> b.hsl
849 (0.0, 1.0, 0.5)
851 Long hex can be accessed directly:
853 >>> b.hex_l = '#123456'
854 >>> b.hex_l
855 '#123456'
856 >>> b.hex
857 '#123456'
859 >>> b.hex_l = '#ff0000'
860 >>> b.hex_l
861 '#ff0000'
862 >>> b.hex
863 '#f00'
865 Convenience
866 -----------
868 >>> c = Color('blue')
869 >>> c
870 <Color blue>
871 >>> c.hue = 0
872 >>> c
873 <Color red>
875 >>> c.saturation = 0.0
876 >>> c.hsl # doctest: +ELLIPSIS
877 (..., 0.0, 0.5)
878 >>> c.rgb
879 (0.5, 0.5, 0.5)
880 >>> c.hex
881 '#7f7f7f'
882 >>> c
883 <Color #7f7f7f>
885 >>> c.luminance = 0.0
886 >>> c
887 <Color black>
889 >>> c.hex
890 '#000'
892 >>> c.green = 1.0
893 >>> c.blue = 1.0
894 >>> c.hex
895 '#0ff'
896 >>> c
897 <Color cyan>
899 >>> c = Color('blue', luminance=0.75)
900 >>> c
901 <Color #7f7fff>
903 >>> c = Color('red', red=0.5)
904 >>> c
905 <Color #7f0000>
907 >>> print(c)
908 #7f0000
910 You can try to query unexisting attributes:
912 >>> c.lightness # doctest: +ELLIPSIS
913 Traceback (most recent call last):
914 ...
915 AttributeError: 'lightness' not found
917 TODO: could add HSV, CMYK, YUV conversion.
919# >>> b.hsv
920# >>> b.value
921# >>> b.cyan
922# >>> b.magenta
923# >>> b.yellow
924# >>> b.key
925# >>> b.cmyk
928 Recursive init
929 --------------
931 To support blind conversion of web strings (or already converted object),
932 the Color object supports instantiation with another Color object.
934 >>> Color(Color(Color('red')))
935 <Color red>
937 Equality support
938 ----------------
940 Default equality is RGB hex comparison:
942 >>> Color('red') == Color('blue')
943 False
944 >>> Color('red') == Color('red')
945 True
946 >>> Color('red') != Color('blue')
947 True
948 >>> Color('red') != Color('red')
949 False
951 But this can be changed:
953 >>> saturation_equality = lambda c1, c2: c1.luminance == c2.luminance
954 >>> Color('red', equality=saturation_equality) == Color('blue')
955 True
958 Subclassing support
959 -------------------
961 You should be able to subclass ``Color`` object without any issues::
963 >>> class Tint(Color):
964 ... pass
966 And keep the internal API working::
968 >>> Tint("red").hsl
969 (0.0, 1.0, 0.5)
971 """
973 _hsl = None ## internal representation
975 def __init__(self, color=None,
976 pick_for=None, picker=RGB_color_picker, pick_key=hash_or_str,
977 **kwargs):
979 if pick_key is None:
980 pick_key = lambda x: x
982 if pick_for is not None:
983 color = picker(pick_key(pick_for))
985 if isinstance(color, Color):
986 self.web = color.web
987 else:
988 self.web = color if color else 'black'
990 self.equality = RGB_equivalence
992 for k, v in kwargs.items():
993 setattr(self, k, v)
995 def __getattr__(self, label):
996 if label.startswith("get_"):
997 raise AttributeError("'%s' not found" % label)
998 try:
999 return getattr(self, 'get_' + label)()
1000 except AttributeError:
1001 raise AttributeError("'%s' not found" % label)
1003 def __setattr__(self, label, value):
1004 if label not in ["_hsl", "equality"]:
1005 fc = getattr(self, 'set_' + label)
1006 fc(value)
1007 else:
1008 self.__dict__[label] = value
1010 ##
1011 ## Get
1012 ##
1014 def get_hsl(self):
1015 return tuple(self._hsl)
1017 def get_hex(self):
1018 return rgb2hex(self.rgb)
1020 def get_hex_l(self):
1021 return rgb2hex(self.rgb, force_long=True)
1023 def get_rgb(self):
1024 return hsl2rgb(self.hsl)
1026 def get_hue(self):
1027 return self.hsl[0]
1029 def get_saturation(self):
1030 return self.hsl[1]
1032 def get_luminance(self):
1033 return self.hsl[2]
1035 def get_red(self):
1036 return self.rgb[0]
1038 def get_green(self):
1039 return self.rgb[1]
1041 def get_blue(self):
1042 return self.rgb[2]
1044 def get_web(self):
1045 return hex2web(self.hex)
1047 ##
1048 ## Set
1049 ##
1051 def set_hsl(self, value):
1052 self._hsl = list(value)
1054 def set_rgb(self, value):
1055 self.hsl = rgb2hsl(value)
1057 def set_hue(self, value):
1058 self._hsl[0] = value
1060 def set_saturation(self, value):
1061 self._hsl[1] = value
1063 def set_luminance(self, value):
1064 self._hsl[2] = value
1066 def set_red(self, value):
1067 _, g, b = self.rgb
1068 self.rgb = (value, g, b)
1070 def set_green(self, value):
1071 r, _, b = self.rgb
1072 self.rgb = (r, value, b)
1074 def set_blue(self, value):
1075 r, g, _ = self.rgb
1076 self.rgb = (r, g, value)
1078 def set_hex(self, value):
1079 self.rgb = hex2rgb(value)
1081 set_hex_l = set_hex
1083 def set_web(self, value):
1084 self.hex = web2hex(value)
1086 ## range of color generation
1088 def range_to(self, value, steps):
1089 for hsl in color_scale(self._hsl, Color(value).hsl, steps - 1):
1090 yield Color(hsl=hsl)
1092 ##
1093 ## Convenience
1094 ##
1096 def __str__(self):
1097 return "%s" % self.web
1099 def __repr__(self):
1100 return "<Color %s>" % self.web
1102 def __eq__(self, other):
1103 if isinstance(other, Color):
1104 return self.equality(self, other)
1105 return NotImplemented
1107 if sys.version_info[0] == 2:
1108 ## Note: intended to be a backport of python 3 behavior
1109 def __ne__(self, other):
1110 equal = self.__eq__(other)
1111 return equal if equal is NotImplemented else not equal
1114RGB_equivalence = lambda c1, c2: c1.hex_l == c2.hex_l
1115HSL_equivalence = lambda c1, c2: c1._hsl == c2._hsl
1118def make_color_factory(**kwargs_defaults):
1120 def ColorFactory(*args, **kwargs):
1121 new_kwargs = kwargs_defaults.copy()
1122 new_kwargs.update(kwargs)
1123 return Color(*args, **new_kwargs)
1124 return ColorFactory