• 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 "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