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