11#!/usr/bin/env python3
22"""
3- Tools for converting between various colorspaces. Adapted from
4- `seaborn <https://github.com/mwaskom/seaborn/blob/master/seaborn/external/husl.py>`__
5- and `hsluv-python <https://github.com/hsluv/hsluv-python/blob/master/hsluv.py>`__.
3+ Tools for converting between various colorspaces. Adapted from `seaborn
4+ <https://github.com/mwaskom/seaborn/blob/master/seaborn/external/husl.py>`__
5+ and `hsluv-python
6+ <https://github.com/hsluv/hsluv-python/blob/master/hsluv.py>`__.
67For more info on colorspaces see the
7- `CIULUV specification <https://en.wikipedia.org/wiki/CIELUV>`__,
8- the `CIE 1931 colorspace <https://en.wikipedia.org/wiki/CIE_1931_color_space>`__,
8+ `CIULUV specification <https://en.wikipedia.org/wiki/CIELUV>`__, the
9+ `CIE 1931 colorspace <https://en.wikipedia.org/wiki/CIE_1931_color_space>`__,
910the `HCL colorspace <https://en.wikipedia.org/wiki/HCL_color_space>`__,
1011and the `HSLuv system <http://www.hsluv.org/implementations/>`__.
1112
3637 [3.2406 , - 1.5372 , - 0.4986 ],
3738 [- 0.9689 , 1.8758 , 0.0415 ],
3839 [0.0557 , - 0.2040 , 1.0570 ]
39- ]
40+ ]
4041m_inv = [
4142 [0.4124 , 0.3576 , 0.1805 ],
4243 [0.2126 , 0.7152 , 0.0722 ],
4344 [0.0193 , 0.1192 , 0.9505 ]
44- ]
45+ ]
4546# Hard-coded D65 illuminant (has to do with expected light intensity and
4647# white balance that falls upon the generated color)
4748# See: https://en.wikipedia.org/wiki/Illuminant_D65
4849# Also: https://github.com/hsluv/hsluv-python/issues/3
49- refX = 0.95047
50- refY = 1.00000
51- refZ = 1.08883
52- refU = 0.19784
53- refV = 0.46834
50+ refX = 0.95047
51+ refY = 1.00000
52+ refZ = 1.08883
53+ refU = 0.19784
54+ refV = 0.46834
5455lab_e = 0.008856
5556lab_k = 903.3
5657
57- #------------------------------------------------------------------------------#
58- # Public API
59- #------------------------------------------------------------------------------#
60- # Basic conversion
58+
6159def hsluv_to_rgb (h , s , l ):
6260 return lchuv_to_rgb (* hsluv_to_lchuv ([h , s , l ]))
6361
62+
6463def hsluv_to_hex (h , s , l ):
6564 return rgb_to_hex (hsluv_to_rgb (h , s , l ))
6665
66+
6767def rgb_to_hsluv (r , g , b ):
6868 return lchuv_to_hsluv (rgb_to_lchuv (r , g , b ))
6969
70+
7071def hex_to_hsluv (color ):
7172 return rgb_to_hsluv (* hex_to_rgb (color ))
7273
74+
7375def hpluv_to_rgb (h , s , l ):
7476 return lchuv_to_rgb (* hpluv_to_lchuv ([h , s , l ]))
7577
78+
7679def hpluv_to_hex (h , s , l ):
7780 return rgb_to_hex (hpluv_to_rgb (h , s , l ))
7881
82+
7983def rgb_to_hpluv (r , g , b ):
8084 return lchuv_to_hpluv (rgb_to_lchuv (r , g , b ))
8185
86+
8287def hex_to_hpluv (color ):
8388 return rgb_to_hpluv (* hex_to_rgb (color ))
8489
90+
8591def lchuv_to_rgb (l , c , h ):
8692 return CIExyz_to_rgb (CIEluv_to_CIExyz (lchuv_to_CIEluv ([l , c , h ])))
8793
94+
8895def rgb_to_lchuv (r , g , b ):
8996 return CIEluv_to_lchuv (CIExyz_to_CIEluv (rgb_to_CIExyz ([r , g , b ])))
9097
91- # Make ordering of channels consistent with above functions
98+
9299def hsl_to_rgb (h , s , l ):
93100 h /= 360.0
94101 s /= 100.0
95- l /= 100.0
96- return hls_to_rgb (h , l , s )
102+ l /= 100.0 # noqa
103+ return hls_to_rgb (h , l , s )
104+
97105
98106def rgb_to_hsl (r , g , b ):
99107 h , l , s = rgb_to_hls (r , g , b )
100108 h *= 360.0
101109 s *= 100.0
102- l *= 100.0
110+ l *= 100.0 # noqa
103111 return h , s , l
104112
113+
105114def hcl_to_rgb (h , c , l ):
106115 return CIExyz_to_rgb (CIEluv_to_CIExyz (lchuv_to_CIEluv ([l , c , h ])))
107116
117+
108118def rgb_to_hcl (r , g , b ):
109119 l , c , h = CIEluv_to_lchuv (CIExyz_to_CIEluv (rgb_to_CIExyz ([r , g , b ])))
110120 return h , c , l
111121
112- #------------------------------------------------------------------------------#
113- # RGB to HEX conversions
114- #------------------------------------------------------------------------------#
122+
115123def rgb_prepare (triple ):
116124 ret = []
117125 for ch in triple :
@@ -122,13 +130,16 @@ def rgb_prepare(triple):
122130 ch = 0
123131 if ch > 1 :
124132 ch = 1
125- ret .append (int (round (ch * 255 + 0.001 , 0 ))) # the +0.001 fixes rounding error
133+ # the +0.001 fixes rounding error
134+ ret .append (int (round (ch * 255 + 0.001 , 0 )))
126135 return ret
127136
137+
128138def rgb_to_hex (triple ):
129139 [r , g , b ] = triple
130140 return '#%02x%02x%02x' % tuple (rgb_prepare ([r , g , b ]))
131141
142+
132143def hex_to_rgb (color ):
133144 if color .startswith ('#' ):
134145 color = color [1 :]
@@ -137,9 +148,7 @@ def hex_to_rgb(color):
137148 b = int (color [4 :6 ], 16 ) / 255.0
138149 return [r , g , b ]
139150
140- #------------------------------------------------------------------------------#
141- # Helper functions for fancier conversions
142- #------------------------------------------------------------------------------#
151+
143152def max_chroma (L , H ):
144153 hrad = math .radians (H )
145154 sinH = (math .sin (hrad ))
@@ -162,16 +171,19 @@ def max_chroma(L, H):
162171 # print('maxima', result)
163172 return result
164173
174+
165175def hrad_extremum (L ):
166- lhs = (math .pow (L , 3.0 ) + 48.0 * math .pow (L , 2.0 ) + 768.0 * L + 4096.0 ) / 1560896.0
176+ lhs = (math .pow (L , 3.0 ) + 48.0 * math .pow (L , 2.0 )
177+ + 768.0 * L + 4096.0 ) / 1560896.0
167178 rhs = 1107.0 / 125000.0
168179 sub = lhs if lhs > rhs else 10.0 * L / 9033.0
169- chroma = float (" inf" )
180+ chroma = float (' inf' )
170181 result = None
171182 for row in m :
172183 for limit in (0.0 , 1.0 ):
173184 [m1 , m2 , m3 ] = row
174- top = - 3015466475.0 * m3 * sub + 603093295.0 * m2 * sub - 603093295.0 * limit
185+ top = - 3015466475.0 * m3 * sub + 603093295.0 * m2 * sub \
186+ - 603093295.0 * limit
175187 bottom = 1356959916.0 * m1 * sub - 452319972.0 * m3 * sub
176188 hrad = math .atan2 (top , bottom )
177189 if limit == 0.0 :
@@ -182,13 +194,12 @@ def hrad_extremum(L):
182194 result = hrad
183195 return result
184196
197+
185198def max_chroma_pastel (L ):
186199 H = math .degrees (hrad_extremum (L ))
187200 return max_chroma (L , H )
188201
189- #------------------------------------------------------------------------------#
190- # Converting between the fancy colorspaces
191- #------------------------------------------------------------------------------#
202+
192203def hsluv_to_lchuv (triple ):
193204 H , S , L = triple
194205 if L > 99.9999999 :
@@ -201,6 +212,7 @@ def hsluv_to_lchuv(triple):
201212 # raise ValueError(f'HSL color {triple} is outside LCH colorspace.')
202213 return [L , C , H ]
203214
215+
204216def lchuv_to_hsluv (triple ):
205217 L , C , H = triple
206218 if L > 99.9999999 :
@@ -211,6 +223,7 @@ def lchuv_to_hsluv(triple):
211223 S = 100.0 * C / mx
212224 return [H , S , L ]
213225
226+
214227def hpluv_to_lchuv (triple ):
215228 H , S , L = triple
216229 if L > 99.9999999 :
@@ -223,6 +236,7 @@ def hpluv_to_lchuv(triple):
223236 # raise ValueError(f'HPL color {triple} is outside LCH colorspace.')
224237 return [L , C , H ]
225238
239+
226240def lchuv_to_hpluv (triple ):
227241 L , C , H = triple
228242 if L > 99.9999999 :
@@ -233,34 +247,37 @@ def lchuv_to_hpluv(triple):
233247 S = 100.0 * C / mx
234248 return [H , S , L ]
235249
236- #------------------------------------------------------------------------------#
237- # Converting to CIE official colorspace coordinates
238- #------------------------------------------------------------------------------#
250+
239251def dot_product (a , b ):
240- return sum (i * j for i ,j in zip (a ,b ))
252+ return sum (i * j for i , j in zip (a , b ))
241253 # return sum(map(operator.mul, a, b))
242254
255+
243256def from_linear (c ):
244257 if c <= 0.0031308 :
245258 return 12.92 * c
246259 else :
247260 return (1.055 * math .pow (c , 1.0 / 2.4 ) - 0.055 )
248261
262+
249263def to_linear (c ):
250264 a = 0.055
251265 if c > 0.04045 :
252266 return (math .pow ((c + a ) / (1.0 + a ), 2.4 ))
253267 else :
254268 return (c / 12.92 )
255269
270+
256271def CIExyz_to_rgb (triple ):
257272 CIExyz = map (lambda row : dot_product (row , triple ), m )
258273 return list (map (from_linear , CIExyz ))
259274
275+
260276def rgb_to_CIExyz (triple ):
261277 rgbl = list (map (to_linear , triple ))
262278 return list (map (lambda row : dot_product (row , rgbl ), m_inv ))
263279
280+
264281def CIEluv_to_lchuv (triple ):
265282 L , U , V = triple
266283 C = (math .pow (math .pow (U , 2 ) + math .pow (V , 2 ), (1.0 / 2.0 )))
@@ -270,33 +287,34 @@ def CIEluv_to_lchuv(triple):
270287 H = 360.0 + H
271288 return [L , C , H ]
272289
290+
273291def lchuv_to_CIEluv (triple ):
274292 L , C , H = triple
275293 Hrad = math .radians (H )
276294 U = (math .cos (Hrad ) * C )
277295 V = (math .sin (Hrad ) * C )
278296 return [L , U , V ]
279297
280- #------------------------------------------------------------------------------#
281- # Converting between different CIE standards
282- #------------------------------------------------------------------------------#
298+
283299# Try setting gamma from: https://en.wikipedia.org/wiki/HCL_color_space
284300# The 3.0 used below should be the same; don't mess with it
285- gamma = 3.0 # tunable? nah, get weird stuff
301+ gamma = 3.0 # tunable? nah, get weird stuff
302+
286303
287- # Functiona
288304def CIEfunc (t ):
289305 if t > lab_e :
290306 return (math .pow (t , 1.0 / gamma ))
291307 else :
292308 return (7.787 * t + 16.0 / 116.0 )
293309
310+
294311def CIEfunc_inverse (t ):
295312 if math .pow (t , 3.0 ) > lab_e :
296313 return (math .pow (t , gamma ))
297314 else :
298315 return (116.0 * t - 16.0 ) / lab_k
299316
317+
300318def CIExyz_to_CIEluv (triple ):
301319 X , Y , Z = triple
302320 if X == Y == Z == 0.0 :
@@ -311,6 +329,7 @@ def CIExyz_to_CIEluv(triple):
311329 V = 13.0 * L * (varV - refV )
312330 return [L , U , V ]
313331
332+
314333def CIEluv_to_CIExyz (triple ):
315334 L , U , V = triple
316335 if L == 0 :
@@ -322,4 +341,3 @@ def CIEluv_to_CIExyz(triple):
322341 X = 0.0 - (9.0 * Y * varU ) / ((varU - 4.0 ) * varV - varU * varV )
323342 Z = (9.0 * Y - (15.0 * varV * Y ) - (varV * X )) / (3.0 * varV )
324343 return [X , Y , Z ]
325-
0 commit comments