1 /*
2 * Copyright 2017 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 "SkottieAnimator.h"
9
10 #include "SkCubicMap.h"
11 #include "SkJSONCPP.h"
12 #include "SkottieProperties.h"
13 #include "SkottieParser.h"
14 #include "SkTArray.h"
15
16 #include <memory>
17
18 namespace skottie {
19
20 namespace {
21
22 #define LOG SkDebugf
23
LogFail(const Json::Value & json,const char * msg)24 bool LogFail(const Json::Value& json, const char* msg) {
25 const auto dump = json.toStyledString();
26 LOG("!! %s: %s", msg, dump.c_str());
27 return false;
28 }
29
30 template <typename T>
31 static inline T lerp(const T&, const T&, float);
32
33 template <>
lerp(const ScalarValue & v0,const ScalarValue & v1,float t)34 ScalarValue lerp(const ScalarValue& v0, const ScalarValue& v1, float t) {
35 SkASSERT(t >= 0 && t <= 1);
36 return v0 + (v1 - v0) * t;
37 }
38
39 template <>
lerp(const VectorValue & v0,const VectorValue & v1,float t)40 VectorValue lerp(const VectorValue& v0, const VectorValue& v1, float t) {
41 SkASSERT(v0.size() == v1.size());
42
43 VectorValue v;
44 v.reserve(v0.size());
45
46 for (size_t i = 0; i < v0.size(); ++i) {
47 v.push_back(lerp(v0[i], v1[i], t));
48 }
49
50 return v;
51 }
52
53 template <>
lerp(const ShapeValue & v0,const ShapeValue & v1,float t)54 ShapeValue lerp(const ShapeValue& v0, const ShapeValue& v1, float t) {
55 SkASSERT(t >= 0 && t <= 1);
56 SkASSERT(v1.isInterpolatable(v0));
57
58 ShapeValue v;
59 SkAssertResult(v1.interpolate(v0, t, &v));
60 v.setIsVolatile(true);
61
62 return v;
63 }
64
65 class KeyframeAnimatorBase : public sksg::Animator {
66 public:
count() const67 int count() const { return fRecs.count(); }
68
69 protected:
70 KeyframeAnimatorBase() = default;
71
72 struct KeyframeRec {
73 float t0, t1;
74 int vidx0, vidx1, // v0/v1 indices
75 cmidx; // cubic map index
76
containsskottie::__anona46874230111::KeyframeAnimatorBase::KeyframeRec77 bool contains(float t) const { return t0 <= t && t <= t1; }
isConstantskottie::__anona46874230111::KeyframeAnimatorBase::KeyframeRec78 bool isConstant() const { return vidx0 == vidx1; }
isValidskottie::__anona46874230111::KeyframeAnimatorBase::KeyframeRec79 bool isValid() const {
80 SkASSERT(t0 <= t1);
81 // Constant frames don't need/use t1 and vidx1.
82 return t0 < t1 || this->isConstant();
83 }
84 };
85
frame(float t)86 const KeyframeRec& frame(float t) {
87 if (!fCachedRec || !fCachedRec->contains(t)) {
88 fCachedRec = findFrame(t);
89 }
90 return *fCachedRec;
91 }
92
localT(const KeyframeRec & rec,float t) const93 float localT(const KeyframeRec& rec, float t) const {
94 SkASSERT(rec.isValid());
95 SkASSERT(!rec.isConstant());
96 SkASSERT(t > rec.t0 && t < rec.t1);
97
98 auto lt = (t - rec.t0) / (rec.t1 - rec.t0);
99
100 return rec.cmidx < 0
101 ? lt
102 : SkTPin(fCubicMaps[rec.cmidx].computeYFromX(lt), 0.0f, 1.0f);
103 }
104
105 virtual int parseValue(const Json::Value&) = 0;
106
parseKeyFrames(const Json::Value & jframes)107 void parseKeyFrames(const Json::Value& jframes) {
108 if (!jframes.isArray())
109 return;
110
111 for (const auto& jframe : jframes) {
112 if (!jframe.isObject())
113 continue;
114
115 float t0;
116 if (!Parse(jframe["t"], &t0)) {
117 continue;
118 }
119
120 if (!fRecs.empty()) {
121 if (fRecs.back().t1 >= t0) {
122 LOG("!! Ignoring out-of-order key frame (t:%f < t:%f)\n", t0, fRecs.back().t1);
123 continue;
124 }
125 // Back-fill t1 in prev interval. Note: we do this even if we end up discarding
126 // the current interval (to support "t"-only final frames).
127 fRecs.back().t1 = t0;
128 }
129
130 const auto vidx0 = this->parseValue(jframe["s"]);
131 if (vidx0 < 0) {
132 continue;
133 }
134
135 // Defaults for constant frames.
136 int vidx1 = vidx0, cmidx = -1;
137
138 if (!ParseDefault(jframe["h"], false)) {
139 // Regular frame, requires an end value.
140 vidx1 = this->parseValue(jframe["e"]);
141 if (vidx1 < 0) {
142 continue;
143 }
144
145 // default is linear lerp
146 static constexpr SkPoint kDefaultC0 = { 0, 0 },
147 kDefaultC1 = { 1, 1 };
148 const auto c0 = ParseDefault(jframe["i"], kDefaultC0),
149 c1 = ParseDefault(jframe["o"], kDefaultC1);
150
151 if (c0 != kDefaultC0 || c1 != kDefaultC1) {
152 // TODO: is it worth de-duping these?
153 cmidx = fCubicMaps.count();
154 fCubicMaps.emplace_back();
155 // TODO: why do we have to plug these inverted?
156 fCubicMaps.back().setPts(c1, c0);
157 }
158 }
159
160 fRecs.push_back({t0, t0, vidx0, vidx1, cmidx });
161 }
162
163 // If we couldn't determine a valid t1 for the last frame, discard it.
164 if (!fRecs.empty() && !fRecs.back().isValid()) {
165 fRecs.pop_back();
166 }
167
168 SkASSERT(fRecs.empty() || fRecs.back().isValid());
169 }
170
171 private:
findFrame(float t) const172 const KeyframeRec* findFrame(float t) const {
173 SkASSERT(!fRecs.empty());
174
175 auto f0 = &fRecs.front(),
176 f1 = &fRecs.back();
177
178 SkASSERT(f0->isValid());
179 SkASSERT(f1->isValid());
180
181 if (t < f0->t0) {
182 return f0;
183 }
184
185 if (t > f1->t1) {
186 return f1;
187 }
188
189 while (f0 != f1) {
190 SkASSERT(f0 < f1);
191 SkASSERT(t >= f0->t0 && t <= f1->t1);
192
193 const auto f = f0 + (f1 - f0) / 2;
194 SkASSERT(f->isValid());
195
196 if (t > f->t1) {
197 f0 = f + 1;
198 } else {
199 f1 = f;
200 }
201 }
202
203 SkASSERT(f0 == f1);
204 SkASSERT(f0->contains(t));
205
206 return f0;
207 }
208
209 SkTArray<KeyframeRec> fRecs;
210 SkTArray<SkCubicMap> fCubicMaps;
211 const KeyframeRec* fCachedRec = nullptr;
212
213 using INHERITED = sksg::Animator;
214 };
215
216 template <typename T>
217 class KeyframeAnimator final : public KeyframeAnimatorBase {
218 public:
Make(const Json::Value & jframes,std::function<void (const T &)> && apply)219 static std::unique_ptr<KeyframeAnimator> Make(const Json::Value& jframes,
220 std::function<void(const T&)>&& apply) {
221 std::unique_ptr<KeyframeAnimator> animator(new KeyframeAnimator(jframes, std::move(apply)));
222 if (!animator->count())
223 return nullptr;
224
225 return animator;
226 }
227
228 protected:
onTick(float t)229 void onTick(float t) override {
230 T val;
231 this->eval(this->frame(t), t, &val);
232
233 fApplyFunc(val);
234 }
235
236 private:
KeyframeAnimator(const Json::Value & jframes,std::function<void (const T &)> && apply)237 KeyframeAnimator(const Json::Value& jframes,
238 std::function<void(const T&)>&& apply)
239 : fApplyFunc(std::move(apply)) {
240 this->parseKeyFrames(jframes);
241 }
242
parseValue(const Json::Value & jv)243 int parseValue(const Json::Value& jv) override {
244 T val;
245 if (!Parse(jv, &val) || (!fVs.empty() &&
246 ValueTraits<T>::Cardinality(val) != ValueTraits<T>::Cardinality(fVs.back()))) {
247 return -1;
248 }
249
250 // TODO: full deduping?
251 if (fVs.empty() || val != fVs.back()) {
252 fVs.push_back(std::move(val));
253 }
254 return fVs.count() - 1;
255 }
256
eval(const KeyframeRec & rec,float t,T * v) const257 void eval(const KeyframeRec& rec, float t, T* v) const {
258 SkASSERT(rec.isValid());
259 if (rec.isConstant() || t <= rec.t0) {
260 *v = fVs[rec.vidx0];
261 } else if (t >= rec.t1) {
262 *v = fVs[rec.vidx1];
263 } else {
264 const auto lt = this->localT(rec, t);
265 const auto& v0 = fVs[rec.vidx0];
266 const auto& v1 = fVs[rec.vidx1];
267 *v = lerp(v0, v1, lt);
268 }
269 }
270
271 const std::function<void(const T&)> fApplyFunc;
272 SkTArray<T> fVs;
273
274
275 using INHERITED = KeyframeAnimatorBase;
276 };
277
278 template <typename T>
BindPropertyImpl(const Json::Value & jprop,sksg::AnimatorList * animators,std::function<void (const T &)> && apply,const T * noop=nullptr)279 static inline bool BindPropertyImpl(const Json::Value& jprop,
280 sksg::AnimatorList* animators,
281 std::function<void(const T&)>&& apply,
282 const T* noop = nullptr) {
283 if (!jprop.isObject())
284 return false;
285
286 const auto& jpropA = jprop["a"];
287 const auto& jpropK = jprop["k"];
288
289 // Older Json versions don't have an "a" animation marker.
290 // For those, we attempt to parse both ways.
291 if (!ParseDefault(jpropA, false)) {
292 T val;
293 if (Parse<T>(jpropK, &val)) {
294 // Static property.
295 if (noop && val == *noop)
296 return false;
297
298 apply(val);
299 return true;
300 }
301
302 if (!jpropA.isNull()) {
303 return LogFail(jprop, "Could not parse (explicit) static property");
304 }
305 }
306
307 // Keyframe property.
308 auto animator = KeyframeAnimator<T>::Make(jpropK, std::move(apply));
309
310 if (!animator) {
311 return LogFail(jprop, "Could not parse keyframed property");
312 }
313
314 animators->push_back(std::move(animator));
315
316 return true;
317 }
318
319 class SplitPointAnimator final : public sksg::Animator {
320 public:
Make(const Json::Value & jprop,std::function<void (const VectorValue &)> && apply,const VectorValue *)321 static std::unique_ptr<SplitPointAnimator> Make(const Json::Value& jprop,
322 std::function<void(const VectorValue&)>&& apply,
323 const VectorValue*) {
324 if (!jprop.isObject())
325 return nullptr;
326
327 std::unique_ptr<SplitPointAnimator> split_animator(
328 new SplitPointAnimator(std::move(apply)));
329
330 // This raw pointer is captured in lambdas below. But the lambdas are owned by
331 // the object itself, so the scope is bound to the life time of the object.
332 auto* split_animator_ptr = split_animator.get();
333
334 if (!BindPropertyImpl<ScalarValue>(jprop["x"], &split_animator->fAnimators,
335 [split_animator_ptr](const ScalarValue& x) { split_animator_ptr->setX(x); }) ||
336 !BindPropertyImpl<ScalarValue>(jprop["y"], &split_animator->fAnimators,
337 [split_animator_ptr](const ScalarValue& y) { split_animator_ptr->setY(y); })) {
338 LogFail(jprop, "Could not parse split property");
339 return nullptr;
340 }
341
342 if (split_animator->fAnimators.empty()) {
343 // Static split property, no need to hold on to the split animator.
344 return nullptr;
345 }
346
347 return split_animator;
348 }
349
onTick(float t)350 void onTick(float t) override {
351 for (const auto& animator : fAnimators) {
352 animator->tick(t);
353 }
354
355 const VectorValue vec = { fX, fY };
356 fApplyFunc(vec);
357 }
358
setX(const ScalarValue & x)359 void setX(const ScalarValue& x) { fX = x; }
setY(const ScalarValue & y)360 void setY(const ScalarValue& y) { fY = y; }
361
362 private:
SplitPointAnimator(std::function<void (const VectorValue &)> && apply)363 explicit SplitPointAnimator(std::function<void(const VectorValue&)>&& apply)
364 : fApplyFunc(std::move(apply)) {}
365
366 const std::function<void(const VectorValue&)> fApplyFunc;
367 sksg::AnimatorList fAnimators;
368
369 ScalarValue fX = 0,
370 fY = 0;
371
372 using INHERITED = sksg::Animator;
373 };
374
BindSplitPositionProperty(const Json::Value & jprop,sksg::AnimatorList * animators,std::function<void (const VectorValue &)> && apply,const VectorValue * noop)375 bool BindSplitPositionProperty(const Json::Value& jprop,
376 sksg::AnimatorList* animators,
377 std::function<void(const VectorValue&)>&& apply,
378 const VectorValue* noop) {
379 if (auto split_animator = SplitPointAnimator::Make(jprop, std::move(apply), noop)) {
380 animators->push_back(std::unique_ptr<sksg::Animator>(split_animator.release()));
381 return true;
382 }
383
384 return false;
385 }
386
387 } // namespace
388
389 template <>
BindProperty(const Json::Value & jprop,sksg::AnimatorList * animators,std::function<void (const ScalarValue &)> && apply,const ScalarValue * noop)390 bool BindProperty(const Json::Value& jprop,
391 sksg::AnimatorList* animators,
392 std::function<void(const ScalarValue&)>&& apply,
393 const ScalarValue* noop) {
394 return BindPropertyImpl(jprop, animators, std::move(apply), noop);
395 }
396
397 template <>
BindProperty(const Json::Value & jprop,sksg::AnimatorList * animators,std::function<void (const VectorValue &)> && apply,const VectorValue * noop)398 bool BindProperty(const Json::Value& jprop,
399 sksg::AnimatorList* animators,
400 std::function<void(const VectorValue&)>&& apply,
401 const VectorValue* noop) {
402 return ParseDefault(jprop["s"], false)
403 ? BindSplitPositionProperty(jprop, animators, std::move(apply), noop)
404 : BindPropertyImpl(jprop, animators, std::move(apply), noop);
405 }
406
407 template <>
BindProperty(const Json::Value & jprop,sksg::AnimatorList * animators,std::function<void (const ShapeValue &)> && apply,const ShapeValue * noop)408 bool BindProperty(const Json::Value& jprop,
409 sksg::AnimatorList* animators,
410 std::function<void(const ShapeValue&)>&& apply,
411 const ShapeValue* noop) {
412 return BindPropertyImpl(jprop, animators, std::move(apply), noop);
413 }
414
415 } // namespace skottie
416