1 /*
2 * Copyright 2020 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/SkContourMeasure.h"
9 #include "include/core/SkCubicMap.h"
10 #include "include/core/SkM44.h"
11 #include "include/core/SkPathBuilder.h"
12 #include "include/core/SkPoint.h"
13 #include "include/core/SkRefCnt.h"
14 #include "include/core/SkScalar.h"
15 #include "include/core/SkString.h"
16 #include "include/private/base/SkAssert.h"
17 #include "include/private/base/SkTo.h"
18 #include "modules/skottie/include/Skottie.h"
19 #include "modules/skottie/include/SlotManager.h"
20 #include "modules/skottie/src/SkottieJson.h"
21 #include "modules/skottie/src/SkottiePriv.h"
22 #include "modules/skottie/src/SkottieValue.h"
23 #include "modules/skottie/src/animator/Animator.h"
24 #include "modules/skottie/src/animator/KeyframeAnimator.h"
25 #include "src/utils/SkJSON.h"
26
27 #include <algorithm>
28 #include <cmath>
29 #include <utility>
30 #include <vector>
31
32 namespace skottie::internal {
33
34 namespace {
35
36 // Spatial 2D specialization: stores SkV2s and optional contour interpolators externally.
37 class Vec2KeyframeAnimator final : public KeyframeAnimator {
38 public:
39 struct SpatialValue {
40 Vec2Value v2;
41 sk_sp<SkContourMeasure> cmeasure;
42 };
43
Vec2KeyframeAnimator(std::vector<Keyframe> kfs,std::vector<SkCubicMap> cms,std::vector<SpatialValue> vs,Vec2Value * vec_target,float * rot_target)44 Vec2KeyframeAnimator(std::vector<Keyframe> kfs, std::vector<SkCubicMap> cms,
45 std::vector<SpatialValue> vs, Vec2Value* vec_target, float* rot_target)
46 : INHERITED(std::move(kfs), std::move(cms))
47 , fValues(std::move(vs))
48 , fVecTarget(vec_target)
49 , fRotTarget(rot_target) {}
50
51 private:
update(const Vec2Value & new_vec_value,const Vec2Value & new_tan_value)52 StateChanged update(const Vec2Value& new_vec_value, const Vec2Value& new_tan_value) {
53 auto changed = (new_vec_value != *fVecTarget);
54 *fVecTarget = new_vec_value;
55
56 if (fRotTarget) {
57 const auto new_rot_value = SkRadiansToDegrees(std::atan2(new_tan_value.y,
58 new_tan_value.x));
59 changed |= new_rot_value != *fRotTarget;
60 *fRotTarget = new_rot_value;
61 }
62
63 return changed;
64 }
65
onSeek(float t)66 StateChanged onSeek(float t) override {
67 auto get_lerp_info = [this](float t) {
68 auto lerp_info = this->getLERPInfo(t);
69
70 // When tracking rotation/orientation, the last keyframe requires special handling:
71 // it doesn't store any spatial information but it is expected to maintain the
72 // previous orientation (per AE semantics).
73 //
74 // The easiest way to achieve this is to actually swap with the previous keyframe,
75 // with an adjusted weight of 1.
76 const auto vidx = lerp_info.vrec0.idx;
77 if (fRotTarget && vidx == fValues.size() - 1 && vidx > 0) {
78 SkASSERT(!fValues[vidx].cmeasure);
79 SkASSERT(lerp_info.vrec1.idx == vidx);
80
81 // Change LERPInfo{0, SIZE - 1, SIZE - 1}
82 // to LERPInfo{1, SIZE - 2, SIZE - 1}
83 lerp_info.weight = 1;
84 lerp_info.vrec0 = {vidx - 1};
85
86 // This yields equivalent lerp results because keyframed values are contiguous
87 // i.e frame[n-1].end_val == frame[n].start_val.
88 }
89
90 return lerp_info;
91 };
92
93 const auto lerp_info = get_lerp_info(t);
94
95 const auto& v0 = fValues[lerp_info.vrec0.idx];
96 if (v0.cmeasure) {
97 // Spatial keyframe: the computed weight is relative to the interpolation path
98 // arc length.
99 SkPoint pos;
100 SkVector tan;
101 const float len = v0.cmeasure->length(),
102 distance = len * lerp_info.weight;
103 if (v0.cmeasure->getPosTan(distance, &pos, &tan)) {
104 // Easing can yield a sub/super normal weight, which in turn can cause the
105 // interpolation position to become negative or larger than the path length.
106 // In those cases the expectation is to extrapolate using the endpoint tangent.
107 if (distance < 0 || distance > len) {
108 const float overshoot = std::copysign(std::max(-distance, distance - len),
109 distance);
110 pos += tan * overshoot;
111 }
112
113 return this->update({ pos.fX, pos.fY }, {tan.fX, tan.fY});
114 }
115 }
116
117 const auto& v1 = fValues[lerp_info.vrec1.idx];
118 const auto tan = v1.v2 - v0.v2;
119
120 return this->update(Lerp(v0.v2, v1.v2, lerp_info.weight), tan);
121 }
122
123 const std::vector<Vec2KeyframeAnimator::SpatialValue> fValues;
124 Vec2Value* fVecTarget;
125 float* fRotTarget;
126
127 using INHERITED = KeyframeAnimator;
128 };
129
130 class Vec2ExpressionAnimator final : public Animator {
131 public:
Vec2ExpressionAnimator(sk_sp<ExpressionEvaluator<std::vector<float>>> expression_evaluator,Vec2Value * target_value)132 Vec2ExpressionAnimator(sk_sp<ExpressionEvaluator<std::vector<float>>> expression_evaluator,
133 Vec2Value* target_value)
134 : fExpressionEvaluator(std::move(expression_evaluator))
135 , fTarget(target_value) {}
136
137 private:
138
onSeek(float t)139 StateChanged onSeek(float t) override {
140 auto old_value = *fTarget;
141
142 std::vector<float> result = fExpressionEvaluator->evaluate(t);
143 fTarget->x = result.size() > 0 ? result[0] : 0;
144 fTarget->y = result.size() > 1 ? result[1] : 0;
145
146 return *fTarget != old_value;
147 }
148
149 sk_sp<ExpressionEvaluator<std::vector<float>>> fExpressionEvaluator;
150 Vec2Value* fTarget;
151 };
152
153 class Vec2AnimatorBuilder final : public AnimatorBuilder {
154 public:
Vec2AnimatorBuilder(Vec2Value * vec_target,float * rot_target)155 Vec2AnimatorBuilder(Vec2Value* vec_target, float* rot_target)
156 : INHERITED(Keyframe::Value::Type::kIndex)
157 , fVecTarget(vec_target)
158 , fRotTarget(rot_target) {}
159
makeFromKeyframes(const AnimationBuilder & abuilder,const skjson::ArrayValue & jkfs)160 sk_sp<KeyframeAnimator> makeFromKeyframes(const AnimationBuilder& abuilder,
161 const skjson::ArrayValue& jkfs) override {
162 SkASSERT(jkfs.size() > 0);
163
164 fValues.reserve(jkfs.size());
165 if (!this->parseKeyframes(abuilder, jkfs)) {
166 return nullptr;
167 }
168 fValues.shrink_to_fit();
169
170 return sk_sp<Vec2KeyframeAnimator>(
171 new Vec2KeyframeAnimator(std::move(fKFs),
172 std::move(fCMs),
173 std::move(fValues),
174 fVecTarget,
175 fRotTarget));
176 }
177
makeFromExpression(ExpressionManager & em,const char * expr)178 sk_sp<Animator> makeFromExpression(ExpressionManager& em, const char* expr) override {
179 sk_sp<ExpressionEvaluator<std::vector<SkScalar>>> expression_evaluator =
180 em.createArrayExpressionEvaluator(expr);
181 return sk_make_sp<Vec2ExpressionAnimator>(expression_evaluator, fVecTarget);
182 }
183
parseValue(const AnimationBuilder &,const skjson::Value & jv) const184 bool parseValue(const AnimationBuilder&, const skjson::Value& jv) const override {
185 return ::skottie::Parse(jv, fVecTarget);
186 }
187
188 private:
backfill_spatial(const Vec2KeyframeAnimator::SpatialValue & val)189 void backfill_spatial(const Vec2KeyframeAnimator::SpatialValue& val) {
190 SkASSERT(!fValues.empty());
191 auto& prev_val = fValues.back();
192 SkASSERT(!prev_val.cmeasure);
193
194 if (val.v2 == prev_val.v2) {
195 // spatial interpolation only make sense for noncoincident values
196 return;
197 }
198
199 // Check whether v0 and v1 have the same direction AND ||v0||>=||v1||
200 auto check_vecs = [](const SkV2& v0, const SkV2& v1) {
201 const auto v0_len2 = v0.lengthSquared(),
202 v1_len2 = v1.lengthSquared();
203
204 // check magnitude
205 if (v0_len2 < v1_len2) {
206 return false;
207 }
208
209 // v0, v1 have the same direction iff dot(v0,v1) = ||v0||*||v1||
210 // <=> dot(v0,v1)^2 = ||v0||^2 * ||v1||^2
211 const auto dot = v0.dot(v1);
212 return SkScalarNearlyEqual(dot * dot, v0_len2 * v1_len2);
213 };
214
215 if (check_vecs(val.v2 - prev_val.v2, fTo) &&
216 check_vecs(prev_val.v2 - val.v2, fTi)) {
217 // Both control points lie on the [prev_val..val] segment
218 // => we can power-reduce the Bezier "curve" to a straight line.
219 return;
220 }
221
222 // Finally, this looks like a legitimate spatial keyframe.
223 SkPathBuilder p;
224 p.moveTo (prev_val.v2.x , prev_val.v2.y);
225 p.cubicTo(prev_val.v2.x + fTo.x, prev_val.v2.y + fTo.y,
226 val.v2.x + fTi.x, val.v2.y + fTi.y,
227 val.v2.x, val.v2.y);
228 prev_val.cmeasure = SkContourMeasureIter(p.detach(), false).next();
229 }
230
parseKFValue(const AnimationBuilder &,const skjson::ObjectValue & jkf,const skjson::Value & jv,Keyframe::Value * v)231 bool parseKFValue(const AnimationBuilder&,
232 const skjson::ObjectValue& jkf,
233 const skjson::Value& jv,
234 Keyframe::Value* v) override {
235 Vec2KeyframeAnimator::SpatialValue val;
236 if (!::skottie::Parse(jv, &val.v2)) {
237 return false;
238 }
239
240 if (fPendingSpatial) {
241 this->backfill_spatial(val);
242 }
243
244 // Track the last keyframe spatial tangents (checked on next parseValue).
245 fTi = ParseDefault<SkV2>(jkf["ti"], {0,0});
246 fTo = ParseDefault<SkV2>(jkf["to"], {0,0});
247 fPendingSpatial = fTi != SkV2{0,0} || fTo != SkV2{0,0};
248
249 if (fValues.empty() || val.v2 != fValues.back().v2 || fPendingSpatial) {
250 fValues.push_back(std::move(val));
251 }
252
253 v->idx = SkToU32(fValues.size() - 1);
254
255 return true;
256 }
257
258 std::vector<Vec2KeyframeAnimator::SpatialValue> fValues;
259 Vec2Value* fVecTarget; // required
260 float* fRotTarget; // optional
261 SkV2 fTi{0,0},
262 fTo{0,0};
263 bool fPendingSpatial = false;
264
265 using INHERITED = AnimatorBuilder;
266 };
267
268 } // namespace
269
bindAutoOrientable(const AnimationBuilder & abuilder,const skjson::ObjectValue * jprop,Vec2Value * v,float * orientation)270 bool AnimatablePropertyContainer::bindAutoOrientable(const AnimationBuilder& abuilder,
271 const skjson::ObjectValue* jprop,
272 Vec2Value* v, float* orientation) {
273 if (!jprop) {
274 return false;
275 }
276
277 if (const auto* sid = ParseSlotID(jprop)) {
278 fHasSlotID = true;
279 abuilder.fSlotManager->trackVec2Value(SkString(sid->begin()), v, sk_ref_sp(this));
280 }
281
282 if (!ParseDefault<bool>((*jprop)["s"], false)) {
283 // Regular (static or keyframed) 2D value.
284 Vec2AnimatorBuilder builder(v, orientation);
285 return this->bindImpl(abuilder, jprop, builder);
286 }
287
288 // Separate-dimensions vector value: each component is animated independently.
289 bool boundX = this->bind(abuilder, (*jprop)["x"], &v->x);
290 bool boundY = this->bind(abuilder, (*jprop)["y"], &v->y);
291 return boundX || boundY;
292 }
293
294 template <>
bind(const AnimationBuilder & abuilder,const skjson::ObjectValue * jprop,Vec2Value * v)295 bool AnimatablePropertyContainer::bind<Vec2Value>(const AnimationBuilder& abuilder,
296 const skjson::ObjectValue* jprop,
297 Vec2Value* v) {
298 return this->bindAutoOrientable(abuilder, jprop, v, nullptr);
299 }
300
301 } // namespace skottie::internal
302