Line data Source code
1 : /*
2 : * Copyright 2016 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 : #ifndef SkLinearBitmapPipeline_sampler_DEFINED
9 : #define SkLinearBitmapPipeline_sampler_DEFINED
10 :
11 : #include <tuple>
12 :
13 : #include "SkAutoMalloc.h"
14 : #include "SkColor.h"
15 : #include "SkColorPriv.h"
16 : #include "SkFixed.h" // for SkFixed1 only. Don't use SkFixed in this file.
17 : #include "SkHalf.h"
18 : #include "SkLinearBitmapPipeline_core.h"
19 : #include "SkNx.h"
20 : #include "SkPM4fPriv.h"
21 :
22 : namespace {
23 : // Explaination of the math:
24 : // 1 - x x
25 : // +--------+--------+
26 : // | | |
27 : // 1 - y | px00 | px10 |
28 : // | | |
29 : // +--------+--------+
30 : // | | |
31 : // y | px01 | px11 |
32 : // | | |
33 : // +--------+--------+
34 : //
35 : //
36 : // Given a pixelxy each is multiplied by a different factor derived from the fractional part of x
37 : // and y:
38 : // * px00 -> (1 - x)(1 - y) = 1 - x - y + xy
39 : // * px10 -> x(1 - y) = x - xy
40 : // * px01 -> (1 - x)y = y - xy
41 : // * px11 -> xy
42 : // So x * y is calculated first and then used to calculate all the other factors.
43 0 : static Sk4s SK_VECTORCALL bilerp4(Sk4s xs, Sk4s ys, Sk4f px00, Sk4f px10,
44 : Sk4f px01, Sk4f px11) {
45 : // Calculate fractional xs and ys.
46 0 : Sk4s fxs = xs - xs.floor();
47 0 : Sk4s fys = ys - ys.floor();
48 0 : Sk4s fxys{fxs * fys};
49 0 : Sk4f sum = px11 * fxys;
50 0 : sum = sum + px01 * (fys - fxys);
51 0 : sum = sum + px10 * (fxs - fxys);
52 0 : sum = sum + px00 * (Sk4f{1.0f} - fxs - fys + fxys);
53 0 : return sum;
54 : }
55 :
56 : ////////////////////////////////////////////////////////////////////////////////////////////////////
57 : // PixelGetter is the lowest level interface to the source data. There is a PixelConverter for each
58 : // of the different SkColorTypes.
59 : template <SkColorType, SkGammaType> class PixelConverter;
60 :
61 : // Alpha handling:
62 : // The alpha from the paint (tintColor) is used in the blend part of the pipeline to modulate
63 : // the entire bitmap. So, the tint color is given an alpha of 1.0 so that the later alpha can
64 : // modulate this color later.
65 : template <>
66 : class PixelConverter<kAlpha_8_SkColorType, kLinear_SkGammaType> {
67 : public:
68 : using Element = uint8_t;
69 0 : PixelConverter(const SkPixmap& srcPixmap, SkColor tintColor) {
70 0 : fTintColor = SkColor4f::FromColor(tintColor);
71 0 : fTintColor.fA = 1.0f;
72 0 : }
73 :
74 0 : Sk4f toSk4f(const Element pixel) const {
75 0 : return Sk4f::Load(&fTintColor) * (pixel * (1.0f/255.0f));
76 : }
77 :
78 : private:
79 : SkColor4f fTintColor;
80 : };
81 :
82 : template <SkGammaType gammaType>
83 0 : static inline Sk4f pmcolor_to_rgba(SkPMColor pixel) {
84 0 : return swizzle_rb_if_bgra(
85 : (gammaType == kSRGB_SkGammaType) ? Sk4f_fromS32(pixel)
86 0 : : Sk4f_fromL32(pixel));
87 : }
88 :
89 : template <SkGammaType gammaType>
90 : class PixelConverter<kRGB_565_SkColorType, gammaType> {
91 : public:
92 : using Element = uint16_t;
93 0 : PixelConverter(const SkPixmap& srcPixmap) { }
94 :
95 0 : Sk4f toSk4f(Element pixel) const {
96 0 : return pmcolor_to_rgba<gammaType>(SkPixel16ToPixel32(pixel));
97 : }
98 : };
99 :
100 : template <SkGammaType gammaType>
101 : class PixelConverter<kARGB_4444_SkColorType, gammaType> {
102 : public:
103 : using Element = uint16_t;
104 0 : PixelConverter(const SkPixmap& srcPixmap) { }
105 :
106 0 : Sk4f toSk4f(Element pixel) const {
107 0 : return pmcolor_to_rgba<gammaType>(SkPixel4444ToPixel32(pixel));
108 : }
109 : };
110 :
111 : template <SkGammaType gammaType>
112 : class PixelConverter<kRGBA_8888_SkColorType, gammaType> {
113 : public:
114 : using Element = uint32_t;
115 0 : PixelConverter(const SkPixmap& srcPixmap) { }
116 :
117 0 : Sk4f toSk4f(Element pixel) const {
118 : return gammaType == kSRGB_SkGammaType
119 : ? Sk4f_fromS32(pixel)
120 0 : : Sk4f_fromL32(pixel);
121 : }
122 : };
123 :
124 : template <SkGammaType gammaType>
125 : class PixelConverter<kBGRA_8888_SkColorType, gammaType> {
126 : public:
127 : using Element = uint32_t;
128 0 : PixelConverter(const SkPixmap& srcPixmap) { }
129 :
130 0 : Sk4f toSk4f(Element pixel) const {
131 0 : return swizzle_rb(
132 0 : gammaType == kSRGB_SkGammaType ? Sk4f_fromS32(pixel) : Sk4f_fromL32(pixel));
133 : }
134 : };
135 :
136 : template <SkGammaType gammaType>
137 0 : class PixelConverter<kIndex_8_SkColorType, gammaType> {
138 : public:
139 : using Element = uint8_t;
140 0 : PixelConverter(const SkPixmap& srcPixmap)
141 0 : : fColorTableSize(srcPixmap.ctable()->count()){
142 0 : SkColorTable* skColorTable = srcPixmap.ctable();
143 0 : SkASSERT(skColorTable != nullptr);
144 :
145 0 : fColorTable = (Sk4f*)SkAlign16((intptr_t)fColorTableStorage.get());
146 0 : for (int i = 0; i < fColorTableSize; i++) {
147 0 : fColorTable[i] = pmcolor_to_rgba<gammaType>((*skColorTable)[i]);
148 : }
149 0 : }
150 :
151 : PixelConverter(const PixelConverter& strategy)
152 : : fColorTableSize{strategy.fColorTableSize}{
153 : fColorTable = (Sk4f*)SkAlign16((intptr_t)fColorTableStorage.get());
154 : for (int i = 0; i < fColorTableSize; i++) {
155 : fColorTable[i] = strategy.fColorTable[i];
156 : }
157 : }
158 :
159 0 : Sk4f toSk4f(Element index) const {
160 0 : return fColorTable[index];
161 : }
162 :
163 : private:
164 : static const size_t kColorTableSize = sizeof(Sk4f[256]) + 12;
165 : const int fColorTableSize;
166 : SkAutoMalloc fColorTableStorage{kColorTableSize};
167 : Sk4f* fColorTable;
168 : };
169 :
170 : template <SkGammaType gammaType>
171 : class PixelConverter<kGray_8_SkColorType, gammaType> {
172 : public:
173 : using Element = uint8_t;
174 0 : PixelConverter(const SkPixmap& srcPixmap) { }
175 :
176 0 : Sk4f toSk4f(Element pixel) const {
177 : float gray = (gammaType == kSRGB_SkGammaType)
178 : ? sk_linear_from_srgb[pixel]
179 0 : : pixel * (1/255.0f);
180 0 : return {gray, gray, gray, 1.0f};
181 : }
182 : };
183 :
184 : template <>
185 : class PixelConverter<kRGBA_F16_SkColorType, kLinear_SkGammaType> {
186 : public:
187 : using Element = uint64_t;
188 0 : PixelConverter(const SkPixmap& srcPixmap) { }
189 :
190 0 : Sk4f toSk4f(const Element pixel) const {
191 0 : return SkHalfToFloat_finite_ftz(pixel);
192 : }
193 : };
194 :
195 : class PixelAccessorShim {
196 : public:
197 0 : explicit PixelAccessorShim(SkLinearBitmapPipeline::PixelAccessorInterface* accessor)
198 0 : : fPixelAccessor(accessor) { }
199 :
200 0 : void SK_VECTORCALL getFewPixels(
201 : int n, Sk4i xs, Sk4i ys, Sk4f* px0, Sk4f* px1, Sk4f* px2) const {
202 0 : fPixelAccessor->getFewPixels(n, xs, ys, px0, px1, px2);
203 0 : }
204 :
205 0 : void SK_VECTORCALL get4Pixels(
206 : Sk4i xs, Sk4i ys, Sk4f* px0, Sk4f* px1, Sk4f* px2, Sk4f* px3) const {
207 0 : fPixelAccessor->get4Pixels(xs, ys, px0, px1, px2, px3);
208 0 : }
209 :
210 0 : void get4Pixels(
211 : const void* src, int index, Sk4f* px0, Sk4f* px1, Sk4f* px2, Sk4f* px3) const {
212 0 : fPixelAccessor->get4Pixels(src, index, px0, px1, px2, px3);
213 0 : }
214 :
215 0 : Sk4f getPixelFromRow(const void* row, int index) const {
216 0 : return fPixelAccessor->getPixelFromRow(row, index);
217 : }
218 :
219 : Sk4f getPixelAt(int index) const {
220 : return fPixelAccessor->getPixelAt(index);
221 : }
222 :
223 0 : const void* row(int y) const {
224 0 : return fPixelAccessor->row(y);
225 : }
226 :
227 : private:
228 : SkLinearBitmapPipeline::PixelAccessorInterface* const fPixelAccessor;
229 : };
230 :
231 : ////////////////////////////////////////////////////////////////////////////////////////////////////
232 : // PixelAccessor handles all the same plumbing for all the PixelGetters.
233 : template <SkColorType colorType, SkGammaType gammaType>
234 0 : class PixelAccessor final : public SkLinearBitmapPipeline::PixelAccessorInterface {
235 : using Element = typename PixelConverter<colorType, gammaType>::Element;
236 : public:
237 : template <typename... Args>
238 0 : PixelAccessor(const SkPixmap& srcPixmap, Args&&... args)
239 0 : : fSrc{static_cast<const Element*>(srcPixmap.addr())}
240 0 : , fWidth{srcPixmap.rowBytesAsPixels()}
241 0 : , fConverter{srcPixmap, std::move<Args>(args)...} { }
242 :
243 0 : void SK_VECTORCALL getFewPixels (
244 : int n, Sk4i xs, Sk4i ys, Sk4f* px0, Sk4f* px1, Sk4f* px2) const override {
245 0 : Sk4i bufferLoc = ys * fWidth + xs;
246 0 : switch (n) {
247 : case 3:
248 0 : *px2 = this->getPixelAt(bufferLoc[2]);
249 : case 2:
250 0 : *px1 = this->getPixelAt(bufferLoc[1]);
251 : case 1:
252 0 : *px0 = this->getPixelAt(bufferLoc[0]);
253 : default:
254 0 : break;
255 : }
256 0 : }
257 :
258 0 : void SK_VECTORCALL get4Pixels(
259 : Sk4i xs, Sk4i ys, Sk4f* px0, Sk4f* px1, Sk4f* px2, Sk4f* px3) const override {
260 0 : Sk4i bufferLoc = ys * fWidth + xs;
261 0 : *px0 = this->getPixelAt(bufferLoc[0]);
262 0 : *px1 = this->getPixelAt(bufferLoc[1]);
263 0 : *px2 = this->getPixelAt(bufferLoc[2]);
264 0 : *px3 = this->getPixelAt(bufferLoc[3]);
265 0 : }
266 :
267 0 : void get4Pixels(
268 : const void* src, int index, Sk4f* px0, Sk4f* px1, Sk4f* px2, Sk4f* px3) const override {
269 0 : *px0 = this->getPixelFromRow(src, index + 0);
270 0 : *px1 = this->getPixelFromRow(src, index + 1);
271 0 : *px2 = this->getPixelFromRow(src, index + 2);
272 0 : *px3 = this->getPixelFromRow(src, index + 3);
273 0 : }
274 :
275 0 : Sk4f getPixelFromRow(const void* row, int index) const override {
276 0 : const Element* src = static_cast<const Element*>(row);
277 0 : return fConverter.toSk4f(src[index]);
278 : }
279 :
280 0 : Sk4f getPixelAt(int index) const override {
281 0 : return this->getPixelFromRow(fSrc, index);
282 : }
283 :
284 0 : const void* row(int y) const override { return fSrc + y * fWidth; }
285 :
286 : private:
287 : const Element* const fSrc;
288 : const int fWidth;
289 : PixelConverter<colorType, gammaType> fConverter;
290 : };
291 :
292 : // We're moving through source space at a rate of 1 source pixel per 1 dst pixel.
293 : // We'll never re-use pixels, but we can at least load contiguous pixels.
294 : template <typename Next, typename Strategy>
295 0 : static void src_strategy_blend(Span span, Next* next, Strategy* strategy) {
296 : SkPoint start;
297 : SkScalar length;
298 : int count;
299 0 : std::tie(start, length, count) = span;
300 0 : int ix = SkScalarFloorToInt(X(start));
301 0 : const void* row = strategy->row((int)std::floor(Y(start)));
302 0 : if (length > 0) {
303 0 : while (count >= 4) {
304 : Sk4f px0, px1, px2, px3;
305 0 : strategy->get4Pixels(row, ix, &px0, &px1, &px2, &px3);
306 0 : next->blend4Pixels(px0, px1, px2, px3);
307 0 : ix += 4;
308 0 : count -= 4;
309 : }
310 :
311 0 : while (count > 0) {
312 0 : next->blendPixel(strategy->getPixelFromRow(row, ix));
313 0 : ix += 1;
314 0 : count -= 1;
315 : }
316 : } else {
317 0 : while (count >= 4) {
318 : Sk4f px0, px1, px2, px3;
319 0 : strategy->get4Pixels(row, ix - 3, &px3, &px2, &px1, &px0);
320 0 : next->blend4Pixels(px0, px1, px2, px3);
321 0 : ix -= 4;
322 0 : count -= 4;
323 : }
324 :
325 0 : while (count > 0) {
326 0 : next->blendPixel(strategy->getPixelFromRow(row, ix));
327 0 : ix -= 1;
328 0 : count -= 1;
329 : }
330 : }
331 0 : }
332 :
333 : // -- NearestNeighborSampler -----------------------------------------------------------------------
334 : // NearestNeighborSampler - use nearest neighbor filtering to create runs of destination pixels.
335 : template<typename Accessor, typename Next>
336 0 : class NearestNeighborSampler : public SkLinearBitmapPipeline::SampleProcessorInterface {
337 : public:
338 : template<typename... Args>
339 0 : NearestNeighborSampler(SkLinearBitmapPipeline::BlendProcessorInterface* next, Args&& ... args)
340 0 : : fNext{next}, fAccessor{std::forward<Args>(args)...} { }
341 :
342 : NearestNeighborSampler(SkLinearBitmapPipeline::BlendProcessorInterface* next,
343 : const NearestNeighborSampler& sampler)
344 : : fNext{next}, fAccessor{sampler.fAccessor} { }
345 :
346 0 : void SK_VECTORCALL pointListFew(int n, Sk4s xs, Sk4s ys) override {
347 0 : SkASSERT(0 < n && n < 4);
348 : Sk4f px0, px1, px2;
349 0 : fAccessor.getFewPixels(n, SkNx_cast<int>(xs), SkNx_cast<int>(ys), &px0, &px1, &px2);
350 0 : if (n >= 1) fNext->blendPixel(px0);
351 0 : if (n >= 2) fNext->blendPixel(px1);
352 0 : if (n >= 3) fNext->blendPixel(px2);
353 0 : }
354 :
355 0 : void SK_VECTORCALL pointList4(Sk4s xs, Sk4s ys) override {
356 : Sk4f px0, px1, px2, px3;
357 0 : fAccessor.get4Pixels(SkNx_cast<int>(xs), SkNx_cast<int>(ys), &px0, &px1, &px2, &px3);
358 0 : fNext->blend4Pixels(px0, px1, px2, px3);
359 0 : }
360 :
361 0 : void pointSpan(Span span) override {
362 0 : SkASSERT(!span.isEmpty());
363 : SkPoint start;
364 : SkScalar length;
365 : int count;
366 0 : std::tie(start, length, count) = span;
367 0 : SkScalar absLength = SkScalarAbs(length);
368 0 : if (absLength < (count - 1)) {
369 0 : this->spanSlowRate(span);
370 0 : } else if (absLength == (count - 1)) {
371 0 : src_strategy_blend(span, fNext, &fAccessor);
372 : } else {
373 0 : this->spanFastRate(span);
374 : }
375 0 : }
376 :
377 0 : void repeatSpan(Span span, int32_t repeatCount) override {
378 0 : while (repeatCount > 0) {
379 0 : this->pointSpan(span);
380 0 : repeatCount--;
381 : }
382 0 : }
383 :
384 : private:
385 : // When moving through source space more slowly than dst space (zoomed in),
386 : // we'll be sampling from the same source pixel more than once.
387 0 : void spanSlowRate(Span span) {
388 : SkPoint start; SkScalar length; int count;
389 0 : std::tie(start, length, count) = span;
390 0 : SkScalar x = X(start);
391 : // fx is a fixed 48.16 number.
392 0 : int64_t fx = static_cast<int64_t>(x * SK_Fixed1);
393 0 : SkScalar dx = length / (count - 1);
394 : // fdx is a fixed 48.16 number.
395 0 : int64_t fdx = static_cast<int64_t>(dx * SK_Fixed1);
396 :
397 0 : const void* row = fAccessor.row((int)std::floor(Y(start)));
398 0 : Next* next = fNext;
399 :
400 0 : int64_t ix = fx >> 16;
401 0 : int64_t prevIX = ix;
402 0 : Sk4f fpixel = fAccessor.getPixelFromRow(row, ix);
403 :
404 : // When dx is less than one, each pixel is used more than once. Using the fixed point fx
405 : // allows the code to quickly check that the same pixel is being used. The code uses this
406 : // same pixel check to do the sRGB and normalization only once.
407 0 : auto getNextPixel = [&]() {
408 0 : if (ix != prevIX) {
409 0 : fpixel = fAccessor.getPixelFromRow(row, ix);
410 0 : prevIX = ix;
411 : }
412 0 : fx += fdx;
413 0 : ix = fx >> 16;
414 0 : return fpixel;
415 0 : };
416 :
417 0 : while (count >= 4) {
418 0 : Sk4f px0 = getNextPixel();
419 0 : Sk4f px1 = getNextPixel();
420 0 : Sk4f px2 = getNextPixel();
421 0 : Sk4f px3 = getNextPixel();
422 0 : next->blend4Pixels(px0, px1, px2, px3);
423 0 : count -= 4;
424 : }
425 0 : while (count > 0) {
426 0 : next->blendPixel(getNextPixel());
427 0 : count -= 1;
428 : }
429 0 : }
430 :
431 : // We're moving through source space at a rate of 1 source pixel per 1 dst pixel.
432 : // We'll never re-use pixels, but we can at least load contiguous pixels.
433 : void spanUnitRate(Span span) {
434 : src_strategy_blend(span, fNext, &fAccessor);
435 : }
436 :
437 : // We're moving through source space faster than dst (zoomed out),
438 : // so we'll never reuse a source pixel or be able to do contiguous loads.
439 0 : void spanFastRate(Span span) {
440 0 : span_fallback(span, this);
441 0 : }
442 :
443 : Next* const fNext;
444 : Accessor fAccessor;
445 : };
446 :
447 : // From an edgeType, the integer value of a pixel vs, and the integer value of the extreme edge
448 : // vMax, take the point which might be off the tile by one pixel and either wrap it or pin it to
449 : // generate the right pixel. The value vs is on the interval [-1, vMax + 1]. It produces a value
450 : // on the interval [0, vMax].
451 : // Note: vMax is not width or height, but width-1 or height-1 because it is the largest valid pixel.
452 0 : static inline int adjust_edge(SkShader::TileMode edgeType, int vs, int vMax) {
453 0 : SkASSERT(-1 <= vs && vs <= vMax + 1);
454 0 : switch (edgeType) {
455 : case SkShader::kClamp_TileMode:
456 : case SkShader::kMirror_TileMode:
457 0 : vs = std::max(vs, 0);
458 0 : vs = std::min(vs, vMax);
459 0 : break;
460 : case SkShader::kRepeat_TileMode:
461 0 : vs = (vs <= vMax) ? vs : 0;
462 0 : vs = (vs >= 0) ? vs : vMax;
463 0 : break;
464 : }
465 0 : SkASSERT(0 <= vs && vs <= vMax);
466 0 : return vs;
467 : }
468 :
469 : // From a sample point on the tile, return the top or left filter value.
470 : // The result r should be in the range (0, 1]. Since this represents the weight given to the top
471 : // left element, then if x == 0.5 the filter value should be 1.0.
472 : // The input sample point must be on the tile, therefore it must be >= 0.
473 0 : static SkScalar sample_to_filter(SkScalar x) {
474 0 : SkASSERT(x >= 0.0f);
475 : // The usual form of the top or left edge is x - .5, but since we are working on the unit
476 : // square, then x + .5 works just as well. This also guarantees that v > 0.0 allowing the use
477 : // of trunc.
478 0 : SkScalar v = x + 0.5f;
479 : // Produce the top or left offset a value on the range [0, 1).
480 0 : SkScalar f = v - SkScalarTruncToScalar(v);
481 : // Produce the filter value which is on the range (0, 1].
482 0 : SkScalar r = 1.0f - f;
483 0 : SkASSERT(0.0f < r && r <= 1.0f);
484 0 : return r;
485 : }
486 :
487 : // -- BilerpSampler --------------------------------------------------------------------------------
488 : // BilerpSampler - use a bilerp filter to create runs of destination pixels.
489 : // Note: in the code below, there are two types of points
490 : // * sample points - these are the points passed in by pointList* and Spans.
491 : // * filter points - are created from a sample point to form the coordinates of the points
492 : // to use in the filter and to generate the filter values.
493 : template<typename Accessor, typename Next>
494 0 : class BilerpSampler : public SkLinearBitmapPipeline::SampleProcessorInterface {
495 : public:
496 : template<typename... Args>
497 0 : BilerpSampler(
498 : SkLinearBitmapPipeline::BlendProcessorInterface* next,
499 : SkISize dimensions,
500 : SkShader::TileMode xTile, SkShader::TileMode yTile,
501 : Args&& ... args
502 : )
503 : : fNext{next}
504 : , fXEdgeType{xTile}
505 0 : , fXMax{dimensions.width() - 1}
506 : , fYEdgeType{yTile}
507 0 : , fYMax{dimensions.height() - 1}
508 0 : , fAccessor{std::forward<Args>(args)...} { }
509 :
510 : BilerpSampler(SkLinearBitmapPipeline::BlendProcessorInterface* next,
511 : const BilerpSampler& sampler)
512 : : fNext{next}
513 : , fXEdgeType{sampler.fXEdgeType}
514 : , fXMax{sampler.fXMax}
515 : , fYEdgeType{sampler.fYEdgeType}
516 : , fYMax{sampler.fYMax}
517 : , fAccessor{sampler.fAccessor} { }
518 :
519 0 : void SK_VECTORCALL pointListFew(int n, Sk4s xs, Sk4s ys) override {
520 0 : SkASSERT(0 < n && n < 4);
521 0 : auto bilerpPixel = [&](int index) {
522 0 : return this->bilerpSamplePoint(SkPoint{xs[index], ys[index]});
523 0 : };
524 :
525 0 : if (n >= 1) fNext->blendPixel(bilerpPixel(0));
526 0 : if (n >= 2) fNext->blendPixel(bilerpPixel(1));
527 0 : if (n >= 3) fNext->blendPixel(bilerpPixel(2));
528 0 : }
529 :
530 0 : void SK_VECTORCALL pointList4(Sk4s xs, Sk4s ys) override {
531 0 : auto bilerpPixel = [&](int index) {
532 0 : return this->bilerpSamplePoint(SkPoint{xs[index], ys[index]});
533 0 : };
534 0 : fNext->blend4Pixels(bilerpPixel(0), bilerpPixel(1), bilerpPixel(2), bilerpPixel(3));
535 0 : }
536 :
537 0 : void pointSpan(Span span) override {
538 0 : SkASSERT(!span.isEmpty());
539 : SkPoint start;
540 : SkScalar length;
541 : int count;
542 0 : std::tie(start, length, count) = span;
543 :
544 : // Nothing to do.
545 0 : if (count == 0) {
546 0 : return;
547 : }
548 :
549 : // Trivial case. No sample points are generated other than start.
550 0 : if (count == 1) {
551 0 : fNext->blendPixel(this->bilerpSamplePoint(start));
552 0 : return;
553 : }
554 :
555 : // Note: the following code could be done in terms of dx = length / (count -1), but that
556 : // would introduce a divide that is not needed for the most common dx == 1 cases.
557 0 : SkScalar absLength = SkScalarAbs(length);
558 0 : if (absLength == 0.0f) {
559 : // |dx| == 0
560 : // length is zero, so clamp an edge pixel.
561 0 : this->spanZeroRate(span);
562 0 : } else if (absLength < (count - 1)) {
563 : // 0 < |dx| < 1.
564 0 : this->spanSlowRate(span);
565 0 : } else if (absLength == (count - 1)) {
566 : // |dx| == 1.
567 0 : if (sample_to_filter(span.startX()) == 1.0f
568 0 : && sample_to_filter(span.startY()) == 1.0f) {
569 : // All the pixels are aligned with the dest; go fast.
570 0 : src_strategy_blend(span, fNext, &fAccessor);
571 : } else {
572 : // There is some sub-pixel offsets, so bilerp.
573 0 : this->spanUnitRate(span);
574 : }
575 0 : } else if (absLength < 2.0f * (count - 1)) {
576 : // 1 < |dx| < 2.
577 0 : this->spanMediumRate(span);
578 : } else {
579 : // |dx| >= 2.
580 0 : this->spanFastRate(span);
581 : }
582 : }
583 :
584 0 : void repeatSpan(Span span, int32_t repeatCount) override {
585 0 : while (repeatCount > 0) {
586 0 : this->pointSpan(span);
587 0 : repeatCount--;
588 : }
589 0 : }
590 :
591 : private:
592 :
593 : // Convert a sample point to the points used by the filter.
594 0 : void filterPoints(SkPoint sample, Sk4i* filterXs, Sk4i* filterYs) {
595 : // May be less than zero. Be careful to use Floor.
596 0 : int x0 = adjust_edge(fXEdgeType, SkScalarFloorToInt(X(sample) - 0.5), fXMax);
597 : // Always greater than zero. Use the faster Trunc.
598 0 : int x1 = adjust_edge(fXEdgeType, SkScalarTruncToInt(X(sample) + 0.5), fXMax);
599 0 : int y0 = adjust_edge(fYEdgeType, SkScalarFloorToInt(Y(sample) - 0.5), fYMax);
600 0 : int y1 = adjust_edge(fYEdgeType, SkScalarTruncToInt(Y(sample) + 0.5), fYMax);
601 :
602 0 : *filterXs = Sk4i{x0, x1, x0, x1};
603 0 : *filterYs = Sk4i{y0, y0, y1, y1};
604 0 : }
605 :
606 : // Given a sample point, generate a color by bilerping the four filter points.
607 0 : Sk4f bilerpSamplePoint(SkPoint sample) {
608 : Sk4i iXs, iYs;
609 0 : filterPoints(sample, &iXs, &iYs);
610 : Sk4f px00, px10, px01, px11;
611 0 : fAccessor.get4Pixels(iXs, iYs, &px00, &px10, &px01, &px11);
612 0 : return bilerp4(Sk4f{X(sample) - 0.5f}, Sk4f{Y(sample) - 0.5f}, px00, px10, px01, px11);
613 : }
614 :
615 : // Get two pixels at x from row0 and row1.
616 0 : void get2PixelColumn(const void* row0, const void* row1, int x, Sk4f* px0, Sk4f* px1) {
617 0 : *px0 = fAccessor.getPixelFromRow(row0, x);
618 0 : *px1 = fAccessor.getPixelFromRow(row1, x);
619 0 : }
620 :
621 : // |dx| == 0. This code assumes that length is zero.
622 0 : void spanZeroRate(Span span) {
623 : SkPoint start; SkScalar length; int count;
624 0 : std::tie(start, length, count) = span;
625 0 : SkASSERT(length == 0.0f);
626 :
627 : // Filter for the blending of the top and bottom pixels.
628 0 : SkScalar filterY = sample_to_filter(Y(start));
629 :
630 : // Generate the four filter points from the sample point start. Generate the row* values.
631 : Sk4i iXs, iYs;
632 0 : this->filterPoints(start, &iXs, &iYs);
633 0 : const void* const row0 = fAccessor.row(iYs[0]);
634 0 : const void* const row1 = fAccessor.row(iYs[2]);
635 :
636 : // Get the two pixels that make up the clamping pixel.
637 : Sk4f pxTop, pxBottom;
638 0 : this->get2PixelColumn(row0, row1, SkScalarFloorToInt(X(start)), &pxTop, &pxBottom);
639 0 : Sk4f pixel = pxTop * filterY + (1.0f - filterY) * pxBottom;
640 :
641 0 : while (count >= 4) {
642 0 : fNext->blend4Pixels(pixel, pixel, pixel, pixel);
643 0 : count -= 4;
644 : }
645 0 : while (count > 0) {
646 0 : fNext->blendPixel(pixel);
647 0 : count -= 1;
648 : }
649 0 : }
650 :
651 : // 0 < |dx| < 1. This code reuses the calculations from previous pixels to reduce
652 : // computation. In particular, several destination pixels maybe generated from the same four
653 : // source pixels.
654 : // In the following code a "part" is a combination of two pixels from the same column of the
655 : // filter.
656 0 : void spanSlowRate(Span span) {
657 : SkPoint start; SkScalar length; int count;
658 0 : std::tie(start, length, count) = span;
659 :
660 : // Calculate the distance between each sample point.
661 0 : const SkScalar dx = length / (count - 1);
662 0 : SkASSERT(-1.0f < dx && dx < 1.0f && dx != 0.0f);
663 :
664 : // Generate the filter values for the top-left corner.
665 : // Note: these values are in filter space; this has implications about how to adjust
666 : // these values at each step. For example, as the sample point increases, the filter
667 : // value decreases, this is because the filter and position are related by
668 : // (1 - (X(sample) - .5)) % 1. The (1 - stuff) causes the filter to move in the opposite
669 : // direction of the sample point which is increasing by dx.
670 0 : SkScalar filterX = sample_to_filter(X(start));
671 0 : SkScalar filterY = sample_to_filter(Y(start));
672 :
673 : // Generate the four filter points from the sample point start. Generate the row* values.
674 : Sk4i iXs, iYs;
675 0 : this->filterPoints(start, &iXs, &iYs);
676 0 : const void* const row0 = fAccessor.row(iYs[0]);
677 0 : const void* const row1 = fAccessor.row(iYs[2]);
678 :
679 : // Generate part of the filter value at xColumn.
680 0 : auto partAtColumn = [&](int xColumn) {
681 0 : int adjustedColumn = adjust_edge(fXEdgeType, xColumn, fXMax);
682 : Sk4f pxTop, pxBottom;
683 0 : this->get2PixelColumn(row0, row1, adjustedColumn, &pxTop, &pxBottom);
684 0 : return pxTop * filterY + (1.0f - filterY) * pxBottom;
685 0 : };
686 :
687 : // The leftPart is made up of two pixels from the left column of the filter, right part
688 : // is similar. The top and bottom pixels in the *Part are created as a linear blend of
689 : // the top and bottom pixels using filterY. See the partAtColumn function above.
690 0 : Sk4f leftPart = partAtColumn(iXs[0]);
691 0 : Sk4f rightPart = partAtColumn(iXs[1]);
692 :
693 : // Create a destination color by blending together a left and right part using filterX.
694 0 : auto bilerp = [&](const Sk4f& leftPart, const Sk4f& rightPart) {
695 0 : Sk4f pixel = leftPart * filterX + rightPart * (1.0f - filterX);
696 0 : return check_pixel(pixel);
697 0 : };
698 :
699 : // Send the first pixel to the destination. This simplifies the loop structure so that no
700 : // extra pixels are fetched for the last iteration of the loop.
701 0 : fNext->blendPixel(bilerp(leftPart, rightPart));
702 0 : count -= 1;
703 :
704 0 : if (dx > 0.0f) {
705 : // * positive direction - generate destination pixels by sliding the filter from left
706 : // to right.
707 0 : int rightPartCursor = iXs[1];
708 :
709 : // Advance the filter from left to right. Remember that moving the top-left corner of
710 : // the filter to the right actually makes the filter value smaller.
711 0 : auto advanceFilter = [&]() {
712 0 : filterX -= dx;
713 0 : if (filterX <= 0.0f) {
714 0 : filterX += 1.0f;
715 0 : leftPart = rightPart;
716 0 : rightPartCursor += 1;
717 0 : rightPart = partAtColumn(rightPartCursor);
718 : }
719 0 : SkASSERT(0.0f < filterX && filterX <= 1.0f);
720 :
721 0 : return bilerp(leftPart, rightPart);
722 0 : };
723 :
724 0 : while (count >= 4) {
725 0 : Sk4f px0 = advanceFilter(),
726 0 : px1 = advanceFilter(),
727 0 : px2 = advanceFilter(),
728 0 : px3 = advanceFilter();
729 0 : fNext->blend4Pixels(px0, px1, px2, px3);
730 0 : count -= 4;
731 : }
732 :
733 0 : while (count > 0) {
734 0 : fNext->blendPixel(advanceFilter());
735 0 : count -= 1;
736 : }
737 : } else {
738 : // * negative direction - generate destination pixels by sliding the filter from
739 : // right to left.
740 0 : int leftPartCursor = iXs[0];
741 :
742 : // Advance the filter from right to left. Remember that moving the top-left corner of
743 : // the filter to the left actually makes the filter value larger.
744 0 : auto advanceFilter = [&]() {
745 : // Remember, dx < 0 therefore this adds |dx| to filterX.
746 0 : filterX -= dx;
747 : // At this point filterX may be > 1, and needs to be wrapped back on to the filter
748 : // interval, and the next column in the filter is calculated.
749 0 : if (filterX > 1.0f) {
750 0 : filterX -= 1.0f;
751 0 : rightPart = leftPart;
752 0 : leftPartCursor -= 1;
753 0 : leftPart = partAtColumn(leftPartCursor);
754 : }
755 0 : SkASSERT(0.0f < filterX && filterX <= 1.0f);
756 :
757 0 : return bilerp(leftPart, rightPart);
758 0 : };
759 :
760 0 : while (count >= 4) {
761 0 : Sk4f px0 = advanceFilter(),
762 0 : px1 = advanceFilter(),
763 0 : px2 = advanceFilter(),
764 0 : px3 = advanceFilter();
765 0 : fNext->blend4Pixels(px0, px1, px2, px3);
766 0 : count -= 4;
767 : }
768 :
769 0 : while (count > 0) {
770 0 : fNext->blendPixel(advanceFilter());
771 0 : count -= 1;
772 : }
773 : }
774 0 : }
775 :
776 : // |dx| == 1. Moving through source space at a rate of 1 source pixel per 1 dst pixel.
777 : // Every filter part is used for two destination pixels, and the code can bulk load four
778 : // pixels at a time.
779 0 : void spanUnitRate(Span span) {
780 : SkPoint start; SkScalar length; int count;
781 0 : std::tie(start, length, count) = span;
782 0 : SkASSERT(SkScalarAbs(length) == (count - 1));
783 :
784 : // Calculate the four filter points of start, and use the two different Y values to
785 : // generate the row pointers.
786 : Sk4i iXs, iYs;
787 0 : filterPoints(start, &iXs, &iYs);
788 0 : const void* row0 = fAccessor.row(iYs[0]);
789 0 : const void* row1 = fAccessor.row(iYs[2]);
790 :
791 : // Calculate the filter values for the top-left filter element.
792 0 : const SkScalar filterX = sample_to_filter(X(start));
793 0 : const SkScalar filterY = sample_to_filter(Y(start));
794 :
795 : // Generate part of the filter value at xColumn.
796 0 : auto partAtColumn = [&](int xColumn) {
797 0 : int adjustedColumn = adjust_edge(fXEdgeType, xColumn, fXMax);
798 : Sk4f pxTop, pxBottom;
799 0 : this->get2PixelColumn(row0, row1, adjustedColumn, &pxTop, &pxBottom);
800 0 : return pxTop * filterY + (1.0f - filterY) * pxBottom;
801 0 : };
802 :
803 0 : auto get4Parts = [&](int ix, Sk4f* part0, Sk4f* part1, Sk4f* part2, Sk4f* part3) {
804 : // Check if the pixels needed are near the edges. If not go fast using bulk pixels,
805 : // otherwise be careful.
806 0 : if (0 <= ix && ix <= fXMax - 3) {
807 : Sk4f px00, px10, px20, px30,
808 : px01, px11, px21, px31;
809 0 : fAccessor.get4Pixels(row0, ix, &px00, &px10, &px20, &px30);
810 0 : fAccessor.get4Pixels(row1, ix, &px01, &px11, &px21, &px31);
811 0 : *part0 = filterY * px00 + (1.0f - filterY) * px01;
812 0 : *part1 = filterY * px10 + (1.0f - filterY) * px11;
813 0 : *part2 = filterY * px20 + (1.0f - filterY) * px21;
814 0 : *part3 = filterY * px30 + (1.0f - filterY) * px31;
815 : } else {
816 0 : *part0 = partAtColumn(ix + 0);
817 0 : *part1 = partAtColumn(ix + 1);
818 0 : *part2 = partAtColumn(ix + 2);
819 0 : *part3 = partAtColumn(ix + 3);
820 : }
821 0 : };
822 :
823 0 : auto bilerp = [&](const Sk4f& part0, const Sk4f& part1) {
824 0 : return part0 * filterX + part1 * (1.0f - filterX);
825 0 : };
826 :
827 0 : if (length > 0) {
828 : // * positive direction - generate destination pixels by sliding the filter from left
829 : // to right.
830 :
831 : // overlapPart is the filter part from the end of the previous four pixels used at
832 : // the start of the next four pixels.
833 0 : Sk4f overlapPart = partAtColumn(iXs[0]);
834 0 : int rightColumnCursor = iXs[1];
835 0 : while (count >= 4) {
836 : Sk4f part0, part1, part2, part3;
837 0 : get4Parts(rightColumnCursor, &part0, &part1, &part2, &part3);
838 0 : Sk4f px0 = bilerp(overlapPart, part0);
839 0 : Sk4f px1 = bilerp(part0, part1);
840 0 : Sk4f px2 = bilerp(part1, part2);
841 0 : Sk4f px3 = bilerp(part2, part3);
842 0 : overlapPart = part3;
843 0 : fNext->blend4Pixels(px0, px1, px2, px3);
844 0 : rightColumnCursor += 4;
845 0 : count -= 4;
846 : }
847 :
848 0 : while (count > 0) {
849 0 : Sk4f rightPart = partAtColumn(rightColumnCursor);
850 :
851 0 : fNext->blendPixel(bilerp(overlapPart, rightPart));
852 0 : overlapPart = rightPart;
853 0 : rightColumnCursor += 1;
854 0 : count -= 1;
855 : }
856 : } else {
857 : // * negative direction - generate destination pixels by sliding the filter from
858 : // right to left.
859 0 : Sk4f overlapPart = partAtColumn(iXs[1]);
860 0 : int leftColumnCursor = iXs[0];
861 :
862 0 : while (count >= 4) {
863 : Sk4f part0, part1, part2, part3;
864 0 : get4Parts(leftColumnCursor - 3, &part3, &part2, &part1, &part0);
865 0 : Sk4f px0 = bilerp(part0, overlapPart);
866 0 : Sk4f px1 = bilerp(part1, part0);
867 0 : Sk4f px2 = bilerp(part2, part1);
868 0 : Sk4f px3 = bilerp(part3, part2);
869 0 : overlapPart = part3;
870 0 : fNext->blend4Pixels(px0, px1, px2, px3);
871 0 : leftColumnCursor -= 4;
872 0 : count -= 4;
873 : }
874 :
875 0 : while (count > 0) {
876 0 : Sk4f leftPart = partAtColumn(leftColumnCursor);
877 :
878 0 : fNext->blendPixel(bilerp(leftPart, overlapPart));
879 0 : overlapPart = leftPart;
880 0 : leftColumnCursor -= 1;
881 0 : count -= 1;
882 : }
883 : }
884 0 : }
885 :
886 : // 1 < |dx| < 2. Going through the source pixels at a faster rate than the dest pixels, but
887 : // still slow enough to take advantage of previous calculations.
888 0 : void spanMediumRate(Span span) {
889 : SkPoint start; SkScalar length; int count;
890 0 : std::tie(start, length, count) = span;
891 :
892 : // Calculate the distance between each sample point.
893 0 : const SkScalar dx = length / (count - 1);
894 0 : SkASSERT((-2.0f < dx && dx < -1.0f) || (1.0f < dx && dx < 2.0f));
895 :
896 : // Generate the filter values for the top-left corner.
897 : // Note: these values are in filter space; this has implications about how to adjust
898 : // these values at each step. For example, as the sample point increases, the filter
899 : // value decreases, this is because the filter and position are related by
900 : // (1 - (X(sample) - .5)) % 1. The (1 - stuff) causes the filter to move in the opposite
901 : // direction of the sample point which is increasing by dx.
902 0 : SkScalar filterX = sample_to_filter(X(start));
903 0 : SkScalar filterY = sample_to_filter(Y(start));
904 :
905 : // Generate the four filter points from the sample point start. Generate the row* values.
906 : Sk4i iXs, iYs;
907 0 : this->filterPoints(start, &iXs, &iYs);
908 0 : const void* const row0 = fAccessor.row(iYs[0]);
909 0 : const void* const row1 = fAccessor.row(iYs[2]);
910 :
911 : // Generate part of the filter value at xColumn.
912 0 : auto partAtColumn = [&](int xColumn) {
913 0 : int adjustedColumn = adjust_edge(fXEdgeType, xColumn, fXMax);
914 : Sk4f pxTop, pxBottom;
915 0 : this->get2PixelColumn(row0, row1, adjustedColumn, &pxTop, &pxBottom);
916 0 : return pxTop * filterY + (1.0f - filterY) * pxBottom;
917 0 : };
918 :
919 : // The leftPart is made up of two pixels from the left column of the filter, right part
920 : // is similar. The top and bottom pixels in the *Part are created as a linear blend of
921 : // the top and bottom pixels using filterY. See the nextPart function below.
922 0 : Sk4f leftPart = partAtColumn(iXs[0]);
923 0 : Sk4f rightPart = partAtColumn(iXs[1]);
924 :
925 : // Create a destination color by blending together a left and right part using filterX.
926 0 : auto bilerp = [&](const Sk4f& leftPart, const Sk4f& rightPart) {
927 0 : Sk4f pixel = leftPart * filterX + rightPart * (1.0f - filterX);
928 0 : return check_pixel(pixel);
929 0 : };
930 :
931 : // Send the first pixel to the destination. This simplifies the loop structure so that no
932 : // extra pixels are fetched for the last iteration of the loop.
933 0 : fNext->blendPixel(bilerp(leftPart, rightPart));
934 0 : count -= 1;
935 :
936 0 : if (dx > 0.0f) {
937 : // * positive direction - generate destination pixels by sliding the filter from left
938 : // to right.
939 0 : int rightPartCursor = iXs[1];
940 :
941 : // Advance the filter from left to right. Remember that moving the top-left corner of
942 : // the filter to the right actually makes the filter value smaller.
943 0 : auto advanceFilter = [&]() {
944 0 : filterX -= dx;
945 : // At this point filterX is less than zero, but might actually be less than -1.
946 0 : if (filterX > -1.0f) {
947 0 : filterX += 1.0f;
948 0 : leftPart = rightPart;
949 0 : rightPartCursor += 1;
950 0 : rightPart = partAtColumn(rightPartCursor);
951 : } else {
952 0 : filterX += 2.0f;
953 0 : rightPartCursor += 2;
954 0 : leftPart = partAtColumn(rightPartCursor - 1);
955 0 : rightPart = partAtColumn(rightPartCursor);
956 : }
957 0 : SkASSERT(0.0f < filterX && filterX <= 1.0f);
958 :
959 0 : return bilerp(leftPart, rightPart);
960 0 : };
961 :
962 0 : while (count >= 4) {
963 0 : Sk4f px0 = advanceFilter(),
964 0 : px1 = advanceFilter(),
965 0 : px2 = advanceFilter(),
966 0 : px3 = advanceFilter();
967 0 : fNext->blend4Pixels(px0, px1, px2, px3);
968 0 : count -= 4;
969 : }
970 :
971 0 : while (count > 0) {
972 0 : fNext->blendPixel(advanceFilter());
973 0 : count -= 1;
974 : }
975 : } else {
976 : // * negative direction - generate destination pixels by sliding the filter from
977 : // right to left.
978 0 : int leftPartCursor = iXs[0];
979 :
980 0 : auto advanceFilter = [&]() {
981 : // Remember, dx < 0 therefore this adds |dx| to filterX.
982 0 : filterX -= dx;
983 : // At this point, filterX is greater than one, but may actually be greater than two.
984 0 : if (filterX < 2.0f) {
985 0 : filterX -= 1.0f;
986 0 : rightPart = leftPart;
987 0 : leftPartCursor -= 1;
988 0 : leftPart = partAtColumn(leftPartCursor);
989 : } else {
990 0 : filterX -= 2.0f;
991 0 : leftPartCursor -= 2;
992 0 : rightPart = partAtColumn(leftPartCursor - 1);
993 0 : leftPart = partAtColumn(leftPartCursor);
994 : }
995 0 : SkASSERT(0.0f < filterX && filterX <= 1.0f);
996 0 : return bilerp(leftPart, rightPart);
997 0 : };
998 :
999 0 : while (count >= 4) {
1000 0 : Sk4f px0 = advanceFilter(),
1001 0 : px1 = advanceFilter(),
1002 0 : px2 = advanceFilter(),
1003 0 : px3 = advanceFilter();
1004 0 : fNext->blend4Pixels(px0, px1, px2, px3);
1005 0 : count -= 4;
1006 : }
1007 :
1008 0 : while (count > 0) {
1009 0 : fNext->blendPixel(advanceFilter());
1010 0 : count -= 1;
1011 : }
1012 : }
1013 0 : }
1014 :
1015 : // We're moving through source space faster than dst (zoomed out),
1016 : // so we'll never reuse a source pixel or be able to do contiguous loads.
1017 0 : void spanFastRate(Span span) {
1018 : SkPoint start; SkScalar length; int count;
1019 0 : std::tie(start, length, count) = span;
1020 0 : SkScalar x = X(start);
1021 0 : SkScalar y = Y(start);
1022 :
1023 0 : SkScalar dx = length / (count - 1);
1024 0 : while (count > 0) {
1025 0 : fNext->blendPixel(this->bilerpSamplePoint(SkPoint{x, y}));
1026 0 : x += dx;
1027 0 : count -= 1;
1028 : }
1029 0 : }
1030 :
1031 : Next* const fNext;
1032 : const SkShader::TileMode fXEdgeType;
1033 : const int fXMax;
1034 : const SkShader::TileMode fYEdgeType;
1035 : const int fYMax;
1036 : Accessor fAccessor;
1037 : };
1038 :
1039 : } // namespace
1040 :
1041 : #endif // SkLinearBitmapPipeline_sampler_DEFINED
|