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

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. 

29 

30"Defines the main :class:`Color` class of the package." 

31 

32from __future__ import ( 

33 unicode_literals, 

34 print_function, 

35 division, 

36 absolute_import, 

37) 

38 

39import re 

40 

41from . import conversions as cv, types, attr, deltae, tables, easings 

42 

43# Make Py2's str and range equivalent to Py3's 

44str = type('') # pylint: disable=redefined-builtin,invalid-name 

45 

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 

50 

51 

52class Color(types.RGB): 

53 """ 

54 The Color class is a tuple which represents a color as linear red, green, 

55 and blue components. 

56 

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

62 

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

73 

74 The specific forms that the default constructor will accept are enumerated 

75 below: 

76 

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

137 

138 If the constructor parameters do not conform to any of the variants in the 

139 table above, a :exc:`ValueError` will be raised. 

140 

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

146 

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) 

153 

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

158 

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

163 

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

168 

169 Values are clipped to ensure the resulting color is still valid:: 

170 

171 >>> Color('#ff00ff') + Color('#ff0000') 

172 <Color html='#ff00ff' rgb=(1, 0, 1)> 

173 

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

177 

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

182 

183 You can even manipulate non-primary attributes like hue, saturation, and 

184 lightness with standard addition, subtraction or multiplication operators:: 

185 

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) 

192 

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

198 

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

204 

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 

210 

211 .. attribute:: red 

212 

213 Return the red value as a :class:`Red` instance 

214 

215 .. attribute:: green 

216 

217 Return the green value as a :class:`Green` instance 

218 

219 .. attribute:: blue 

220 

221 Return the blue value as a :class:`Blue` instance 

222 

223 .. attribute:: repr_style 

224 

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

228 

229 >>> Color('#f00') 

230 <Color html='#ff0000' rgb=(1, 0, 0)> 

231 >>> Color.repr_style = 'html' 

232 >>> Color('#f00') 

233 Color('#ff0000') 

234 

235 The following values are valid: 

236 

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 

251 

252 __slots__ = () 

253 

254 repr_style = 'default' 

255 

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) 

263 

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) 

273 

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

327 

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

335 

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

341 

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

352 

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

360 

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

368 

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. 

374 

375 .. _RGB: https://en.wikipedia.org/wiki/RGB_color_space 

376 """ 

377 return cls.from_rgb(*cv.rgb_bytes_to_rgb(r, g, b)) 

378 

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. 

385 

386 .. _Y'UV: https://en.wikipedia.org/wiki/YUV 

387 """ 

388 return cls.from_rgb(*cv.yuv_to_rgb(y, u, v)) 

389 

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. 

397 

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

401 

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. 

407 

408 .. _Y'IQ: https://en.wikipedia.org/wiki/YIQ 

409 """ 

410 return cls.from_rgb(*cv.yiq_to_rgb(y, i, q)) 

411 

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. 

417 

418 .. _HLS: https://en.wikipedia.org/wiki/HSL_and_HSV 

419 """ 

420 return cls.from_rgb(*cv.hls_to_rgb(h, l, s)) 

421 

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. 

427 

428 .. _HSV: https://en.wikipedia.org/wiki/HSL_and_HSV 

429 """ 

430 return cls.from_rgb(*cv.hsv_to_rgb(h, s, v)) 

431 

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. 

437 

438 .. note:: 

439 

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. 

443 

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

448 

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. 

454 

455 .. note:: 

456 

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. 

460 

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

465 

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. 

472 

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

476 

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. 

483 

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

487 

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. 

494 

495 .. _CIE Luv color space: https://en.wikipedia.org/wiki/CIELUV 

496 """ 

497 return cls.from_xyz(*cv.luv_to_xyz(l, u, v)) 

498 

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 

522 

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 

531 

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 

555 

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 

565 

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 

589 

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 

598 

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' 

647 

648 def __str__(self): 

649 return self.html 

650 

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) 

662 

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) 

669 

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. 

675 

676 .. note:: 

677 

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) 

683 

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) 

691 

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) 

698 

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) 

707 

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) 

716 

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) 

724 

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. 

731 

732 .. _CIE 1931 color space: https://en.wikipedia.org/wiki/CIE_1931_color_space 

733 """ 

734 return cv.rgb_to_xyz(*self) 

735 

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`_. 

741 

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) 

746 

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`_. 

752 

753 .. _CIE Luv color space: https://en.wikipedia.org/wiki/CIELUV 

754 """ 

755 return cv.xyz_to_luv(*self.xyz) 

756 

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) 

764 

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) 

772 

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

778 

779 .. note:: 

780 

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. 

784 

785 .. _Color FAQ: http://poynton.ca/notes/colour_and_gamma/ColorFAQ.html#RTFToC24 

786 """ 

787 return cv.rgb_to_cmy(*self) 

788 

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

794 

795 .. note:: 

796 

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. 

800 

801 .. _Color FAQ: http://poynton.ca/notes/colour_and_gamma/ColorFAQ.html#RTFToC24 

802 """ 

803 return cv.cmy_to_cmyk(*self.cmy) 

804 

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

812 

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

820 

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

828 

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. 

834 

835 .. _luma: https://en.wikipedia.org/wiki/Luma_(video) 

836 """ 

837 return attr.Luma(self.yuv[0]) 

838 

839 def difference(self, other, method='euclid'): 

840 """ 

841 Determines the difference between this color and *other* using the 

842 specified *method*. 

843 

844 :param Color other: 

845 The color to compare this color to. 

846 

847 :param str method: 

848 The algorithm to use in the comparison. Valid values are: 

849 

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. 

861 

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

870 

871 For example:: 

872 

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 

887 

888 .. note:: 

889 

890 Instead of using this method, you may wish to simply use the 

891 various difference functions (:func:`euclid`, :func:`cie1976`, 

892 etc.) directly. 

893 

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) 

912 

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

917 

918 :param Color other: 

919 The color that will end the gradient (with the color the method is 

920 called upon starting the gradient) 

921 

922 :param int steps: 

923 The unqiue number of colors to include in the generated gradient. 

924 Defaults to 10 if unspecified. 

925 

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

932 

933 :return: 

934 A generator yielding *steps* :class:`Color` instances which fade 

935 from this color to *other*. 

936 

937 For example:: 

938 

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

954 

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