1 /*
2 * Copyright 2019 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/text/TextAnimator.h"
9
10 #include "include/core/SkColor.h"
11 #include "include/core/SkPoint.h"
12 #include "include/private/SkNx.h"
13 #include "modules/skottie/src/text/RangeSelector.h"
14 #include "modules/skottie/src/text/TextAdapter.h"
15 #include "src/utils/SkJSON.h"
16
17 namespace skottie {
18 namespace internal {
19
20 /*
21 * Text layers can have optional text property animators.
22 *
23 * Each animator consists of
24 *
25 * 1) a list of animated properties (e.g. position, fill color, etc)
26 *
27 * 2) a list of range selectors
28 *
29 * Animated properties yield new values to be applied to the text, while range selectors
30 * determine the text subset these new values are applied to.
31 *
32 * The best way to think of range selectors is in terms of coverage: they combine to generate
33 * a coverage value [0..1] for each text fragment/glyph. This coverage is then used to modulate
34 * how the new property value is applied to a given fragment (interpolation weight).
35 *
36 * Note: Bodymovin currently only supports a single selector.
37 *
38 * JSON structure:
39 *
40 * "t": { // text node
41 * "a": [ // animators list
42 * { // animator node
43 * "s": {...}, // selector node
44 * "a": { // animator properties node
45 * "a": {} // optional anchor point value
46 * "p": {}, // optional position value
47 * "s": {}, // optional scale value
48 * "o": {}, // optional opacity
49 * "fc": {}, // optional fill color value
50 * "sc": {}, // optional stroke color value
51 *
52 * // TODO: more props?
53 * }
54 * },
55 * ...
56 * ],
57 * ...
58 * }
59 */
Make(const skjson::ObjectValue * janimator,const AnimationBuilder * abuilder)60 sk_sp<TextAnimator> TextAnimator::Make(const skjson::ObjectValue* janimator,
61 const AnimationBuilder* abuilder) {
62 if (!janimator) {
63 return nullptr;
64 }
65
66 const skjson::ObjectValue* jprops = (*janimator)["a"];
67 if (!jprops) {
68 return nullptr;
69 }
70
71 std::vector<sk_sp<RangeSelector>> selectors;
72
73 // Depending on compat mode and whether more than one selector is present,
74 // BM exports either an array or a single object.
75 if (const skjson::ArrayValue* jselectors = (*janimator)["s"]) {
76 selectors.reserve(jselectors->size());
77 for (const skjson::ObjectValue* jselector : *jselectors) {
78 if (auto sel = RangeSelector::Make(*jselector, abuilder)) {
79 selectors.push_back(std::move(sel));
80 }
81 }
82 } else {
83 if (auto sel = RangeSelector::Make((*janimator)["s"], abuilder)) {
84 selectors.reserve(1);
85 selectors.push_back(std::move(sel));
86 }
87 }
88
89 return sk_sp<TextAnimator>(new TextAnimator(std::move(selectors), *jprops, abuilder));
90 }
91
modulateProps(const DomainMaps & maps,ModulatorBuffer & buf) const92 void TextAnimator::modulateProps(const DomainMaps& maps, ModulatorBuffer& buf) const {
93 // No selectors -> full coverage.
94 const auto initial_coverage = fSelectors.empty() ? 1.f : 0.f;
95
96 // Coverage is scoped per animator.
97 for (auto& mod : buf) {
98 mod.coverage = initial_coverage;
99 }
100
101 // Accumulate selector coverage.
102 for (const auto& selector : fSelectors) {
103 selector->modulateCoverage(maps, buf);
104 }
105
106 // Modulate animated props.
107 for (auto& mod : buf) {
108 mod.props = this->modulateProps(mod.props, mod.coverage);
109 }
110 }
111
modulateProps(const AnimatedProps & props,float amount) const112 TextAnimator::AnimatedProps TextAnimator::modulateProps(const AnimatedProps& props,
113 float amount) const {
114 auto modulated_props = props;
115
116 // Transform props compose.
117 modulated_props.position += fTextProps.position * amount;
118 modulated_props.rotation += fTextProps.rotation * amount;
119 modulated_props.tracking += fTextProps.tracking * amount;
120 modulated_props.scale *= 1 + (fTextProps.scale - 1) * amount;
121
122 const auto lerp_color = [](SkColor c0, SkColor c1, float t) {
123 const auto c0_4f = SkNx_cast<float>(Sk4b::Load(&c0)),
124 c1_4f = SkNx_cast<float>(Sk4b::Load(&c1)),
125 c_4f = c0_4f + (c1_4f - c0_4f) * t;
126
127 SkColor c;
128 SkNx_cast<uint8_t>(Sk4f_round(c_4f)).store(&c);
129 return c;
130 };
131
132 // Colors and opacity are overridden, and use a clamped amount value.
133 const auto clamped_amount = std::max(amount, 0.0f);
134 if (fHasFillColor) {
135 modulated_props.fill_color = lerp_color(props.fill_color,
136 fTextProps.fill_color,
137 clamped_amount);
138 }
139 if (fHasStrokeColor) {
140 modulated_props.stroke_color = lerp_color(props.stroke_color,
141 fTextProps.stroke_color,
142 clamped_amount);
143 }
144 modulated_props.opacity *= 1 + (fTextProps.opacity - 1) * clamped_amount;
145
146 return modulated_props;
147 }
148
TextAnimator(std::vector<sk_sp<RangeSelector>> && selectors,const skjson::ObjectValue & jprops,const AnimationBuilder * abuilder)149 TextAnimator::TextAnimator(std::vector<sk_sp<RangeSelector>>&& selectors,
150 const skjson::ObjectValue& jprops,
151 const AnimationBuilder* abuilder)
152 : fSelectors(std::move(selectors)) {
153
154 // It's *probably* OK to capture a raw pointer to this animator, because the lambda
155 // life time is limited to |ascope|, which is also the container for the TextAnimatorList
156 // owning us. But for peace of mind (and future-proofing) let's grab a ref.
157 auto animator = sk_ref_sp(this);
158
159 abuilder->bindProperty<VectorValue>(jprops["p"],
160 [animator](const VectorValue& p) {
161 animator->fTextProps.position = ValueTraits<VectorValue>::As<SkPoint>(p);
162 });
163 abuilder->bindProperty<ScalarValue>(jprops["s"],
164 [animator](const ScalarValue& s) {
165 // Scale is 100-based.
166 animator->fTextProps.scale = s * 0.01f;
167 });
168 abuilder->bindProperty<ScalarValue>(jprops["r"],
169 [animator](const ScalarValue& r) {
170 animator->fTextProps.rotation = r;
171 });
172 fHasFillColor = abuilder->bindProperty<VectorValue>(jprops["fc"],
173 [animator](const VectorValue& fc) {
174 animator->fTextProps.fill_color = ValueTraits<VectorValue>::As<SkColor>(fc);
175 });
176 fHasStrokeColor = abuilder->bindProperty<VectorValue>(jprops["sc"],
177 [animator](const VectorValue& sc) {
178 animator->fTextProps.stroke_color = ValueTraits<VectorValue>::As<SkColor>(sc);
179 });
180 abuilder->bindProperty<ScalarValue>(jprops["o"],
181 [animator](const ScalarValue& o) {
182 // Opacity is 100-based.
183 animator->fTextProps.opacity = SkTPin<float>(o * 0.01f, 0, 1);
184 });
185 abuilder->bindProperty<ScalarValue>(jprops["t"],
186 [animator](const ScalarValue& t) {
187 animator->fTextProps.tracking = t;
188 });
189 }
190
Make(const skjson::ArrayValue & janimators,const AnimationBuilder * abuilder,sk_sp<TextAdapter> adapter)191 sk_sp<TextAnimatorList> TextAnimatorList::Make(const skjson::ArrayValue& janimators,
192 const AnimationBuilder* abuilder,
193 sk_sp<TextAdapter> adapter) {
194 AnimationBuilder::AutoScope ascope(abuilder);
195 std::vector<sk_sp<TextAnimator>> animators;
196 animators.reserve(janimators.size());
197
198 for (const skjson::ObjectValue* janimator : janimators) {
199 if (auto animator = TextAnimator::Make(janimator, abuilder)) {
200 animators.push_back(std::move(animator));
201 }
202 }
203
204 auto local_animator_scope = ascope.release();
205
206 if (animators.empty()) {
207 return nullptr;
208 }
209
210 return sk_sp<TextAnimatorList>(new TextAnimatorList(std::move(adapter),
211 std::move(local_animator_scope),
212 std::move(animators)));
213 }
214
TextAnimatorList(sk_sp<TextAdapter> adapter,sksg::AnimatorList && alist,std::vector<sk_sp<TextAnimator>> && tanimators)215 TextAnimatorList::TextAnimatorList(sk_sp<TextAdapter> adapter,
216 sksg::AnimatorList&& alist,
217 std::vector<sk_sp<TextAnimator>>&& tanimators)
218 : INHERITED(std::move(alist))
219 , fAnimators(std::move(tanimators))
220 , fAdapter(std::move(adapter)) {}
221
222 TextAnimatorList::~TextAnimatorList() = default;
223
onTick(float t)224 void TextAnimatorList::onTick(float t) {
225 // First, update all locally-scoped animated props.
226 this->INHERITED::onTick(t);
227
228 // Then push the final property values to the text adapter.
229 fAdapter->applyAnimators(fAnimators);
230 }
231
232 } // namespace internal
233 } // namespace skottie
234