• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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