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