python-lottie  0.7.0+dev66cafb9
A framework to work with lottie files and telegram animated stickers (tgs)
animation.py
Go to the documentation of this file.
1 import random
2 import math
3 from ..nvector import NVector
4 from ..objects.shapes import Path
5 from .. import objects
6 from ..objects import easing
7 from ..objects import properties
8 
9 
10 def shake(position_prop, x_radius, y_radius, start_time, end_time, n_frames, interp=easing.Linear()):
11  if not isinstance(position_prop, list):
12  position_prop = [position_prop]
13 
14  n_frames = int(round(n_frames))
15  frame_time = (end_time - start_time) / n_frames
16  startpoints = list(map(
17  lambda pp: pp.get_value(start_time),
18  position_prop
19  ))
20 
21  for i in range(n_frames):
22  x = (random.random() * 2 - 1) * x_radius
23  y = (random.random() * 2 - 1) * y_radius
24  for pp, start in zip(position_prop, startpoints):
25  px = start[0] + x
26  py = start[1] + y
27  pp.add_keyframe(start_time + i * frame_time, NVector(px, py), interp)
28 
29  for pp, start in zip(position_prop, startpoints):
30  pp.add_keyframe(end_time, start, interp)
31 
32 
33 def rot_shake(rotation_prop, angles, start_time, end_time, n_frames):
34  frame_time = (end_time - start_time) / n_frames
35  start = rotation_prop.get_value(start_time)
36 
37  for i in range(0, n_frames):
38  a = angles[i % len(angles)] * math.sin(i/n_frames * math.pi)
39  rotation_prop.add_keyframe(start_time + i * frame_time, start + a)
40  rotation_prop.add_keyframe(end_time, start)
41 
42 
43 def spring_pull(position_prop, point, start_time, end_time, falloff=15, oscillations=7):
44  start = position_prop.get_value(start_time)
45  d = start-point
46 
47  delta = (end_time - start_time) / oscillations
48 
49  for i in range(oscillations):
50  time_x = i / oscillations
51  factor = math.cos(time_x * math.pi * oscillations) * (1-time_x**(1/falloff))
52  p = point + d * factor
53  position_prop.add_keyframe(start_time + delta * i, p)
54 
55  position_prop.add_keyframe(end_time, point)
56 
57 
58 def follow_path(position_prop, bezier, start_time, end_time, n_keyframes,
59  reverse=False, offset=NVector(0, 0), start_t=0, rotation_prop=None, rotation_offset=0):
60  delta = (end_time - start_time) / (n_keyframes-1)
61  fact = start_t
62  factd = 1 / (n_keyframes-1)
63 
64  if rotation_prop:
65  start_rot = rotation_prop.get_value(start_time) if rotation_offset is None else rotation_offset
66 
67  for i in range(n_keyframes):
68  time = start_time + i * delta
69 
70  if fact > 1 + factd/2:
71  fact -= 1
72  if time != start_time:
73  easing.Jump()(position_prop.keyframes[-1])
74  if rotation_prop:
75  easing.Jump()(rotation_prop.keyframes[-1])
76 
77  f = 1 - fact if reverse else fact
78  position_prop.add_keyframe(time, bezier.point_at(f)+offset)
79 
80  if rotation_prop:
81  rotation_prop.add_keyframe(time, bezier.tangent_angle_at(f) / math.pi * 180 + start_rot)
82 
83  fact += factd
84 
85 
86 def generate_path_appear(bezier, appear_start, appear_end, n_keyframes, reverse=False):
87  obj = Path()
88  beziers = []
89  maxp = 0
90 
91  time_delta = (appear_end - appear_start) / n_keyframes
92  for i in range(n_keyframes+1):
93  time = appear_start + i * time_delta
94  t2 = (time - appear_start) / (appear_end - appear_start)
95 
96  if reverse:
97  t2 = 1 - t2
98  segment = bezier.segment(t2, 1)
99  segment.reverse()
100  else:
101  segment = bezier.segment(0, t2)
102 
103  beziers.append(segment)
104  if len(segment.vertices) > maxp:
105  maxp = len(segment.vertices)
106 
107  obj.shape.add_keyframe(time, segment)
108 
109  for segment in beziers:
110  deltap = maxp - len(segment.vertices)
111  if deltap > 0:
112  segment.vertices += [segment.vertices[-1]] * deltap
113  segment.in_tangents += [NVector(0, 0)] * deltap
114  segment.out_tangents += [NVector(0, 0)] * deltap
115 
116  return obj
117 
118 
119 def generate_path_disappear(bezier, disappear_start, disappear_end, n_keyframes, reverse=False):
120  obj = Path()
121  beziers = []
122  maxp = 0
123 
124  time_delta = (disappear_end - disappear_start) / n_keyframes
125  for i in range(n_keyframes+1):
126  time = disappear_start + i * time_delta
127  t1 = (time - disappear_start) / (disappear_end - disappear_start)
128  if reverse:
129  t1 = 1 - t1
130  segment = bezier.segment(0, t1)
131  else:
132  segment = bezier.segment(1, t1)
133  segment.reverse()
134 
135  beziers.append(segment)
136  if len(segment.vertices) > maxp:
137  maxp = len(segment.vertices)
138 
139  obj.shape.add_keyframe(time, segment)
140 
141  for segment in beziers:
142  deltap = maxp - len(segment.vertices)
143  if deltap > 0:
144  segment.vertices += [segment.vertices[-1]] * deltap
145  segment.in_tangents += [NVector(0, 0)] * deltap
146  segment.out_tangents += [NVector(0, 0)] * deltap
147 
148  return obj
149 
150 
151 def generate_path_segment(bezier, appear_start, appear_end, disappear_start, disappear_end, n_keyframes, reverse=False):
152  obj = Path()
153  beziers = []
154  maxp = 0
155 
156  # HACK: For some reason reversed works better
157  if not reverse:
158  bezier.reverse()
159 
160  time_delta = (appear_end - appear_start) / n_keyframes
161  for i in range(n_keyframes+1):
162  time = appear_start + i * time_delta
163  t1 = (time - disappear_start) / (disappear_end - disappear_start)
164  t2 = (time - appear_start) / (appear_end - appear_start)
165 
166  t1 = max(0, min(1, t1))
167  t2 = max(0, min(1, t2))
168 
169  #if reverse:
170  if True:
171  t1 = 1 - t1
172  t2 = 1 - t2
173  segment = bezier.segment(t2, t1)
174  segment.reverse()
175  #else:
176  #segment = bezier.segment(t1, t2)
177  #segment.reverse()
178 
179  beziers.append(segment)
180  if len(segment.vertices) > maxp:
181  maxp = len(segment.vertices)
182 
183  obj.shape.add_keyframe(time, segment)
184 
185  for segment in beziers:
186  deltap = maxp - len(segment.vertices)
187  if deltap > 0:
188  segment.split_self_chunks(deltap+1)
189 
190  # HACK: Restore
191  if not reverse:
192  bezier.reverse()
193  return obj
194 
195 
197  def __init__(self, time_start, time_end, n_frames):
198  """!
199  @param time_start When the animation shall start
200  @param time_end When the animation shall end
201  @param n_frames Number of frames in the animation
202  """
203  ## When the animation shall start
204  self.time_starttime_start = time_start
205  ## When the animation shall end
206  self.time_endtime_end = time_end
207  ## Number of frames in the animation
208  self.n_framesn_frames = n_frames
209  ## Length of a frame
210  self.time_deltatime_delta = (time_end - time_start) / n_frames
211 
212  def animate_point(self, prop):
213  startpos = prop.get_value(self.time_starttime_start)
214  for f in range(self.n_framesn_frames+1):
215  p = self._on_displace_on_displace(startpos, f)
216  prop.add_keyframe(self.frame_timeframe_time(f), startpos+p)
217 
218  def _on_displace(self, startpos, f):
219  raise NotImplementedError()
220 
221  def animate_bezier(self, prop):
222  initial = prop.get_value(self.time_starttime_start)
223 
224  for f in range(self.n_framesn_frames+1):
225  bezier = objects.Bezier()
226  bezier.closed = initial.closed
227 
228  for pi in range(len(initial.vertices)):
229  startpos = initial.vertices[pi]
230  dp = self._on_displace_on_displace(startpos, f)
231  t1sp = initial.in_tangents[pi] + startpos
232  t1fin = initial.in_tangents[pi] + self._on_displace_on_displace(t1sp, f) - dp
233  t2sp = initial.out_tangents[pi] + startpos
234  t2fin = initial.out_tangents[pi] + self._on_displace_on_displace(t2sp, f) - dp
235 
236  bezier.add_point(dp + startpos, t1fin, t2fin)
237 
238  prop.add_keyframe(self.frame_timeframe_time(f), bezier)
239 
240  def frame_time(self, f):
241  return f * self.time_deltatime_delta + self.time_starttime_start
242 
243  def _init_lerp(self, val_from, val_to, easing):
244  self._kf_kf = properties.OffsetKeyframe(0, NVector(val_from), easing, NVector(val_to))
245 
246  def _lerp_get(self, offset):
247  return self._kf_kf.interpolated_value(offset / self.n_framesn_frames)[0]
248 
249 
251  def __init__(
252  self,
253  wavelength,
254  amplitude,
255  time_start,
256  time_end,
257  n_frames,
258  speed=1,
259  axis=90,
260  ):
261  """!
262  Displaces points as if they were following a sine wave
263 
264  @param wavelength Distance between consecutive peaks
265  @param amplitude Distance from a peak to the original position
266  @param time_start When the animation shall start
267  @param time_end When the animation shall end
268  @param n_frames Number of keyframes to add
269  @param speed Number of peaks a point will go through in the given time
270  If negative, it will go the other way
271  @param axis Wave peak direction
272  """
273  super().__init__(time_start, time_end, n_frames)
274 
275  self.wavelengthwavelength = wavelength
276  self.amplitudeamplitude = amplitude
277  self.speed_fspeed_f = math.pi * 2 * speed
278  self.axisaxis = axis / 180 * math.pi
279 
280  def _on_displace(self, startpos, f):
281  off = -math.sin(startpos[0]/self.wavelengthwavelength*math.pi*2-f*self.speed_fspeed_f/self.n_framesn_frames) * self.amplitudeamplitude
282  return NVector(off * math.cos(self.axisaxis), off * math.sin(self.axisaxis))
283 
284 
286  def __init__(
287  self,
288  waves,
289  time_start,
290  time_end,
291  n_frames,
292  speed=1,
293  axis=90,
294  amplitude_scale=1,
295  ):
296  """!
297  Displaces points as if they were following a sine wave
298 
299  @param waves List of tuples (wavelength, amplitude)
300  @param time_start When the animation shall start
301  @param time_end When the animation shall end
302  @param n_frames Number of keyframes to add
303  @param speed Number of peaks a point will go through in the given time
304  If negative, it will go the other way
305  @param axis Wave peak direction
306  @param amplitude_scale Multiplies the resulting amplitude by this factor
307  """
308  super().__init__(time_start, time_end, n_frames)
309 
310  self.waveswaves = waves
311  self.speed_fspeed_f = math.pi * 2 * speed
312  self.axisaxis = axis / 180 * math.pi
313  self.amplitude_scaleamplitude_scale = amplitude_scale
314 
315  def _on_displace(self, startpos, f):
316  off = 0
317  for wavelength, amplitude in self.waveswaves:
318  off -= math.sin(startpos[0]/wavelength*math.pi*2-f*self.speed_fspeed_f/self.n_framesn_frames) * amplitude
319 
320  off *= self.amplitude_scaleamplitude_scale
321  return NVector(off * math.cos(self.axisaxis), off * math.sin(self.axisaxis))
322 
323 
325  def __init__(self, x, y, keep):
326  self.xx = x / x.length
327  self.yy = y / y.length
328  self.keepkeep = keep / keep.length # should be the cross product
329 
330  def rot_center(self, center, point):
331  return (
332  self.xx * self.xx.dot(center) +
333  self.yy * self.yy.dot(center) +
334  self.keepkeep * self.keepkeep.dot(point)
335  )
336 
337  def extract_component(self, vector, axis):
338  return sum(vector.element_scaled(axis).components)
339 
340  @classmethod
341  def from_points(cls, keep_point, center=NVector(0, 0, 0)):
342  keep = keep_point - center
343  keep /= keep.length
344  # Hughes-Moller to find x and y
345  if abs(keep.x) > abs(keep.z):
346  y = NVector(-keep.y, keep.x, 0)
347  else:
348  y = NVector(0, -keep.z, keep.y)
349  y /= y.length
350  x = y.cross(keep)
351  return cls(x, y, keep)
352 
353 
355  axis_x = DepthRotationAxis(NVector(0, 0, 1), NVector(0, 1, 0), NVector(1, 0, 0))
356  axis_y = DepthRotationAxis(NVector(1, 0, 0), NVector(0, 0, 1), NVector(0, 1, 0))
357  axis_z = DepthRotationAxis(NVector(1, 0, 0), NVector(0, 1, 0), NVector(0, 0, 1))
358 
359  def __init__(self, center):
360  self.centercenter = center
361 
362  def rotate3d_y(self, point, angle):
363  return self.rotate3drotate3d(point, angle, self.axis_yaxis_y)
364  # Hard-coded version:
365  #c = NVector(self.center.x, point.y, self.center.z)
366  #rad = angle * math.pi / 180
367  #delta = point - c
368  #pol_l = delta.length
369  #pol_a = math.atan2(delta.z, delta.x)
370  #dest_a = pol_a + rad
371  #return NVector(
372  # c.x + pol_l * math.cos(dest_a),
373  # point.y,
374  # c.z + pol_l * math.sin(dest_a)
375  #)
376 
377  def rotate3d_x(self, point, angle):
378  return self.rotate3drotate3d(point, angle, self.axis_xaxis_x)
379  # Hard-coded version:
380  #c = NVector(point.x, self.center.y, self.center.z)
381  #rad = angle * math.pi / 180
382  #delta = point - c
383  #pol_l = delta.length
384  #pol_a = math.atan2(delta.y, delta.z)
385  #dest_a = pol_a + rad
386  #return NVector(
387  # point.x,
388  # c.y + pol_l * math.sin(dest_a),
389  # c.z + pol_l * math.cos(dest_a),
390  #)
391 
392  def rotate3d_z(self, point, angle):
393  return self.rotate3drotate3d(point, angle, self.axis_zaxis_z)
394 
395  def rotate3d(self, point, angle, axis):
396  c = axis.rot_center(self.centercenter, point)
397  rad = angle * math.pi / 180
398  delta = point - c
399  pol_l = delta.length
400  pol_a = math.atan2(
401  axis.extract_component(delta, axis.y),
402  axis.extract_component(delta, axis.x)
403  )
404  dest_a = pol_a + rad
405  return c + axis.x * pol_l * math.cos(dest_a) + axis.y * pol_l * math.sin(dest_a)
406 
407 
409  axis_x = DepthRotation.axis_x
410  axis_y = DepthRotation.axis_y
411  axis_z = DepthRotation.axis_z
412 
413  def __init__(self, center, time_start, time_end, n_frames, axis,
414  depth=0, angle=360, anglestart=0, ease=easing.Linear()):
415  super().__init__(time_start, time_end, n_frames)
416  self.rotationrotation = DepthRotation(center)
417  if isinstance(axis, NVector):
418  axis = DepthRotationAxis.from_points(axis)
419  self.axisaxis = axis
420  self.depthdepth = depth
421  self._angle_angle = angle
422  self.anglestartanglestart = anglestart
423  self.easeease = ease
424  self._init_lerp_init_lerp(0, angle, ease)
425 
426  @property
427  def angle(self):
428  return self._angle_angle
429 
430  @angle.setter
431  def angle(self, value):
432  self._angle_angle = value
433  self._init_lerp_init_lerp(0, value, self.easeease)
434 
435  def _on_displace(self, startpos, f):
436  angle = self.anglestartanglestart + self._lerp_get_lerp_get(f)
437  if len(startpos) < 3:
438  startpos = NVector(*(startpos.components + [self.depthdepth]))
439  return self.rotationrotation.rotate3d(startpos, angle, self.axisaxis) - startpos
440 
441 
443  def __init__(self, topleft, bottomright):
444  self.toplefttopleft = topleft
445  self.sizesize = bottomright - topleft
446  self.keyframeskeyframes = []
447 
448  @property
449  def time_start(self):
450  return self.keyframeskeyframes[0][0]
451 
452  def add_reset_keyframe(self, time):
453  self.add_keyframeadd_keyframe(
454  time,
455  self.toplefttopleft.clone(),
456  NVector(self.toplefttopleft.x + self.sizesize.x, self.toplefttopleft.y),
457  NVector(self.toplefttopleft.x + self.sizesize.x, self.toplefttopleft.y + self.sizesize.y),
458  NVector(self.toplefttopleft.x, self.toplefttopleft.y + self.sizesize.y),
459  )
460 
461  def add_keyframe(self, time, tl, tr, br, bl):
462  self.keyframeskeyframes.append([
463  time,
464  tl.clone(),
465  tr.clone(),
466  br.clone(),
467  bl.clone()
468  ])
469 
470  def _on_displace(self, startpos, f):
471  _, tl, tr, br, bl = self.keyframeskeyframes[f]
472  relp = startpos - self.toplefttopleft
473  relp.x /= self.sizesize.x
474  relp.y /= self.sizesize.y
475 
476  x1 = tl.lerp(tr, relp.x)
477  x2 = bl.lerp(br, relp.x)
478 
479  #return x1.lerp(x2, relp.y)
480  return x1.lerp(x2, relp.y) - startpos
481 
482  @property
483  def n_frames(self):
484  return len(self.keyframeskeyframes)-1
485 
486  def frame_time(self, f):
487  return self.keyframeskeyframes[f][0]
488 
489 
491  """!
492  Given a displacer and a function that returns a factor for a point,
493  multiplies the effect of the displacer by the factor
494  """
495  def __init__(self, displacer, dampener):
496  self.displacerdisplacer = displacer
497  self.dampenerdampener = dampener
498 
499  @property
500  def time_start(self):
501  return self.displacerdisplacer.time_start
502 
503  def _on_displace(self, startpos, f):
504  disp = self.displacerdisplacer._on_displace(startpos, f)
505  damp = self.dampenerdampener(startpos)
506  return disp * damp
507 
508  @property
509  def n_frames(self):
510  return self.displacerdisplacer.n_frames
511 
512  def frame_time(self, f):
513  return self.displacerdisplacer.frame_time(f)
514 
515 
517  def __init__(
518  self,
519  origin,
520  range,
521  offset_func,
522  time_start, time_end, n_frames,
523  falloff_exp=1,
524  ):
525  """!
526  @brief Uses a custom offset function, and applies a falloff to the displacement
527 
528  @param origin Origin point for the falloff
529  @param range Radius after which the points will not move
530  @param offset_func Function returning an offset given a ratio of the time
531  @param time_start When the animation shall start
532  @param time_end When the animation shall end
533  @param n_frames Number of frames in the animation
534  @param falloff_exp Exponent for the falloff
535  """
536  super().__init__(time_start, time_end, n_frames)
537  self.originorigin = origin
538  self.rangerange = range
539  self.offset_funcoffset_func = offset_func
540  self.falloff_expfalloff_exp = falloff_exp
541 
542  def _on_displace(self, startpos, f):
543  influence = 1 - min(1, (startpos - self.originorigin).length / self.rangerange) ** self.falloff_expfalloff_exp
544  return self.offset_funcoffset_func(f / self.n_framesn_frames) * influence
Single bezier curve.
Definition: bezier.py:123
Animatable Bezier curve.
Definition: shapes.py:401
def extract_component(self, vector, axis)
Definition: animation.py:337
def from_points(cls, keep_point, center=NVector(0, 0, 0))
Definition: animation.py:341
def rot_center(self, center, point)
Definition: animation.py:330
def __init__(self, center, time_start, time_end, n_frames, axis, depth=0, angle=360, anglestart=0, ease=easing.Linear())
Definition: animation.py:414
def rotate3d_z(self, point, angle)
Definition: animation.py:392
def rotate3d_y(self, point, angle)
Definition: animation.py:362
def rotate3d_x(self, point, angle)
Definition: animation.py:377
def rotate3d(self, point, angle, axis)
Definition: animation.py:395
Given a displacer and a function that returns a factor for a point, multiplies the effect of the disp...
Definition: animation.py:490
def __init__(self, displacer, dampener)
Definition: animation.py:495
def __init__(self, topleft, bottomright)
Definition: animation.py:443
def add_keyframe(self, time, tl, tr, br, bl)
Definition: animation.py:461
def __init__(self, origin, range, offset_func, time_start, time_end, n_frames, falloff_exp=1)
Uses a custom offset function, and applies a falloff to the displacement.
Definition: animation.py:524
def __init__(self, waves, time_start, time_end, n_frames, speed=1, axis=90, amplitude_scale=1)
Displaces points as if they were following a sine wave.
Definition: animation.py:295
time_start
When the animation shall start.
Definition: animation.py:204
time_end
When the animation shall end.
Definition: animation.py:206
def __init__(self, time_start, time_end, n_frames)
Definition: animation.py:197
n_frames
Number of frames in the animation.
Definition: animation.py:208
def _on_displace(self, startpos, f)
Definition: animation.py:218
def _init_lerp(self, val_from, val_to, easing)
Definition: animation.py:243
def __init__(self, wavelength, amplitude, time_start, time_end, n_frames, speed=1, axis=90)
Displaces points as if they were following a sine wave.
Definition: animation.py:260
def follow_path(position_prop, bezier, start_time, end_time, n_keyframes, reverse=False, offset=NVector(0, 0), start_t=0, rotation_prop=None, rotation_offset=0)
Definition: animation.py:59
def generate_path_disappear(bezier, disappear_start, disappear_end, n_keyframes, reverse=False)
Definition: animation.py:119
def rot_shake(rotation_prop, angles, start_time, end_time, n_frames)
Definition: animation.py:33
def generate_path_appear(bezier, appear_start, appear_end, n_keyframes, reverse=False)
Definition: animation.py:86
def spring_pull(position_prop, point, start_time, end_time, falloff=15, oscillations=7)
Definition: animation.py:43
def shake(position_prop, x_radius, y_radius, start_time, end_time, n_frames, interp=easing.Linear())
Definition: animation.py:10
def generate_path_segment(bezier, appear_start, appear_end, disappear_start, disappear_end, n_keyframes, reverse=False)
Definition: animation.py:151