2 from functools
import reduce
3 from .base
import LottieObject, LottieProp, PseudoList, PseudoBool
5 from ..nvector
import NVector
6 from .bezier
import Bezier
7 from ..utils.color
import Color
12 NEWTON_MIN_SLOPE = 0.001
13 SUBDIVISION_PRECISION = 0.0000001
14 SUBDIVISION_MAX_ITERATIONS = 10
15 SPLINE_TABLE_SIZE = 11
16 SAMPLE_STEP_SIZE = 1.0 / (SPLINE_TABLE_SIZE - 1.0)
25 return cls(keyframe.out_value, keyframe.in_value)
34 return 1 - 3 * c2 + 3 * c1
37 return 3 * c2 - 6 * c1
42 def _bezier_component(self, t, c1, c2):
43 return ((self.
_a(c1, c2) * t + self.
_b(c1, c2)) * t + self.
_c(c1)) * t
51 def _slope_component(self, t, c1, c2):
52 return 3 * self.
_a(c1, c2) * t * t + 2 * self.
_b(c1, c2) * t + self.
_c(c1)
60 def _binary_subdivide(self, x, interval_start, interval_end):
67 t = interval_start + (interval_end - interval_start) / 2.0
75 def _newton_raphson(self, x, t_guess):
81 t_guess -= current_x / slope
84 def _get_sample_values(self):
97 while current_sample != last_sample
and sample_values[current_sample] <= x:
102 dist = (x - sample_values[current_sample]) / (sample_values[current_sample+1] - sample_values[current_sample])
107 if initial_slope == 0:
127 @param time Start time of keyframe segment
128 @param easing_function Callable that performs the easing
140 easing_function(self)
150 return KeyframeBezier.from_keyframe(self).
bezier()
153 return KeyframeBezier.from_keyframe(self).y_at_x(ratio)
156 return "%s %s" % (self.
time, self.start)
162 Keyframe for MultiDimensional values
166 Imagine a quadratic bezier, with starting point at (0, 0) and end point at (1, 1).
168 @p out_value and @p in_value are the other two handles for a quadratic bezier,
169 expressed as absolute values in this 0-1 space.
171 See also https://cubic-bezier.com/
179 def __init__(self, time=0, start=None, end=None, easing_function=None, in_tan=None, out_tan=None):
180 Keyframe.__init__(self, time, easing_function)
191 end = next_start
if self.
end is None else self.
end
204 return bezier.point_at(ratio)
207 return self.
start.lerp(end, lerpv)
210 end = next_start
if self.
end is None else self.
end
217 return bezier.tangent_angle_at(ratio)
220 return "<%s.%s %s %s%s>" % (
221 type(self).__module__,
225 (
" -> %s" % self.
end)
if self.
end is not None else ""
230 keyframe_type = Keyframe
246 Sets a fixed value, removing animated keyframes
252 def add_keyframe(self, time, value, interp=easing.Linear(), *args, **kwargs):
254 @param time The time this keyframe appears in
255 @param value The value the property should have at @p time
256 @param interp The easing callable used to update the tangents of the previous keyframe
257 @param args Extra arguments to pass the keyframe constructor
258 @param kwargs Extra arguments to pass the keyframe constructor
259 @note Always call add_keyframe with increasing @p time value
284 @brief Returns the value of the property at the given frame/time
294 def _get_value_helper(self, time):
298 if time - k.time <= 0:
299 if k.start
is not None:
302 kp = self.
keyframes[i-1]
if i > 0
else None
304 t = (time - kp.time) / (k.time - kp.time)
309 val = kp.interpolated_value(t, end)
310 return val, end, kp, t
311 return val,
None,
None,
None
312 if k.end
is not None:
314 return val,
None,
None,
None
331 return "<%s.%s %s>" % (type(self).__module__, type(self).__name__, val)
336 return str(self.
value)
341 @todo Remove similar functionality from SVG/sif parsers
344 for animatable
in items:
345 if animatable.animated:
346 keyframes.extend(animatable.keyframes)
350 for keyframe
in sorted(keyframes, key=
lambda kf: kf.time):
351 if new_kframes
and new_kframes[-1].time == keyframe.time:
353 kfcopy = keyframe.clone()
354 kfcopy.start = conversion(*(i.get_value(keyframe.time)
for i
in items))
355 new_kframes.append(kfcopy)
357 for i
in range(0, len(new_kframes) - 1):
358 new_kframes[i].end = new_kframes[i+1].start
364 obj = super().
load(lottiedict)
365 if "a" not in lottiedict:
375 if isinstance(l[
"k"], list)
and l[
"k"]
and isinstance(l[
"k"][0], dict):
387 An animatable property that holds a NVector
389 keyframe_type = OffsetKeyframe
391 LottieProp(
"value",
"k", NVector,
False, prop_not_animated),
392 LottieProp(
"property_index",
"ix", int,
False),
393 LottieProp(
"animated",
"a", PseudoBool,
False),
394 LottieProp(
"keyframes",
"k", OffsetKeyframe,
True, prop_animated),
400 @brief Returns the value tangent angle of the property at the given frame/time
407 return kp.interpolated_tangent_angle(t, end)
411 return self.
keyframes[0].interpolated_tangent_angle(0, end)
419 Keyframe for Positional values
428 keyframe_type = PositionKeyframe
430 LottieProp(
"value",
"k", NVector,
False, prop_not_animated),
431 LottieProp(
"property_index",
"ix", int,
False),
432 LottieProp(
"animated",
"a", PseudoBool,
False),
433 LottieProp(
"keyframes",
"k", OffsetKeyframe,
True, prop_animated),
438 obj = super().
load(lottiedict)
439 if lottiedict.get(
"s",
False):
445 def _load_split(cls, lottiedict, obj):
447 Value.load(lottiedict.get(
"x", {})),
448 Value.load(lottiedict.get(
"y", {})),
450 if "z" in lottiedict:
451 components.append(Value.load(lottiedict.get(
"z", {})))
453 has_anim = any(x
for x
in components
if x.animated)
455 obj.value =
NVector(*(a.value
for a
in components))
467 An animatable property that holds a Color
469 keyframe_type = OffsetKeyframe
471 LottieProp(
"value",
"k", Color,
False, prop_not_animated),
472 LottieProp(
"property_index",
"ix", int,
False),
473 LottieProp(
"animated",
"a", PseudoBool,
False),
474 LottieProp(
"keyframes",
"k", OffsetKeyframe,
True, prop_animated),
482 Represents colors and offsets in a gradient
484 Colors are represented as a flat list interleaving offsets and color components in weird ways
485 There are two possible layouts:
487 Without alpha, the colors are a sequence of offset, r, g, b
489 With alpha, same as above but at the end of the list there is a sequence of offset, alpha
493 For the gradient [0, red], [0.5, yellow], [1, green]
494 The list would be [0, 1, 0, 0, 0.5, 1, 1, 0, 1, 0, 1, 0]
496 For the gradient [0, red at 80% opacity], [0.5, yellow at 70% opacity], [1, green at 60% opacity]
497 The list would be [0, 1, 0, 0, 0.5, 1, 1, 0, 1, 0, 1, 0, 0, 0.8, 0.5, 0.7, 1, 0.6]
515 Converts a list of colors (Color) to tuples (offset, color)
518 (i / (len(colors)-1), color)
519 for i, color
in enumerate(colors)
524 @param stops iterable of (offset, Color) tuples
525 @param keyframe keyframe index (or None if not animated)
528 if self.
colors.animated
and keyframe
is not None:
530 self.
colors.keyframes[keyframe-1].end = flat
531 self.
colors.keyframes[keyframe].start = flat
533 self.
colors.clear_animation(flat)
534 self.
count = len(stops)
536 def _flatten_stops(self, stops):
537 flattened_colors =
NVector(*reduce(
540 [off] + color.components[:3]
541 for off, color
in stops
545 if any(len(c) > 3
for o, c
in stops):
546 flattened_colors.components += reduce(
550 for off, color
in stops
553 return flattened_colors
555 def _get_alpha(self, color):
560 def _add_to_flattened(self, offset, color, flattened):
561 flat = [offset] + list(color[:3])
562 rgb_size = 4 * self.
count
564 if len(flattened) == rgb_size:
566 flattened.extend(flat)
567 if self.
count == 0
and len(color) > 3:
568 flattened.append(offset)
569 flattened.append(color[3])
571 flattened[rgb_size:rgb_size] = flat
572 flattened.append(offset)
578 for kf
in self.
colors.keyframes:
593 @param time Frame time
594 @param stops Iterable of (offset, Color) tuples
595 @param ease Easing function
600 if keyframe
is not None:
601 colors = self.
colors.keyframes[keyframe].start
603 colors = self.
colors.value
606 def _stops_from_flat(self, colors):
607 if len(colors) == 4 * self.
count:
608 for i
in range(self.
count):
610 yield colors[off],
Color(*colors[off+1:off+4])
612 for i
in range(self.
count):
614 aoff = self.
count * 4 + i * 2 + 1
615 yield colors[off],
Color(colors[off+1], colors[off+2], colors[off+3], colors[aoff])
624 An animatable property that holds a float
626 keyframe_type = OffsetKeyframe
628 LottieProp(
"value",
"k", float,
False, prop_not_animated),
629 LottieProp(
"property_index",
"ix", int,
False),
630 LottieProp(
"animated",
"a", PseudoBool,
False),
631 LottieProp(
"keyframes",
"k", keyframe_type,
True, prop_animated),
651 Keyframe holding Bezier objects
658 def __init__(self, time=0, start=None, end=None, easing_function=None):
659 Keyframe.__init__(self, time, easing_function)
666 end = next_start
if self.
end is None else self.
end
673 if ratio == 0
or len(self.
start.vertices) != len(end.vertices):
678 bez.closed = self.
start.closed
679 for i
in range(len(self.
start.vertices)):
680 bez.vertices.append(self.
start.vertices[i].lerp(end.vertices[i], lerpv))
681 bez.in_tangents.append(self.
start.in_tangents[i].lerp(end.in_tangents[i], lerpv))
682 bez.out_tangents.append(self.
start.out_tangents[i].lerp(end.out_tangents[i], lerpv))
689 An animatable property that holds a Bezier
691 keyframe_type = ShapePropKeyframe
693 LottieProp(
"value",
"k", Bezier,
False, prop_not_animated),
695 LottieProp(
"property_index",
"ix", float,
False),
696 LottieProp(
"animated",
"a", PseudoBool,
False),
697 LottieProp(
"keyframes",
"k", keyframe_type,
True, prop_animated),
707 An animatable property that is split into individually anaimated components