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