Flutter Engine
The Flutter Engine
RangeSelector.cpp
Go to the documentation of this file.
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 */
8
19#include "src/utils/SkJSON.h"
20
21#include <algorithm>
22#include <limits>
23#include <vector>
24
25namespace skottie {
26namespace internal {
27
28namespace {
29
30// Maps a 1-based JSON enum to one of the values in the array.
31template <typename T, typename TArray>
32T 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
52template <RangeSelector::Units>
53struct UnitTraits;
54
55template <>
56struct UnitTraits<RangeSelector::Units::kPercentage> {
57 static constexpr auto Defaults() {
58 return std::make_tuple<float, float, float>(0, 100, 0);
59 }
60
61 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
67template <>
68struct UnitTraits<RangeSelector::Units::kIndex> {
69 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
74 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
79class CoverageProcessor {
80public:
81 CoverageProcessor(const TextAnimator::DomainMaps& maps,
85 : fDst(dst)
86 , fDomainSize(dst.size()) {
87
89 fProc = &CoverageProcessor::add_proc;
90
91 switch (domain) {
93 // Direct (1-to-1) index mapping.
94 break;
96 fMap = &maps.fNonWhitespaceMap;
97 break;
99 fMap = &maps.fWordsMap;
100 break;
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
115 size_t size() const { return fDomainSize; }
116
117 void operator()(float amount, size_t offset, size_t count) const {
118 (this->*fProc)(amount, offset, count);
119 }
120
121private:
122 // mode: kAdd
123 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.
132 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
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
197struct ShapeInfo {
200 float e0, e1, crs;
201};
202
203SkVector EaseVec(float ease) {
204 return (ease < 0) ? SkVector{0, -ease} : SkVector{ease, 0};
205}
206
207struct ShapeGenerator {
210 float e0, e1, crs;
211
212 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
219 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);
223
225 }
226};
227
228static 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
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
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
308RangeSelector::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) {
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
325std::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)
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 */
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
int count
Definition: FontMgrTest.cpp:50
SkRect fDst
Definition: LatticeOp.cpp:381
IsFiniteProc fProc
Definition: MathBench.cpp:219
float crs
SkCubicMap ease_mapper
float e1
SkCubicMap shape_mapper
float e0
SkVector ctrl1
SkVector ctrl0
#define SkASSERT(cond)
Definition: SkAssert.h:116
constexpr float SK_FloatInfinity
static constexpr float sk_ieee_float_divide(float numer, float denom)
constexpr float SK_FloatNegativeInfinity
void swap(sk_sp< T > &a, sk_sp< T > &b)
Definition: SkRefCnt.h:341
constexpr size_t SkToSizeT(S x)
Definition: SkTo.h:31
GrStyledShape fShape
Shape
Definition: aaxfermodes.cpp:43
GLenum type
float computeYFromX(float x) const
Definition: SkCubicMap.cpp:61
bool bind(const AnimationBuilder &, const skjson::ObjectValue *, T *)
void log(Logger::Level, const skjson::Value *, const char fmt[],...) const SK_PRINTF_LIKE(4
Definition: Skottie.cpp:71
static sk_sp< RangeSelector > Make(const skjson::ObjectValue *, const AnimationBuilder *, AnimatablePropertyContainer *)
void modulateCoverage(const TextAnimator::DomainMaps &, TextAnimator::ModulatorBuffer &) const
std::vector< DomainSpan > DomainMap
Definition: TextAnimator.h:82
std::vector< AnimatedPropsModulator > ModulatorBuffer
Definition: TextAnimator.h:69
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition: main.cc:19
struct MyStruct s
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static float min(float r, float g, float b)
Definition: hsl.cpp:48
static FunctionPtr Resolve(Thread *thread, Zone *zone, const GrowableArray< const Instance * > &caller_arguments, const Class &receiver_class, const String &name, const Array &descriptor)
def gen()
Definition: dom.py:77
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive mode
Definition: switches.h:228
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
Definition: gen.py:1
dst
Definition: cp.py:12
const myers::Point & get< 1 >(const myers::Segment &s)
Definition: Myers.h:81
const myers::Point & get< 0 >(const myers::Segment &s)
Definition: Myers.h:80
sh
Definition: run_sh.py:10
#define T
Definition: precompiler.cc:65
SeparatedVector2 offset
SkBlendMode fMode
Definition: xfermodes.cpp:52