Coverage for /usr/lib/python3/dist-packages/colorzero/conversions.py: 37%

151 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""" 

31Defines all conversion functions used by colorzero to convert between the 

32various color systems implemented. References used in the development of these 

33routines are as follows: 

34 

35* `Charles Poynton's Color FAQ`_ 

36* `Bruce Lindbloom's Color Equations`_ 

37* `RGB color space`_ article from Wikipedia 

38* `SRGB`_ article from Wikipedia 

39* `YUV`_ article from Wikipedia 

40* `YIQ`_ article from Wikipedia 

41* `HSL and HSV`_ article from Wikipedia 

42* `CIE 1931 color space`_ article from Wikipedia 

43 

44.. _RGB color space: https://en.wikipedia.org/wiki/RGB_color_space 

45.. _SRGB: https://en.wikipedia.org/wiki/SRGB 

46.. _YUV: https://en.wikipedia.org/wiki/YUV 

47.. _YIQ: https://en.wikipedia.org/wiki/YIQ 

48.. _HSL and HSV: https://en.wikipedia.org/wiki/HSL_and_HSV 

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

50.. _Charles Poynton's Color FAQ: http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html 

51.. _Bruce Lindbloom's Color Equations: https://www.brucelindbloom.com/ 

52""" 

53 

54from __future__ import ( 

55 unicode_literals, 

56 print_function, 

57 division, 

58 absolute_import, 

59) 

60 

61import colorsys 

62from collections import namedtuple 

63from fractions import Fraction 

64try: 

65 from itertools import izip as zip # pylint: disable=redefined-builtin 

66 def round(number, ndigits=0, _round=round): 

67 # Ensure round returns an int when ndigits is 0; back-ported behaviour 

68 # for python 2.x 

69 # pylint: disable=redefined-builtin,missing-docstring 

70 return _round(number, ndigits) if ndigits else int(_round(number)) 

71except ImportError: 

72 pass 

73 

74from .tables import NAMED_COLORS 

75from .types import RGB, YIQ, YUV, CMY, CMYK, HLS, HSV, XYZ, Luv, Lab 

76 

77# Lots of the conversion functions use single character parameter names and 

78# variables internally; this is is normal and in keeping with most of the 

79# referenced sources 

80# pylint: disable=invalid-name 

81 

82 

83# Utility functions and constants ############################################ 

84 

85def clamp_float(v): 

86 "Clamp *v* to the range 0.0 to 1.0 inclusive" 

87 return max(0.0, min(1.0, v)) 

88 

89 

90def clamp_bytes(v): 

91 "Clamp *v* to the range 0 to 255 inclusive" 

92 return max(0, min(255, v)) 

93 

94 

95def to_srgb(c): 

96 "Convert a linear RGB value (0..1) to the sRGB color space" 

97 return 12.92 * c if c <= 0.0031308 else (1.055 * c ** (1 / 2.4) - 0.055) 

98 

99 

100def from_srgb(c): 

101 "Convert an RGB value from the sRGB color space to linear RGB" 

102 return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4 

103 

104 

105def xyz_to_uv(x, y, z): 

106 "Calculate the U, V values from an XYZ color" 

107 d = x + 15 * y + 3 * z 

108 return (0, 0) if d == 0 else (4 * x / d, 9 * y / d) 

109 

110 

111def matrix_mult(m, n): 

112 "Generator function that multiplies matrices *m* and *n*" 

113 return ( 

114 sum(mval * nval for mval, nval in zip(mrow, n)) 

115 for mrow in m 

116 ) 

117 

118 

119class YUVCoefficients(namedtuple('YUVCoefficients', ( 

120 'Wr', 'Wg', 'Wb', 

121 'Umax', 'Vmax', 'U', 'V', 

122 'Rv', 'Gu', 'Gv', 'Bu'))): 

123 "Represents coefficients for the BT.601 and BT.709 standards." 

124 def __new__(cls, Umax=0.436, Vmax=0.615, **kwargs): 

125 try: 

126 Wr = kwargs['Wr'] 

127 Wb = kwargs['Wb'] 

128 except KeyError as e: 

129 raise TypeError('YUVCoefficients() missing required keyword ' 

130 'argument: %s' % str(e)) 

131 Wg = (1 - Wr - Wb) 

132 U = Umax / (1 - Wb) 

133 V = Vmax / (1 - Wr) 

134 Rv = (1 - Wr) / Vmax 

135 Bu = (1 - Wb) / Umax 

136 Gu = (Wb * (1 - Wb)) / (Umax * Wg) 

137 Gv = (Wr * (1 - Wr)) / (Vmax * Wg) 

138 return super(YUVCoefficients, cls).__new__(cls, 

139 Wr, Wg, Wb, 

140 Umax, Vmax, U, V, 

141 Rv, Gu, Gv, Bu) 

142 

143 

144BT601 = YUVCoefficients(Wr=0.299, Wb=0.114) 

145BT709 = YUVCoefficients(Wr=0.2126, Wb=0.0722) 

146SMPTE240M = YUVCoefficients(Wr=0.212, Wb=0.087) 

147# TODO define some API to use these in Color 

148 

149 

150# The standard illuminants in the CIE XYZ space 

151D50 = XYZ(0.966797, 1.0, 0.825188) 

152D65 = XYZ(0.95047, 1.0, 1.08883) 

153# TODO define some more of these and figure out some API to use them in Color 

154# TODO what about standard observers? color temperature? 

155 

156 

157# Conversion functions ####################################################### 

158 

159def rgb_to_yiq(r, g, b): 

160 "Convert a linear RGB color to YIQ" 

161 # Coefficients from Python 3.4+ 

162 y = 0.30 * r + 0.59 * g + 0.11 * b 

163 i = 0.74 * (r - y) - 0.27 * (b - y) 

164 q = 0.48 * (r - y) + 0.41 * (b - y) 

165 return YIQ(y, i, q) 

166 

167 

168def yiq_to_rgb(y, i, q): 

169 "Convert a YIQ color to linear RGB" 

170 # Coefficients from Python 3.4+ 

171 return RGB( 

172 clamp_float(y + 0.9468822170900693 * i + 0.6235565819861433 * q), 

173 clamp_float(y - 0.27478764629897834 * i - 0.6356910791873801 * q), 

174 clamp_float(y - 1.1085450346420322 * i + 1.7090069284064666 * q), 

175 ) 

176 

177 

178def rgb_to_hls(r, g, b): 

179 "Convert a linear RGB color to HLS" 

180 return HLS(*colorsys.rgb_to_hls(r, g, b)) 

181 

182 

183def hls_to_rgb(h, l, s): 

184 "Convert an HLS color to linear RGB" 

185 return RGB(*colorsys.hls_to_rgb(h, l, s)) 

186 

187 

188def rgb_to_hsv(r, g, b): 

189 "Convert a linear RGB color to HSV" 

190 return HSV(*colorsys.rgb_to_hsv(r, g, b)) 

191 

192 

193def hsv_to_rgb(h, s, v): 

194 "Convert an HSV color to linear RGB" 

195 return RGB(*colorsys.hsv_to_rgb(h, s, v)) 

196 

197 

198def rgb_to_rgb_bytes(r, g, b): 

199 "Convert a linear RGB color to RGB888" 

200 return RGB(int(round(r * 255)), int(round(g * 255)), int(round(b * 255))) 

201 

202 

203def rgb_bytes_to_rgb(r, g, b): 

204 "Convert an RGB888 color to linear RGB" 

205 return RGB(r / 255, g / 255, b / 255) 

206 

207 

208def rgb_bytes_to_html(r, g, b): 

209 "Convert RGB888 to the HTML representation" 

210 return '#%02x%02x%02x' % (r, g, b) 

211 

212 

213def rgb_bytes_to_rgb24(r, g, b): 

214 "Convert RGB888 to RGB24" 

215 return (b << 16) | (g << 8) | r 

216 

217 

218def rgb24_to_rgb_bytes(n): 

219 "Convert RGB24 to RGB888" 

220 return RGB(n & 0xFF, (n >> 8) & 0xFF, (n >> 16) & 0xFF) 

221 

222 

223def html_to_rgb_bytes(html): 

224 "Convert the HTML color representation to RGB888" 

225 if html.startswith('#'): 

226 try: 

227 if len(html) == 7: 

228 return RGB( 

229 int(html[1:3], base=16), 

230 int(html[3:5], base=16), 

231 int(html[5:7], base=16) 

232 ) 

233 elif len(html) == 4: 

234 return RGB( 

235 int(html[1:2], base=16) * 0x11, 

236 int(html[2:3], base=16) * 0x11, 

237 int(html[3:4], base=16) * 0x11 

238 ) 

239 except ValueError: 

240 pass 

241 raise ValueError('%s is not a valid HTML color specification' % html) 

242 

243 

244def name_to_html(name): 

245 "Convert a named color to the HTML representation" 

246 try: 

247 return NAMED_COLORS[name] 

248 except KeyError: 

249 raise ValueError('invalid color name %s' % name) 

250 

251 

252def rgb_to_rgb565(r, g, b): 

253 "Convert linear RGB to RGB565" 

254 return ( 

255 (int(r * 0xF800) & 0xF800) | 

256 (int(g * 0x07E0) & 0x07E0) | 

257 (int(b * 0x001F) & 0x001F) 

258 ) 

259 

260 

261def rgb565_to_rgb(rgb565): 

262 "Convert RGB565 to linear RGB" 

263 r = (rgb565 & 0xF800) / 0xF800 

264 g = (rgb565 & 0x07E0) / 0x07E0 

265 b = (rgb565 & 0x001F) / 0x001F 

266 return RGB(r, g, b) 

267 

268 

269def rgb_to_yuv(r, g, b, std=BT601): 

270 """ 

271 Convert linear RGB to Y'CbCr using the specified coefficients (the default 

272 coefficients are from BT.601) 

273 """ 

274 y = std.Wr * r + std.Wg * g + std.Wb * b 

275 return YUV(y, std.U * (b - y), std.V * (r - y)) 

276 

277 

278def yuv_to_rgb(y, u, v, std=BT601): 

279 """ 

280 Convert Y'CbCr to linear RGB using the specified coefficients (the default 

281 coefficients are from BT.601) 

282 """ 

283 return RGB( 

284 clamp_float(y + std.Rv * v), 

285 clamp_float(y - std.Gu * u - std.Gv * v), 

286 clamp_float(y + std.Bu * u), 

287 ) 

288 

289 

290def rgb_bytes_to_yuv_bytes(r, g, b): 

291 "Convert RGB888 to YUV444 bytes using studio swing from BT.601" 

292 # pylint: disable=bad-whitespace 

293 return YUV( 

294 (( 66 * r + 129 * g + 25 * b + 128) >> 8) + 16, 

295 ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128, 

296 ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128, 

297 ) 

298 

299 

300def yuv_bytes_to_rgb_bytes(y, u, v): 

301 "Convert YUV444 bytes to RGB888 using studio swing from BT.601" 

302 c = y - 16 

303 d = u - 128 

304 e = v - 128 

305 return RGB( 

306 clamp_bytes((298 * c + 409 * e + 128) >> 8), 

307 clamp_bytes((298 * c - 100 * d - 208 * e + 128) >> 8), 

308 clamp_bytes((298 * c + 516 * d + 128) >> 8), 

309 ) 

310 

311 

312def rgb_to_cmy(r, g, b): 

313 "Convert linear RGB to CMY using the subtractive method" 

314 return CMY(1 - r, 1 - g, 1 - b) 

315 

316 

317def cmy_to_rgb(c, m, y): 

318 "Convert CMY to linear RGB using the subtractive method" 

319 return RGB(1 - c, 1 - m, 1 - y) 

320 

321 

322def cmy_to_cmyk(c, m, y): 

323 "Calculate the black component of CMY to convert to CMYK" 

324 k = min(c, m, y) 

325 if k == 1.0: 

326 return CMYK(0.0, 0.0, 0.0, 1.0) 

327 else: 

328 d = 1.0 - k 

329 return CMYK((c - k) / d, (m - k) / d, (y - k) / d, k) 

330 

331 

332def cmyk_to_cmy(c, m, y, k): 

333 "Remove the black component from CMYK to yield CMY" 

334 n = 1 - k 

335 return CMY(c * n + k, m * n + k, y * n + k) 

336 

337 

338def rgb_to_xyz(r, g, b): 

339 """ 

340 Convert linear RGB to CIE XYZ representation. RGB is assumed to be sRGB and 

341 conversion uses D65 as reference white. 

342 """ 

343 return XYZ(*matrix_mult( 

344 ((0.4124564, 0.3575761, 0.1804375), 

345 (0.2126729, 0.7151522, 0.0721750), 

346 (0.0193339, 0.1191920, 0.9503041)), 

347 (from_srgb(r), from_srgb(g), from_srgb(b)) 

348 )) 

349 

350 

351def xyz_to_rgb(x, y, z): 

352 """ 

353 Convert CIE XYZ representation to linear RGB. sRGB is used as the output 

354 color space, and D65 as reference white. 

355 """ 

356 # pylint: disable=bad-whitespace 

357 m = matrix_mult( 

358 (( 3.2404542, -1.5371385, -0.4985314), 

359 (-0.9692660, 1.8760108, 0.0415560), 

360 ( 0.0556434, -0.2040259, 1.0572252)), 

361 (x, y, z) 

362 ) 

363 return RGB(*(to_srgb(c) for c in m)) 

364 

365 

366def luv_to_xyz(l, u, v, white=D65): 

367 "Convert CIE L*u*v* to CIE XYZ representation" 

368 if l == 0: 

369 return XYZ(0, 0, 0) 

370 uw, vw = xyz_to_uv(*white) 

371 u_prime = u / (13 * l) + uw 

372 v_prime = v / (13 * l) + vw 

373 y = white.y * ( 

374 l * Fraction(3, 29) ** 3 if l <= 8 else 

375 ((l + 16) / 116) ** 3 

376 ) 

377 return XYZ( 

378 y * (9 * u_prime) / (4 * v_prime), 

379 y, 

380 y * (12 - 3 * u_prime - 20 * v_prime) / (4 * v_prime), 

381 ) 

382 

383 

384def xyz_to_luv(x, y, z, white=D65): 

385 "Convert CIE XYZ to CIE L*u*v* representation" 

386 uw, vw = xyz_to_uv(*white) 

387 u, v = xyz_to_uv(x, y, z) 

388 K = Fraction(29, 3) ** 3 

389 e = Fraction(6, 29) ** 3 

390 y_prime = y / white.y 

391 L = 116 * y_prime ** Fraction(1, 3) - 16 if y_prime > e else K * y_prime 

392 return Luv( 

393 L, 

394 13 * L * (u - uw), 

395 13 * L * (v - vw), 

396 ) 

397 

398 

399def lab_to_xyz(l, a, b, white=D65): 

400 "Convert CIE L*a*b* to CIE XYZ representation" 

401 theta = Fraction(6, 29) 

402 fy = (l + 16) / 116 

403 fx = fy + a / 500 

404 fz = fy - b / 200 

405 xyz = ( 

406 n ** 3 if n > theta else 3 * theta ** 2 * (n - Fraction(4, 29)) 

407 for n in (fx, fy, fz) 

408 ) 

409 return XYZ(*(n * m for n, m in zip(xyz, white))) 

410 

411 

412def xyz_to_lab(x, y, z, white=D65): 

413 "Convert CIE XYZ to CIE L*a*b* representation" 

414 theta = Fraction(6, 29) 

415 x, y, z = (n / m for n, m in zip((x, y, z), white)) 

416 fx, fy, fz = ( 

417 t ** Fraction(1, 3) if t > theta ** 3 else 

418 t / (3 * theta ** 2) + Fraction(4, 29) 

419 for t in (x, y, z) 

420 ) 

421 return Lab(116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz))