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

233 statements  

1# -*- coding: utf-8 -*- 

2"""Color Library 

3 

4.. :doctest: 

5 

6This module defines several color formats that can be converted to one or 

7another. 

8 

9Formats 

10------- 

11 

12HSL: 

13 3-uple of Hue, Saturation, Lightness all between 0.0 and 1.0 

14 

15RGB: 

16 3-uple of Red, Green, Blue all between 0.0 and 1.0 

17 

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' 

21 

22WEB: 

23 string object that defaults to HEX representation or human if possible 

24 

25Usage 

26----- 

27 

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. 

30 

31Please see the documentation of this object for more information. 

32 

33.. note:: Some constants are defined for convenience in HSL, RGB, HEX 

34 

35""" 

36 

37from __future__ import with_statement, print_function 

38 

39import hashlib 

40import re 

41import sys 

42 

43 

44## 

45## Some Constants 

46## 

47 

48## Soften inequalities and some rounding issue based on float 

49FLOAT_ERROR = 0.0000005 

50 

51 

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} 

194 

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) 

200 

201 

202LONG_HEX_COLOR = re.compile(r'^#[0-9a-fA-F]{6}$') 

203SHORT_HEX_COLOR = re.compile(r'^#[0-9a-fA-F]{3}$') 

204 

205 

206class C_HSL: 

207 

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)) 

214 

215 

216HSL = C_HSL() 

217 

218 

219class C_RGB: 

220 """RGB colors container 

221 

222 Provides a quick color access. 

223 

224 >>> from colour import RGB 

225 

226 >>> RGB.WHITE 

227 (1.0, 1.0, 1.0) 

228 >>> RGB.BLUE 

229 (0.0, 0.0, 1.0) 

230 

231 >>> RGB.DONOTEXISTS # doctest: +ELLIPSIS 

232 Traceback (most recent call last): 

233 ... 

234 AttributeError: ... has no attribute 'DONOTEXISTS' 

235 

236 """ 

237 

238 def __getattr__(self, value): 

239 return hsl2rgb(getattr(HSL, value)) 

240 

241 

242class C_HEX: 

243 """RGB colors container 

244 

245 Provides a quick color access. 

246 

247 >>> from colour import HEX 

248 

249 >>> HEX.WHITE 

250 '#fff' 

251 >>> HEX.BLUE 

252 '#00f' 

253 

254 >>> HEX.DONOTEXISTS # doctest: +ELLIPSIS 

255 Traceback (most recent call last): 

256 ... 

257 AttributeError: ... has no attribute 'DONOTEXISTS' 

258 

259 """ 

260 

261 def __getattr__(self, value): 

262 return rgb2hex(getattr(RGB, value)) 

263 

264RGB = C_RGB() 

265HEX = C_HEX() 

266 

267 

268## 

269## Conversion function 

270## 

271 

272def hsl2rgb(hsl): 

273 """Convert HSL representation towards RGB 

274 

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 

279 

280 Hue, Saturation, Range from Lightness is a float between 0 and 1 

281 

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). 

286 

287 This algorithm came from: 

288 http://www.easyrgb.com/index.php?X=MATH&H=19#text19 

289 

290 Here are some quick notion of HSL to RGB conversion: 

291 

292 >>> from colour import hsl2rgb 

293 

294 With a lightness put at 0, RGB is always rgbblack 

295 

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) 

302 

303 Same for lightness put at 1, RGB is always rgbwhite 

304 

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) 

311 

312 With saturation put at 0, the RGB should be equal to Lightness: 

313 

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) 

320 

321 With saturation put at 1, and lightness put to 0.5, we can find 

322 normal full red, green, blue colors: 

323 

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) 

332 

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. 

338 

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. 

344 

345 """ 

346 h, s, l = [float(v) for v in hsl] 

347 

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.") 

352 

353 if s == 0: 

354 return l, l, l 

355 

356 if l < 0.5: 

357 v2 = l * (1.0 + s) 

358 else: 

359 v2 = (l + s) - (s * l) 

360 

361 v1 = 2.0 * l - v2 

362 

363 r = _hue2rgb(v1, v2, h + (1.0 / 3)) 

364 g = _hue2rgb(v1, v2, h) 

365 b = _hue2rgb(v1, v2, h - (1.0 / 3)) 

366 

367 return r, g, b 

368 

369 

370def rgb2hsl(rgb): 

371 """Convert RGB representation towards HSL 

372 

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 

377 

378 This algorithm came from: 

379 http://www.easyrgb.com/index.php?X=MATH&H=19#text19 

380 

381 Here are some quick notion of RGB to HSL conversion: 

382 

383 >>> from colour import rgb2hsl 

384 

385 Note that if red amount is equal to green and blue, then you 

386 should have a gray value (from black to white). 

387 

388 

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) 

395 

396 If only one color is different from the others, it defines the 

397 direct Hue: 

398 

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...) 

403 

404 Having only one value set, you can check that: 

405 

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) 

412 

413 Regression check upon very close values in every component of 

414 red, green and blue: 

415 

416 >>> rgb2hsl((0.9999999999999999, 1.0, 0.9999999999999994)) 

417 (0.0, 0.0, 0.999...) 

418 

419 Of course: 

420 

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. 

425 

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. 

431 

432 """ 

433 r, g, b = [float(v) for v in rgb] 

434 

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)) 

439 

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 

443 

444 vsum = vmin + vmax 

445 

446 l = vsum / 2 

447 

448 if diff < FLOAT_ERROR: ## This is a gray, no chroma... 

449 return (0.0, 0.0, l) 

450 

451 ## 

452 ## Chromatic data... 

453 ## 

454 

455 ## Saturation 

456 if l < 0.5: 

457 s = diff / vsum 

458 else: 

459 s = diff / (2.0 - vsum) 

460 

461 dr = (((vmax - r) / 6) + (diff / 2)) / diff 

462 dg = (((vmax - g) / 6) + (diff / 2)) / diff 

463 db = (((vmax - b) / 6) + (diff / 2)) / diff 

464 

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 

471 

472 if h < 0: h += 1 

473 if h > 1: h -= 1 

474 

475 return (h, s, l) 

476 

477 

478def _hue2rgb(v1, v2, vH): 

479 """Private helper function (Do not call directly) 

480 

481 :param vH: rotation around the chromatic circle (between 0..1) 

482 

483 """ 

484 

485 while vH < 0: vH += 1 

486 while vH > 1: vH -= 1 

487 

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 

491 

492 return v1 

493 

494 

495def rgb2hex(rgb, force_long=False): 

496 """Transform RGB tuple to hex RGB representation 

497 

498 :param rgb: RGB 3-uple of float between 0 and 1 

499 :rtype: 3 hex char or 6 hex char string representation 

500 

501 Usage 

502 ----- 

503 

504 >>> from colour import rgb2hex 

505 

506 >>> rgb2hex((0.0,1.0,0.0)) 

507 '#0f0' 

508 

509 Rounding try to be as natural as possible: 

510 

511 >>> rgb2hex((0.0,0.999999,1.0)) 

512 '#0ff' 

513 

514 And if not possible, the 6 hex char representation is used: 

515 

516 >>> rgb2hex((0.23,1.0,1.0)) 

517 '#3bffff' 

518 

519 >>> rgb2hex((0.0,0.999999,1.0), force_long=True) 

520 '#00ffff' 

521 

522 """ 

523 

524 hx = ''.join(["%02x" % int(c * 255 + 0.5 - FLOAT_ERROR) 

525 for c in rgb]) 

526 

527 if not force_long and hx[0::2] == hx[1::2]: 

528 hx = ''.join(hx[0::2]) 

529 

530 return "#%s" % hx 

531 

532 

533def hex2rgb(str_rgb): 

534 """Transform hex RGB representation to RGB tuple 

535 

536 :param str_rgb: 3 hex char or 6 hex char string representation 

537 :rtype: RGB 3-uple of float between 0 and 1 

538 

539 >>> from colour import hex2rgb 

540 

541 >>> hex2rgb('#00ff00') 

542 (0.0, 1.0, 0.0) 

543 

544 >>> hex2rgb('#0f0') 

545 (0.0, 1.0, 0.0) 

546 

547 >>> hex2rgb('#aaa') # doctest: +ELLIPSIS 

548 (0.66..., 0.66..., 0.66...) 

549 

550 >>> hex2rgb('#aa') # doctest: +ELLIPSIS 

551 Traceback (most recent call last): 

552 ... 

553 ValueError: Invalid value '#aa' provided for rgb color. 

554 

555 """ 

556 

557 try: 

558 rgb = str_rgb[1:] 

559 

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) 

569 

570 return tuple([float(int(v, 16)) / 255 for v in (r, g, b)]) 

571 

572 

573def hex2web(hex): 

574 """Converts HEX representation to WEB 

575 

576 :param rgb: 3 hex char or 6 hex char string representation 

577 :rtype: web string representation (human readable if possible) 

578 

579 WEB representation uses X11 rgb.txt to define conversion 

580 between RGB and english color names. 

581 

582 Usage 

583 ===== 

584 

585 >>> from colour import hex2web 

586 

587 >>> hex2web('#ff0000') 

588 'red' 

589 

590 >>> hex2web('#aaaaaa') 

591 '#aaa' 

592 

593 >>> hex2web('#abc') 

594 '#abc' 

595 

596 >>> hex2web('#acacac') 

597 '#acacac' 

598 

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() 

607 

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 

615 

616 

617def web2hex(web, force_long=False): 

618 """Converts WEB representation to HEX 

619 

620 :param rgb: web string representation (human readable if possible) 

621 :rtype: 3 hex char or 6 hex char string representation 

622 

623 WEB representation uses X11 rgb.txt to define conversion 

624 between RGB and english color names. 

625 

626 Usage 

627 ===== 

628 

629 >>> from colour import web2hex 

630 

631 >>> web2hex('red') 

632 '#f00' 

633 

634 >>> web2hex('#aaa') 

635 '#aaa' 

636 

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. 

641 

642 >>> web2hex('#aaa', force_long=True) 

643 '#aaaaaa' 

644 

645 >>> web2hex('#aaaaaa') 

646 '#aaaaaa' 

647 

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. 

652 

653 >>> web2hex('pinky') # doctest: +ELLIPSIS 

654 Traceback (most recent call last): 

655 ... 

656 ValueError: 'pinky' is not a recognized color. 

657 

658 And color names are case insensitive: 

659 

660 >>> Color('RED') 

661 <Color red> 

662 

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) 

672 

673 web = web.lower() 

674 if web not in COLOR_NAME_TO_RGB: 

675 raise ValueError("%r is not a recognized color." % web) 

676 

677 ## convert dec to hex: 

678 

679 return rgb2hex([float(int(v)) / 255 for v in COLOR_NAME_TO_RGB[web]], 

680 force_long) 

681 

682 

683## Missing functions conversion 

684 

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)) 

691 

692 

693def color_scale(begin_hsl, end_hsl, nb): 

694 """Returns a list of nb color HSL tuples between begin_hsl and end_hsl 

695 

696 >>> from colour import color_scale 

697 

698 >>> [rgb2hex(hsl2rgb(hsl)) for hsl in color_scale((0, 1, 0.5), 

699 ... (1, 1, 0.5), 3)] 

700 ['#f00', '#0f0', '#00f', '#f00'] 

701 

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'] 

707 

708 Of course, asking for negative values is not supported: 

709 

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). 

714 

715 """ 

716 

717 if nb < 0: 

718 raise ValueError( 

719 "Unsupported negative number of colors (nb=%r)." % nb) 

720 

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) 

723 

724 def mul(step, value): 

725 return tuple([v * value for v in step]) 

726 

727 def add_v(step, step2): 

728 return tuple([v + step2[i] for i, v in enumerate(step)]) 

729 

730 return [add_v(begin_hsl, mul(step, r)) for r in range(0, nb + 1)] 

731 

732 

733## 

734## Color Pickers 

735## 

736 

737def RGB_color_picker(obj): 

738 """Build a color representation from the string representation of an object 

739 

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:: 

743 

744 >>> from colour import RGB_color_picker, Color 

745 

746 Same inputs produce the same result:: 

747 

748 >>> RGB_color_picker("Something") == RGB_color_picker("Something") 

749 True 

750 

751 ... but different inputs produce different colors:: 

752 

753 >>> RGB_color_picker("Something") != RGB_color_picker("Something else") 

754 True 

755 

756 In any case, we still get a ``Color`` object:: 

757 

758 >>> isinstance(RGB_color_picker("Something"), Color) 

759 True 

760 

761 """ 

762 

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() 

767 

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)] 

772 

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) 

780 

781 return Color(rgb2hex(components)) ## Profit! 

782 

783 

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) 

791 

792 

793## 

794## All purpose object 

795## 

796 

797class Color(object): 

798 """Abstraction of a color object 

799 

800 Color object keeps information of a color. It can input/output to different 

801 format (HSL, RGB, HEX, WEB) and their partial representation. 

802 

803 >>> from colour import Color, HSL 

804 

805 >>> b = Color() 

806 >>> b.hsl = HSL.BLUE 

807 

808 Access values 

809 ------------- 

810 

811 >>> b.hue # doctest: +ELLIPSIS 

812 0.66... 

813 >>> b.saturation 

814 1.0 

815 >>> b.luminance 

816 0.5 

817 

818 >>> b.red 

819 0.0 

820 >>> b.blue 

821 1.0 

822 >>> b.green 

823 0.0 

824 

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' 

831 

832 Change values 

833 ------------- 

834 

835 Let's change Hue toward red tint: 

836 

837 >>> b.hue = 0.0 

838 >>> b.hex 

839 '#f00' 

840 

841 >>> b.hue = 2.0/3 

842 >>> b.hex 

843 '#00f' 

844 

845 In the other way round: 

846 

847 >>> b.hex = '#f00' 

848 >>> b.hsl 

849 (0.0, 1.0, 0.5) 

850 

851 Long hex can be accessed directly: 

852 

853 >>> b.hex_l = '#123456' 

854 >>> b.hex_l 

855 '#123456' 

856 >>> b.hex 

857 '#123456' 

858 

859 >>> b.hex_l = '#ff0000' 

860 >>> b.hex_l 

861 '#ff0000' 

862 >>> b.hex 

863 '#f00' 

864 

865 Convenience 

866 ----------- 

867 

868 >>> c = Color('blue') 

869 >>> c 

870 <Color blue> 

871 >>> c.hue = 0 

872 >>> c 

873 <Color red> 

874 

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> 

884 

885 >>> c.luminance = 0.0 

886 >>> c 

887 <Color black> 

888 

889 >>> c.hex 

890 '#000' 

891 

892 >>> c.green = 1.0 

893 >>> c.blue = 1.0 

894 >>> c.hex 

895 '#0ff' 

896 >>> c 

897 <Color cyan> 

898 

899 >>> c = Color('blue', luminance=0.75) 

900 >>> c 

901 <Color #7f7fff> 

902 

903 >>> c = Color('red', red=0.5) 

904 >>> c 

905 <Color #7f0000> 

906 

907 >>> print(c) 

908 #7f0000 

909 

910 You can try to query unexisting attributes: 

911 

912 >>> c.lightness # doctest: +ELLIPSIS 

913 Traceback (most recent call last): 

914 ... 

915 AttributeError: 'lightness' not found 

916 

917 TODO: could add HSV, CMYK, YUV conversion. 

918 

919# >>> b.hsv 

920# >>> b.value 

921# >>> b.cyan 

922# >>> b.magenta 

923# >>> b.yellow 

924# >>> b.key 

925# >>> b.cmyk 

926 

927 

928 Recursive init 

929 -------------- 

930 

931 To support blind conversion of web strings (or already converted object), 

932 the Color object supports instantiation with another Color object. 

933 

934 >>> Color(Color(Color('red'))) 

935 <Color red> 

936 

937 Equality support 

938 ---------------- 

939 

940 Default equality is RGB hex comparison: 

941 

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 

950 

951 But this can be changed: 

952 

953 >>> saturation_equality = lambda c1, c2: c1.luminance == c2.luminance 

954 >>> Color('red', equality=saturation_equality) == Color('blue') 

955 True 

956 

957 

958 Subclassing support 

959 ------------------- 

960 

961 You should be able to subclass ``Color`` object without any issues:: 

962 

963 >>> class Tint(Color): 

964 ... pass 

965 

966 And keep the internal API working:: 

967 

968 >>> Tint("red").hsl 

969 (0.0, 1.0, 0.5) 

970 

971 """ 

972 

973 _hsl = None ## internal representation 

974 

975 def __init__(self, color=None, 

976 pick_for=None, picker=RGB_color_picker, pick_key=hash_or_str, 

977 **kwargs): 

978 

979 if pick_key is None: 

980 pick_key = lambda x: x 

981 

982 if pick_for is not None: 

983 color = picker(pick_key(pick_for)) 

984 

985 if isinstance(color, Color): 

986 self.web = color.web 

987 else: 

988 self.web = color if color else 'black' 

989 

990 self.equality = RGB_equivalence 

991 

992 for k, v in kwargs.items(): 

993 setattr(self, k, v) 

994 

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) 

1002 

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 

1009 

1010 ## 

1011 ## Get 

1012 ## 

1013 

1014 def get_hsl(self): 

1015 return tuple(self._hsl) 

1016 

1017 def get_hex(self): 

1018 return rgb2hex(self.rgb) 

1019 

1020 def get_hex_l(self): 

1021 return rgb2hex(self.rgb, force_long=True) 

1022 

1023 def get_rgb(self): 

1024 return hsl2rgb(self.hsl) 

1025 

1026 def get_hue(self): 

1027 return self.hsl[0] 

1028 

1029 def get_saturation(self): 

1030 return self.hsl[1] 

1031 

1032 def get_luminance(self): 

1033 return self.hsl[2] 

1034 

1035 def get_red(self): 

1036 return self.rgb[0] 

1037 

1038 def get_green(self): 

1039 return self.rgb[1] 

1040 

1041 def get_blue(self): 

1042 return self.rgb[2] 

1043 

1044 def get_web(self): 

1045 return hex2web(self.hex) 

1046 

1047 ## 

1048 ## Set 

1049 ## 

1050 

1051 def set_hsl(self, value): 

1052 self._hsl = list(value) 

1053 

1054 def set_rgb(self, value): 

1055 self.hsl = rgb2hsl(value) 

1056 

1057 def set_hue(self, value): 

1058 self._hsl[0] = value 

1059 

1060 def set_saturation(self, value): 

1061 self._hsl[1] = value 

1062 

1063 def set_luminance(self, value): 

1064 self._hsl[2] = value 

1065 

1066 def set_red(self, value): 

1067 _, g, b = self.rgb 

1068 self.rgb = (value, g, b) 

1069 

1070 def set_green(self, value): 

1071 r, _, b = self.rgb 

1072 self.rgb = (r, value, b) 

1073 

1074 def set_blue(self, value): 

1075 r, g, _ = self.rgb 

1076 self.rgb = (r, g, value) 

1077 

1078 def set_hex(self, value): 

1079 self.rgb = hex2rgb(value) 

1080 

1081 set_hex_l = set_hex 

1082 

1083 def set_web(self, value): 

1084 self.hex = web2hex(value) 

1085 

1086 ## range of color generation 

1087 

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) 

1091 

1092 ## 

1093 ## Convenience 

1094 ## 

1095 

1096 def __str__(self): 

1097 return "%s" % self.web 

1098 

1099 def __repr__(self): 

1100 return "<Color %s>" % self.web 

1101 

1102 def __eq__(self, other): 

1103 if isinstance(other, Color): 

1104 return self.equality(self, other) 

1105 return NotImplemented 

1106 

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 

1112 

1113 

1114RGB_equivalence = lambda c1, c2: c1.hex_l == c2.hex_l 

1115HSL_equivalence = lambda c1, c2: c1._hsl == c2._hsl 

1116 

1117 

1118def make_color_factory(**kwargs_defaults): 

1119 

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