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 | |
18 | 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 | |
48 | 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 | |
73 | 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 | SkASSERT(seg.kf0->v != seg.kf1->v); |
82 | const auto mapper_index = SkToSizeT(seg.kf0->mapping - Keyframe::kCubicIndexOffset); |
83 | w = fCMs[mapper_index].computeYFromX(w); |
84 | } |
85 | |
86 | return w; |
87 | } |
88 | |
89 | KeyframeAnimatorBuilder::~KeyframeAnimatorBuilder() = default; |
90 | |
91 | bool KeyframeAnimatorBuilder::parseKeyframes(const AnimationBuilder& abuilder, |
92 | const skjson::ArrayValue& jkfs) { |
93 | // Keyframe format: |
94 | // |
95 | // [ // array of |
96 | // { |
97 | // "t": <float> // keyframe time |
98 | // "s": <T> // keyframe value |
99 | // "h": <bool> // optional constant/hold keyframe marker |
100 | // "i": [<float,float>] // optional "in" Bezier control point |
101 | // "o": [<float,float>] // optional "out" Bezier control point |
102 | // }, |
103 | // ... |
104 | // ] |
105 | // |
106 | // Legacy keyframe format: |
107 | // |
108 | // [ // array of |
109 | // { |
110 | // "t": <float> // keyframe time |
111 | // "s": <T> // keyframe start value |
112 | // "e": <T> // keyframe end value |
113 | // "h": <bool> // optional constant/hold keyframe marker (constant mapping) |
114 | // "i": [<float,float>] // optional "in" Bezier control point (cubic mapping) |
115 | // "o": [<float,float>] // optional "out" Bezier control point (cubic mapping) |
116 | // }, |
117 | // ... |
118 | // { |
119 | // "t": <float> // last keyframe only specifies a t |
120 | // // the value is prev. keyframe end value |
121 | // } |
122 | // ] |
123 | // |
124 | // Note: the legacy format contains duplicates, as normal frames are contiguous: |
125 | // frame(n).e == frame(n+1).s |
126 | |
127 | const auto parse_value = [&](const skjson::ObjectValue& jkf, size_t i, Keyframe::Value* v) { |
128 | auto parsed = this->parseKFValue(abuilder, jkf, jkf["s" ], v); |
129 | |
130 | // A missing value is only OK for the last legacy KF |
131 | // (where it is pulled from prev KF 'end' value). |
132 | if (!parsed && i > 0 && i == jkfs.size() - 1) { |
133 | const skjson::ObjectValue* prev_kf = jkfs[i - 1]; |
134 | SkASSERT(prev_kf); |
135 | parsed = this->parseKFValue(abuilder, jkf, (*prev_kf)["e" ], v); |
136 | } |
137 | |
138 | return parsed; |
139 | }; |
140 | |
141 | bool constant_value = true; |
142 | |
143 | fKFs.reserve(jkfs.size()); |
144 | |
145 | for (size_t i = 0; i < jkfs.size(); ++i) { |
146 | const skjson::ObjectValue* jkf = jkfs[i]; |
147 | if (!jkf) { |
148 | return false; |
149 | } |
150 | |
151 | float t; |
152 | if (!Parse<float>((*jkf)["t" ], &t)) { |
153 | return false; |
154 | } |
155 | |
156 | Keyframe::Value v; |
157 | if (!parse_value(*jkf, i, &v)) { |
158 | return false; |
159 | } |
160 | |
161 | if (i > 0) { |
162 | auto& prev_kf = fKFs.back(); |
163 | |
164 | // Ts must be strictly monotonic. |
165 | if (t <= prev_kf.t) { |
166 | return false; |
167 | } |
168 | |
169 | // We can power-reduce the mapping of repeated values (implicitly constant). |
170 | if (v == prev_kf.v) { |
171 | prev_kf.mapping = Keyframe::kConstantMapping; |
172 | } |
173 | } |
174 | |
175 | fKFs.push_back({t, v, this->parseMapping(*jkf)}); |
176 | |
177 | constant_value = constant_value && (v == fKFs.front().v); |
178 | } |
179 | |
180 | SkASSERT(fKFs.size() == jkfs.size()); |
181 | fCMs.shrink_to_fit(); |
182 | |
183 | if (constant_value) { |
184 | // When all keyframes hold the same value, we can discard all but one |
185 | // (interpolation has no effect). |
186 | fKFs.resize(1); |
187 | } |
188 | |
189 | #if(DUMP_KF_RECORDS) |
190 | SkDEBUGF("Animator[%p], values: %lu, KF records: %zu\n" , |
191 | this, fKFs.back().v_idx + 1, fKFs.size()); |
192 | for (const auto& kf : fKFs) { |
193 | SkDEBUGF(" { t: %1.3f, v_idx: %lu, mapping: %lu }\n" , kf.t, kf.v_idx, kf.mapping); |
194 | } |
195 | #endif |
196 | return true; |
197 | } |
198 | |
199 | uint32_t KeyframeAnimatorBuilder::parseMapping(const skjson::ObjectValue& jkf) { |
200 | if (ParseDefault(jkf["h" ], false)) { |
201 | return Keyframe::kConstantMapping; |
202 | } |
203 | |
204 | SkPoint c0, c1; |
205 | if (!Parse(jkf["o" ], &c0) || |
206 | !Parse(jkf["i" ], &c1) || |
207 | SkCubicMap::IsLinear(c0, c1)) { |
208 | return Keyframe::kLinearMapping; |
209 | } |
210 | |
211 | // De-dupe sequential cubic mappers. |
212 | if (c0 != prev_c0 || c1 != prev_c1 || fCMs.empty()) { |
213 | fCMs.emplace_back(c0, c1); |
214 | prev_c0 = c0; |
215 | prev_c1 = c1; |
216 | } |
217 | |
218 | SkASSERT(!fCMs.empty()); |
219 | return SkToU32(fCMs.size()) - 1 + Keyframe::kCubicIndexOffset; |
220 | } |
221 | |
222 | } // namespace skottie::internal |
223 | |