xref: /aosp_15_r20/external/skia/modules/skottie/src/animator/Vec2KeyframeAnimator.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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