• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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