• 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/SkottieValue.h"
14 #include "modules/skottie/src/animator/Animator.h"
15 #include "modules/skottie/src/text/RangeSelector.h"
16 #include "src/utils/SkJSON.h"
17 
18 namespace skottie {
19 namespace internal {
20 
21 /*
22  * Text layers can have optional text property animators.
23  *
24  * Each animator consists of
25  *
26  *   1) a list of animated properties (e.g. position, fill color, etc)
27  *
28  *   2) a list of range selectors
29  *
30  * Animated properties yield new values to be applied to the text, while range selectors
31  * determine the text subset these new values are applied to.
32  *
33  * The best way to think of range selectors is in terms of coverage: they combine to generate
34  * a coverage value [0..1] for each text fragment/glyph.  This coverage is then used to modulate
35  * how the new property value is applied to a given fragment (interpolation weight).
36  *
37  * Note: Bodymovin currently only supports a single selector.
38  *
39  * JSON structure:
40  *
41  * "t": {              // text node
42  *   "a": [            // animators list
43  *     {               // animator node
44  *       "s": {...},   // selector node
45  *       "a": {        // animator properties node
46  *         "a":  {}    // optional anchor point value
47  *         "p":  {},   // optional position value
48  *         "s":  {},   // optional scale value
49  *         "o":  {},   // optional opacity
50  *         "fc": {},   // optional fill color value
51  *         "sc": {},   // optional stroke color value
52  *
53  *         // TODO: more props?
54  *       }
55  *     },
56  *     ...
57  *   ],
58  *   ...
59  * }
60  */
Make(const skjson::ObjectValue * janimator,const AnimationBuilder * abuilder,AnimatablePropertyContainer * acontainer)61 sk_sp<TextAnimator> TextAnimator::Make(const skjson::ObjectValue* janimator,
62                                        const AnimationBuilder* abuilder,
63                                        AnimatablePropertyContainer* acontainer) {
64     if (!janimator) {
65         return nullptr;
66     }
67 
68     const skjson::ObjectValue* jprops = (*janimator)["a"];
69     if (!jprops) {
70         return nullptr;
71     }
72 
73     std::vector<sk_sp<RangeSelector>> selectors;
74 
75     // Depending on compat mode and whether more than one selector is present,
76     // BM exports either an array or a single object.
77     if (const skjson::ArrayValue* jselectors = (*janimator)["s"]) {
78         selectors.reserve(jselectors->size());
79         for (const skjson::ObjectValue* jselector : *jselectors) {
80             if (auto sel = RangeSelector::Make(*jselector, abuilder, acontainer)) {
81                 selectors.push_back(std::move(sel));
82             }
83         }
84     } else {
85         if (auto sel = RangeSelector::Make((*janimator)["s"], abuilder, acontainer)) {
86             selectors.reserve(1);
87             selectors.push_back(std::move(sel));
88         }
89     }
90 
91     return sk_sp<TextAnimator>(
92                 new TextAnimator(std::move(selectors), *jprops, abuilder, acontainer));
93 }
94 
modulateProps(const DomainMaps & maps,ModulatorBuffer & buf) const95 void TextAnimator::modulateProps(const DomainMaps& maps, ModulatorBuffer& buf) const {
96     // No selectors -> full coverage.
97     const auto initial_coverage = fSelectors.empty() ? 1.f : 0.f;
98 
99     // Coverage is scoped per animator.
100     for (auto& mod : buf) {
101         mod.coverage = initial_coverage;
102     }
103 
104     // Accumulate selector coverage.
105     for (const auto& selector : fSelectors) {
106         selector->modulateCoverage(maps, buf);
107     }
108 
109     // Modulate animated props.
110     for (auto& mod : buf) {
111         mod.props = this->modulateProps(mod.props, mod.coverage);
112     }
113 }
114 
modulateProps(const ResolvedProps & props,float amount) const115 TextAnimator::ResolvedProps TextAnimator::modulateProps(const ResolvedProps& props,
116                                                         float amount) const {
117     auto modulated_props = props;
118 
119     // Transform props compose.
120     modulated_props.position += static_cast<SkV3>(fTextProps.position) * amount;
121     modulated_props.rotation += fTextProps.rotation * amount;
122     modulated_props.tracking += fTextProps.tracking * amount;
123     modulated_props.scale    *= SkV3{1,1,1} +
124             (static_cast<SkV3>(fTextProps.scale) * 0.01f - SkV3{1,1,1}) * amount;
125 
126     // ... as does blur and line spacing
127     modulated_props.blur         += fTextProps.blur         * amount;
128     modulated_props.line_spacing += fTextProps.line_spacing * amount;
129 
130     const auto lerp_color = [](SkColor c0, SkColor c1, float t) {
131         const auto c0_4f = SkNx_cast<float>(Sk4b::Load(&c0)),
132                    c1_4f = SkNx_cast<float>(Sk4b::Load(&c1)),
133                     c_4f = c0_4f + (c1_4f - c0_4f) * t;
134 
135         SkColor c;
136         SkNx_cast<uint8_t>(Sk4f_round(c_4f)).store(&c);
137         return c;
138     };
139 
140     // Colors and opacity are overridden, and use a clamped amount value.
141     const auto clamped_amount = std::max(amount, 0.0f);
142     if (fHasFillColor) {
143         const auto fc = static_cast<SkColor>(fTextProps.fill_color);
144         modulated_props.fill_color = lerp_color(props.fill_color, fc, clamped_amount);
145     }
146     if (fHasStrokeColor) {
147         const auto sc = static_cast<SkColor>(fTextProps.stroke_color);
148         modulated_props.stroke_color = lerp_color(props.stroke_color, sc, clamped_amount);
149     }
150 
151     const auto modulate_opacity = [](float o0, float o1, float t) {
152         return o0 + o0*(o1 - 1)*t;
153     };
154 
155     const auto adjust_opacity = [&](SkColor c, float o, float t) {
156         // 255-based
157         const auto alpha = modulate_opacity(SkColorGetA(c), o, t);
158 
159         return SkColorSetA(c, SkScalarRoundToInt(alpha));
160     };
161 
162     modulated_props.fill_color = adjust_opacity(modulated_props.fill_color,
163                                                 fTextProps.fill_opacity * 0.01f,
164                                                 clamped_amount);
165     modulated_props.stroke_color = adjust_opacity(modulated_props.stroke_color,
166                                                   fTextProps.stroke_opacity * 0.01f,
167                                                   clamped_amount);
168     modulated_props.opacity = modulate_opacity(modulated_props.opacity,
169                                                fTextProps.opacity * 0.01f,
170                                                clamped_amount);
171 
172     return modulated_props;
173 }
174 
TextAnimator(std::vector<sk_sp<RangeSelector>> && selectors,const skjson::ObjectValue & jprops,const AnimationBuilder * abuilder,AnimatablePropertyContainer * acontainer)175 TextAnimator::TextAnimator(std::vector<sk_sp<RangeSelector>>&& selectors,
176                            const skjson::ObjectValue& jprops,
177                            const AnimationBuilder* abuilder,
178                            AnimatablePropertyContainer* acontainer)
179     : fSelectors(std::move(selectors))
180     , fRequiresAnchorPoint(false) {
181 
182     acontainer->bind(*abuilder, jprops["p" ], fTextProps.position);
183     acontainer->bind(*abuilder, jprops["o" ], fTextProps.opacity);
184     acontainer->bind(*abuilder, jprops["fo"], fTextProps.fill_opacity);
185     acontainer->bind(*abuilder, jprops["so"], fTextProps.stroke_opacity);
186     acontainer->bind(*abuilder, jprops["t" ], fTextProps.tracking);
187     acontainer->bind(*abuilder, jprops["ls"], fTextProps.line_spacing);
188 
189     // Scale and rotation are anchor-point-dependent.
190     fRequiresAnchorPoint |= acontainer->bind(*abuilder, jprops["s"], fTextProps.scale);
191 
192     // Depending on whether we're in 2D/3D mode, some of these will stick and some will not.
193     // It's fine either way.
194     fRequiresAnchorPoint |= acontainer->bind(*abuilder, jprops["rx"], fTextProps.rotation.x);
195     fRequiresAnchorPoint |= acontainer->bind(*abuilder, jprops["ry"], fTextProps.rotation.y);
196     fRequiresAnchorPoint |= acontainer->bind(*abuilder, jprops["r" ], fTextProps.rotation.z);
197 
198     fHasFillColor   = acontainer->bind(*abuilder, jprops["fc"], fTextProps.fill_color  );
199     fHasStrokeColor = acontainer->bind(*abuilder, jprops["sc"], fTextProps.stroke_color);
200     fHasBlur        = acontainer->bind(*abuilder, jprops["bl"], fTextProps.blur        );
201 }
202 
203 } // namespace internal
204 } // namespace skottie
205