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 "include/core/SkCubicMap.h"
9 #include "include/core/SkString.h"
10 #include "modules/skottie/src/SkottieJson.h"
11 #include "modules/skottie/src/SkottiePriv.h"
12 #include "modules/skottie/src/SkottieValue.h"
13 #include "modules/skottie/src/text/TextValue.h"
14 #include "modules/sksg/include/SkSGScene.h"
15
16 #include <memory>
17 #include <vector>
18
19 namespace skottie {
20 namespace internal {
21
22 namespace {
23
24 class KeyframeAnimatorBase : public sksg::Animator {
25 public:
count() const26 size_t count() const { return fRecs.size(); }
27
28 protected:
29 KeyframeAnimatorBase() = default;
30
31 struct KeyframeRec {
32 float t0, t1;
33 int vidx0, vidx1, // v0/v1 indices
34 cmidx; // cubic map index
35
containsskottie::internal::__anon9e47bd0f0111::KeyframeAnimatorBase::KeyframeRec36 bool contains(float t) const { return t0 <= t && t <= t1; }
isConstantskottie::internal::__anon9e47bd0f0111::KeyframeAnimatorBase::KeyframeRec37 bool isConstant() const { return vidx0 == vidx1; }
isValidskottie::internal::__anon9e47bd0f0111::KeyframeAnimatorBase::KeyframeRec38 bool isValid() const {
39 SkASSERT(t0 <= t1);
40 // Constant frames don't need/use t1 and vidx1.
41 return t0 < t1 || this->isConstant();
42 }
43 };
44
frame(float t)45 const KeyframeRec& frame(float t) {
46 if (!fCachedRec || !fCachedRec->contains(t)) {
47 fCachedRec = findFrame(t);
48 }
49 return *fCachedRec;
50 }
51
localT(const KeyframeRec & rec,float t) const52 float localT(const KeyframeRec& rec, float t) const {
53 SkASSERT(rec.isValid());
54 SkASSERT(!rec.isConstant());
55 SkASSERT(t > rec.t0 && t < rec.t1);
56
57 auto lt = (t - rec.t0) / (rec.t1 - rec.t0);
58
59 return rec.cmidx < 0
60 ? lt
61 : fCubicMaps[rec.cmidx].computeYFromX(lt);
62 }
63
64 virtual int parseValue(const skjson::Value&, const AnimationBuilder* abuilder) = 0;
65
parseKeyFrames(const skjson::ArrayValue & jframes,const AnimationBuilder * abuilder)66 void parseKeyFrames(const skjson::ArrayValue& jframes, const AnimationBuilder* abuilder) {
67 // Logically, a keyframe is defined as a (t0, t1, v0, v1) tuple: a given value
68 // is interpolated in the [v0..v1] interval over the [t0..t1] time span.
69 //
70 // There are three interestingly-different keyframe formats handled here.
71 //
72 // 1) Legacy keyframe format
73 //
74 // - normal keyframes specify t0 ("t"), v0 ("s") and v1 ("e")
75 // - last frame only specifies a t0
76 // - t1[frame] == t0[frame + 1]
77 // - the last entry (where we cannot determine t1) is ignored
78 //
79 // 2) Regular (new) keyframe format
80 //
81 // - all keyframes specify t0 ("t") and v0 ("s")
82 // - t1[frame] == t0[frame + 1]
83 // - v1[frame] == v0[frame + 1]
84 // - the last entry (where we cannot determine t1/v1) is ignored
85 //
86 // 3) Text value keyframe format
87 //
88 // - similar to case #2, all keyframes specify t0 & v0
89 // - unlike case #2, all keyframes are assumed to be constant (v1 == v0),
90 // and the last frame is not discarded (its t1 is assumed -> inf)
91 //
92
93 SkPoint prev_c0 = { 0, 0 },
94 prev_c1 = prev_c0;
95
96 for (const skjson::ObjectValue* jframe : jframes) {
97 if (!jframe) continue;
98
99 float t0;
100 if (!Parse<float>((*jframe)["t"], &t0))
101 continue;
102
103 const auto v0_idx = this->parseValue((*jframe)["s"], abuilder),
104 v1_idx = this->parseValue((*jframe)["e"], abuilder);
105
106 if (!fRecs.empty()) {
107 if (fRecs.back().t1 >= t0) {
108 abuilder->log(Logger::Level::kWarning, nullptr,
109 "Ignoring out-of-order key frame (t:%f < t:%f).",
110 t0, fRecs.back().t1);
111 continue;
112 }
113
114 // Back-fill t1 and v1 (if needed).
115 auto& prev = fRecs.back();
116 prev.t1 = t0;
117
118 // Previous keyframe did not specify an end value (case #2, #3).
119 if (prev.vidx1 < 0) {
120 // If this frame has no v0, we're in case #3 (constant text value),
121 // otherwise case #2 (v0 for current frame is the same as prev frame v1).
122 prev.vidx1 = v0_idx < 0 ? prev.vidx0 : v0_idx;
123 }
124 }
125
126 // Start value 's' is required.
127 if (v0_idx < 0)
128 continue;
129
130 if ((v1_idx < 0) && ParseDefault((*jframe)["h"], false)) {
131 // Constant keyframe ("h": true).
132 fRecs.push_back({t0, t0, v0_idx, v0_idx, -1 });
133 continue;
134 }
135
136 const auto cubic_mapper_index = [&]() -> int {
137 // Do we have non-linear control points?
138 SkPoint c0, c1;
139 if (!Parse((*jframe)["o"], &c0) ||
140 !Parse((*jframe)["i"], &c1) ||
141 SkCubicMap::IsLinear(c0, c1)) {
142 // No need for a cubic mapper.
143 return -1;
144 }
145
146 // De-dupe sequential cubic mappers.
147 if (c0 != prev_c0 || c1 != prev_c1) {
148 fCubicMaps.emplace_back(c0, c1);
149 prev_c0 = c0;
150 prev_c1 = c1;
151 }
152
153 SkASSERT(!fCubicMaps.empty());
154 return SkToInt(fCubicMaps.size()) - 1;
155 };
156
157 fRecs.push_back({t0, t0, v0_idx, v1_idx, cubic_mapper_index()});
158 }
159
160 if (!fRecs.empty()) {
161 auto& last = fRecs.back();
162
163 // If the last entry has only a v0, we're in case #3 - make it a constant frame.
164 if (last.vidx0 >= 0 && last.vidx1 < 0) {
165 last.vidx1 = last.vidx0;
166 last.t1 = last.t0;
167 }
168
169 // If we couldn't determine a valid t1 for the last frame, discard it
170 // (most likely the last frame entry for all 3 cases).
171 if (!last.isValid()) {
172 fRecs.pop_back();
173 }
174 }
175
176 fRecs.shrink_to_fit();
177 fCubicMaps.shrink_to_fit();
178
179 SkASSERT(fRecs.empty() || fRecs.back().isValid());
180 }
181
reserve(size_t frame_count)182 void reserve(size_t frame_count) {
183 fRecs.reserve(frame_count);
184 fCubicMaps.reserve(frame_count);
185 }
186
187 private:
findFrame(float t) const188 const KeyframeRec* findFrame(float t) const {
189 SkASSERT(!fRecs.empty());
190
191 auto f0 = &fRecs.front(),
192 f1 = &fRecs.back();
193
194 SkASSERT(f0->isValid());
195 SkASSERT(f1->isValid());
196
197 if (t < f0->t0) {
198 return f0;
199 }
200
201 if (t > f1->t1) {
202 return f1;
203 }
204
205 while (f0 != f1) {
206 SkASSERT(f0 < f1);
207 SkASSERT(t >= f0->t0 && t <= f1->t1);
208
209 const auto f = f0 + (f1 - f0) / 2;
210 SkASSERT(f->isValid());
211
212 if (t > f->t1) {
213 f0 = f + 1;
214 } else {
215 f1 = f;
216 }
217 }
218
219 SkASSERT(f0 == f1);
220 SkASSERT(f0->contains(t));
221
222 return f0;
223 }
224
225 std::vector<KeyframeRec> fRecs;
226 std::vector<SkCubicMap> fCubicMaps;
227 const KeyframeRec* fCachedRec = nullptr;
228
229 using INHERITED = sksg::Animator;
230 };
231
232 template <typename T>
233 class KeyframeAnimator final : public KeyframeAnimatorBase {
234 public:
Make(const skjson::ArrayValue * jv,const AnimationBuilder * abuilder,std::function<void (const T &)> && apply)235 static sk_sp<KeyframeAnimator> Make(const skjson::ArrayValue* jv,
236 const AnimationBuilder* abuilder,
237 std::function<void(const T&)>&& apply) {
238 if (!jv) return nullptr;
239
240 sk_sp<KeyframeAnimator> animator(new KeyframeAnimator(*jv, abuilder, std::move(apply)));
241
242 return animator->count() ? animator : nullptr;
243 }
244
245 protected:
onTick(float t)246 void onTick(float t) override {
247 fApplyFunc(*this->eval(this->frame(t), t, &fScratch));
248 }
249
250 private:
KeyframeAnimator(const skjson::ArrayValue & jframes,const AnimationBuilder * abuilder,std::function<void (const T &)> && apply)251 KeyframeAnimator(const skjson::ArrayValue& jframes,
252 const AnimationBuilder* abuilder,
253 std::function<void(const T&)>&& apply)
254 : fApplyFunc(std::move(apply)) {
255 // Generally, each keyframe holds two values (start, end) and a cubic mapper. Except
256 // the last frame, which only holds a marker timestamp. Then, the values series is
257 // contiguous (keyframe[i].end == keyframe[i + 1].start), and we dedupe them.
258 // => we'll store (keyframes.size) values and (keyframe.size - 1) recs and cubic maps.
259 fVs.reserve(jframes.size());
260 this->reserve(SkTMax<size_t>(jframes.size(), 1) - 1);
261
262 this->parseKeyFrames(jframes, abuilder);
263
264 fVs.shrink_to_fit();
265 }
266
parseValue(const skjson::Value & jv,const AnimationBuilder * abuilder)267 int parseValue(const skjson::Value& jv, const AnimationBuilder* abuilder) override {
268 T val;
269 if (!ValueTraits<T>::FromJSON(jv, abuilder, &val) ||
270 (!fVs.empty() && !ValueTraits<T>::CanLerp(val, fVs.back()))) {
271 return -1;
272 }
273
274 // TODO: full deduping?
275 if (fVs.empty() || val != fVs.back()) {
276 fVs.push_back(std::move(val));
277 }
278 return SkToInt(fVs.size()) - 1;
279 }
280
eval(const KeyframeRec & rec,float t,T * v) const281 const T* eval(const KeyframeRec& rec, float t, T* v) const {
282 SkASSERT(rec.isValid());
283 if (rec.isConstant() || t <= rec.t0) {
284 return &fVs[rec.vidx0];
285 } else if (t >= rec.t1) {
286 return &fVs[rec.vidx1];
287 }
288
289 const auto lt = this->localT(rec, t);
290 const auto& v0 = fVs[rec.vidx0];
291 const auto& v1 = fVs[rec.vidx1];
292 ValueTraits<T>::Lerp(v0, v1, lt, v);
293
294 return v;
295 }
296
297 const std::function<void(const T&)> fApplyFunc;
298 std::vector<T> fVs;
299
300 // LERP storage: we use this to temporarily store interpolation results.
301 // Alternatively, the temp result could live on the stack -- but for vector values that would
302 // involve dynamic allocations on each tick. This a trade-off to avoid allocator pressure
303 // during animation.
304 T fScratch; // lerp storage
305
306 using INHERITED = KeyframeAnimatorBase;
307 };
308
309 template <typename T>
BindPropertyImpl(const skjson::ObjectValue * jprop,const AnimationBuilder * abuilder,AnimatorScope * ascope,std::function<void (const T &)> && apply,const T * noop=nullptr)310 static inline bool BindPropertyImpl(const skjson::ObjectValue* jprop,
311 const AnimationBuilder* abuilder,
312 AnimatorScope* ascope,
313 std::function<void(const T&)>&& apply,
314 const T* noop = nullptr) {
315 if (!jprop) return false;
316
317 const auto& jpropA = (*jprop)["a"];
318 const auto& jpropK = (*jprop)["k"];
319
320 if (!(*jprop)["x"].is<skjson::NullValue>()) {
321 abuilder->log(Logger::Level::kWarning, nullptr, "Unsupported expression.");
322 }
323
324 // Older Json versions don't have an "a" animation marker.
325 // For those, we attempt to parse both ways.
326 if (!ParseDefault<bool>(jpropA, false)) {
327 T val;
328 if (ValueTraits<T>::FromJSON(jpropK, abuilder, &val)) {
329 // Static property.
330 if (noop && val == *noop)
331 return false;
332
333 apply(val);
334 return true;
335 }
336
337 if (!jpropA.is<skjson::NullValue>()) {
338 abuilder->log(Logger::Level::kError, jprop,
339 "Could not parse (explicit) static property.");
340 return false;
341 }
342 }
343
344 // Keyframe property.
345 auto animator = KeyframeAnimator<T>::Make(jpropK, abuilder, std::move(apply));
346
347 if (!animator) {
348 abuilder->log(Logger::Level::kError, jprop, "Could not parse keyframed property.");
349 return false;
350 }
351
352 ascope->push_back(std::move(animator));
353
354 return true;
355 }
356
357 class SplitPointAnimator final : public sksg::Animator {
358 public:
Make(const skjson::ObjectValue * jprop,const AnimationBuilder * abuilder,std::function<void (const VectorValue &)> && apply,const VectorValue *)359 static sk_sp<SplitPointAnimator> Make(const skjson::ObjectValue* jprop,
360 const AnimationBuilder* abuilder,
361 std::function<void(const VectorValue&)>&& apply,
362 const VectorValue*) {
363 if (!jprop) return nullptr;
364
365 sk_sp<SplitPointAnimator> split_animator(new SplitPointAnimator(std::move(apply)));
366
367 // This raw pointer is captured in lambdas below. But the lambdas are owned by
368 // the object itself, so the scope is bound to the life time of the object.
369 auto* split_animator_ptr = split_animator.get();
370
371 if (!BindPropertyImpl<ScalarValue>((*jprop)["x"], abuilder, &split_animator->fAnimators,
372 [split_animator_ptr](const ScalarValue& x) { split_animator_ptr->setX(x); }) ||
373 !BindPropertyImpl<ScalarValue>((*jprop)["y"], abuilder, &split_animator->fAnimators,
374 [split_animator_ptr](const ScalarValue& y) { split_animator_ptr->setY(y); })) {
375 abuilder->log(Logger::Level::kError, jprop, "Could not parse split property.");
376 return nullptr;
377 }
378
379 if (split_animator->fAnimators.empty()) {
380 // Static split property: commit the (buffered) value and discard.
381 split_animator->onTick(0);
382 return nullptr;
383 }
384
385 return split_animator;
386 }
387
onTick(float t)388 void onTick(float t) override {
389 for (const auto& animator : fAnimators) {
390 animator->tick(t);
391 }
392
393 const VectorValue vec = { fX, fY };
394 fApplyFunc(vec);
395 }
396
setX(const ScalarValue & x)397 void setX(const ScalarValue& x) { fX = x; }
setY(const ScalarValue & y)398 void setY(const ScalarValue& y) { fY = y; }
399
400 private:
SplitPointAnimator(std::function<void (const VectorValue &)> && apply)401 explicit SplitPointAnimator(std::function<void(const VectorValue&)>&& apply)
402 : fApplyFunc(std::move(apply)) {}
403
404 const std::function<void(const VectorValue&)> fApplyFunc;
405 sksg::AnimatorList fAnimators;
406
407 ScalarValue fX = 0,
408 fY = 0;
409
410 using INHERITED = sksg::Animator;
411 };
412
BindSplitPositionProperty(const skjson::Value & jv,const AnimationBuilder * abuilder,AnimatorScope * ascope,std::function<void (const VectorValue &)> && apply,const VectorValue * noop)413 bool BindSplitPositionProperty(const skjson::Value& jv,
414 const AnimationBuilder* abuilder,
415 AnimatorScope* ascope,
416 std::function<void(const VectorValue&)>&& apply,
417 const VectorValue* noop) {
418 if (auto split_animator = SplitPointAnimator::Make(jv, abuilder, std::move(apply), noop)) {
419 ascope->push_back(std::move(split_animator));
420 return true;
421 }
422
423 return false;
424 }
425
426 } // namespace
427
428 template <>
bindProperty(const skjson::Value & jv,std::function<void (const ScalarValue &)> && apply,const ScalarValue * noop) const429 bool AnimationBuilder::bindProperty(const skjson::Value& jv,
430 std::function<void(const ScalarValue&)>&& apply,
431 const ScalarValue* noop) const {
432 return BindPropertyImpl(jv, this, fCurrentAnimatorScope, std::move(apply), noop);
433 }
434
435 template <>
bindProperty(const skjson::Value & jv,std::function<void (const VectorValue &)> && apply,const VectorValue * noop) const436 bool AnimationBuilder::bindProperty(const skjson::Value& jv,
437 std::function<void(const VectorValue&)>&& apply,
438 const VectorValue* noop) const {
439 if (!jv.is<skjson::ObjectValue>())
440 return false;
441
442 return ParseDefault<bool>(jv.as<skjson::ObjectValue>()["s"], false)
443 ? BindSplitPositionProperty(jv, this, fCurrentAnimatorScope, std::move(apply), noop)
444 : BindPropertyImpl(jv, this, fCurrentAnimatorScope, std::move(apply), noop);
445 }
446
447 template <>
bindProperty(const skjson::Value & jv,std::function<void (const ShapeValue &)> && apply,const ShapeValue * noop) const448 bool AnimationBuilder::bindProperty(const skjson::Value& jv,
449 std::function<void(const ShapeValue&)>&& apply,
450 const ShapeValue* noop) const {
451 return BindPropertyImpl(jv, this, fCurrentAnimatorScope, std::move(apply), noop);
452 }
453
454 template <>
bindProperty(const skjson::Value & jv,std::function<void (const TextValue &)> && apply,const TextValue * noop) const455 bool AnimationBuilder::bindProperty(const skjson::Value& jv,
456 std::function<void(const TextValue&)>&& apply,
457 const TextValue* noop) const {
458 return BindPropertyImpl(jv, this, fCurrentAnimatorScope, std::move(apply), noop);
459 }
460
461 } // namespace internal
462 } // namespace skottie
463