• 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 "modules/skottie/src/SkottieJson.h"
11 #include "modules/skottie/src/SkottieValue.h"
12 
13 #include <algorithm>
14 #include <cmath>
15 
16 namespace skottie {
17 namespace internal {
18 
19 namespace  {
20 
21 // Maps a 1-based JSON enum to one of the values in the array.
22 template <typename T, typename TArray>
ParseEnum(const TArray & arr,const skjson::Value & jenum,const AnimationBuilder * abuilder,const char * warn_name)23 T ParseEnum(const TArray& arr, const skjson::Value& jenum,
24             const AnimationBuilder* abuilder, const char* warn_name) {
25 
26     const auto idx = ParseDefault<int>(jenum, 1);
27 
28     if (idx > 0 && SkToSizeT(idx) <= SK_ARRAY_COUNT(arr)) {
29         return arr[idx - 1];
30     }
31 
32     // For animators without selectors, BM emits dummy selector entries with 0 (inval) props.
33     // Supress warnings for these as they are "normal".
34     if (idx != 0) {
35         abuilder->log(Logger::Level::kWarning, nullptr,
36                       "Ignoring unknown range selector %s '%d'", warn_name, idx);
37     }
38 
39     static_assert(SK_ARRAY_COUNT(arr) > 0, "");
40     return arr[0];
41 }
42 
43 template <RangeSelector::Units>
44 struct UnitTraits;
45 
46 template <>
47 struct UnitTraits<RangeSelector::Units::kPercentage> {
Defaultsskottie::internal::__anon4a3cc3110111::UnitTraits48     static constexpr auto Defaults() {
49         return std::make_tuple<float, float, float>(0, 100, 0);
50     }
51 
Resolveskottie::internal::__anon4a3cc3110111::UnitTraits52     static auto Resolve(float s, float e, float o, size_t domain_size) {
53         return std::make_tuple(domain_size * (s + o) / 100,
54                                domain_size * (e + o) / 100);
55     }
56 };
57 
58 template <>
59 struct UnitTraits<RangeSelector::Units::kIndex> {
Defaultsskottie::internal::__anon4a3cc3110111::UnitTraits60     static constexpr auto Defaults() {
61         // It's OK to default fEnd to FLOAT_MAX, as it gets clamped when resolved.
62         return std::make_tuple<float, float, float>(0, std::numeric_limits<float>::max(), 0);
63     }
64 
Resolveskottie::internal::__anon4a3cc3110111::UnitTraits65     static auto Resolve(float s, float e, float o, size_t domain_size) {
66         return std::make_tuple(s + o, e + o);
67     }
68 };
69 
70 class CoverageProcessor {
71 public:
CoverageProcessor(const TextAnimator::DomainMaps & maps,RangeSelector::Domain domain,RangeSelector::Mode mode,TextAnimator::ModulatorBuffer & dst)72     CoverageProcessor(const TextAnimator::DomainMaps& maps,
73                       RangeSelector::Domain domain,
74                       RangeSelector::Mode mode,
75                       TextAnimator::ModulatorBuffer& dst)
76         : fDst(dst)
77         , fDomainSize(dst.size()) {
78 
79         SkASSERT(mode == RangeSelector::Mode::kAdd);
80         fProc = &CoverageProcessor::add_proc;
81 
82         switch (domain) {
83         case RangeSelector::Domain::kChars:
84             // Direct (1-to-1) index mapping.
85             break;
86         case RangeSelector::Domain::kCharsExcludingSpaces:
87             fMap = &maps.fNonWhitespaceMap;
88             break;
89         case RangeSelector::Domain::kWords:
90             fMap = &maps.fWordsMap;
91             break;
92         case RangeSelector::Domain::kLines:
93             fMap = &maps.fLinesMap;
94             break;
95         }
96 
97         // When no domain map is active, fProc points directly to the mode proc.
98         // Otherwise, we punt through a domain mapper proxy.
99         if (fMap) {
100             fMappedProc = fProc;
101             fProc = &CoverageProcessor::domain_map_proc;
102             fDomainSize = fMap->size();
103         }
104     }
105 
size() const106     size_t size() const { return fDomainSize; }
107 
operator ()(float amount,size_t offset,size_t count) const108     void operator()(float amount, size_t offset, size_t count) const {
109         (this->*fProc)(amount, offset, count);
110     }
111 
112 private:
113     // mode: kAdd
add_proc(float amount,size_t offset,size_t count) const114     void add_proc(float amount, size_t offset, size_t count) const {
115         if (!amount || !count) return;
116 
117         for (auto* dst = fDst.data() + offset; dst < fDst.data() + offset + count; ++dst) {
118             dst->coverage = SkTPin<float>(dst->coverage + amount, -1, 1);
119         }
120     }
121 
122     // A proxy for mapping domain indices to the target buffer.
domain_map_proc(float amount,size_t offset,size_t count) const123     void domain_map_proc(float amount, size_t offset, size_t count) const {
124         SkASSERT(fMap);
125         SkASSERT(fMappedProc);
126 
127         for (auto i = offset; i < offset + count; ++i) {
128             const auto& span = (*fMap)[i];
129             (this->*fMappedProc)(amount, span.fOffset, span.fCount);
130         }
131     }
132 
133     using ProcT = void(CoverageProcessor::*)(float amount, size_t offset, size_t count) const;
134 
135     TextAnimator::ModulatorBuffer& fDst;
136     ProcT                          fProc,
137                                    fMappedProc = nullptr;
138     const TextAnimator::DomainMap* fMap = nullptr;
139     size_t                         fDomainSize;
140 };
141 
142 // Each shape generator is defined in a normalized domain, over three |t| intervals:
143 //
144 //   (-inf..0) -> lo (constant value)
145 //   [0..1]    -> func(t)
146 //   (1..+inf) -> hi (constant value)
147 //
148 struct ShapeGenerator {
149     float lo,             // constant value for t < 0
150           hi;             // constant value for t > 1
151     float (*func)(float); // shape generator for t in [0..1]
152 
operator ()skottie::internal::__anon4a3cc3110111::ShapeGenerator153     float operator()(float t) const { return this->func(t); }
154 };
155 
156 static const ShapeGenerator gShapeGenerators[] = {
157     // Shape::kSquare
__anon4a3cc3110202()158     { 0, 0, [](float  )->float { return 1.0f; }},
159     // Shape::kRampUp
__anon4a3cc3110302()160     { 0, 1, [](float t)->float { return t; }},
161     // Shape::kRampDown
__anon4a3cc3110402()162     { 1, 0, [](float t)->float { return 1 - t; }},
163     // Shape::kTriangle
__anon4a3cc3110502()164     { 0, 0, [](float t)->float { return 1 - std::abs(0.5f - t) / 0.5f; }},
165     // Shape::kRound
__anon4a3cc3110602()166     { 0, 0, [](float t)->float {
167                                  static constexpr auto cx  = 0.5f,
168                                                        cx2 = cx * cx;
169                                  return std::sqrt(cx2 - (t - cx) * (t - cx));
170     }},
171     // Shape::kSmooth
__anon4a3cc3110702()172     { 0, 0, [](float t)->float { return (std::cos(SK_FloatPI * (1 + 2 * t)) + 1) * 0.5f; }},
173 };
174 
Lerp(float a,float b,float t)175 float Lerp(float a, float b, float t) { return a + (b - a) * t; }
176 
177 } // namespace
178 
Make(const skjson::ObjectValue * jrange,const AnimationBuilder * abuilder)179 sk_sp<RangeSelector> RangeSelector::Make(const skjson::ObjectValue* jrange,
180                                          const AnimationBuilder* abuilder) {
181     if (!jrange) {
182         return nullptr;
183     }
184 
185     enum : int32_t {
186              kRange_SelectorType = 0,
187         kExpression_SelectorType = 1,
188 
189         // kWiggly_SelectorType = ? (not exported)
190     };
191 
192     {
193         const auto type = ParseDefault<int>((*jrange)["t"], kRange_SelectorType);
194         if (type != kRange_SelectorType) {
195             abuilder->log(Logger::Level::kWarning, nullptr,
196                           "Ignoring unsupported selector type '%d'", type);
197             return nullptr;
198         }
199     }
200 
201     static constexpr Units gUnitMap[] = {
202         Units::kPercentage,  // 'r': 1
203         Units::kIndex,       // 'r': 2
204     };
205 
206     static constexpr Domain gDomainMap[] = {
207         Domain::kChars,                 // 'b': 1
208         Domain::kCharsExcludingSpaces,  // 'b': 2
209         Domain::kWords,                 // 'b': 3
210         Domain::kLines,                 // 'b': 4
211     };
212 
213     static constexpr Mode gModeMap[] = {
214         Mode::kAdd,          // 'm': 1
215     };
216 
217     static constexpr Shape gShapeMap[] = {
218         Shape::kSquare,      // 'sh': 1
219         Shape::kRampUp,      // 'sh': 2
220         Shape::kRampDown,    // 'sh': 3
221         Shape::kTriangle,    // 'sh': 4
222         Shape::kRound,       // 'sh': 5
223         Shape::kSmooth,      // 'sh': 6
224     };
225 
226     auto selector = sk_sp<RangeSelector>(
227             new RangeSelector(ParseEnum<Units> (gUnitMap  , (*jrange)["r" ], abuilder, "units" ),
228                               ParseEnum<Domain>(gDomainMap, (*jrange)["b" ], abuilder, "domain"),
229                               ParseEnum<Mode>  (gModeMap  , (*jrange)["m" ], abuilder, "mode"  ),
230                               ParseEnum<Shape> (gShapeMap , (*jrange)["sh"], abuilder, "shape" )));
231 
232     abuilder->bindProperty<ScalarValue>((*jrange)["s"],
233         [selector](const ScalarValue& s) {
234             selector->fStart = s;
235         });
236     abuilder->bindProperty<ScalarValue>((*jrange)["e"],
237         [selector](const ScalarValue& e) {
238             selector->fEnd = e;
239         });
240     abuilder->bindProperty<ScalarValue>((*jrange)["o"],
241         [selector](const ScalarValue& o) {
242             selector->fOffset = o;
243         });
244     abuilder->bindProperty<ScalarValue>((*jrange)["a"],
245         [selector](const ScalarValue& a) {
246             selector->fAmount = a;
247         });
248 
249     return selector;
250 }
251 
RangeSelector(Units u,Domain d,Mode m,Shape sh)252 RangeSelector::RangeSelector(Units u, Domain d, Mode m, Shape sh)
253     : fUnits(u)
254     , fDomain(d)
255     , fMode(m)
256     , fShape(sh) {
257 
258     // Range defaults are unit-specific.
259     switch (fUnits) {
260     case Units::kPercentage:
261         std::tie(fStart, fEnd, fOffset) = UnitTraits<Units::kPercentage>::Defaults();
262         break;
263     case Units::kIndex:
264         std::tie(fStart, fEnd, fOffset) = UnitTraits<Units::kIndex     >::Defaults();
265         break;
266     }
267 }
268 
resolve(size_t len) const269 std::tuple<float, float> RangeSelector::resolve(size_t len) const {
270     float f_i0, f_i1;
271 
272     SkASSERT(fUnits == Units::kPercentage || fUnits == Units::kIndex);
273     const auto resolver = (fUnits == Units::kPercentage)
274             ? UnitTraits<Units::kPercentage>::Resolve
275             : UnitTraits<Units::kIndex     >::Resolve;
276 
277     std::tie(f_i0, f_i1) = resolver(fStart, fEnd, fOffset, len);
278     if (f_i0 > f_i1) {
279         std::swap(f_i0, f_i1);
280     }
281 
282     return std::make_tuple(f_i0, f_i1);
283 }
284 
285 /*
286  * General RangeSelector operation:
287  *
288  *   1) The range is resolved to a target domain (characters, words, etc) interval, based on
289  *      |start|, |end|, |offset|, |units|.
290  *
291  *   2) A shape generator is mapped to this interval and applied across the whole domain, yielding
292  *      coverage values in [0..1].
293  *
294  *   2') When the interval extremes don't coincide with fragment boundaries, the corresponding
295  *      fragment coverage is further modulated for partial interval overlap.
296  *
297  *   3) The coverage is then scaled by the |amount| parameter.
298  *
299  *   4) Finally, the resulting coverage is accumulated to existing fragment coverage based on
300  *      the specified Mode (add, difference, etc).
301  */
modulateCoverage(const TextAnimator::DomainMaps & maps,TextAnimator::ModulatorBuffer & mbuf) const302 void RangeSelector::modulateCoverage(const TextAnimator::DomainMaps& maps,
303                                      TextAnimator::ModulatorBuffer& mbuf) const {
304     const CoverageProcessor coverage_proc(maps, fDomain, fMode, mbuf);
305     if (coverage_proc.size() == 0) {
306         return;
307     }
308 
309     // Amount is percentage based [-100% .. 100%].
310     const auto amount = SkTPin<float>(fAmount / 100, -1, 1);
311 
312     // First, resolve to a float range in the given domain.
313     const auto f_range = this->resolve(coverage_proc.size());
314 
315     // f_range pinned to [0..domain_size].
316     const auto f_dom_size = static_cast<float>(coverage_proc.size()),
317                        f0 = SkTPin(std::get<0>(f_range), 0.0f, f_dom_size),
318                        f1 = SkTPin(std::get<1>(f_range), 0.0f, f_dom_size);
319 
320     SkASSERT(static_cast<size_t>(fShape) < SK_ARRAY_COUNT(gShapeGenerators));
321     const auto& generator = gShapeGenerators[static_cast<size_t>(fShape)];
322 
323     // Blit constant coverage outside the shape.
324     {
325         // Constant coverage count before the shape left edge, and after the right edge.
326         const auto count_lo = static_cast<size_t>(std::floor(f0)),
327                    count_hi = static_cast<size_t>(f_dom_size - std::ceil (f1));
328         SkASSERT(count_lo <= coverage_proc.size());
329         SkASSERT(count_hi <= coverage_proc.size());
330 
331         coverage_proc(amount * generator.lo, 0                              , count_lo);
332         coverage_proc(amount * generator.hi, coverage_proc.size() - count_hi, count_hi);
333 
334         if (count_lo == coverage_proc.size() || count_hi == coverage_proc.size()) {
335             // The shape is completely outside the domain - we're done.
336             return;
337         }
338     }
339 
340     // Integral/index range.
341     const auto i0 = std::min<size_t>(f0, coverage_proc.size() - 1),
342                i1 = std::min<size_t>(f1, coverage_proc.size() - 1);
343     SkASSERT(i0 <= i1);
344 
345     const auto range_span = std::get<1>(f_range) - std::get<0>(f_range);
346     if (SkScalarNearlyZero(range_span)) {
347         // Empty range - the shape is collapsed. Modulate with lo/hi weighted average.
348         SkASSERT(i0 == i1);
349         const auto ratio = f0 - i0,
350                 coverage = Lerp(generator.lo, generator.hi, ratio);
351         coverage_proc(amount * coverage, i0, 1);
352 
353         return;
354     }
355 
356     // At this point the clamped range maps to the index interval [i0..i1],
357     // with the left/right edges falling within i0/i1, respectively:
358     //
359     //    -----------  ------------------  ------------------  -----------
360     //   |  0 |    | .. |    | i0 |    | .. |    | i1 |    | .. |    |  N |
361     //    -----------  ------------------  ------------------  -----------
362     //                          ^                   ^
363     //                          [___________________]
364     //
365     //                         f0                   f1
366     //
367     // Note: i0 and i1 can have partial coverage, and also i0 may be the same as i1.
368 
369     // Computes partial coverage when one or both range edges fall within the same index [i].
370     const auto partial_coverage = [&](float shape_val, float i) {
371         // At least one of the range edges falls within the current fragment.
372         SkASSERT(SkScalarNearlyEqual(i, std::round(i)));
373         SkASSERT((i <= f0 && f0 <= i + 1) || (i <= f1 && f1 <= i + 1));
374 
375         // The resulting coverage is a three-way weighted average
376         // of the three range segments (lo, shape_val, hi).
377         const auto lo_weight = std::max(f0 - i, 0.0f),
378                    mi_weight = std::min(f1 - i, 1.0f) - lo_weight,
379                    hi_weight = std::max(i + 1 - f1, 0.0f);
380 
381         SkASSERT(0 <= lo_weight && lo_weight <= 1);
382         SkASSERT(0 <= mi_weight && mi_weight <= 1);
383         SkASSERT(0 <= hi_weight && hi_weight <= 1);
384         SkASSERT(SkScalarNearlyEqual(lo_weight + mi_weight + hi_weight, 1));
385 
386         return lo_weight * generator.lo +
387                mi_weight * shape_val +
388                hi_weight * generator.hi;
389     };
390 
391     // The shape domain [0..1] is mapped to the range.
392     const auto dt = 1 / range_span;
393           // note: we sample mid-fragment
394           auto  t = (i0 + 0.5f - std::get<0>(f_range)) / range_span;
395 
396     // [i0] may have partial coverage.
397     coverage_proc(amount * partial_coverage(generator(std::max(t, 0.0f)), i0), i0, 1);
398 
399     // If the whole range falls within a single fragment, we're done.
400     if (i0 == i1) {
401         return;
402     }
403 
404     t += dt;
405 
406     // [i0+1..i1-1] has full coverage.
407     for (auto i = i0 + 1; i < i1; ++i) {
408         SkASSERT(0 <= t && t <= 1);
409         coverage_proc(amount * generator(t), i, 1);
410         t += dt;
411     }
412 
413     // [i1] may have partial coverage.
414     coverage_proc(amount * partial_coverage(generator(std::min(t, 1.0f)), i1), i1, 1);
415 }
416 
417 } // namespace internal
418 } // namespace skottie
419