Flutter Engine
The Flutter Engine
SkBitmapProcState_matrixProcs.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2008 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
19#include "src/core/SkMemset.h"
20
21#include <cstdint>
22#include <cstring>
23
24/*
25 * The decal_ functions require that
26 * 1. dx > 0
27 * 2. [fx, fx+dx, fx+2dx, fx+3dx, ... fx+(count-1)dx] are all <= maxX
28 *
29 * In addition, we use SkFractionalInt to keep more fractional precision than
30 * just SkFixed, so we will abort the decal_ call if dx is very small, since
31 * the decal_ function just operates on SkFixed. If that were changed, we could
32 * skip the very_small test here.
33 */
35 SkFixed dx,
36 int count, unsigned max) {
37 SkASSERT(count > 0);
38
39 // if decal_ kept SkFractionalInt precision, this would just be dx <= 0
40 // I just made up the 1/256. Just don't want to perceive accumulated error
41 // if we truncate frDx and lose its low bits.
42 if (dx <= SK_Fixed1 / 256) {
43 return false;
44 }
45
46 // Note: it seems the test should be (fx <= max && lastFx <= max); but
47 // historically it's been a strict inequality check, and changing produces
48 // unexpected diffs. Further investigation is needed.
49
50 // We cast to unsigned so we don't have to check for negative values, which
51 // will now appear as very large positive values, and thus fail our test!
52 if ((unsigned)SkFixedFloorToInt(fx) >= max) {
53 return false;
54 }
55
56 // Promote to 64bit (48.16) to avoid overflow.
57 const uint64_t lastFx = fx + sk_64_mul(dx, count - 1);
58
59 return SkTFitsIn<int32_t>(lastFx) && (unsigned)SkFixedFloorToInt(SkTo<int32_t>(lastFx)) < max;
60}
61
62// When not filtering, we store 32-bit y, 16-bit x, 16-bit x, 16-bit x, ...
63// When filtering we write out 32-bit encodings, pairing 14.4 x0 with 14-bit x1.
64
65// The clamp routines may try to fall into one of these unclamped decal fast-paths.
66// (Only clamp works in the right coordinate space to check for decal.)
67static void decal_nofilter_scale(uint32_t dst[], SkFixed fx, SkFixed dx, int count) {
68 // can_truncate_to_fixed_for_decal() checked only that stepping fx+=dx count-1
69 // times doesn't overflow fx, so we take unusual care not to step count times.
70 for (; count > 2; count -= 2) {
71 *dst++ = pack_two_shorts( (fx + 0) >> 16,
72 (fx + dx) >> 16);
73 fx += dx+dx;
74 }
75
76 SkASSERT(count <= 2);
77 switch (count) {
78 case 2: ((uint16_t*)dst)[1] = SkToU16((fx + dx) >> 16); [[fallthrough]];
79 case 1: ((uint16_t*)dst)[0] = SkToU16((fx + 0) >> 16);
80 }
81}
82
83// A generic implementation for unfiltered scale+translate, templated on tiling method.
84template <unsigned (*tilex)(SkFixed, int), unsigned (*tiley)(SkFixed, int), bool tryDecal>
86 uint32_t xy[], int count, int x, int y) {
87 SkASSERT(s.fInvMatrix.isScaleTranslate());
88
89 // Write out our 32-bit y, and get our intial fx.
91 {
92 const SkBitmapProcStateAutoMapper mapper(s, x, y);
93 *xy++ = tiley(mapper.fixedY(), s.fPixmap.height() - 1);
94 fx = mapper.fractionalIntX();
95 }
96
97 const unsigned maxX = s.fPixmap.width() - 1;
98 if (0 == maxX) {
99 // If width == 1, all the x-values must refer to that pixel, and must be zero.
100 memset(xy, 0, count * sizeof(uint16_t));
101 return;
102 }
103
104 const SkFractionalInt dx = s.fInvSxFractionalInt;
105
106 if (tryDecal) {
107 const SkFixed fixedFx = SkFractionalIntToFixed(fx);
108 const SkFixed fixedDx = SkFractionalIntToFixed(dx);
109
110 if (can_truncate_to_fixed_for_decal(fixedFx, fixedDx, count, maxX)) {
111 decal_nofilter_scale(xy, fixedFx, fixedDx, count);
112 return;
113 }
114 }
115
116 // Remember, each x-coordinate is 16-bit.
117 for (; count >= 2; count -= 2) {
118 *xy++ = pack_two_shorts(tilex(SkFractionalIntToFixed(fx ), maxX),
119 tilex(SkFractionalIntToFixed(fx + dx), maxX));
120 fx += dx+dx;
121 }
122
123 auto xx = (uint16_t*)xy;
124 while (count --> 0) {
125 *xx++ = tilex(SkFractionalIntToFixed(fx), maxX);
126 fx += dx;
127 }
128}
129
130template <unsigned (*tilex)(SkFixed, int), unsigned (*tiley)(SkFixed, int)>
132 uint32_t xy[], int count, int x, int y) {
133 SkASSERT(!s.fInvMatrix.hasPerspective());
134
135 const SkBitmapProcStateAutoMapper mapper(s, x, y);
136
137 SkFractionalInt fx = mapper.fractionalIntX(),
138 fy = mapper.fractionalIntY(),
139 dx = s.fInvSxFractionalInt,
140 dy = s.fInvKyFractionalInt;
141 int maxX = s.fPixmap.width () - 1,
142 maxY = s.fPixmap.height() - 1;
143
144 while (count --> 0) {
145 *xy++ = (tiley(SkFractionalIntToFixed(fy), maxY) << 16)
146 | (tilex(SkFractionalIntToFixed(fx), maxX) );
147 fx += dx;
148 fy += dy;
149 }
150}
151
152// used when both tilex and tiley are clamp
153// Extract the high four fractional bits from fx, the lerp parameter when filtering.
154static unsigned extract_low_bits_clamp_clamp(SkFixed fx, int /*max*/) {
155 // If we're already scaled up to by max like clamp/decal,
156 // just grab the high four fractional bits.
157 return (fx >> 12) & 0xf;
158}
159
160//used when one of tilex and tiley is not clamp
161static unsigned extract_low_bits_general(SkFixed fx, int max) {
162 // In repeat or mirror fx is in [0,1], so scale up by max first.
163 // TODO: remove the +1 here and the -1 at the call sites...
164 return extract_low_bits_clamp_clamp((fx & 0xffff) * (max+1), max);
165}
166
167// Takes a SkFixed number and packs it into a 32bit integer in the following schema:
168// 14 bits to represent the low integer value (n)
169// 4 bits to represent a linear distance between low and high (floored to nearest 1/16)
170// 14 bits to represent the high integer value (n+1)
171// If f is less than 0, then both integers will be 0. If f is greater than or equal to max, both
172// integers will be that max value. In all cases, the middle 4 bits will represent the fractional
173// part (to a resolution of 1/16). If the two integers are equal, doing any linear interpolation
174// will result in the same integer, so the fractional part does not matter.
175//
176// The "one" parameter corresponds to the maximum distance between the high and low coordinate.
177// For the clamp operation, this is just SkFixed1, but for others it is 1 / pixmap width because the
178// distances are already normalized to between 0 and 1.0.
179//
180// See also SK_OPTS_NS::decode_packed_coordinates_and_weight for unpacking this value.
181template <unsigned (*tile)(SkFixed, int), unsigned (*extract_low_bits)(SkFixed, int)>
182SK_NO_SANITIZE("signed-integer-overflow")
183static uint32_t pack(SkFixed f, unsigned max, SkFixed one) {
184 uint32_t packed = tile(f, max); // low coordinate in high bits
185 packed = (packed << 4) | extract_low_bits(f, max); // (lerp weight _is_ coord fractional part)
186 packed = (packed << 14) | tile((f + one), max); // high coordinate in low bits
187 return packed;
188}
189
190template <unsigned (*tilex)(SkFixed, int), unsigned (*tiley)(SkFixed, int), unsigned (*extract_low_bits)(SkFixed, int), bool tryDecal>
192 uint32_t xy[], int count, int x, int y) {
193 SkASSERT(s.fInvMatrix.isScaleTranslate());
194
195 const unsigned maxX = s.fPixmap.width() - 1;
196 const SkFractionalInt dx = s.fInvSxFractionalInt;
198 {
199 const SkBitmapProcStateAutoMapper mapper(s, x, y);
200 const unsigned maxY = s.fPixmap.height() - 1;
201 // compute our two Y values up front
202 *xy++ = pack<tiley, extract_low_bits>(mapper.fixedY(), maxY, s.fFilterOneY);
203 // now initialize fx
204 fx = mapper.fractionalIntX();
205 }
206
207 // For historical reasons we check both ends are < maxX rather than <= maxX.
208 // TODO: try changing this? See also can_truncate_to_fixed_for_decal().
209 if (tryDecal &&
210 (unsigned)SkFractionalIntToInt(fx ) < maxX &&
211 (unsigned)SkFractionalIntToInt(fx + dx*(count-1)) < maxX) {
212 while (count --> 0) {
213 SkFixed fixedFx = SkFractionalIntToFixed(fx);
214 SkASSERT((fixedFx >> (16 + 14)) == 0);
215 *xy++ = (fixedFx >> 12 << 14) | ((fixedFx >> 16) + 1);
216 fx += dx;
217 }
218 return;
219 }
220
221 while (count --> 0) {
222 *xy++ = pack<tilex, extract_low_bits>(SkFractionalIntToFixed(fx), maxX, s.fFilterOneX);
223 fx += dx;
224 }
225}
226
227template <unsigned (*tilex)(SkFixed, int), unsigned (*tiley)(SkFixed, int), unsigned (*extract_low_bits)(SkFixed, int)>
229 uint32_t xy[], int count, int x, int y) {
230 SkASSERT(!s.fInvMatrix.hasPerspective());
231
232 const SkBitmapProcStateAutoMapper mapper(s, x, y);
233
234 SkFixed oneX = s.fFilterOneX,
235 oneY = s.fFilterOneY;
236
237 SkFractionalInt fx = mapper.fractionalIntX(),
238 fy = mapper.fractionalIntY(),
239 dx = s.fInvSxFractionalInt,
240 dy = s.fInvKyFractionalInt;
241 unsigned maxX = s.fPixmap.width () - 1,
242 maxY = s.fPixmap.height() - 1;
243 while (count --> 0) {
244 *xy++ = pack<tiley, extract_low_bits>(SkFractionalIntToFixed(fy), maxY, oneY);
245 *xy++ = pack<tilex, extract_low_bits>(SkFractionalIntToFixed(fx), maxX, oneX);
246
247 fy += dy;
248 fx += dx;
249 }
250}
251
252// Helper to ensure that when we shift down, we do it w/o sign-extension
253// so the caller doesn't have to manually mask off the top 16 bits.
254static inline unsigned SK_USHIFT16(unsigned x) {
255 return x >> 16;
256}
257
258static unsigned repeat(SkFixed fx, int max) {
259 SkASSERT(max < 65535);
260 return SK_USHIFT16((unsigned)(fx & 0xFFFF) * (max + 1));
261}
262static unsigned mirror(SkFixed fx, int max) {
263 SkASSERT(max < 65535);
264 // s is 0xFFFFFFFF if we're on an odd interval, or 0 if an even interval
265 SkFixed s = SkLeftShift(fx, 15) >> 31;
266
267 // This should be exactly the same as repeat(fx ^ s, max) from here on.
268 return SK_USHIFT16( ((fx ^ s) & 0xFFFF) * (max + 1) );
269}
270
271static unsigned clamp(SkFixed fx, int max) {
272 return SkTPin(fx >> 16, 0, max);
273}
274
276 nofilter_scale <clamp, clamp, true>, filter_scale <clamp, clamp, extract_low_bits_clamp_clamp, true>,
277 nofilter_affine<clamp, clamp>, filter_affine<clamp, clamp, extract_low_bits_clamp_clamp>,
278};
280 nofilter_scale <repeat, repeat, false>, filter_scale <repeat, repeat, extract_low_bits_general, false>,
281 nofilter_affine<repeat, repeat>, filter_affine<repeat, repeat, extract_low_bits_general>
282};
284 nofilter_scale <mirror, mirror, false>, filter_scale <mirror, mirror, extract_low_bits_general, false>,
285 nofilter_affine<mirror, mirror>, filter_affine<mirror, mirror, extract_low_bits_general>,
286};
287
288
289///////////////////////////////////////////////////////////////////////////////
290// This next chunk has some specializations for unfiltered translate-only matrices.
291
292static inline U16CPU int_clamp(int x, int n) {
293 if (x < 0) { x = 0; }
294 if (x >= n) { x = n - 1; }
295 return x;
296}
297
298/* returns 0...(n-1) given any x (positive or negative).
299
300 As an example, if n (which is always positive) is 5...
301
302 x: -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8
303 returns: 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3
304 */
305static inline int sk_int_mod(int x, int n) {
306 SkASSERT(n > 0);
307 if ((unsigned)x >= (unsigned)n) {
308 if (x < 0) {
309 x = n + ~(~x % n);
310 } else {
311 x = x % n;
312 }
313 }
314 return x;
315}
316
317static inline U16CPU int_repeat(int x, int n) {
318 return sk_int_mod(x, n);
319}
320
321static inline U16CPU int_mirror(int x, int n) {
322 x = sk_int_mod(x, 2 * n);
323 if (x >= n) {
324 x = n + ~(x - n);
325 }
326 return x;
327}
328
329static void fill_sequential(uint16_t xptr[], int pos, int count) {
330 while (count --> 0) {
331 *xptr++ = pos++;
332 }
333}
334
335static void fill_backwards(uint16_t xptr[], int pos, int count) {
336 while (count --> 0) {
337 SkASSERT(pos >= 0);
338 *xptr++ = pos--;
339 }
340}
341
342template< U16CPU (tiley)(int x, int n) >
344 uint32_t xy[], int count, int x, int y) {
345 SkASSERT(s.fInvMatrix.isTranslate());
346
347 const SkBitmapProcStateAutoMapper mapper(s, x, y);
348 *xy++ = tiley(mapper.intY(), s.fPixmap.height());
349 int xpos = mapper.intX();
350
351 const int width = s.fPixmap.width();
352 if (1 == width) {
353 // all of the following X values must be 0
354 memset(xy, 0, count * sizeof(uint16_t));
355 return;
356 }
357
358 uint16_t* xptr = reinterpret_cast<uint16_t*>(xy);
359 int n;
360
361 // fill before 0 as needed
362 if (xpos < 0) {
363 n = -xpos;
364 if (n > count) {
365 n = count;
366 }
367 memset(xptr, 0, n * sizeof(uint16_t));
368 count -= n;
369 if (0 == count) {
370 return;
371 }
372 xptr += n;
373 xpos = 0;
374 }
375
376 // fill in 0..width-1 if needed
377 if (xpos < width) {
378 n = width - xpos;
379 if (n > count) {
380 n = count;
381 }
382 fill_sequential(xptr, xpos, n);
383 count -= n;
384 if (0 == count) {
385 return;
386 }
387 xptr += n;
388 }
389
390 // fill the remaining with the max value
391 SkOpts::memset16(xptr, width - 1, count);
392}
393
394template< U16CPU (tiley)(int x, int n) >
396 uint32_t xy[], int count, int x, int y) {
397 SkASSERT(s.fInvMatrix.isTranslate());
398
399 const SkBitmapProcStateAutoMapper mapper(s, x, y);
400 *xy++ = tiley(mapper.intY(), s.fPixmap.height());
401 int xpos = mapper.intX();
402
403 const int width = s.fPixmap.width();
404 if (1 == width) {
405 // all of the following X values must be 0
406 memset(xy, 0, count * sizeof(uint16_t));
407 return;
408 }
409
410 uint16_t* xptr = reinterpret_cast<uint16_t*>(xy);
411 int start = sk_int_mod(xpos, width);
412 int n = width - start;
413 if (n > count) {
414 n = count;
415 }
416 fill_sequential(xptr, start, n);
417 xptr += n;
418 count -= n;
419
420 while (count >= width) {
421 fill_sequential(xptr, 0, width);
422 xptr += width;
423 count -= width;
424 }
425
426 if (count > 0) {
427 fill_sequential(xptr, 0, count);
428 }
429}
430
431template< U16CPU (tiley)(int x, int n) >
433 uint32_t xy[], int count, int x, int y) {
434 SkASSERT(s.fInvMatrix.isTranslate());
435
436 const SkBitmapProcStateAutoMapper mapper(s, x, y);
437 *xy++ = tiley(mapper.intY(), s.fPixmap.height());
438 int xpos = mapper.intX();
439
440 const int width = s.fPixmap.width();
441 if (1 == width) {
442 // all of the following X values must be 0
443 memset(xy, 0, count * sizeof(uint16_t));
444 return;
445 }
446
447 uint16_t* xptr = reinterpret_cast<uint16_t*>(xy);
448 // need to know our start, and our initial phase (forward or backward)
449 bool forward;
450 int n;
451 int start = sk_int_mod(xpos, 2 * width);
452 if (start >= width) {
453 start = width + ~(start - width);
454 forward = false;
455 n = start + 1; // [start .. 0]
456 } else {
457 forward = true;
458 n = width - start; // [start .. width)
459 }
460 if (n > count) {
461 n = count;
462 }
463 if (forward) {
464 fill_sequential(xptr, start, n);
465 } else {
466 fill_backwards(xptr, start, n);
467 }
468 forward = !forward;
469 xptr += n;
470 count -= n;
471
472 while (count >= width) {
473 if (forward) {
474 fill_sequential(xptr, 0, width);
475 } else {
476 fill_backwards(xptr, width - 1, width);
477 }
478 forward = !forward;
479 xptr += width;
480 count -= width;
481 }
482
483 if (count > 0) {
484 if (forward) {
485 fill_sequential(xptr, 0, count);
486 } else {
487 fill_backwards(xptr, width - 1, count);
488 }
489 }
490}
491
492
493///////////////////////////////////////////////////////////////////////////////
494// The main entry point to the file, choosing between everything above.
495
496SkBitmapProcState::MatrixProc SkBitmapProcState::chooseMatrixProc(bool translate_only_matrix) {
499
500 if( fTileModeX == fTileModeY ) {
501 // Check for our special case translate methods when there is no scale/affine/perspective.
502 if (translate_only_matrix && !fBilerp) {
503 switch (fTileModeX) {
504 default: SkASSERT(false); [[fallthrough]];
505 case SkTileMode::kClamp: return clampx_nofilter_trans<int_clamp>;
506 case SkTileMode::kRepeat: return repeatx_nofilter_trans<int_repeat>;
507 case SkTileMode::kMirror: return mirrorx_nofilter_trans<int_mirror>;
508 }
509 }
510
511 // The arrays are all [ nofilter, filter ].
512 int index = fBilerp ? 1 : 0;
514 index |= 2;
515 }
516
518 // clamp gets special version of filterOne, working in non-normalized space (allowing decal)
521 return ClampX_ClampY_Procs[index];
522 }
523
524 // all remaining procs use this form for filterOne, putting them into normalized space.
527
529 return RepeatX_RepeatY_Procs[index];
530 }
531 return MirrorX_MirrorY_Procs[index];
532 }
533
535 return nullptr;
536}
537
538uint32_t sktests::pack_clamp(SkFixed f, unsigned max) {
539 // Based on ClampX_ClampY_Procs[1] (filter_scale)
540 return ::pack<clamp, extract_low_bits_clamp_clamp>(f, max, SK_Fixed1);
541}
542
543uint32_t sktests::pack_repeat(SkFixed f, unsigned max, size_t width) {
544 // Based on RepeatX_RepeatY_Procs[1] (filter_scale)
545 return ::pack<repeat, extract_low_bits_general>(f, max, SK_Fixed1 / width);
546}
547
548uint32_t sktests::pack_mirror(SkFixed f, unsigned max, size_t width) {
549 // Based on MirrorX_MirrorY_Procs[1] (filter_scale)
550 return ::pack<mirror, extract_low_bits_general>(f, max, SK_Fixed1 / width);
551}
int count
Definition: FontMgrTest.cpp:50
SkPoint pos
#define SkASSERT(cond)
Definition: SkAssert.h:116
#define SK_NO_SANITIZE(A)
Definition: SkAttributes.h:59
#define pack_two_shorts(pri, sec)
SkFixed3232 SkFractionalInt
#define SkFractionalIntToFixed(x)
#define SkFractionalIntToInt(x)
static void mirrorx_nofilter_trans(const SkBitmapProcState &s, uint32_t xy[], int count, int x, int y)
static unsigned extract_low_bits_clamp_clamp(SkFixed fx, int)
static void fill_backwards(uint16_t xptr[], int pos, int count)
static void fill_sequential(uint16_t xptr[], int pos, int count)
static U16CPU int_mirror(int x, int n)
static bool can_truncate_to_fixed_for_decal(SkFixed fx, SkFixed dx, int count, unsigned max)
static void filter_scale(const SkBitmapProcState &s, uint32_t xy[], int count, int x, int y)
static unsigned SK_USHIFT16(unsigned x)
static void nofilter_affine(const SkBitmapProcState &s, uint32_t xy[], int count, int x, int y)
static unsigned extract_low_bits_general(SkFixed fx, int max)
static void repeatx_nofilter_trans(const SkBitmapProcState &s, uint32_t xy[], int count, int x, int y)
static U16CPU int_repeat(int x, int n)
static void clampx_nofilter_trans(const SkBitmapProcState &s, uint32_t xy[], int count, int x, int y)
static const SkBitmapProcState::MatrixProc MirrorX_MirrorY_Procs[]
static unsigned mirror(SkFixed fx, int max)
static const SkBitmapProcState::MatrixProc RepeatX_RepeatY_Procs[]
static void decal_nofilter_scale(uint32_t dst[], SkFixed fx, SkFixed dx, int count)
static void nofilter_scale(const SkBitmapProcState &s, uint32_t xy[], int count, int x, int y)
static unsigned repeat(SkFixed fx, int max)
static U16CPU int_clamp(int x, int n)
static int sk_int_mod(int x, int n)
static unsigned clamp(SkFixed fx, int max)
static const SkBitmapProcState::MatrixProc ClampX_ClampY_Procs[]
static uint32_t pack(SkFixed f, unsigned max, SkFixed one)
static void filter_affine(const SkBitmapProcState &s, uint32_t xy[], int count, int x, int y)
unsigned U16CPU
Definition: SkCPUTypes.h:23
int32_t SkFixed
Definition: SkFixed.h:25
#define SK_Fixed1
Definition: SkFixed.h:26
#define SkFixedFloorToInt(x)
Definition: SkFixed.h:78
static constexpr int32_t SkLeftShift(int32_t value, int32_t shift)
Definition: SkMath.h:37
static int64_t sk_64_mul(int64_t a, int64_t b)
Definition: SkMath.h:33
static constexpr const T & SkTPin(const T &x, const T &lo, const T &hi)
Definition: SkTPin.h:19
constexpr uint16_t SkToU16(S x)
Definition: SkTo.h:24
SkFractionalInt fractionalIntY() const
SkFractionalInt fractionalIntX() const
bool isScaleTranslate() const
Definition: SkMatrix.h:236
bool hasPerspective() const
Definition: SkMatrix.h:312
int width() const
Definition: SkPixmap.h:160
int height() const
Definition: SkPixmap.h:166
struct MyStruct s
static float max(float r, float g, float b)
Definition: hsl.cpp:49
double y
double x
void(* memset16)(uint16_t[], uint16_t, int)
skia_private::AutoTArray< sk_sp< SkImageFilter > > filters TypedMatrix matrix TypedMatrix matrix SkScalar dx
Definition: SkRecords.h:208
dst
Definition: cp.py:12
uint32_t pack_repeat(SkFixed f, unsigned max, size_t width)
uint32_t pack_mirror(SkFixed f, unsigned max, size_t width)
uint32_t pack_clamp(SkFixed f, unsigned max)
int32_t width
void(* MatrixProc)(const SkBitmapProcState &, uint32_t bitmapXY[], int count, int x, int y)