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