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_a(c1, c2) * t + self.
_b_b(c1, c2)) * t + self.
_c_c(c1)) * t
51 def _slope_component(self, t, c1, c2):
52 return 3 * self.
_a_a(c1, c2) * t * t + 2 * self.
_b_b(c1, c2) * t + self.
_c_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)
158 return KeyframeBezier.from_keyframe(self).
bezier()
161 return KeyframeBezier.from_keyframe(self).y_at_x(ratio)
164 return "%s %s" % (self.
timetime, self.value)
170 Keyframe for MultiDimensional values
174 Imagine a quadratic bezier, with starting point at (0, 0) and end point at (1, 1).
176 @p out_value and @p in_value are the other two handles for a quadratic bezier,
177 expressed as absolute values in this 0-1 space.
179 See also https://cubic-bezier.com/
187 def __init__(self, time=0, value=None, easing_function=None, end=None):
188 Keyframe.__init__(self, time, easing_function)
196 return self.
valuevalue
204 end = next_value
if self.
endend
is None else self.
endend
206 return self.
valuevalue
208 return self.
valuevalue
212 return self.
valuevalue
213 if isinstance(self, PositionKeyframe)
and self.in_tan
and self.out_tan:
215 bezier.add_point(self.
valuevalue,
NVector(0, 0), self.out_tan)
216 bezier.add_point(end, self.in_tan,
NVector(0, 0))
217 return bezier.point_at(ratio)
220 return self.
valuevalue.lerp(end, lerpv)
223 end = next_value
if self.
endend
is None else self.
endend
224 if end
is None or not self.in_tan
or not self.out_tan:
228 bezier.add_point(self.
valuevalue,
NVector(0, 0), self.out_tan)
229 bezier.add_point(end, self.in_tan,
NVector(0, 0))
230 return bezier.tangent_angle_at(ratio)
233 return "<%s.%s %s %s%s>" % (
234 type(self).__module__,
238 (
" -> %s" % self.
endend)
if self.
endend
is not None else ""
243 keyframe_type = Keyframe
261 Sets a fixed value, removing animated keyframes
263 self.
valuevalue = value
267 def add_keyframe(self, time, value, interp=easing.Linear(), *args, end=
None, **kwargs):
269 @param time The time this keyframe appears in
270 @param value The value the property should have at @p time
271 @param interp The easing callable used to update the tangents of the previous keyframe
272 @param args Extra arguments to pass the keyframe constructor
273 @param kwargs Extra arguments to pass the keyframe constructor
274 @note Always call add_keyframe with increasing @p time value
277 self.
valuevalue =
None
281 if self.
keyframeskeyframes[-1].time == time:
282 if value != self.
keyframeskeyframes[-1].value:
283 self.
keyframeskeyframes[-1].value = value
286 self.
keyframeskeyframes[-1].end = value.clone()
291 easing_function=interp,
301 @brief Returns the value of the property at the given frame/time
304 return self.
valuevalue
311 def _get_value_helper(self, time):
313 for i
in range(len(self.
keyframeskeyframes)):
315 if time - k.time <= 0:
316 if k.value
is not None:
319 kp = self.
keyframeskeyframes[i-1]
if i > 0
else None
321 t = (time - kp.time) / (k.time - kp.time)
326 val = kp.interpolated_value(t, end)
327 return val, end, kp, t
328 return val,
None,
None,
None
329 if k.end
is not None:
331 elif k.value
is not None:
333 return val,
None,
None,
None
347 val =
"%s -> %s" % (self.
keyframeskeyframes[0].value, self.
keyframeskeyframes[-2].end)
349 val = self.
valuevalue
350 return "<%s.%s %s>" % (
type(self).__module__,
type(self).__name__, val)
360 @todo Remove similar functionality from SVG/sif parsers
363 for animatable
in items:
364 if animatable.animated:
365 keyframes.extend(animatable.keyframes)
369 for keyframe
in sorted(keyframes, key=
lambda kf: kf.time):
370 if new_kframes
and new_kframes[-1].time == keyframe.time:
372 kfcopy = keyframe.clone()
373 kfcopy.value = conversion(*(i.get_value(keyframe.time)
for i
in items))
374 new_kframes.append(kfcopy)
376 for i
in range(0, len(new_kframes) - 1):
377 new_kframes[i].end = new_kframes[i+1].value
383 obj = super().
load(lottiedict)
384 if "a" not in lottiedict:
388 def average(self, value, end, value_map=lambda v: v):
390 return value_map(self.
valuevalue)
392 return value_map(self.
keyframeskeyframes[0].value)
395 value = value_map(self.
keyframeskeyframes[0].value) * 0
398 if self.
keyframeskeyframes[0].time > value:
399 weight = self.
keyframeskeyframes[0].time - value
400 value += value_map(self.
keyframeskeyframes[0].value) * weight
401 total_weight += weight
402 elif self.
keyframeskeyframes[0].time < value:
406 weight = self.
keyframeskeyframes[1].time - value
407 value += value_map(self.
keyframeskeyframes[0].value) * weight
408 total_weight += weight
411 value += value_map(self.
keyframeskeyframes[0].value) * weight
412 total_weight += weight
415 value += value_map(self.
keyframeskeyframes[1].value) * half
418 weight = self.
keyframeskeyframes[1].time - value
419 value += value_map(self.
keyframeskeyframes[1].value) * weight
420 total_weight += weight
422 for i
in range(kf_index, len(self.
keyframeskeyframes) - 1):
428 delta = kfn.time - kf.time
429 total_weight += delta
431 value += value_map(kf.value) * delta
433 value += value_map(kf.value) * (delta / 2)
434 value += value_map(kfn.value) * (delta / 2)
440 weight = end - kfn.time
441 value += value_map(kfn.value) * weight
442 total_weight += weight
444 weight = end - kf.time
445 value += value_map(kf.value) * weight
446 total_weight += weight
448 avg = (kf.time + kfn.time) / 2
450 weight = end - kf.time
451 value += value_map(kf.value) * weight
452 total_weight += weight
454 half = (kfn.time - kf.time) / 2
455 value += value_map(kf.value) * half
459 value += value_map(kfn.value) * weight
460 total_weight += weight
462 if total_weight == 0:
464 return value / total_weight
472 if isinstance(l[
"k"], list)
and l[
"k"]
and isinstance(l[
"k"][0], dict):
484 An animatable property that holds a NVector
486 keyframe_type = OffsetKeyframe
488 LottieProp(
"value",
"k", NVector,
False, prop_not_animated),
489 LottieProp(
"property_index",
"ix", int,
False),
490 LottieProp(
"animated",
"a", PseudoBool,
False),
491 LottieProp(
"keyframes",
"k", OffsetKeyframe,
True, prop_animated),
498 @brief Returns the value tangent angle of the property at the given frame/time
505 return kp.interpolated_tangent_angle(t, end)
507 if self.
keyframeskeyframes[0].time >= time:
509 return self.
keyframeskeyframes[0].interpolated_tangent_angle(0, end)
517 Keyframe for Positional values
524 def __init__(self, time=0, value=None, easing_function=None, in_tan=None, out_tan=None, end=None):
525 super().
__init__(time, value, easing_function, end)
533 keyframe_type = PositionKeyframe
535 LottieProp(
"value",
"k", NVector,
False, prop_not_animated),
536 LottieProp(
"property_index",
"ix", int,
False),
537 LottieProp(
"animated",
"a", PseudoBool,
False),
538 LottieProp(
"keyframes",
"k", OffsetKeyframe,
True, prop_animated),
543 obj = super().
load(lottiedict)
544 if lottiedict.get(
"s",
False):
550 def _load_split(cls, lottiedict, obj):
552 Value.load(lottiedict.get(
"x", {})),
553 Value.load(lottiedict.get(
"y", {})),
555 if "z" in lottiedict:
556 components.append(Value.load(lottiedict.get(
"z", {})))
558 has_anim = any(x
for x
in components
if x.animated)
560 obj.value =
NVector(*(a.value
for a
in components))
567 obj.keyframes = cls.
merge_keyframesmerge_keyframes(components, NVector)
572 An animatable property that holds a Color
574 keyframe_type = OffsetKeyframe
576 LottieProp(
"value",
"k", Color,
False, prop_not_animated),
577 LottieProp(
"property_index",
"ix", int,
False),
578 LottieProp(
"animated",
"a", PseudoBool,
False),
579 LottieProp(
"keyframes",
"k", OffsetKeyframe,
True, prop_animated),
588 Represents colors and offsets in a gradient
590 Colors are represented as a flat list interleaving offsets and color components in weird ways
591 There are two possible layouts:
593 Without alpha, the colors are a sequence of offset, r, g, b
595 With alpha, same as above but at the end of the list there is a sequence of offset, alpha
599 For the gradient [0, red], [0.5, yellow], [1, green]
600 The list would be [0, 1, 0, 0, 0.5, 1, 1, 0, 1, 0, 1, 0]
602 For the gradient [0, red at 80% opacity], [0.5, yellow at 70% opacity], [1, green at 60% opacity]
603 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]
621 Converts a list of colors (Color) to tuples (offset, color)
624 (i / (len(colors)-1), color)
625 for i, color
in enumerate(colors)
630 @param stops iterable of (offset, Color) tuples
631 @param keyframe keyframe index (or None if not animated)
634 if self.
colorscolors.animated
and keyframe
is not None:
636 self.
colorscolors.keyframes[keyframe-1].end = flat
637 self.
colorscolors.keyframes[keyframe].value = flat
639 self.
colorscolors.clear_animation(flat)
640 self.
countcount = len(stops)
642 def _flatten_stops(self, stops):
643 flattened_colors =
NVector(*reduce(
646 [off] + color.components[:3]
647 for off, color
in stops
651 if any(len(c) > 3
for o, c
in stops):
652 flattened_colors.components += reduce(
656 for off, color
in stops
659 return flattened_colors
661 def _get_alpha(self, color):
666 def _add_to_flattened(self, offset, color, flattened):
667 flat = [offset] + list(color[:3])
668 rgb_size = 4 * self.
countcount
670 if len(flattened) == rgb_size:
672 flattened.extend(flat)
673 if self.
countcount == 0
and len(color) > 3:
674 flattened.append(offset)
675 flattened.append(color[3])
677 flattened[rgb_size:rgb_size] = flat
678 flattened.append(offset)
679 flattened.append(self.
_get_alpha_get_alpha(color))
682 if self.
colorscolors.animated:
684 for kf
in self.
colorscolors.keyframes:
699 @param time Frame time
700 @param stops Iterable of (offset, Color) tuples
701 @param ease Easing function
706 if keyframe
is not None:
707 colors = self.
colorscolors.keyframes[keyframe].value
709 colors = self.
colorscolors.value
712 def _stops_from_flat(self, colors):
713 if len(colors) == 4 * self.
countcount:
714 for i
in range(self.
countcount):
716 yield colors[off],
Color(*colors[off+1:off+4])
718 for i
in range(self.
countcount):
720 aoff = self.
countcount * 4 + i * 2 + 1
721 yield colors[off],
Color(colors[off+1], colors[off+2], colors[off+3], colors[aoff])
730 An animatable property that holds a float
732 keyframe_type = OffsetKeyframe
734 LottieProp(
"value",
"k", float,
False, prop_not_animated),
735 LottieProp(
"property_index",
"ix", int,
False),
736 LottieProp(
"animated",
"a", PseudoBool,
False),
737 LottieProp(
"keyframes",
"k", keyframe_type,
True, prop_animated),
758 Keyframe holding Bezier objects
765 def __init__(self, time=0, value=None, easing_function=None, end=None):
766 Keyframe.__init__(self, time, easing_function)
774 return self.
valuevalue
782 end = next_value
if self.
endend
is None else self.
endend
784 return self.
valuevalue
786 return self.
valuevalue
789 if ratio == 0
or len(self.
valuevalue.vertices) != len(end.vertices):
790 return self.
valuevalue
794 bez.closed = self.
valuevalue.closed
795 for i
in range(len(self.
valuevalue.vertices)):
796 bez.vertices.append(self.
valuevalue.vertices[i].lerp(end.vertices[i], lerpv))
797 bez.in_tangents.append(self.
valuevalue.in_tangents[i].lerp(end.in_tangents[i], lerpv))
798 bez.out_tangents.append(self.
valuevalue.out_tangents[i].lerp(end.out_tangents[i], lerpv))
805 An animatable property that holds a Bezier
807 keyframe_type = ShapePropKeyframe
809 LottieProp(
"value",
"k", Bezier,
False, prop_not_animated),
811 LottieProp(
"property_index",
"ix", float,
False),
812 LottieProp(
"animated",
"a", PseudoBool,
False),
813 LottieProp(
"keyframes",
"k", keyframe_type,
True, prop_animated),
824 An animatable property that is split into individually anaimated components
Base class for mapping Python classes into Lottie JSON objects.
Lottie <-> Python property mapper.
Bezier handle for keyframe interpolation.
def add_keyframe(self, time, value, interp=easing.Linear(), *args, end=None, **kwargs)
def clear_animation(self, value)
Sets a fixed value, removing animated keyframes.
def get_value(self, time=0)
Returns the value of the property at the given frame/time.
def load(cls, lottiedict)
def merge_keyframes(cls, items, conversion)
animated
Whether it's animated.
def __init__(self, value=None)
property_index
Property index.
def average(self, value, end, value_map=lambda v:v)
def _get_value_helper(self, time)
An animatable property that holds a Color.
Represents colors and offsets in a gradient.
def add_keyframe(self, time, stops, ease=easing.Linear())
def get_stops(self, keyframe=0)
def __init__(self, stops=[])
def _stops_from_flat(self, colors)
def add_color(self, offset, color, keyframe=None)
colors
Animatable colors, as a vector containing [offset, r, g, b] values as a flat array.
def color_to_stops(self, colors)
def _add_to_flattened(self, offset, color, flattened)
def _flatten_stops(self, stops)
def _get_alpha(self, color)
def set_stops(self, stops, keyframe=None)
int SUBDIVISION_MAX_ITERATIONS
def _slope_component(self, t, c1, c2)
def from_keyframe(cls, keyframe)
def _binary_subdivide(self, x, interval_start, interval_end)
float SUBDIVISION_PRECISION
def _newton_raphson(self, x, t_guess)
def _get_sample_values(self)
def __init__(self, h1, h2)
def _bezier_component(self, t, c1, c2)
time
Start time of keyframe segment.
out_value
Bezier curve easing out value.
def lerp_factor(self, ratio)
hold
Jump to the end value.
def __init__(self, time=0, easing_function=None)
in_value
Bezier curve easing in value.
An animatable property that holds a NVector.
def get_tangent_angle(self, time=0)
Returns the value tangent angle of the property at the given frame/time.
Keyframe for MultiDimensional values.
value
Start value of keyframe segment.
end
End value of keyframe segment.
def interpolated_value(self, ratio, next_value=None)
def interpolated_tangent_angle(self, ratio, next_value=None)
def __init__(self, time=0, value=None, easing_function=None, end=None)
Keyframe for Positional values.
out_tan
Out Spatial Tangent.
def __init__(self, time=0, value=None, easing_function=None, in_tan=None, out_tan=None, end=None)
in_tan
In Spatial Tangent.
def load(cls, lottiedict)
Loads from a JSON object.
def _load_split(cls, lottiedict, obj)
Keyframe holding Bezier objects.
def __init__(self, time=0, value=None, easing_function=None, end=None)
end
End value of keyframe segment.
def interpolated_value(self, ratio, next_value=None)
value
Start value of keyframe segment.
An animatable property that holds a Bezier.
def __init__(self, bezier=None)
An animatable property that is split into individually anaimated components.
def __init__(self, x=0, y=0)
An animatable property that holds a float.
def __init__(self, value=0)
def get_value(self, time=0)
Returns the value of the property at the given frame/time.
def add_keyframe(self, time, value, ease=easing.Linear())