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
« prev ^ index » next coverage.py v7.2.7, created at 2024-02-10 12:38 +0000
1# vim: set et sw=4 sts=4 fileencoding=utf-8:
2#
3# The colorzero color library
4# Copyright (c) 2016-2018 Dave Jones <dave@waveform.org.uk>
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above copyright
12# notice, this list of conditions and the following disclaimer in the
13# documentation and/or other materials provided with the distribution.
14# * Neither the name of the copyright holder nor the
15# names of its contributors may be used to endorse or promote products
16# derived from this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28# POSSIBILITY OF SUCH DAMAGE.
30"""
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:
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
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"""
54from __future__ import (
55 unicode_literals,
56 print_function,
57 division,
58 absolute_import,
59)
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
74from .tables import NAMED_COLORS
75from .types import RGB, YIQ, YUV, CMY, CMYK, HLS, HSV, XYZ, Luv, Lab
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
83# Utility functions and constants ############################################
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))
90def clamp_bytes(v):
91 "Clamp *v* to the range 0 to 255 inclusive"
92 return max(0, min(255, v))
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)
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
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)
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 )
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)
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
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?
157# Conversion functions #######################################################
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)
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 )
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))
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))
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))
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))
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)))
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)
208def rgb_bytes_to_html(r, g, b):
209 "Convert RGB888 to the HTML representation"
210 return '#%02x%02x%02x' % (r, g, b)
213def rgb_bytes_to_rgb24(r, g, b):
214 "Convert RGB888 to RGB24"
215 return (b << 16) | (g << 8) | r
218def rgb24_to_rgb_bytes(n):
219 "Convert RGB24 to RGB888"
220 return RGB(n & 0xFF, (n >> 8) & 0xFF, (n >> 16) & 0xFF)
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)
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)
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 )
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)
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))
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 )
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 )
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 )
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)
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)
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)
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)
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 ))
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))
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 )
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 )
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)))
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))