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