Flutter Engine
The Flutter Engine
KeyframeAnimator.cpp
Go to the documentation of this file.
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
9
12#include "src/utils/SkJSON.h"
13
14#include <cstddef>
15
16#define DUMP_KF_RECORDS 0
17
18namespace skottie::internal {
19
21
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
52KeyframeAnimator::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
77float 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
93
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
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 strictly 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
202uint32_t AnimatorBuilder::parseMapping(const skjson::ObjectValue& jkf) {
203 if (ParseDefault(jkf["h"], false)) {
205 }
206
207 SkPoint c0, c1;
208 if (!Parse(jkf["o"], &c0) ||
209 !Parse(jkf["i"], &c1) ||
210 SkCubicMap::IsLinear(c0, c1)) {
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
#define SkASSERT(cond)
Definition: SkAssert.h:116
#define SkDEBUGF(...)
Definition: SkDebug.h:24
constexpr size_t SkToSizeT(S x)
Definition: SkTo.h:31
constexpr uint32_t SkToU32(S x)
Definition: SkTo.h:26
static bool IsLinear(SkPoint p1, SkPoint p2)
Definition: SkCubicMap.h:27
size_t size() const
Definition: SkJSON.h:262
std::vector< SkCubicMap > fCMs
bool parseKeyframes(const AnimationBuilder &, const skjson::ArrayValue &)
virtual bool parseKFValue(const AnimationBuilder &, const skjson::ObjectValue &, const skjson::Value &, Keyframe::Value *)=0
bool Parse(const skjson::Value &jv, const internal::AnimationBuilder &abuilder, TextValue *v)
Definition: TextValue.cpp:32
T ParseDefault(const skjson::Value &v, const T &defaultValue)
Definition: SkottieJson.h:23
SkScalar w
bool equals(const Value &other, Type ty) const
static constexpr uint32_t kConstantMapping
static constexpr uint32_t kCubicIndexOffset
static constexpr uint32_t kLinearMapping