python-lottie  0.6.10+dev2509936
A framework to work with lottie files and telegram animated stickers (tgs)
color.py
Go to the documentation of this file.
1 import enum
2 import math
3 import colorsys
4 from ..nvector import NVector
5 
6 
7 def from_uint8(r, g, b, a=255):
8  return Color(r, g, b, a) / 255
9 
10 
11 class ColorMode(enum.Enum):
12  ## sRGB, Components in [0, 1]
13  RGB = enum.auto()
14  ## HSV, components in [0, 1]
15  HSV = enum.auto()
16  ## HSL, components in [0, 1]
17  HSL = enum.auto()
18  ## CIE XYZ with Illuminant D65. Components in [0, 1]
19  XYZ = enum.auto()
20  ## CIE L*u*v*
21  LUV = enum.auto()
22  ## CIE Lch(uv), polar version of LUV where C is the radius and H an angle in radians
23  LCH_uv = enum.auto()
24  ## CIE L*a*b*
25  LAB = enum.auto()
26  ## CIE LCh(ab), polar version of LAB where C is the radius and H an angle in radians
27  #LCH_ab = enum.auto()
28 
29 
30 def _clamp(x):
31  return max(0, min(1, x))
32 
33 
34 class Conversion:
35  _conv_paths = {
36  (ColorMode.RGB, ColorMode.RGB): [],
37  (ColorMode.RGB, ColorMode.HSV): [],
38  (ColorMode.RGB, ColorMode.HSL): [],
39  (ColorMode.RGB, ColorMode.XYZ): [],
40  (ColorMode.RGB, ColorMode.LUV): [ColorMode.XYZ],
41  (ColorMode.RGB, ColorMode.LAB): [ColorMode.XYZ],
42  (ColorMode.RGB, ColorMode.LCH_uv): [ColorMode.XYZ, ColorMode.LUV],
43  #(ColorMode.RGB, ColorMode.LCH_ab): [ColorMode.XYZ, ColorMode.LAB],
44 
45  (ColorMode.HSV, ColorMode.RGB): [],
46  (ColorMode.HSV, ColorMode.HSV): [],
47  (ColorMode.HSV, ColorMode.HSL): [],
48  (ColorMode.HSV, ColorMode.XYZ): [ColorMode.RGB],
49  (ColorMode.HSV, ColorMode.LUV): [ColorMode.RGB, ColorMode.XYZ],
50  (ColorMode.HSV, ColorMode.LAB): [ColorMode.RGB, ColorMode.XYZ],
51  (ColorMode.HSV, ColorMode.LCH_uv): [ColorMode.RGB, ColorMode.XYZ, ColorMode.LUV],
52  #(ColorMode.HSV, ColorMode.LCH_ab): [ColorMode.RGB, ColorMode.XYZ, ColorMode.LAB],
53 
54  (ColorMode.HSL, ColorMode.RGB): [],
55  (ColorMode.HSL, ColorMode.HSV): [],
56  (ColorMode.HSL, ColorMode.HSL): [],
57  (ColorMode.HSL, ColorMode.XYZ): [ColorMode.RGB],
58  (ColorMode.HSL, ColorMode.LUV): [ColorMode.RGB, ColorMode.XYZ],
59  (ColorMode.HSL, ColorMode.LAB): [ColorMode.RGB, ColorMode.XYZ],
60  (ColorMode.HSL, ColorMode.LCH_uv): [ColorMode.RGB, ColorMode.XYZ, ColorMode.LUV],
61  #(ColorMode.HSL, ColorMode.LCH_ab): [ColorMode.RGB, ColorMode.XYZ, ColorMode.LAB],
62 
63  (ColorMode.XYZ, ColorMode.RGB): [],
64  (ColorMode.XYZ, ColorMode.HSV): [ColorMode.RGB],
65  (ColorMode.XYZ, ColorMode.HSL): [ColorMode.RGB],
66  (ColorMode.XYZ, ColorMode.XYZ): [],
67  (ColorMode.XYZ, ColorMode.LUV): [],
68  (ColorMode.XYZ, ColorMode.LAB): [],
69  (ColorMode.XYZ, ColorMode.LCH_uv): [ColorMode.LUV],
70  #(ColorMode.XYZ, ColorMode.LCH_ab): [ColorMode.LAB],
71 
72  (ColorMode.LCH_uv, ColorMode.RGB): [ColorMode.LUV, ColorMode.XYZ],
73  (ColorMode.LCH_uv, ColorMode.HSV): [ColorMode.LUV, ColorMode.XYZ, ColorMode.RGB],
74  (ColorMode.LCH_uv, ColorMode.HSL): [ColorMode.LUV, ColorMode.XYZ, ColorMode.RGB],
75  (ColorMode.LCH_uv, ColorMode.XYZ): [ColorMode.LUV],
76  (ColorMode.LCH_uv, ColorMode.LUV): [],
77  (ColorMode.LCH_uv, ColorMode.LAB): [ColorMode.LUV, ColorMode.XYZ],
78  (ColorMode.LCH_uv, ColorMode.LCH_uv): [],
79  #(ColorMode.LCH_uv, ColorMode.LCH_ab): [ColorMode.LUV, ColorMode.XYZ, ColorMode.LAB],
80 
81  (ColorMode.LUV, ColorMode.RGB): [ColorMode.XYZ],
82  (ColorMode.LUV, ColorMode.HSV): [ColorMode.XYZ, ColorMode.RGB],
83  (ColorMode.LUV, ColorMode.HSL): [ColorMode.XYZ, ColorMode.RGB],
84  (ColorMode.LUV, ColorMode.XYZ): [],
85  (ColorMode.LUV, ColorMode.LUV): [],
86  (ColorMode.LUV, ColorMode.LAB): [ColorMode.XYZ],
87  (ColorMode.LUV, ColorMode.LCH_uv): [],
88  #(ColorMode.LUV, ColorMode.LCH_ab): [ColorMode.XYZ, ColorMode.LAB],
89 
90  (ColorMode.LAB, ColorMode.RGB): [ColorMode.XYZ],
91  (ColorMode.LAB, ColorMode.HSV): [ColorMode.XYZ, ColorMode.RGB],
92  (ColorMode.LAB, ColorMode.HSL): [ColorMode.XYZ, ColorMode.RGB],
93  (ColorMode.LAB, ColorMode.XYZ): [],
94  (ColorMode.LAB, ColorMode.LUV): [ColorMode.XYZ],
95  (ColorMode.LAB, ColorMode.LAB): [],
96  (ColorMode.LAB, ColorMode.LCH_uv): [ColorMode.XYZ, ColorMode.LUV],
97  #(ColorMode.LAB, ColorMode.LCH_ab): [],
98 
99  #(ColorMode.LCH_ab, ColorMode.RGB): [ColorMode.LAB, ColorMode.XYZ],
100  #(ColorMode.LCH_ab, ColorMode.HSV): [ColorMode.LAB, ColorMode.XYZ, ColorMode.RGB],
101  #(ColorMode.LCH_ab, ColorMode.HSL): [ColorMode.LAB, ColorMode.XYZ, ColorMode.RGB],
102  #(ColorMode.LCH_ab, ColorMode.XYZ): [ColorMode.LAB],
103  #(ColorMode.LCH_ab, ColorMode.LUV): [ColorMode.LAB, ColorMode.XYZ],
104  #(ColorMode.LCH_ab, ColorMode.LAB): [],
105  #(ColorMode.LCH_ab, ColorMode.LCH_uv): [ColorMode.LAB, ColorMode.XYZ, ColorMode.LUV],
106  #(ColorMode.LCH_ab, ColorMode.LCH_ab): [],
107  }
108 
109  @staticmethod
110  def rgb_to_hsv(r, g, b):
111  return colorsys.rgb_to_hsv(r, g, b)
112 
113  @staticmethod
114  def hsv_to_rgb(r, g, b):
115  return colorsys.hsv_to_rgb(r, g, b)
116 
117  @staticmethod
118  def hsl_to_hsv(h, s_hsl, l):
119  v = l + s_hsl * min(l, 1 - l)
120  s_hsv = 0 if v == 0 else 2 - 2 * l / v
121  return (h, s_hsv, v)
122 
123  @staticmethod
124  def hsv_to_hsl(h, s_hsv, v):
125  l = v - v * s_hsv / 2
126  s_hsl = 0 if l in (0, 1) else (v - l) / min(l, 1 - l)
127  return (h, s_hsl, l)
128 
129  @staticmethod
130  def rgb_to_hsl(r, g, b):
131  h, l, s = colorsys.rgb_to_hls(r, g, b)
132  return (h, s, l)
133 
134  @staticmethod
135  def hsl_to_rgb(h, s, l):
136  return colorsys.hls_to_rgb(h, l, s)
137 
138  # http://w3.uqo.ca/missaoui/Publications/TRColorSpace.zip
139  #@staticmethod
140  #def rgb_to_hcl(r, g, b, gamma=3, y0=100):
141  #maxc = max(r, g, b)
142  #minc = min(r, g, b)
143  #if maxc > 0:
144  #alpha = 1/y0 * minc / maxc
145  #else:
146  #alpha = 0
147  #q = math.e ** (alpha * gamma)
148  #h = math.atan2(g - b, r - g)
149  #if h < 0:
150  #h += 2*math.pi
151  #h /= 2*math.pi
152  #c = q / 3 * (abs(r-g) + abs(g-b) + abs(b-r))
153  #l = (q * maxc + (q-1) * minc) / 2
154  #return (h, c, l)
155 
156  #@staticmethod
157  #def hcl_to_rgb(h, c, l, gamma=3, y0=100):
158  #h *= 2*math.pi
159 
160  #q = math.e ** ((1 - 2*c / 4*l) * gamma / y0)
161  #minc = (4*l - 3*c) / (4*q - 2)
162  #maxc = minc + 3*c / 2*q
163 
164  #if h <= math.pi * 1 / 3:
165  #tan = math.tan(3/2*h)
166  #r = maxc
167  #b = minc
168  #g = (r * tan + b) / (1 + tan)
169  #elif h <= math.pi * 2 / 3:
170  #tan = math.tan(3/4*(h-math.pi))
171  #g = maxc
172  #b = minc
173  #r = (g * (1+tan) - b) / tan
174  #elif h <= math.pi * 3 / 3:
175  #tan = math.tan(3/4*(h-math.pi))
176  #g = maxc
177  #r = minc
178  #b = g * (1+tan) - r * tan
179  #elif h <= math.pi * 4 / 3:
180  #tan = math.tan(3/2*(h+math.pi))
181  #b = maxc
182  #r = minc
183  #g = (r * tan + b) / (1 + tan)
184  #elif h <= math.pi * 5 / 3:
185  #tan = math.tan(3/4*h)
186  #b = maxc
187  #g = minc
188  #r = (g * (1+tan) - b) / tan
189  #else:
190  #tan = math.tan(3/4*h)
191  #r = maxc
192  #g = minc
193  #b = g * (1+tan) - r * tan
194 
195  #return _clamp(r), _clamp(g), _clamp(b)
196 
197  @staticmethod
198  def rgb_to_xyz(r, g, b):
199  def _gamma(v):
200  return v / 12.92 if v <= 0.04045 else ((v + 0.055) / 1.055) ** 2.4
201  rgb = (_gamma(r), _gamma(g), _gamma(b))
202  matrix = [
203  [0.4124564, 0.3575761, 0.1804375],
204  [0.2126729, 0.7151522, 0.0721750],
205  [0.0193339, 0.1191920, 0.9503041],
206  ]
207  return tuple(
208  sum(rgb[i] * c for i, c in enumerate(row))
209  for row in matrix
210  )
211 
212  @staticmethod
213  def xyz_to_rgb(x, y, z):
214  def _gamma1(v):
215  return _clamp(v * 12.92 if v <= 0.0031308 else v ** (1/2.4) * 1.055 - 0.055)
216  matrix = [
217  [+3.2404542, -1.5371385, -0.4985314],
218  [-0.9692660, +1.8760108, +0.0415560],
219  [+0.0556434, -0.2040259, +1.0572252],
220  ]
221  xyz = (x, y, z)
222  return tuple(map(_gamma1, (
223  sum(xyz[i] * c for i, c in enumerate(row))
224  for row in matrix
225  )))
226 
227  @staticmethod
228  def xyz_to_luv(x, y, z):
229  u1r = 0.2009
230  v1r = 0.4610
231  yr = 100
232 
233  kap = (29/3)**3
234  eps = (6/29)**3
235 
236  try:
237  u1 = 4*x / (x + 15*y + 3*z)
238  v1 = 9*y / (x + 15*y + 3*z)
239  except ZeroDivisionError:
240  return 0, 0, 0
241 
242  y_r = y/yr
243  l = 166 * y_r ** (1/3) - 16 if y_r > eps else kap * y_r
244  u = 13 * l * (u1 - u1r)
245  v = 13 * l * (v1 - v1r)
246  return l, u, v
247 
248  @staticmethod
249  def luv_to_xyz(l, u, v):
250  u1r = 0.2009
251  v1r = 0.4610
252  yr = 100
253 
254  kap = (29/3)**3
255 
256  if l == 0:
257  u1 = u1r
258  v1 = v1r
259  else:
260  u1 = u / (13 * l) + u1r
261  v1 = v / (13 * l) + v1r
262 
263  y = yr * l / kap if l <= 8 else yr * ((l + 16) / 116) ** 3
264  x = y * 9*u1 / (4*v1)
265  z = y * (12 - 3*u1 - 20*v1) / (4*v1)
266  return x, y, z
267 
268  @staticmethod
269  def luv_to_lch_uv(l, u, v):
270  c = math.hypot(u, v)
271  h = math.atan2(v, u)
272  if h < 0:
273  h += math.tau
274  return l, c, h
275 
276  @staticmethod
277  def lch_uv_to_luv(l, c, h):
278  u = math.cos(h) * c
279  v = math.sin(h) * c
280  return l, u, v
281 
282  @staticmethod
283  def xyz_to_lab(x, y, z):
284  # D65 Illuminant aka sRGB(1,1,1)
285  xn = 0.950489
286  yn = 1
287  zn = 108.8840
288 
289  delta = 6 / 29
290 
291  def f(t):
292  return t ** (1/3) if t > delta ** 3 else t / (3*delta**2) + 4/29
293 
294  fy = f(y/yn)
295  l = 116 * fy - 16
296  a = 500 * (f(x/xn) - fy)
297  b = 200 * (fy - f(z/zn))
298 
299  return l, a, b
300 
301  @staticmethod
302  def lab_to_xyz(l, a, b):
303  # D65 Illuminant aka sRGB(1,1,1)
304  xn = 0.950489
305  yn = 1
306  zn = 108.8840
307 
308  delta = 6 / 29
309 
310  def f1(t):
311  return t**3 if t > delta else 3*delta**2*(t-4/29)
312 
313  l1 = (l+16) / 116
314  x = xn * f1(l1+a/500)
315  y = yn * f1(l1)
316  z = zn * f1(l1-b/200)
317 
318  return x, y, z
319 
320  #@staticmethod
321  #def lab_to_lch_ab(l, a, b):
322  #c = math.hypot(a, b)
323  #h = math.atan2(b, a)
324  #if h < 0:
325  #h += math.tau
326  #return l, c, h
327 
328  #@staticmethod
329  #def lch_ab_to_lab(l, c, h):
330  #a = math.cos(h) * c
331  #b = math.sin(h) * c
332  #return l, a, b
333 
334  @staticmethod
335  def conv_func(mode_from, mode_to):
336  return getattr(Conversion, "%s_to_%s" % (mode_from.name.lower(), mode_to.name.lower()), None)
337 
338  @staticmethod
339  def convert(tuple, mode_from, mode_to):
340  if mode_from == mode_to:
341  return tuple
342 
343  if len(tuple) == 4:
344  alpha = tuple[3]
345  tuple = tuple[:3]
346  else:
347  alpha = None
348 
349  func = Conversion.conv_func(mode_from, mode_to)
350  if func:
351  return func(*tuple)
352 
353  if (mode_from, mode_to) in Conversion._conv_paths:
354  steps = Conversion._conv_paths[(mode_from, mode_to)] + [mode_to]
355  for step in steps:
356  func = Conversion.conv_func(mode_from, step)
357  if not func:
358  raise ValueError("Missing definition for conversion from %s to %s" % (mode_from, step))
359  tuple = func(*tuple)
360  mode_from = step
361  if alpha is not None:
362  tuple += (alpha,)
363  return tuple
364 
365  raise ValueError("No conversion path from %s to %s" % (mode_from, mode_to))
366 
367 
368 class Color(NVector):
369  Mode = ColorMode
370 
371  def __init__(self, c1=0, c2=0, c3=0, a=1, *, mode=ColorMode.RGB):
372  if isinstance(a, ColorMode):
373  raise TypeError("Please update the Color constructor")
374  super().__init__(c1, c2, c3, a)
375  self._mode = mode
376 
377  @property
378  def mode(self):
379  return self._mode
380 
381  def convert(self, v):
382  if v == self._mode:
383  return self
384 
385  self.components = list(Conversion.convert(self.components, self._mode, v))
386 
387  self._mode = v
388  return self
389 
390  def clone(self):
391  return Color(*self.components, mode=self._mode)
392 
393  def converted(self, mode):
394  return self.clone().convert(mode)
395 
396  def to_rgb(self):
397  return self.converted(ColorMode.RGB)
398 
399  def __repr__(self):
400  return "<%s %s [%.3f, %.3f, %.3f, %.3f]>" % (
401  (self.__class__.__name__, self.mode.name) + tuple(self.components)
402  )
403 
404  def component_names(self):
405  comps = None
406 
407  if self._mode == ColorMode.RGB:
408  comps = ({"r", "red"}, {"g", "green"}, {"b", "blue"})
409  elif self._mode == ColorMode.HSV:
410  comps = ({"h", "hue"}, {"s", "saturation"}, {"v", "value"})
411  elif self._mode == ColorMode.HSL:
412  comps = ({"h", "hue"}, {"s", "saturation"}, {"l", "lightness"})
413  elif self._mode == ColorMode.LCH_uv: # in (ColorMode.LCH_uv, ColorMode.LCH_ab):
414  comps = ({"l", "luma", "luminance"}, {"c", "choma"}, {"h", "hue"})
415  elif self._mode == ColorMode.XYZ:
416  comps = "xyz"
417  elif self._mode == ColorMode.LUV:
418  comps = "luv"
419  elif self._mode == ColorMode.LAB:
420  comps = "lab"
421 
422  return comps
423 
424  def _attrindex(self, name):
425  comps = self.component_names()
426 
427  if comps:
428  for i, vals in enumerate(comps):
429  if name in vals:
430  return i
431 
432  return None
433 
434  def __getattr__(self, name):
435  if name not in vars(self) and name not in {"_mode", "components"}:
436  i = self._attrindex(name)
437  if i is not None:
438  return self.components[i]
439  raise AttributeError(name)
440 
441  def __setattr__(self, name, value):
442  if name not in vars(self) and name not in {"_mode", "components"}:
443  i = self._attrindex(name)
444  if i is not None:
445  self.components[i] = value
446  return
447  return super().__setattr__(name, value)
lottie.utils.color.Conversion.luv_to_xyz
def luv_to_xyz(l, u, v)
Definition: color.py:249
lottie.utils.color.Color
Definition: color.py:368
lottie.utils.color.Conversion.rgb_to_xyz
def rgb_to_xyz(r, g, b)
Definition: color.py:198
lottie.utils.color.Conversion.rgb_to_hsv
def rgb_to_hsv(r, g, b)
Definition: color.py:110
lottie.utils.color.Conversion.hsv_to_rgb
def hsv_to_rgb(r, g, b)
Definition: color.py:114
lottie.utils.color.Color.convert
def convert(self, v)
Definition: color.py:381
lottie.utils.color.ColorMode
Definition: color.py:11
lottie.nvector.NVector.clone
def clone(self)
Definition: nvector.py:82
lottie.utils.color.Color._attrindex
def _attrindex(self, name)
Definition: color.py:424
lottie.utils.color.Color.clone
def clone(self)
Definition: color.py:390
lottie.utils.color.Conversion.hsl_to_hsv
def hsl_to_hsv(h, s_hsl, l)
Definition: color.py:118
lottie.utils.color.Color.component_names
def component_names(self)
Definition: color.py:404
lottie.utils.color.Conversion
Definition: color.py:34
lottie.utils.color.Conversion.xyz_to_rgb
def xyz_to_rgb(x, y, z)
Definition: color.py:213
lottie.utils.color.Conversion.luv_to_lch_uv
def luv_to_lch_uv(l, u, v)
Definition: color.py:269
lottie.utils.color.Conversion.hsv_to_hsl
def hsv_to_hsl(h, s_hsv, v)
Definition: color.py:124
lottie.utils.color.Conversion.convert
def convert(tuple, mode_from, mode_to)
Definition: color.py:339
lottie.utils.color.Conversion.lab_to_xyz
def lab_to_xyz(l, a, b)
Definition: color.py:302
lottie.utils.color.Color.converted
def converted(self, mode)
Definition: color.py:393
lottie.utils.color.Conversion.lch_uv_to_luv
def lch_uv_to_luv(l, c, h)
Definition: color.py:277
lottie.utils.color.Conversion.hsl_to_rgb
def hsl_to_rgb(h, s, l)
Definition: color.py:135
lottie.utils.color.Color.to_rgb
def to_rgb(self)
Definition: color.py:396
lottie.utils.color.Color.__init__
def __init__(self, c1=0, c2=0, c3=0, a=1, *mode=ColorMode.RGB)
Definition: color.py:371
lottie.utils.color.Color._mode
_mode
Definition: color.py:375
lottie.utils.color.Color.__getattr__
def __getattr__(self, name)
Definition: color.py:434
lottie.utils.color.Color.__setattr__
def __setattr__(self, name, value)
Definition: color.py:441
lottie.nvector.NVector.components
components
Definition: nvector.py:11
lottie.utils.color.Color.mode
def mode(self)
Definition: color.py:378
lottie.utils.color.Color.__repr__
def __repr__(self)
Definition: color.py:399
lottie.utils.color.Conversion.xyz_to_luv
def xyz_to_luv(x, y, z)
Definition: color.py:228
lottie.utils.color.Conversion.rgb_to_hsl
def rgb_to_hsl(r, g, b)
Definition: color.py:130
lottie.utils.color.from_uint8
def from_uint8(r, g, b, a=255)
Definition: color.py:7
lottie.utils.color.Conversion.xyz_to_lab
def xyz_to_lab(x, y, z)
Definition: color.py:283
lottie.utils.color.Conversion.conv_func
def conv_func(mode_from, mode_to)
Definition: color.py:335
lottie.nvector.NVector
Definition: nvector.py:9