1 /*
2 * Copyright 2020 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "modules/skottie/src/animator/KeyframeAnimator.h"
9
10 #include "modules/skottie/src/SkottieJson.h"
11
12 #define DUMP_KF_RECORDS 0
13
14 namespace skottie::internal {
15
16 KeyframeAnimator::~KeyframeAnimator() = default;
17
getLERPInfo(float t) const18 KeyframeAnimator::LERPInfo KeyframeAnimator::getLERPInfo(float t) const {
19 SkASSERT(!fKFs.empty());
20
21 if (t <= fKFs.front().t) {
22 // Constant/clamped segment.
23 return { 0, fKFs.front().v, fKFs.front().v };
24 }
25 if (t >= fKFs.back().t) {
26 // Constant/clamped segment.
27 return { 0, fKFs.back().v, fKFs.back().v };
28 }
29
30 // Cache the current segment (most queries have good locality).
31 if (!fCurrentSegment.contains(t)) {
32 fCurrentSegment = this->find_segment(t);
33 }
34 SkASSERT(fCurrentSegment.contains(t));
35
36 if (fCurrentSegment.kf0->mapping == Keyframe::kConstantMapping) {
37 // Constant/hold segment.
38 return { 0, fCurrentSegment.kf0->v, fCurrentSegment.kf0->v };
39 }
40
41 return {
42 this->compute_weight(fCurrentSegment, t),
43 fCurrentSegment.kf0->v,
44 fCurrentSegment.kf1->v,
45 };
46 }
47
find_segment(float t) const48 KeyframeAnimator::KFSegment KeyframeAnimator::find_segment(float t) const {
49 SkASSERT(fKFs.size() > 1);
50 SkASSERT(t > fKFs.front().t);
51 SkASSERT(t < fKFs.back().t);
52
53 auto kf0 = &fKFs.front(),
54 kf1 = &fKFs.back();
55
56 // Binary-search, until we reduce to sequential keyframes.
57 while (kf0 + 1 != kf1) {
58 SkASSERT(kf0 < kf1);
59 SkASSERT(kf0->t <= t && t < kf1->t);
60
61 const auto mid_kf = kf0 + (kf1 - kf0) / 2;
62
63 if (t >= mid_kf->t) {
64 kf0 = mid_kf;
65 } else {
66 kf1 = mid_kf;
67 }
68 }
69
70 return {kf0, kf1};
71 }
72
compute_weight(const KFSegment & seg,float t) const73 float KeyframeAnimator::compute_weight(const KFSegment &seg, float t) const {
74 SkASSERT(seg.contains(t));
75
76 // Linear weight.
77 auto w = (t - seg.kf0->t) / (seg.kf1->t - seg.kf0->t);
78
79 // Optional cubic mapper.
80 if (seg.kf0->mapping >= Keyframe::kCubicIndexOffset) {
81 const auto mapper_index = SkToSizeT(seg.kf0->mapping - Keyframe::kCubicIndexOffset);
82 w = fCMs[mapper_index].computeYFromX(w);
83 }
84
85 return w;
86 }
87
88 AnimatorBuilder::~AnimatorBuilder() = default;
89
parseKeyframes(const AnimationBuilder & abuilder,const skjson::ArrayValue & jkfs)90 bool AnimatorBuilder::parseKeyframes(const AnimationBuilder& abuilder,
91 const skjson::ArrayValue& jkfs) {
92 // Keyframe format:
93 //
94 // [ // array of
95 // {
96 // "t": <float> // keyframe time
97 // "s": <T> // keyframe value
98 // "h": <bool> // optional constant/hold keyframe marker
99 // "i": [<float,float>] // optional "in" Bezier control point
100 // "o": [<float,float>] // optional "out" Bezier control point
101 // },
102 // ...
103 // ]
104 //
105 // Legacy keyframe format:
106 //
107 // [ // array of
108 // {
109 // "t": <float> // keyframe time
110 // "s": <T> // keyframe start value
111 // "e": <T> // keyframe end value
112 // "h": <bool> // optional constant/hold keyframe marker (constant mapping)
113 // "i": [<float,float>] // optional "in" Bezier control point (cubic mapping)
114 // "o": [<float,float>] // optional "out" Bezier control point (cubic mapping)
115 // },
116 // ...
117 // {
118 // "t": <float> // last keyframe only specifies a t
119 // // the value is prev. keyframe end value
120 // }
121 // ]
122 //
123 // Note: the legacy format contains duplicates, as normal frames are contiguous:
124 // frame(n).e == frame(n+1).s
125
126 const auto parse_value = [&](const skjson::ObjectValue& jkf, size_t i, Keyframe::Value* v) {
127 auto parsed = this->parseKFValue(abuilder, jkf, jkf["s"], v);
128
129 // A missing value is only OK for the last legacy KF
130 // (where it is pulled from prev KF 'end' value).
131 if (!parsed && i > 0 && i == jkfs.size() - 1) {
132 const skjson::ObjectValue* prev_kf = jkfs[i - 1];
133 SkASSERT(prev_kf);
134 parsed = this->parseKFValue(abuilder, jkf, (*prev_kf)["e"], v);
135 }
136
137 return parsed;
138 };
139
140 bool constant_value = true;
141
142 fKFs.reserve(jkfs.size());
143
144 for (size_t i = 0; i < jkfs.size(); ++i) {
145 const skjson::ObjectValue* jkf = jkfs[i];
146 if (!jkf) {
147 return false;
148 }
149
150 float t;
151 if (!Parse<float>((*jkf)["t"], &t)) {
152 return false;
153 }
154
155 Keyframe::Value v;
156 if (!parse_value(*jkf, i, &v)) {
157 return false;
158 }
159
160 if (i > 0) {
161 auto& prev_kf = fKFs.back();
162
163 // Ts must be strictly monotonic.
164 if (t <= prev_kf.t) {
165 return false;
166 }
167
168 // We can power-reduce the mapping of repeated values (implicitly constant).
169 if (v.equals(prev_kf.v, keyframe_type)) {
170 prev_kf.mapping = Keyframe::kConstantMapping;
171 }
172 }
173
174 fKFs.push_back({t, v, this->parseMapping(*jkf)});
175
176 constant_value = constant_value && (v.equals(fKFs.front().v, keyframe_type));
177 }
178
179 SkASSERT(fKFs.size() == jkfs.size());
180 fCMs.shrink_to_fit();
181
182 if (constant_value) {
183 // When all keyframes hold the same value, we can discard all but one
184 // (interpolation has no effect).
185 fKFs.resize(1);
186 }
187
188 #if(DUMP_KF_RECORDS)
189 SkDEBUGF("Animator[%p], values: %lu, KF records: %zu\n",
190 this, fKFs.back().v_idx + 1, fKFs.size());
191 for (const auto& kf : fKFs) {
192 SkDEBUGF(" { t: %1.3f, v_idx: %lu, mapping: %lu }\n", kf.t, kf.v_idx, kf.mapping);
193 }
194 #endif
195 return true;
196 }
197
parseMapping(const skjson::ObjectValue & jkf)198 uint32_t AnimatorBuilder::parseMapping(const skjson::ObjectValue& jkf) {
199 if (ParseDefault(jkf["h"], false)) {
200 return Keyframe::kConstantMapping;
201 }
202
203 SkPoint c0, c1;
204 if (!Parse(jkf["o"], &c0) ||
205 !Parse(jkf["i"], &c1) ||
206 SkCubicMap::IsLinear(c0, c1)) {
207 return Keyframe::kLinearMapping;
208 }
209
210 // De-dupe sequential cubic mappers.
211 if (c0 != prev_c0 || c1 != prev_c1 || fCMs.empty()) {
212 fCMs.emplace_back(c0, c1);
213 prev_c0 = c0;
214 prev_c1 = c1;
215 }
216
217 SkASSERT(!fCMs.empty());
218 return SkToU32(fCMs.size()) - 1 + Keyframe::kCubicIndexOffset;
219 }
220
221 } // namespace skottie::internal
222