python-lottie  0.6.10+dev2509936
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.vertex = vertex
9  self.in_tangent = in_tangent or NVector(0, 0)
10  self.out_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.bezier = bezier
34  self.index = index
35 
36  @property
37  def vertex(self):
38  return self.bezier.vertices[self.index]
39 
40  @vertex.setter
41  def vertex(self, point):
42  self.bezier.vertices[self.index] = point
43 
44  @property
45  def in_tangent(self):
46  return self.bezier.in_tangents[self.index]
47 
48  @in_tangent.setter
49  def in_tangent(self, point):
50  self.bezier.in_tangents[self.index] = point
51 
52  @property
53  def out_tangent(self):
54  return self.bezier.out_tangents[self.index]
55 
56  @out_tangent.setter
57  def out_tangent(self, point):
58  self.bezier.out_tangents[self.index] = point
59 
60  def relative(self):
61  return self
62 
63 
65  @property
66  def in_tangent(self):
67  return self.bezier.in_tangents[self.index] + self.vertex
68 
69  @in_tangent.setter
70  def in_tangent(self, point):
71  self.bezier.in_tangents[self.index] = point - self.vertex
72 
73  @property
74  def out_tangent(self):
75  return self.bezier.out_tangents[self.index] + self.vertex
76 
77  @out_tangent.setter
78  def out_tangent(self, point):
79  self.bezier.out_tangents[self.index] = point - self.vertex
80 
81  def relative(self):
82  return BezierPointView(self.bezier, self.index)
83 
84 
85 class BezierView:
86  def __init__(self, bezier, absolute=False):
87  self.bezier = bezier
88  self.is_absolute = absolute
89 
90  def point(self, index):
91  if self.is_absolute:
92  return AbsoluteBezierPointView(self.bezier, index)
93  return BezierPointView(self.bezier, index)
94 
95  def __len__(self):
96  return len(self.bezier.vertices)
97 
98  def __getitem__(self, key):
99  if isinstance(key, slice):
100  return [
101  self.point(i)
102  for i in key
103  ]
104  return self.point(key)
105 
106  def __iter__(self):
107  for i in range(len(self)):
108  yield self.point(i)
109 
110  def append(self, point):
111  if isinstance(point, NVector):
112  self.bezier.add_point(point.clone())
113  else:
114  bpt = point.relative()
115  self.bezier.add_point(bpt.vertex.clone(), bpt.in_tangent.clone(), bpt.out_tangent.clone())
116 
117  @property
118  def absolute(self):
119  return BezierView(self.bezier, 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.closed = False
137  ## Cubic bezier handles for the segments before each vertex
138  self.in_tangents = []
139  ## Cubic bezier handles for the segments after each vertex
140  self.out_tangents = []
141  ## Bezier curve vertices.
142  self.vertices = []
143  #self.rel_tangents = rel_tangents
144  ## More convent way to access points
145  self.points = BezierView(self)
146 
147  def clone(self):
148  clone = Bezier()
149  clone.closed = self.closed
150  clone.in_tangents = [p.clone() for p in self.in_tangents]
151  clone.out_tangents = [p.clone() for p in self.out_tangents]
152  clone.vertices = [p.clone() for p in self.vertices]
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.vertices.insert(index, pos)
166  self.in_tangents.insert(index, inp.clone())
167  self.out_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_point(len(self.vertices), 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_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.closed = 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(t)
203  points = self._bezier_points(i, True)
204  return self._solve_bezier(t, points)
205 
206  def tangent_angle_at(self, t):
207  i, t = self._index_t(t)
208  points = self._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(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(t)
222  cub = self._bezier_points(i, True)
223  split1, split2 = self._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(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(t, cub)
237  lin = self._solve_bezier_step(t, quad)
238  k = self._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  i, split1, split2 = self._split(t)
250 
251  seg1 = Bezier()
252  seg2 = Bezier()
253  for j in range(i):
254  seg1.add_point(self.vertices[j].clone(), self.in_tangents[j].clone(), self.out_tangents[j].clone())
255  for j in range(i+2, len(self.vertices)):
256  seg2.add_point(self.vertices[j].clone(), self.in_tangents[j].clone(), self.out_tangents[j].clone())
257 
258  seg1.add_point(split1[0], self.in_tangents[i].clone(), split1[1])
259  seg1.add_point(split1[3], split1[2], split2[1])
260 
261  seg2.insert_point(0, split2[0], split1[2], split2[1])
262  seg2.insert_point(1, split2[3], split2[2], self.out_tangents[i+1].clone())
263 
264  return seg1, seg2
265 
266  def segment(self, t1, t2):
267  """!
268  Splits a Bezier in two points and returns the segment between the
269  @param t1 A value between 0 and 1, percentage along the length of the curve
270  @param t2 A value between 0 and 1, percentage along the length of the curve
271  @returns Bezier object that correspond to the segment between @p t1 and @p t2
272  """
273  if self.closed and self.vertices and self.vertices[-1] != self.vertices[0]:
274  copy = self.clone()
275  copy.add_point(self.vertices[0])
276  copy.closed = False
277  return copy.segment(t1, t2)
278 
279  if t1 > 1:
280  t1 = 1
281  if t2 > 1:
282  t2 = 1
283 
284  if t1 > t2:
285  t1, t2 = t2, t1
286  elif t1 == t2:
287  seg = Bezier()
288  p = self.point_at(t1)
289  seg.add_point(p)
290  seg.add_point(p)
291  return seg
292 
293  seg1, seg2 = self.split_at(t1)
294  t2p = (t2-t1) / (1-t1)
295  seg3, seg4 = seg2.split_at(t2p)
296  return seg3
297 
298  def split_self_multi(self, positions):
299  """!
300  Adds more points to the Bezier
301  @param positions list of percentages along the curve
302  """
303  if not len(positions):
304  return
305  t1 = positions[0]
306  seg1, seg2 = self.split_at(t1)
307  self.vertices = []
308  self.in_tangents = []
309  self.out_tangents = []
310 
311  self.vertices = seg1.vertices[:-1]
312  self.in_tangents = seg1.in_tangents[:-1]
313  self.out_tangents = seg1.out_tangents[:-1]
314 
315  for t2 in positions[1:]:
316  t = (t2-t1) / (1-t1)
317  seg1, seg2 = seg2.split_at(t)
318  t1 = t
319  self.vertices += seg1.vertices[:-1]
320  self.in_tangents += seg1.in_tangents[:-1]
321  self.out_tangents += seg1.out_tangents[:-1]
322 
323  self.vertices += seg2.vertices
324  self.in_tangents += seg2.in_tangents
325  self.out_tangents += seg2.out_tangents
326 
328  """!
329  Adds a point in the middle of the segment between every pair of points in the Bezier
330  """
331  vertices = self.vertices
332  in_tangents = self.in_tangents
333  out_tangents = self.out_tangents
334 
335  self.vertices = []
336  self.in_tangents = []
337  self.out_tangents = []
338 
339  for i in range(len(vertices)-1):
340  tocut = [vertices[i], out_tangents[i]+vertices[i], in_tangents[i+1]+vertices[i+1], vertices[i+1]]
341  split1, split2 = self._split_segment(0.5, tocut)
342  if i:
343  self.out_tangents[-1] = split1[1]
344  else:
345  self.add_point(vertices[0], in_tangents[0], split1[1])
346  self.add_point(split1[3], split1[2], split2[1])
347  self.add_point(vertices[i+1], split2[2], NVector(0, 0))
348 
349  def split_self_chunks(self, n_chunks):
350  """!
351  Adds points the Bezier, splitting it into @p n_chunks additional chunks.
352  """
353  splits = [i/n_chunks for i in range(1, n_chunks)]
354  return self.split_self_multi(splits)
355 
356  def _bezier_points(self, i, optimize):
357  v1 = self.vertices[i].clone()
358  v2 = self.vertices[i+1].clone()
359  points = [v1]
360  t1 = self.out_tangents[i].clone()
361  if not optimize or t1.length != 0:
362  points.append(t1+v1)
363  t2 = self.in_tangents[i+1].clone()
364  if not optimize or t1.length != 0:
365  points.append(t2+v2)
366  points.append(v2)
367  return points
368 
369  def _solve_bezier_step(self, t, points):
370  next = []
371  p1 = points[0]
372  for p2 in points[1:]:
373  next.append(p1 * (1-t) + p2 * t)
374  p1 = p2
375  return next
376 
377  def _solve_bezier_coeff(self, i, n, t):
378  return (
379  math.factorial(n) / (math.factorial(i) * math.factorial(n - i)) # (n choose i)
380  * (t ** i) * ((1 - t) ** (n-i))
381  )
382 
383  def _solve_bezier(self, t, points):
384  n = len(points) - 1
385  if n > 0:
386  return sum((
387  points[i] * self._solve_bezier_coeff(i, n, t)
388  for i in range(n+1)
389  ), NVector(0, 0))
390 
391  #while len(points) > 1:
392  #points = self._solve_bezier_step(t, points)
393  return points[0]
394 
395  def _index_t(self, t):
396  if t <= 0:
397  return 0, 0
398 
399  if t >= 1:
400  return len(self.vertices)-2, 1
401 
402  n = len(self.vertices)-1
403  for i in range(n):
404  if (i+1) / n > t:
405  break
406 
407  return i, (t - (i/n)) * n
408 
409  def reverse(self):
410  """!
411  Reverses the Bezier curve
412  """
413  self.vertices = list(reversed(self.vertices))
414  out_tangents = list(reversed(self.in_tangents))
415  in_tangents = list(reversed(self.out_tangents))
416  self.in_tangents = in_tangents
417  self.out_tangents = out_tangents
418 
419  """def to_absolute(self):
420  if self.rel_tangents:
421  self.rel_tangents = False
422  for i in range(len(self.vertices)):
423  p = self.vertices[i]
424  self.in_tangents[i] += p
425  self.out_tangents[i] += p
426  return self"""
427 
428  def rounded(self, round_distance):
429  cloned = Bezier()
430  cloned.closed = self.closed
431  # value from https://spencermortensen.com/articles/bezier-circle/
432  round_corner = 0.5519
433 
434  def _get_vt(closest_index):
435  closer_v = self.vertices[closest_index]
436  distance = (current - closer_v).length
437  new_pos_perc = min(distance/2, round_distance) / distance if distance else 0
438  vert = current + (closer_v - current) * new_pos_perc
439  tan = - (vert - current) * round_corner
440  return vert, tan
441 
442  for i, current in enumerate(self.vertices):
443  if not self.closed and (i == 0 or i == len(self.points) - 1):
444  cloned.points.append(self.points[i])
445  else:
446  vert1, out_t = _get_vt(i - 1)
447  cloned.add_point(vert1, NVector(0, 0), out_t)
448  vert2, in_t = _get_vt((i+1) % len(self.points))
449  cloned.add_point(vert2, in_t, NVector(0, 0))
450 
451  return cloned
452 
453  def scale(self, amount):
454  for vl in (self.vertices, self.in_tangents, self.out_tangents):
455  for v in vl:
456  v *= amount
457 
458  def lerp(self, other, t):
459  if len(other.vertices) != len(self.vertices):
460  if t < 1:
461  return self.clone()
462  return other.clone()
463 
464  bez = Bezier()
465  bez.closed = self.closed
466 
467  for vlist_name in ["vertices", "in_tangents", "out_tangents"]:
468  vlist = getattr(self, vlist_name)
469  olist = getattr(other, vlist_name)
470  out = getattr(bez, vlist_name)
471  for v, o in zip(vlist, olist):
472  out.append(v.lerp(o, t))
473 
474  return bez
475 
476  def rough_length(self):
477  if len(self.vertices) < 2:
478  return 0
479  last = self.vertices[0]
480  length = 0
481  for v in self.vertices[1:]:
482  length += (v-last).length
483  last = v
484  if self.closed:
485  length += (last-self.vertices[0]).length
486  return length
lottie.objects.bezier.Bezier.clone
def clone(self)
Returns a copy of the object.
Definition: bezier.py:147
lottie.objects.bezier.Bezier.add_smooth_point
def add_smooth_point(self, pos, inp)
Appends a point with symmetrical tangents.
Definition: bezier.py:181
lottie.objects.bezier.Bezier.out_tangents
out_tangents
Cubic bezier handles for the segments after each vertex.
Definition: bezier.py:140
lottie.objects.bezier.BezierPointView.out_tangent
def out_tangent(self)
Definition: bezier.py:53
lottie.objects.bezier.Bezier.__init__
def __init__(self)
Definition: bezier.py:134
lottie.objects.bezier.Bezier.points
points
More convent way to access points.
Definition: bezier.py:145
lottie.objects.bezier.Bezier.split_at
def split_at(self, t)
Get two pieces out of a Bezier curve.
Definition: bezier.py:243
lottie.objects.bezier.Bezier._index_t
def _index_t(self, t)
Definition: bezier.py:395
lottie.objects.bezier.BezierPointView
Definition: bezier.py:28
lottie.objects.bezier.BezierPoint.smooth
def smooth(cls, point, in_tangent)
Definition: bezier.py:16
lottie.objects.bezier.BezierView
Definition: bezier.py:85
lottie.objects.bezier.Bezier
Single bezier curve.
Definition: bezier.py:123
lottie.objects.bezier.AbsoluteBezierPointView.out_tangent
def out_tangent(self)
Definition: bezier.py:74
lottie.objects.bezier.BezierPointView.in_tangent
def in_tangent(self)
Definition: bezier.py:45
lottie.objects.bezier.BezierView.is_absolute
is_absolute
Definition: bezier.py:88
lottie.objects.bezier.Bezier.lerp
def lerp(self, other, t)
Definition: bezier.py:458
lottie.objects.base.LottieBase.clone
def clone(self)
Returns a copy of the object.
Definition: base.py:26
lottie.objects.bezier.AbsoluteBezierPointView.relative
def relative(self)
Definition: bezier.py:81
lottie.objects.bezier.Bezier.split_self_multi
def split_self_multi(self, positions)
Adds more points to the Bezier.
Definition: bezier.py:298
lottie.objects.base.LottieObject
Base class for mapping Python classes into Lottie JSON objects.
Definition: base.py:224
lottie.objects.bezier.BezierView.__iter__
def __iter__(self)
Definition: bezier.py:106
lottie.objects.bezier.Bezier.tangent_angle_at
def tangent_angle_at(self, t)
Definition: bezier.py:206
lottie.objects.bezier.Bezier._bezier_points
def _bezier_points(self, i, optimize)
Definition: bezier.py:356
lottie.objects.bezier.BezierView.append
def append(self, point)
Definition: bezier.py:110
lottie.objects.bezier.BezierPoint.relative
def relative(self)
Definition: bezier.py:12
lottie.objects.bezier.Bezier.split_each_segment
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:327
lottie.objects.bezier.Bezier._split
def _split(self, t)
Definition: bezier.py:220
lottie.objects.base.LottieProp
Lottie <-> Python property mapper.
Definition: base.py:88
lottie.objects.bezier.BezierPoint.vertex
vertex
Definition: bezier.py:8
lottie.objects.bezier.BezierPointView.bezier
bezier
Definition: bezier.py:33
lottie.objects.bezier.BezierPointView.__init__
def __init__(self, bezier, index)
Definition: bezier.py:32
lottie.objects.bezier.BezierPoint.__init__
def __init__(self, vertex, in_tangent=None, out_tangent=None)
Definition: bezier.py:7
lottie.objects.bezier.Bezier._split_segment
def _split_segment(self, t, cub)
Definition: bezier.py:226
lottie.objects.bezier.BezierPointView.vertex
def vertex(self)
Definition: bezier.py:37
lottie.objects.bezier.BezierView.__init__
def __init__(self, bezier, absolute=False)
Definition: bezier.py:86
lottie.objects.bezier.BezierPoint.out_tangent
out_tangent
Definition: bezier.py:10
lottie.objects.bezier.AbsoluteBezierPointView
Definition: bezier.py:64
lottie.objects.bezier.Bezier._solve_bezier_coeff
def _solve_bezier_coeff(self, i, n, t)
Definition: bezier.py:377
lottie.objects.bezier.Bezier.add_point
def add_point(self, pos, inp=NVector(0, 0), outp=NVector(0, 0))
Appends a point to the curve.
Definition: bezier.py:173
lottie.objects.bezier.Bezier.rounded
def rounded(self, round_distance)
Definition: bezier.py:428
lottie.objects.bezier.BezierPoint.in_tangent
in_tangent
Definition: bezier.py:9
lottie.objects.bezier.BezierView.absolute
def absolute(self)
Definition: bezier.py:118
lottie.objects.bezier.AbsoluteBezierPointView.in_tangent
def in_tangent(self)
Definition: bezier.py:66
lottie.objects.bezier.Bezier._solve_bezier
def _solve_bezier(self, t, points)
Definition: bezier.py:383
lottie.objects.bezier.BezierView.__len__
def __len__(self)
Definition: bezier.py:95
lottie.objects.bezier.Bezier.close
def close(self, closed=True)
Updates self.closed.
Definition: bezier.py:189
lottie.objects.bezier.Bezier.point_at
def point_at(self, t)
Definition: bezier.py:197
lottie.objects.bezier.Bezier.in_tangents
in_tangents
Cubic bezier handles for the segments before each vertex.
Definition: bezier.py:138
lottie.objects.bezier.Bezier.reverse
def reverse(self)
Reverses the Bezier curve.
Definition: bezier.py:409
lottie.objects.bezier.Bezier.scale
def scale(self, amount)
Definition: bezier.py:453
lottie.objects.bezier.Bezier.insert_point
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
lottie.objects.bezier.BezierPoint
Definition: bezier.py:6
lottie.objects.bezier.Bezier.closed
closed
Closed property of shape.
Definition: bezier.py:136
lottie.objects.bezier.BezierView.point
def point(self, index)
Definition: bezier.py:90
lottie.objects.bezier.BezierPointView.relative
def relative(self)
Definition: bezier.py:60
lottie.objects.bezier.Bezier._solve_bezier_step
def _solve_bezier_step(self, t, points)
Definition: bezier.py:369
lottie.objects.bezier.Bezier.vertices
vertices
Bezier curve vertices.
Definition: bezier.py:142
lottie.objects.bezier.BezierView.__getitem__
def __getitem__(self, key)
Definition: bezier.py:98
lottie.objects.bezier.Bezier.split_self_chunks
def split_self_chunks(self, n_chunks)
Adds points the Bezier, splitting it into n_chunks additional chunks.
Definition: bezier.py:349
lottie.objects.bezier.Bezier.segment
def segment(self, t1, t2)
Splits a Bezier in two points and returns the segment between the.
Definition: bezier.py:266
lottie.objects.bezier.Bezier.rough_length
def rough_length(self)
Definition: bezier.py:476
lottie.objects.bezier.BezierPointView.index
index
Definition: bezier.py:34
lottie.objects.bezier.BezierPoint.from_absolute
def from_absolute(cls, point, in_tangent=None, out_tangent=None)
Definition: bezier.py:20
lottie.objects.bezier.BezierView.bezier
bezier
Definition: bezier.py:87
lottie.nvector.NVector
Definition: nvector.py:9