xref: /aosp_15_r20/external/skia/modules/skottie/src/text/RangeSelector.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 #include "modules/skottie/src/text/RangeSelector.h"
8 
9 #include "include/core/SkCubicMap.h"
10 #include "include/core/SkPoint.h"
11 #include "include/private/base/SkAssert.h"
12 #include "include/private/base/SkFloatingPoint.h"
13 #include "include/private/base/SkTPin.h"
14 #include "include/private/base/SkTo.h"
15 #include "modules/skottie/include/Skottie.h"
16 #include "modules/skottie/src/SkottieJson.h"
17 #include "modules/skottie/src/SkottiePriv.h"
18 #include "modules/skottie/src/animator/Animator.h"
19 #include "src/utils/SkJSON.h"
20 
21 #include <algorithm>
22 #include <limits>
23 #include <vector>
24 
25 namespace skottie {
26 namespace internal {
27 
28 namespace  {
29 
30 // Maps a 1-based JSON enum to one of the values in the array.
31 template <typename T, typename TArray>
ParseEnum(const TArray & arr,const skjson::Value & jenum,const AnimationBuilder * abuilder,const char * warn_name)32 T ParseEnum(const TArray& arr, const skjson::Value& jenum,
33             const AnimationBuilder* abuilder, const char* warn_name) {
34 
35     const auto idx = ParseDefault<int>(jenum, 1);
36 
37     if (idx > 0 && SkToSizeT(idx) <= std::size(arr)) {
38         return arr[idx - 1];
39     }
40 
41     // For animators without selectors, BM emits placeholder selector entries with 0 (inval) props.
42     // Supress warnings for these as they are "normal".
43     if (idx != 0) {
44         abuilder->log(Logger::Level::kWarning, nullptr,
45                       "Ignoring unknown range selector %s '%d'", warn_name, idx);
46     }
47 
48     SkASSERT(std::size(arr) > 0);
49     return arr[0];
50 }
51 
52 template <RangeSelector::Units>
53 struct UnitTraits;
54 
55 template <>
56 struct UnitTraits<RangeSelector::Units::kPercentage> {
Defaultsskottie::internal::__anon27683a020111::UnitTraits57     static constexpr auto Defaults() {
58         return std::make_tuple<float, float, float>(0, 100, 0);
59     }
60 
Resolveskottie::internal::__anon27683a020111::UnitTraits61     static auto Resolve(float s, float e, float o, size_t domain_size) {
62         return std::make_tuple(domain_size * (s + o) / 100,
63                                domain_size * (e + o) / 100);
64     }
65 };
66 
67 template <>
68 struct UnitTraits<RangeSelector::Units::kIndex> {
Defaultsskottie::internal::__anon27683a020111::UnitTraits69     static constexpr auto Defaults() {
70         // It's OK to default fEnd to FLOAT_MAX, as it gets clamped when resolved.
71         return std::make_tuple<float, float, float>(0, std::numeric_limits<float>::max(), 0);
72     }
73 
Resolveskottie::internal::__anon27683a020111::UnitTraits74     static auto Resolve(float s, float e, float o, size_t domain_size) {
75         return std::make_tuple(s + o, e + o);
76     }
77 };
78 
79 class CoverageProcessor {
80 public:
CoverageProcessor(const TextAnimator::DomainMaps & maps,RangeSelector::Domain domain,RangeSelector::Mode mode,TextAnimator::ModulatorBuffer & dst)81     CoverageProcessor(const TextAnimator::DomainMaps& maps,
82                       RangeSelector::Domain domain,
83                       RangeSelector::Mode mode,
84                       TextAnimator::ModulatorBuffer& dst)
85         : fDst(dst)
86         , fDomainSize(dst.size()) {
87 
88         SkASSERT(mode == RangeSelector::Mode::kAdd);
89         fProc = &CoverageProcessor::add_proc;
90 
91         switch (domain) {
92         case RangeSelector::Domain::kChars:
93             // Direct (1-to-1) index mapping.
94             break;
95         case RangeSelector::Domain::kCharsExcludingSpaces:
96             fMap = &maps.fNonWhitespaceMap;
97             break;
98         case RangeSelector::Domain::kWords:
99             fMap = &maps.fWordsMap;
100             break;
101         case RangeSelector::Domain::kLines:
102             fMap = &maps.fLinesMap;
103             break;
104         }
105 
106         // When no domain map is active, fProc points directly to the mode proc.
107         // Otherwise, we punt through a domain mapper proxy.
108         if (fMap) {
109             fMappedProc = fProc;
110             fProc = &CoverageProcessor::domain_map_proc;
111             fDomainSize = fMap->size();
112         }
113     }
114 
size() const115     size_t size() const { return fDomainSize; }
116 
operator ()(float amount,size_t offset,size_t count) const117     void operator()(float amount, size_t offset, size_t count) const {
118         (this->*fProc)(amount, offset, count);
119     }
120 
121 private:
122     // mode: kAdd
add_proc(float amount,size_t offset,size_t count) const123     void add_proc(float amount, size_t offset, size_t count) const {
124         if (!amount || !count) return;
125 
126         for (auto* dst = fDst.data() + offset; dst < fDst.data() + offset + count; ++dst) {
127             dst->coverage = SkTPin<float>(dst->coverage + amount, -1, 1);
128         }
129     }
130 
131     // A proxy for mapping domain indices to the target buffer.
domain_map_proc(float amount,size_t offset,size_t count) const132     void domain_map_proc(float amount, size_t offset, size_t count) const {
133         SkASSERT(fMap);
134         SkASSERT(fMappedProc);
135 
136         for (auto i = offset; i < offset + count; ++i) {
137             const auto& span = (*fMap)[i];
138             (this->*fMappedProc)(amount, span.fOffset, span.fCount);
139         }
140     }
141 
142     using ProcT = void(CoverageProcessor::*)(float amount, size_t offset, size_t count) const;
143 
144     TextAnimator::ModulatorBuffer& fDst;
145     ProcT                          fProc,
146                                    fMappedProc = nullptr;
147     const TextAnimator::DomainMap* fMap = nullptr;
148     size_t                         fDomainSize;
149 };
150 
151 
152 /*
153   Selector shapes can be generalized as a signal generator with the following
154   parameters/properties:
155 
156 
157   1  +               -------------------------
158      |              /.           .           .\
159      |             / .           .           . \
160      |            /  .           .           .  \
161      |           /   .           .           .   \
162      |          /    .           .           .    \
163      |         /     .           .           .     \
164      |        /      .           .           .      \
165      |       /       .           .           .       \
166   0  +----------------------------------------------------------
167             ^ <----->            ^            <-----> ^
168            e0   crs             sp              crs    e1
169 
170 
171     * e0, e1: left/right edges
172     * sp    : symmetry/reflection point (sp == (e0+e1)/2)
173     * crs   : cubic ramp size (transitional portion mapped using a Bezier easing function)
174 
175   Based on these,
176 
177             |  0                  , t <= e0
178             |
179             |  Bez((t-e0)/crs)    , e0 < t < e0+crs
180      F(t) = |
181             |  1                  , e0 + crs <= t <= sp
182             |
183             |  F(reflect(t,sp))   , t > sp
184 
185 
186    Tweaking this function's parameters, we can achieve all range selectors shapes:
187 
188      - square    -> e0:    0, e1:    1, crs: 0
189      - ramp up   -> e0:    0, e1: +inf, crs: 1
190      - ramp down -> e0: -inf, e1:    1, crs: 1
191      - triangle  -> e0:    0, e1:    1, crs: 0.5
192      - round     -> e0:    0, e1:    1, crs: 0.5   (nonlinear cubic mapper)
193      - smooth    -> e0:    0, e1:    1, crs: 0.5   (nonlinear cubic mapper)
194 
195 */
196 
197 struct ShapeInfo {
198    SkVector ctrl0,
199             ctrl1;
200    float    e0, e1, crs;
201 };
202 
EaseVec(float ease)203 SkVector EaseVec(float ease) {
204     return (ease < 0) ? SkVector{0, -ease} : SkVector{ease, 0};
205 }
206 
207 struct ShapeGenerator {
208     SkCubicMap shape_mapper,
209                 ease_mapper;
210     float      e0, e1, crs;
211 
ShapeGeneratorskottie::internal::__anon27683a020111::ShapeGenerator212     ShapeGenerator(const ShapeInfo& sinfo, float ease_lo, float ease_hi)
213         : shape_mapper(sinfo.ctrl0, sinfo.ctrl1)
214         , ease_mapper(EaseVec(ease_lo), SkVector{1,1} - EaseVec(ease_hi))
215         , e0(sinfo.e0)
216         , e1(sinfo.e1)
217         , crs(sinfo.crs) {}
218 
operator ()skottie::internal::__anon27683a020111::ShapeGenerator219     float operator()(float t) const {
220         // SkCubicMap clamps its input, so we can let it all hang out.
221         t = std::min(t - e0, e1 - t);
222         t = sk_ieee_float_divide(t, crs);
223 
224         return ease_mapper.computeYFromX(shape_mapper.computeYFromX(t));
225     }
226 };
227 
228 static constexpr ShapeInfo gShapeInfo[] = {
229     { {0  ,0  }, {1  ,1}, 0                       , 1               , 0.0f }, // Shape::kSquare
230     { {0  ,0  }, {1  ,1}, 0                       , SK_FloatInfinity, 1.0f }, // Shape::kRampUp
231     { {0  ,0  }, {1  ,1}, SK_FloatNegativeInfinity, 1               , 1.0f }, // Shape::kRampDown
232     { {0  ,0  }, {1  ,1}, 0                       , 1               , 0.5f }, // Shape::kTriangle
233     { {0  ,.5f}, {.5f,1}, 0                       , 1               , 0.5f }, // Shape::kRound
234     { {.5f,0  }, {.5f,1}, 0                       , 1               , 0.5f }, // Shape::kSmooth
235 };
236 
237 } // namespace
238 
Make(const skjson::ObjectValue * jrange,const AnimationBuilder * abuilder,AnimatablePropertyContainer * acontainer)239 sk_sp<RangeSelector> RangeSelector::Make(const skjson::ObjectValue* jrange,
240                                          const AnimationBuilder* abuilder,
241                                          AnimatablePropertyContainer* acontainer) {
242     if (!jrange) {
243         return nullptr;
244     }
245 
246     enum : int32_t {
247              kRange_SelectorType = 0,
248         kExpression_SelectorType = 1,
249 
250         // kWiggly_SelectorType = ? (not exported)
251     };
252 
253     {
254         const auto type = ParseDefault<int>((*jrange)["t"], kRange_SelectorType);
255         if (type != kRange_SelectorType) {
256             abuilder->log(Logger::Level::kWarning, nullptr,
257                           "Ignoring unsupported selector type '%d'", type);
258             return nullptr;
259         }
260     }
261 
262     static constexpr Units gUnitMap[] = {
263         Units::kPercentage,  // 'r': 1
264         Units::kIndex,       // 'r': 2
265     };
266 
267     static constexpr Domain gDomainMap[] = {
268         Domain::kChars,                 // 'b': 1
269         Domain::kCharsExcludingSpaces,  // 'b': 2
270         Domain::kWords,                 // 'b': 3
271         Domain::kLines,                 // 'b': 4
272     };
273 
274     static constexpr Mode gModeMap[] = {
275         Mode::kAdd,          // 'm': 1
276     };
277 
278     static constexpr Shape gShapeMap[] = {
279         Shape::kSquare,      // 'sh': 1
280         Shape::kRampUp,      // 'sh': 2
281         Shape::kRampDown,    // 'sh': 3
282         Shape::kTriangle,    // 'sh': 4
283         Shape::kRound,       // 'sh': 5
284         Shape::kSmooth,      // 'sh': 6
285     };
286 
287     auto selector = sk_sp<RangeSelector>(
288             new RangeSelector(ParseEnum<Units> (gUnitMap  , (*jrange)["r" ], abuilder, "units" ),
289                               ParseEnum<Domain>(gDomainMap, (*jrange)["b" ], abuilder, "domain"),
290                               ParseEnum<Mode>  (gModeMap  , (*jrange)["m" ], abuilder, "mode"  ),
291                               ParseEnum<Shape> (gShapeMap , (*jrange)["sh"], abuilder, "shape" )));
292 
293     acontainer->bind(*abuilder, (*jrange)["s" ], &selector->fStart );
294     acontainer->bind(*abuilder, (*jrange)["e" ], &selector->fEnd   );
295     acontainer->bind(*abuilder, (*jrange)["o" ], &selector->fOffset);
296     acontainer->bind(*abuilder, (*jrange)["a" ], &selector->fAmount);
297     acontainer->bind(*abuilder, (*jrange)["ne"], &selector->fEaseLo);
298     acontainer->bind(*abuilder, (*jrange)["xe"], &selector->fEaseHi);
299 
300     // Optional square "smoothness" prop.
301     if (selector->fShape == Shape::kSquare) {
302         acontainer->bind(*abuilder, (*jrange)["sm" ], &selector->fSmoothness);
303     }
304 
305     return selector;
306 }
307 
RangeSelector(Units u,Domain d,Mode m,Shape sh)308 RangeSelector::RangeSelector(Units u, Domain d, Mode m, Shape sh)
309     : fUnits(u)
310     , fDomain(d)
311     , fMode(m)
312     , fShape(sh) {
313 
314     // Range defaults are unit-specific.
315     switch (fUnits) {
316     case Units::kPercentage:
317         std::tie(fStart, fEnd, fOffset) = UnitTraits<Units::kPercentage>::Defaults();
318         break;
319     case Units::kIndex:
320         std::tie(fStart, fEnd, fOffset) = UnitTraits<Units::kIndex     >::Defaults();
321         break;
322     }
323 }
324 
resolve(size_t len) const325 std::tuple<float, float> RangeSelector::resolve(size_t len) const {
326     float f_i0, f_i1;
327 
328     SkASSERT(fUnits == Units::kPercentage || fUnits == Units::kIndex);
329     const auto resolver = (fUnits == Units::kPercentage)
330             ? UnitTraits<Units::kPercentage>::Resolve
331             : UnitTraits<Units::kIndex     >::Resolve;
332 
333     std::tie(f_i0, f_i1) = resolver(fStart, fEnd, fOffset, len);
334     if (f_i0 > f_i1) {
335         std::swap(f_i0, f_i1);
336     }
337 
338     return std::make_tuple(f_i0, f_i1);
339 }
340 
341 /*
342  * General RangeSelector operation:
343  *
344  *   1) The range is resolved to a target domain (characters, words, etc) interval, based on
345  *      |start|, |end|, |offset|, |units|.
346  *
347  *   2) A shape generator is mapped to this interval and applied across the whole domain, yielding
348  *      coverage values in [0..1].
349  *
350  *   3) The coverage is then scaled by the |amount| parameter.
351  *
352  *   4) Finally, the resulting coverage is accumulated to existing fragment coverage based on
353  *      the specified Mode (add, difference, etc).
354  */
modulateCoverage(const TextAnimator::DomainMaps & maps,TextAnimator::ModulatorBuffer & mbuf) const355 void RangeSelector::modulateCoverage(const TextAnimator::DomainMaps& maps,
356                                      TextAnimator::ModulatorBuffer& mbuf) const {
357     const CoverageProcessor coverage_proc(maps, fDomain, fMode, mbuf);
358     if (coverage_proc.size() == 0) {
359         return;
360     }
361 
362     // Amount, ease-low and ease-high are percentage-based [-100% .. 100%].
363     const auto amount = SkTPin<float>(fAmount / 100, -1, 1),
364               ease_lo = SkTPin<float>(fEaseLo / 100, -1, 1),
365               ease_hi = SkTPin<float>(fEaseHi / 100, -1, 1);
366 
367     // Resolve to a float range in the given domain.
368     const auto range = this->resolve(coverage_proc.size());
369     auto          r0 = std::get<0>(range),
370                  len = std::max(std::get<1>(range) - r0, std::numeric_limits<float>::epsilon());
371 
372     SkASSERT(static_cast<size_t>(fShape) < std::size(gShapeInfo));
373     ShapeGenerator gen(gShapeInfo[static_cast<size_t>(fShape)], ease_lo, ease_hi);
374 
375     if (fShape == Shape::kSquare) {
376         // Canonical square generators have collapsed ramps, but AE square selectors have
377         // an additional "smoothness" property (0..1) which introduces a non-zero transition.
378         // We achieve this by moving the range edges outward by |smoothness|/2, and adjusting
379         // the generator cubic ramp size.
380 
381         // smoothness is percentage-based [0..100]
382         const auto smoothness = SkTPin<float>(fSmoothness / 100, 0, 1);
383 
384         r0  -= smoothness / 2;
385         len += smoothness;
386 
387         gen.crs += smoothness / len;
388     }
389 
390     SkASSERT(len > 0);
391     const auto dt = 1 / len;
392           auto  t = (0.5f - r0) / len; // sampling bias: mid-unit
393 
394     for (size_t i = 0; i < coverage_proc.size(); ++i, t += dt) {
395         coverage_proc(amount * gen(t), i, 1);
396     }
397 }
398 
399 } // namespace internal
400 } // namespace skottie
401