xref: /aosp_15_r20/external/skia/modules/skottie/src/animator/KeyframeAnimator.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 "modules/skottie/src/animator/KeyframeAnimator.h"
9 
10 #include "include/private/base/SkTo.h"
11 #include "modules/skottie/src/SkottieJson.h"
12 #include "src/utils/SkJSON.h"
13 
14 #include <cstddef>
15 
16 #define DUMP_KF_RECORDS 0
17 
18 namespace skottie::internal {
19 
20 KeyframeAnimator::~KeyframeAnimator() = default;
21 
getLERPInfo(float t) const22 KeyframeAnimator::LERPInfo KeyframeAnimator::getLERPInfo(float t) const {
23     SkASSERT(!fKFs.empty());
24 
25     if (t <= fKFs.front().t) {
26         // Constant/clamped segment.
27         return { 0, fKFs.front().v, fKFs.front().v };
28     }
29     if (t >= fKFs.back().t) {
30         // Constant/clamped segment.
31         return { 0, fKFs.back().v, fKFs.back().v };
32     }
33 
34     // Cache the current segment (most queries have good locality).
35     if (!fCurrentSegment.contains(t)) {
36         fCurrentSegment = this->find_segment(t);
37     }
38     SkASSERT(fCurrentSegment.contains(t));
39 
40     if (fCurrentSegment.kf0->mapping == Keyframe::kConstantMapping) {
41         // Constant/hold segment.
42         return { 0, fCurrentSegment.kf0->v, fCurrentSegment.kf0->v };
43     }
44 
45     return {
46         this->compute_weight(fCurrentSegment, t),
47         fCurrentSegment.kf0->v,
48         fCurrentSegment.kf1->v,
49     };
50 }
51 
find_segment(float t) const52 KeyframeAnimator::KFSegment KeyframeAnimator::find_segment(float t) const {
53     SkASSERT(fKFs.size() > 1);
54     SkASSERT(t > fKFs.front().t);
55     SkASSERT(t < fKFs.back().t);
56 
57     auto kf0 = &fKFs.front(),
58          kf1 = &fKFs.back();
59 
60     // Binary-search, until we reduce to sequential keyframes.
61     while (kf0 + 1 != kf1) {
62         SkASSERT(kf0 < kf1);
63         SkASSERT(kf0->t <= t && t < kf1->t);
64 
65         const auto mid_kf = kf0 + (kf1 - kf0) / 2;
66 
67         if (t >= mid_kf->t) {
68             kf0 = mid_kf;
69         } else {
70             kf1 = mid_kf;
71         }
72     }
73 
74     return {kf0, kf1};
75 }
76 
compute_weight(const KFSegment & seg,float t) const77 float KeyframeAnimator::compute_weight(const KFSegment &seg, float t) const {
78     SkASSERT(seg.contains(t));
79 
80     // Linear weight.
81     auto w = (t - seg.kf0->t) / (seg.kf1->t - seg.kf0->t);
82 
83     // Optional cubic mapper.
84     if (seg.kf0->mapping >= Keyframe::kCubicIndexOffset) {
85         const auto mapper_index = SkToSizeT(seg.kf0->mapping - Keyframe::kCubicIndexOffset);
86         w = fCMs[mapper_index].computeYFromX(w);
87     }
88 
89     return w;
90 }
91 
92 AnimatorBuilder::~AnimatorBuilder() = default;
93 
parseKeyframes(const AnimationBuilder & abuilder,const skjson::ArrayValue & jkfs)94 bool AnimatorBuilder::parseKeyframes(const AnimationBuilder& abuilder,
95                                      const skjson::ArrayValue& jkfs) {
96     // Keyframe format:
97     //
98     // [                        // array of
99     //   {
100     //     "t": <float>         // keyframe time
101     //     "s": <T>             // keyframe value
102     //     "h": <bool>          // optional constant/hold keyframe marker
103     //     "i": [<float,float>] // optional "in" Bezier control point
104     //     "o": [<float,float>] // optional "out" Bezier control point
105     //   },
106     //   ...
107     // ]
108     //
109     // Legacy keyframe format:
110     //
111     // [                        // array of
112     //   {
113     //     "t": <float>         // keyframe time
114     //     "s": <T>             // keyframe start value
115     //     "e": <T>             // keyframe end value
116     //     "h": <bool>          // optional constant/hold keyframe marker (constant mapping)
117     //     "i": [<float,float>] // optional "in" Bezier control point (cubic mapping)
118     //     "o": [<float,float>] // optional "out" Bezier control point (cubic mapping)
119     //   },
120     //   ...
121     //   {
122     //     "t": <float>         // last keyframe only specifies a t
123     //                          // the value is prev. keyframe end value
124     //   }
125     // ]
126     //
127     // Note: the legacy format contains duplicates, as normal frames are contiguous:
128     //       frame(n).e == frame(n+1).s
129 
130     const auto parse_value = [&](const skjson::ObjectValue& jkf, size_t i, Keyframe::Value* v) {
131         auto parsed = this->parseKFValue(abuilder, jkf, jkf["s"], v);
132 
133         // A missing value is only OK for the last legacy KF
134         // (where it is pulled from prev KF 'end' value).
135         if (!parsed && i > 0 && i == jkfs.size() - 1) {
136             const skjson::ObjectValue* prev_kf = jkfs[i - 1];
137             SkASSERT(prev_kf);
138             parsed = this->parseKFValue(abuilder, jkf, (*prev_kf)["e"], v);
139         }
140 
141         return parsed;
142     };
143 
144     bool constant_value = true;
145 
146     fKFs.reserve(jkfs.size());
147 
148     for (size_t i = 0; i < jkfs.size(); ++i) {
149         const skjson::ObjectValue* jkf = jkfs[i];
150         if (!jkf) {
151             return false;
152         }
153 
154         float t;
155         if (!Parse<float>((*jkf)["t"], &t)) {
156             return false;
157         }
158 
159         Keyframe::Value v;
160         if (!parse_value(*jkf, i, &v)) {
161             return false;
162         }
163 
164         if (i > 0) {
165             auto& prev_kf = fKFs.back();
166 
167             // Ts must be monotonic.
168             if (t < prev_kf.t) {
169                 return false;
170             }
171 
172             // We can power-reduce the mapping of repeated values (implicitly constant).
173             if (v.equals(prev_kf.v, keyframe_type)) {
174                 prev_kf.mapping = Keyframe::kConstantMapping;
175             }
176         }
177 
178         fKFs.push_back({t, v, this->parseMapping(*jkf)});
179 
180         constant_value = constant_value && (v.equals(fKFs.front().v, keyframe_type));
181     }
182 
183     SkASSERT(fKFs.size() == jkfs.size());
184     fCMs.shrink_to_fit();
185 
186     if (constant_value) {
187         // When all keyframes hold the same value, we can discard all but one
188         // (interpolation has no effect).
189         fKFs.resize(1);
190     }
191 
192 #if(DUMP_KF_RECORDS)
193     SkDEBUGF("Animator[%p], values: %lu, KF records: %zu\n",
194              this, fKFs.back().v_idx + 1, fKFs.size());
195     for (const auto& kf : fKFs) {
196         SkDEBUGF("  { t: %1.3f, v_idx: %lu, mapping: %lu }\n", kf.t, kf.v_idx, kf.mapping);
197     }
198 #endif
199     return true;
200 }
201 
parseMapping(const skjson::ObjectValue & jkf)202 uint32_t AnimatorBuilder::parseMapping(const skjson::ObjectValue& jkf) {
203     if (ParseDefault(jkf["h"], false)) {
204         return Keyframe::kConstantMapping;
205     }
206 
207     SkPoint c0, c1;
208     if (!Parse(jkf["o"], &c0) ||
209         !Parse(jkf["i"], &c1) ||
210         SkCubicMap::IsLinear(c0, c1)) {
211         return Keyframe::kLinearMapping;
212     }
213 
214     // De-dupe sequential cubic mappers.
215     if (c0 != prev_c0 || c1 != prev_c1 || fCMs.empty()) {
216         fCMs.emplace_back(c0, c1);
217         prev_c0 = c0;
218         prev_c1 = c1;
219     }
220 
221     SkASSERT(!fCMs.empty());
222     return SkToU32(fCMs.size()) - 1 + Keyframe::kCubicIndexOffset;
223 }
224 
225 } // namespace skottie::internal
226