python-lottie  0.7.0+dev418bd09
A framework to work with lottie files and telegram animated stickers (tgs)
bezier.py
Go to the documentation of this file.
1 import math
2 from .base import LottieObject, LottieProp
3 from ..nvector import NVector
4 
5 
6 class BezierPoint:
7  def __init__(self, vertex, in_tangent=None, out_tangent=None):
8  self.vertexvertex = vertex
9  self.in_tangentin_tangent = in_tangent or NVector(0, 0)
10  self.out_tangentout_tangent = out_tangent or NVector(0, 0)
11 
12  def relative(self):
13  return self
14 
15  @classmethod
16  def smooth(cls, point, in_tangent):
17  return cls(point, in_tangent, -in_tangent)
18 
19  @classmethod
20  def from_absolute(cls, point, in_tangent=None, out_tangent=None):
21  if not in_tangent:
22  in_tangent = point.clone()
23  if not out_tangent:
24  out_tangent = point.clone()
25  return BezierPoint(point, in_tangent, out_tangent)
26 
27 
29  """
30  View for bezier point
31  """
32  def __init__(self, bezier, index):
33  self.bezierbezier = bezier
34  self.indexindex = index
35 
36  @property
37  def vertex(self):
38  return self.bezierbezier.vertices[self.indexindex]
39 
40  @vertex.setter
41  def vertex(self, point):
42  self.bezierbezier.vertices[self.indexindex] = point
43 
44  @property
45  def in_tangent(self):
46  return self.bezierbezier.in_tangents[self.indexindex]
47 
48  @in_tangent.setter
49  def in_tangent(self, point):
50  self.bezierbezier.in_tangents[self.indexindex] = point
51 
52  @property
53  def out_tangent(self):
54  return self.bezierbezier.out_tangents[self.indexindex]
55 
56  @out_tangent.setter
57  def out_tangent(self, point):
58  self.bezierbezier.out_tangents[self.indexindex] = point
59 
60  def relative(self):
61  return self
62 
63 
65  @property
66  def in_tangent(self):
67  return self.bezierbezier.in_tangents[self.indexindex] + self.vertexvertexvertex
68 
69  @in_tangent.setter
70  def in_tangent(self, point):
71  self.bezierbezier.in_tangents[self.indexindex] = point - self.vertexvertexvertex
72 
73  @property
74  def out_tangent(self):
75  return self.bezierbezier.out_tangents[self.indexindex] + self.vertexvertexvertex
76 
77  @out_tangent.setter
78  def out_tangent(self, point):
79  self.bezierbezier.out_tangents[self.indexindex] = point - self.vertexvertexvertex
80 
81  def relative(self):
82  return BezierPointView(self.bezierbezier, self.indexindex)
83 
84 
85 class BezierView:
86  def __init__(self, bezier, absolute=False):
87  self.bezierbezier = bezier
88  self.is_absoluteis_absolute = absolute
89 
90  def point(self, index):
91  if self.is_absoluteis_absolute:
92  return AbsoluteBezierPointView(self.bezierbezier, index)
93  return BezierPointView(self.bezierbezier, index)
94 
95  def __len__(self):
96  return len(self.bezierbezier.vertices)
97 
98  def __getitem__(self, key):
99  if isinstance(key, slice):
100  return [
101  self.pointpoint(i)
102  for i in key
103  ]
104  return self.pointpoint(key)
105 
106  def __iter__(self):
107  for i in range(len(self)):
108  yield self.pointpoint(i)
109 
110  def append(self, point):
111  if isinstance(point, NVector):
112  self.bezierbezier.add_point(point.clone())
113  else:
114  bpt = point.relative()
115  self.bezierbezier.add_point(bpt.vertex.clone(), bpt.in_tangent.clone(), bpt.out_tangent.clone())
116 
117  @property
118  def absolute(self):
119  return BezierView(self.bezierbezier, True)
120 
121 
122 ## @ingroup Lottie
124  """!
125  Single bezier curve
126  """
127  _props = [
128  LottieProp("closed", "c", bool, False),
129  LottieProp("in_tangents", "i", NVector, True),
130  LottieProp("out_tangents", "o", NVector, True),
131  LottieProp("vertices", "v", NVector, True),
132  ]
133 
134  def __init__(self):
135  ## Closed property of shape
136  self.closedclosed = False
137  ## Cubic bezier handles for the segments before each vertex
138  self.in_tangentsin_tangents = []
139  ## Cubic bezier handles for the segments after each vertex
140  self.out_tangentsout_tangents = []
141  ## Bezier curve vertices.
142  self.verticesvertices = []
143  #self.rel_tangents = rel_tangents
144  ## More convent way to access points
145  self.pointspoints = BezierView(self)
146 
147  def clone(self):
148  clone = Bezier()
149  clone.closed = self.closedclosed
150  clone.in_tangents = [p.clone() for p in self.in_tangentsin_tangents]
151  clone.out_tangents = [p.clone() for p in self.out_tangentsout_tangents]
152  clone.vertices = [p.clone() for p in self.verticesvertices]
153  #clone.rel_tangents = self.rel_tangents
154  return clone
155 
156  def insert_point(self, index, pos, inp=NVector(0, 0), outp=NVector(0, 0)):
157  """!
158  Inserts a point at the given index
159  @param index Index to insert the point at
160  @param pos Point to add
161  @param inp Tangent entering the point, as a vector relative to @p pos
162  @param outp Tangent exiting the point, as a vector relative to @p pos
163  @returns @c self, for easy chaining
164  """
165  self.verticesvertices.insert(index, pos)
166  self.in_tangentsin_tangents.insert(index, inp.clone())
167  self.out_tangentsout_tangents.insert(index, outp.clone())
168  #if not self.rel_tangents:
169  #self.in_tangents[-1] += pos
170  #self.out_tangents[-1] += pos
171  return self
172 
173  def add_point(self, pos, inp=NVector(0, 0), outp=NVector(0, 0)):
174  """!
175  Appends a point to the curve
176  @see insert_point
177  """
178  self.insert_pointinsert_point(len(self.verticesvertices), pos, inp, outp)
179  return self
180 
181  def add_smooth_point(self, pos, inp):
182  """!
183  Appends a point with symmetrical tangents
184  @see insert_point
185  """
186  self.add_pointadd_point(pos, inp, -inp)
187  return self
188 
189  def close(self, closed=True):
190  """!
191  Updates self.closed
192  @returns @c self, for easy chaining
193  """
194  self.closedclosed = closed
195  return self
196 
197  def point_at(self, t):
198  """!
199  @param t A value between 0 and 1, percentage along the length of the curve
200  @returns The point at @p t in the curve
201  """
202  i, t = self._index_t_index_t(t)
203  points = self._bezier_points_bezier_points(i, True)
204  return self._solve_bezier_solve_bezier(t, points)
205 
206  def tangent_angle_at(self, t):
207  i, t = self._index_t_index_t(t)
208  points = self._bezier_points_bezier_points(i, True)
209 
210  n = len(points) - 1
211  if n > 0:
212  delta = sum((
213  (points[i+1] - points[i]) * n * self._solve_bezier_coeff_solve_bezier_coeff(i, n - 1, t)
214  for i in range(n)
215  ), NVector(0, 0))
216  return math.atan2(delta.y, delta.x)
217 
218  return 0
219 
220  def _split(self, t):
221  i, t = self._index_t_index_t(t)
222  cub = self._bezier_points_bezier_points(i, True)
223  split1, split2 = self._split_segment_split_segment(t, cub)
224  return i, split1, split2
225 
226  def _split_segment(self, t, cub):
227  if len(cub) == 2:
228  k = self._solve_bezier_step_solve_bezier_step(t, cub)[0]
229  split1 = [cub[0], NVector(0, 0), NVector(0, 0), k]
230  split2 = [k, NVector(0, 0), NVector(0, 0), cub[-1]]
231  return split1, split2
232 
233  if len(cub) == 3:
234  quad = cub
235  else:
236  quad = self._solve_bezier_step_solve_bezier_step(t, cub)
237  lin = self._solve_bezier_step_solve_bezier_step(t, quad)
238  k = self._solve_bezier_step_solve_bezier_step(t, lin)[0]
239  split1 = [cub[0], quad[0]-cub[0], lin[0]-k, k]
240  split2 = [k, lin[-1]-k, quad[-1]-cub[-1], cub[-1]]
241  return split1, split2
242 
243  def split_at(self, t):
244  """!
245  Get two pieces out of a Bezier curve
246  @param t A value between 0 and 1, percentage along the length of the curve
247  @returns Two Bezier objects that correspond to self, but split at @p t
248  """
249  if len(self.verticesvertices) < 2:
250  return Bezier(), self.cloneclonecloneclone()
251 
252  i, split1, split2 = self._split_split(t)
253 
254  seg1 = Bezier()
255  seg2 = Bezier()
256  for j in range(i):
257  seg1.add_point(self.verticesvertices[j].clone(), self.in_tangentsin_tangents[j].clone(), self.out_tangentsout_tangents[j].clone())
258  for j in range(i+2, len(self.verticesvertices)):
259  seg2.add_point(self.verticesvertices[j].clone(), self.in_tangentsin_tangents[j].clone(), self.out_tangentsout_tangents[j].clone())
260 
261  seg1.add_point(split1[0], self.in_tangentsin_tangents[i].clone(), split1[1])
262  seg1.add_point(split1[3], split1[2], split2[1])
263 
264  seg2.insert_point(0, split2[0], split1[2], split2[1])
265  seg2.insert_point(1, split2[3], split2[2], self.out_tangentsout_tangents[i+1].clone())
266 
267  return seg1, seg2
268 
269  def segment(self, t1, t2):
270  """!
271  Splits a Bezier in two points and returns the segment between the
272  @param t1 A value between 0 and 1, percentage along the length of the curve
273  @param t2 A value between 0 and 1, percentage along the length of the curve
274  @returns Bezier object that correspond to the segment between @p t1 and @p t2
275  """
276  if self.closedclosed and self.verticesvertices and self.verticesvertices[-1] != self.verticesvertices[0]:
277  copy = self.cloneclonecloneclone()
278  copy.add_point(self.verticesvertices[0])
279  copy.closed = False
280  return copy.segment(t1, t2)
281 
282  if t1 > 1:
283  t1 = 1
284  if t2 > 1:
285  t2 = 1
286 
287  if t1 > t2:
288  t1, t2 = t2, t1
289  elif t1 == t2:
290  seg = Bezier()
291  p = self.point_atpoint_at(t1)
292  seg.add_point(p)
293  seg.add_point(p)
294  return seg
295 
296  seg1, seg2 = self.split_atsplit_at(t1)
297  t2p = (t2-t1) / (1-t1)
298  seg3, seg4 = seg2.split_at(t2p)
299  return seg3
300 
301  def split_self_multi(self, positions):
302  """!
303  Adds more points to the Bezier
304  @param positions list of percentages along the curve
305  """
306  if not len(positions):
307  return
308  t1 = positions[0]
309  seg1, seg2 = self.split_atsplit_at(t1)
310  self.verticesvertices = []
311  self.in_tangentsin_tangents = []
312  self.out_tangentsout_tangents = []
313 
314  self.verticesvertices = seg1.vertices[:-1]
315  self.in_tangentsin_tangents = seg1.in_tangents[:-1]
316  self.out_tangentsout_tangents = seg1.out_tangents[:-1]
317 
318  for t2 in positions[1:]:
319  t = (t2-t1) / (1-t1)
320  seg1, seg2 = seg2.split_at(t)
321  t1 = t
322  self.verticesvertices += seg1.vertices[:-1]
323  self.in_tangentsin_tangents += seg1.in_tangents[:-1]
324  self.out_tangentsout_tangents += seg1.out_tangents[:-1]
325 
326  self.verticesvertices += seg2.vertices
327  self.in_tangentsin_tangents += seg2.in_tangents
328  self.out_tangentsout_tangents += seg2.out_tangents
329 
331  """!
332  Adds a point in the middle of the segment between every pair of points in the Bezier
333  """
334  vertices = self.verticesvertices
335  in_tangents = self.in_tangentsin_tangents
336  out_tangents = self.out_tangentsout_tangents
337 
338  self.verticesvertices = []
339  self.in_tangentsin_tangents = []
340  self.out_tangentsout_tangents = []
341 
342  for i in range(len(vertices)-1):
343  tocut = [vertices[i], out_tangents[i]+vertices[i], in_tangents[i+1]+vertices[i+1], vertices[i+1]]
344  split1, split2 = self._split_segment_split_segment(0.5, tocut)
345  if i:
346  self.out_tangentsout_tangents[-1] = split1[1]
347  else:
348  self.add_pointadd_point(vertices[0], in_tangents[0], split1[1])
349  self.add_pointadd_point(split1[3], split1[2], split2[1])
350  self.add_pointadd_point(vertices[i+1], split2[2], NVector(0, 0))
351 
352  def split_self_chunks(self, n_chunks):
353  """!
354  Adds points the Bezier, splitting it into @p n_chunks additional chunks.
355  """
356  splits = [i/n_chunks for i in range(1, n_chunks)]
357  return self.split_self_multisplit_self_multi(splits)
358 
359  def _bezier_points(self, i, optimize):
360  v1 = self.verticesvertices[i].clone()
361  v2 = self.verticesvertices[i+1].clone()
362  points = [v1]
363  t1 = self.out_tangentsout_tangents[i].clone()
364  if not optimize or t1.length != 0:
365  points.append(t1+v1)
366  t2 = self.in_tangentsin_tangents[i+1].clone()
367  if not optimize or t1.length != 0:
368  points.append(t2+v2)
369  points.append(v2)
370  return points
371 
372  def _solve_bezier_step(self, t, points):
373  next = []
374  p1 = points[0]
375  for p2 in points[1:]:
376  next.append(p1 * (1-t) + p2 * t)
377  p1 = p2
378  return next
379 
380  def _solve_bezier_coeff(self, i, n, t):
381  return (
382  math.factorial(n) / (math.factorial(i) * math.factorial(n - i)) # (n choose i)
383  * (t ** i) * ((1 - t) ** (n-i))
384  )
385 
386  def _solve_bezier(self, t, points):
387  n = len(points) - 1
388  if n > 0:
389  return sum((
390  points[i] * self._solve_bezier_coeff_solve_bezier_coeff(i, n, t)
391  for i in range(n+1)
392  ), NVector(0, 0))
393 
394  #while len(points) > 1:
395  #points = self._solve_bezier_step(t, points)
396  return points[0]
397 
398  def _index_t(self, t):
399  if t <= 0:
400  return 0, 0
401 
402  if t >= 1:
403  return len(self.verticesvertices)-2, 1
404 
405  n = len(self.verticesvertices)-1
406  for i in range(n):
407  if (i+1) / n > t:
408  break
409 
410  return i, (t - (i/n)) * n
411 
412  def reverse(self):
413  """!
414  Reverses the Bezier curve
415  """
416  self.verticesvertices = list(reversed(self.verticesvertices))
417  out_tangents = list(reversed(self.in_tangentsin_tangents))
418  in_tangents = list(reversed(self.out_tangentsout_tangents))
419  self.in_tangentsin_tangents = in_tangents
420  self.out_tangentsout_tangents = out_tangents
421 
422  """def to_absolute(self):
423  if self.rel_tangents:
424  self.rel_tangents = False
425  for i in range(len(self.vertices)):
426  p = self.vertices[i]
427  self.in_tangents[i] += p
428  self.out_tangents[i] += p
429  return self"""
430 
431  def rounded(self, round_distance):
432  cloned = Bezier()
433  cloned.closed = self.closedclosed
434  # value from https://spencermortensen.com/articles/bezier-circle/
435  round_corner = 0.5519
436 
437  def _get_vt(closest_index):
438  closer_v = self.verticesvertices[closest_index]
439  distance = (current - closer_v).length
440  new_pos_perc = min(distance/2, round_distance) / distance if distance else 0
441  vert = current + (closer_v - current) * new_pos_perc
442  tan = - (vert - current) * round_corner
443  return vert, tan
444 
445  for i, current in enumerate(self.verticesvertices):
446  if not self.closedclosed and (i == 0 or i == len(self.pointspoints) - 1):
447  cloned.points.append(self.pointspoints[i])
448  else:
449  vert1, out_t = _get_vt(i - 1)
450  cloned.add_point(vert1, NVector(0, 0), out_t)
451  vert2, in_t = _get_vt((i+1) % len(self.pointspoints))
452  cloned.add_point(vert2, in_t, NVector(0, 0))
453 
454  return cloned
455 
456  def scale(self, amount):
457  for vl in (self.verticesvertices, self.in_tangentsin_tangents, self.out_tangentsout_tangents):
458  for v in vl:
459  v *= amount
460 
461  def lerp(self, other, t):
462  if len(other.vertices) != len(self.verticesvertices):
463  if t < 1:
464  return self.cloneclonecloneclone()
465  return other.clone()
466 
467  bez = Bezier()
468  bez.closed = self.closedclosed
469 
470  for vlist_name in ["vertices", "in_tangents", "out_tangents"]:
471  vlist = getattr(self, vlist_name)
472  olist = getattr(other, vlist_name)
473  out = getattr(bez, vlist_name)
474  for v, o in zip(vlist, olist):
475  out.append(v.lerp(o, t))
476 
477  return bez
478 
479  def rough_length(self):
480  if len(self.verticesvertices) < 2:
481  return 0
482  last = self.verticesvertices[0]
483  length = 0
484  for v in self.verticesvertices[1:]:
485  length += (v-last).length
486  last = v
487  if self.closedclosed:
488  length += (last-self.verticesvertices[0]).length
489  return length
490 
491 
493  def __init__(self, p0: NVector, p1: NVector, p2: NVector, p3: NVector):
494  self.a, self.b, self.c, self.dd = self.coefficientscoefficients(p0, p1, p2, p3)
495  self.pointspoints = (p0, p1, p2, p3)
496 
497  @staticmethod
498  def coeff3(p0: NVector, p1: NVector, p2: NVector, p3: NVector) -> NVector:
499  """!
500  Coefficient of the cubic term
501  """
502  return p0 - p1 * 3 + p2 * 3 - p3
503 
504  @staticmethod
505  def coeff2(p0: NVector, p1: NVector, p2: NVector, p3: NVector) -> NVector:
506  """!
507  Coefficient of the quadratic term
508  """
509  return -p0 * 3 + p1 * 6 - p2 * 3
510 
511  @staticmethod
512  def coeff1(p0: NVector, p1: NVector, p2: NVector, p3: NVector) -> NVector:
513  """!
514  Coefficient of the linear term
515  """
516  return p0 * 3 - p1 * 3
517 
518  @staticmethod
519  def coeff0(p0: NVector, p1: NVector, p2: NVector, p3: NVector) -> NVector:
520  """!
521  Coefficient of the constant term
522  """
523  return p0
524 
525  @staticmethod
526  def coefficients(p0: NVector, p1: NVector, p2: NVector, p3: NVector):
527  """!
528  Returns a tuble (a, b, c, d) such that
529 
530  `a t**3 + b t**2 + c t + d = 0`
531  """
532  return (
533  CubicBezierSegment.coeff3(p0, p1, p2, p3),
534  CubicBezierSegment.coeff2(p0, p1, p2, p3),
535  CubicBezierSegment.coeff1(p0, p1, p2, p3),
536  CubicBezierSegment.coeff0(p0, p1, p2, p3),
537  )
538 
539  def solve(self, t: float) -> NVector:
540  return ((self.a * t + self.b) * t + self.c) * t + self. d
541 
542  def length(self, steps: int = 20) -> float:
543  length = 0
544 
545  p = self.pointspoints[0]
546  for i in range(1, steps+1):
547  t = i / steps
548  q = self.solvesolve(t)
549  l = (p - q).length
550  length = l
551  p = q
552 
553  return length
def clone(self)
Returns a copy of the object.
Definition: base.py:26
Base class for mapping Python classes into Lottie JSON objects.
Definition: base.py:230
def clone(self)
Returns a copy of the object.
Definition: base.py:304
Lottie <-> Python property mapper.
Definition: base.py:88
def __init__(self, bezier, index)
Definition: bezier.py:32
def smooth(cls, point, in_tangent)
Definition: bezier.py:16
def __init__(self, vertex, in_tangent=None, out_tangent=None)
Definition: bezier.py:7
def from_absolute(cls, point, in_tangent=None, out_tangent=None)
Definition: bezier.py:20
def point(self, index)
Definition: bezier.py:90
def append(self, point)
Definition: bezier.py:110
def __getitem__(self, key)
Definition: bezier.py:98
def __init__(self, bezier, absolute=False)
Definition: bezier.py:86
Single bezier curve.
Definition: bezier.py:123
vertices
Bezier curve vertices.
Definition: bezier.py:142
def reverse(self)
Reverses the Bezier curve.
Definition: bezier.py:412
def split_at(self, t)
Get two pieces out of a Bezier curve.
Definition: bezier.py:243
def add_smooth_point(self, pos, inp)
Appends a point with symmetrical tangents.
Definition: bezier.py:181
def rounded(self, round_distance)
Definition: bezier.py:431
def segment(self, t1, t2)
Splits a Bezier in two points and returns the segment between the.
Definition: bezier.py:269
def _solve_bezier(self, t, points)
Definition: bezier.py:386
def split_self_multi(self, positions)
Adds more points to the Bezier.
Definition: bezier.py:301
def clone(self)
Returns a copy of the object.
Definition: bezier.py:147
def _split_segment(self, t, cub)
Definition: bezier.py:226
def _solve_bezier_coeff(self, i, n, t)
Definition: bezier.py:380
def _solve_bezier_step(self, t, points)
Definition: bezier.py:372
closed
Closed property of shape.
Definition: bezier.py:136
def scale(self, amount)
Definition: bezier.py:456
def split_each_segment(self)
Adds a point in the middle of the segment between every pair of points in the Bezier.
Definition: bezier.py:330
def split_self_chunks(self, n_chunks)
Adds points the Bezier, splitting it into n_chunks additional chunks.
Definition: bezier.py:352
def insert_point(self, index, pos, inp=NVector(0, 0), outp=NVector(0, 0))
Inserts a point at the given index.
Definition: bezier.py:156
def close(self, closed=True)
Updates self.closed.
Definition: bezier.py:189
points
More convent way to access points.
Definition: bezier.py:145
def tangent_angle_at(self, t)
Definition: bezier.py:206
def add_point(self, pos, inp=NVector(0, 0), outp=NVector(0, 0))
Appends a point to the curve.
Definition: bezier.py:173
in_tangents
Cubic bezier handles for the segments before each vertex.
Definition: bezier.py:138
def lerp(self, other, t)
Definition: bezier.py:461
def _bezier_points(self, i, optimize)
Definition: bezier.py:359
out_tangents
Cubic bezier handles for the segments after each vertex.
Definition: bezier.py:140
float length(self, int steps=20)
Definition: bezier.py:542
def coefficients(NVector p0, NVector p1, NVector p2, NVector p3)
Returns a tuble (a, b, c, d) such that.
Definition: bezier.py:526
NVector solve(self, float t)
Definition: bezier.py:539
NVector coeff2(NVector p0, NVector p1, NVector p2, NVector p3)
Coefficient of the quadratic term.
Definition: bezier.py:505
NVector coeff1(NVector p0, NVector p1, NVector p2, NVector p3)
Coefficient of the linear term.
Definition: bezier.py:512
def __init__(self, NVector p0, NVector p1, NVector p2, NVector p3)
Definition: bezier.py:493
NVector coeff3(NVector p0, NVector p1, NVector p2, NVector p3)
Coefficient of the cubic term.
Definition: bezier.py:498
NVector coeff0(NVector p0, NVector p1, NVector p2, NVector p3)
Coefficient of the constant term.
Definition: bezier.py:519