1 /*
2 * Copyright 2011 The Android Open Source Project
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 "SkBlurImageFilter.h"
9
10 #include <algorithm>
11
12 #include "SkArenaAlloc.h"
13 #include "SkAutoPixmapStorage.h"
14 #include "SkBitmap.h"
15 #include "SkColorData.h"
16 #include "SkColorSpaceXformer.h"
17 #include "SkImageFilterPriv.h"
18 #include "SkTFitsIn.h"
19 #include "SkGpuBlurUtils.h"
20 #include "SkNx.h"
21 #include "SkOpts.h"
22 #include "SkReadBuffer.h"
23 #include "SkSpecialImage.h"
24 #include "SkWriteBuffer.h"
25
26 #if SK_SUPPORT_GPU
27 #include "GrContext.h"
28 #include "GrTextureProxy.h"
29 #include "SkGr.h"
30 #endif
31
32 static constexpr double kPi = 3.14159265358979323846264338327950288;
33
34 class SkBlurImageFilterImpl final : public SkImageFilter {
35 public:
36 SkBlurImageFilterImpl(SkScalar sigmaX,
37 SkScalar sigmaY,
38 sk_sp<SkImageFilter> input,
39 const CropRect* cropRect,
40 SkBlurImageFilter::TileMode tileMode);
41
42 SkRect computeFastBounds(const SkRect&) const override;
43
44 SK_TO_STRING_OVERRIDE()
45 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlurImageFilterImpl)
46
47 protected:
48 void flatten(SkWriteBuffer&) const override;
49 sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
50 SkIPoint* offset) const override;
51 sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override;
52 SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix&, MapDirection) const override;
53
54 private:
55 typedef SkImageFilter INHERITED;
56 friend class SkImageFilter;
57
58 #if SK_SUPPORT_GPU
59 sk_sp<SkSpecialImage> gpuFilter(
60 SkSpecialImage *source,
61 SkVector sigma, const sk_sp<SkSpecialImage> &input,
62 SkIRect inputBounds, SkIRect dstBounds, const OutputProperties& outProps) const;
63 #endif
64
65 SkSize fSigma;
66 SkBlurImageFilter::TileMode fTileMode;
67 };
68
69 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkImageFilter)
SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurImageFilterImpl)70 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurImageFilterImpl)
71 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
72
73 ///////////////////////////////////////////////////////////////////////////////
74
75 sk_sp<SkImageFilter> SkBlurImageFilter::Make(SkScalar sigmaX, SkScalar sigmaY,
76 sk_sp<SkImageFilter> input,
77 const SkImageFilter::CropRect* cropRect,
78 TileMode tileMode) {
79 if (sigmaX < SK_ScalarNearlyZero && sigmaY < SK_ScalarNearlyZero && !cropRect) {
80 return input;
81 }
82 return sk_sp<SkImageFilter>(
83 new SkBlurImageFilterImpl(sigmaX, sigmaY, input, cropRect, tileMode));
84 }
85
86 // This rather arbitrary-looking value results in a maximum box blur kernel size
87 // of 1000 pixels on the raster path, which matches the WebKit and Firefox
88 // implementations. Since the GPU path does not compute a box blur, putting
89 // the limit on sigma ensures consistent behaviour between the GPU and
90 // raster paths.
91 #define MAX_SIGMA SkIntToScalar(532)
92
map_sigma(const SkSize & localSigma,const SkMatrix & ctm)93 static SkVector map_sigma(const SkSize& localSigma, const SkMatrix& ctm) {
94 SkVector sigma = SkVector::Make(localSigma.width(), localSigma.height());
95 ctm.mapVectors(&sigma, 1);
96 sigma.fX = SkMinScalar(SkScalarAbs(sigma.fX), MAX_SIGMA);
97 sigma.fY = SkMinScalar(SkScalarAbs(sigma.fY), MAX_SIGMA);
98 return sigma;
99 }
100
SkBlurImageFilterImpl(SkScalar sigmaX,SkScalar sigmaY,sk_sp<SkImageFilter> input,const CropRect * cropRect,SkBlurImageFilter::TileMode tileMode)101 SkBlurImageFilterImpl::SkBlurImageFilterImpl(SkScalar sigmaX,
102 SkScalar sigmaY,
103 sk_sp<SkImageFilter> input,
104 const CropRect* cropRect,
105 SkBlurImageFilter::TileMode tileMode)
106 : INHERITED(&input, 1, cropRect), fSigma{sigmaX, sigmaY}, fTileMode(tileMode) {}
107
CreateProc(SkReadBuffer & buffer)108 sk_sp<SkFlattenable> SkBlurImageFilterImpl::CreateProc(SkReadBuffer& buffer) {
109 SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
110 SkScalar sigmaX = buffer.readScalar();
111 SkScalar sigmaY = buffer.readScalar();
112 SkBlurImageFilter::TileMode tileMode;
113 if (buffer.isVersionLT(SkReadBuffer::kTileModeInBlurImageFilter_Version)) {
114 tileMode = SkBlurImageFilter::kClampToBlack_TileMode;
115 } else {
116 tileMode = buffer.read32LE(SkBlurImageFilter::kLast_TileMode);
117 }
118
119 static_assert(SkBlurImageFilter::kLast_TileMode == 2, "CreateProc");
120
121 return SkBlurImageFilter::Make(
122 sigmaX, sigmaY, common.getInput(0), &common.cropRect(), tileMode);
123 }
124
flatten(SkWriteBuffer & buffer) const125 void SkBlurImageFilterImpl::flatten(SkWriteBuffer& buffer) const {
126 this->INHERITED::flatten(buffer);
127 buffer.writeScalar(fSigma.fWidth);
128 buffer.writeScalar(fSigma.fHeight);
129
130 static_assert(SkBlurImageFilter::kLast_TileMode == 2, "flatten");
131 SkASSERT(fTileMode <= SkBlurImageFilter::kLast_TileMode);
132
133 buffer.writeInt(static_cast<int>(fTileMode));
134 }
135
136 #if SK_SUPPORT_GPU
to_texture_domain_mode(SkBlurImageFilter::TileMode tileMode)137 static GrTextureDomain::Mode to_texture_domain_mode(SkBlurImageFilter::TileMode tileMode) {
138 switch (tileMode) {
139 case SkBlurImageFilter::TileMode::kClamp_TileMode:
140 return GrTextureDomain::kClamp_Mode;
141 case SkBlurImageFilter::TileMode::kClampToBlack_TileMode:
142 return GrTextureDomain::kDecal_Mode;
143 case SkBlurImageFilter::TileMode::kRepeat_TileMode:
144 return GrTextureDomain::kRepeat_Mode;
145 default:
146 SK_ABORT("Unsupported tile mode.");
147 return GrTextureDomain::kDecal_Mode;
148 }
149 }
150 #endif
151
152 // This is defined by the SVG spec:
153 // https://drafts.fxtf.org/filter-effects/#feGaussianBlurElement
calculate_window(double sigma)154 static int calculate_window(double sigma) {
155 // NB 136 is the largest sigma that will not cause a buffer full of 255 mask values to overflow
156 // using the Gauss filter. It also limits the size of buffers used hold intermediate values.
157 // Explanation of maximums:
158 // sum0 = window * 255
159 // sum1 = window * sum0 -> window * window * 255
160 // sum2 = window * sum1 -> window * window * window * 255 -> window^3 * 255
161 //
162 // The value window^3 * 255 must fit in a uint32_t. So,
163 // window^3 < 2^32. window = 255.
164 //
165 // window = floor(sigma * 3 * sqrt(2 * kPi) / 4 + 0.5)
166 // For window <= 255, the largest value for sigma is 136.
167 sigma = SkTPin(sigma, 0.0, 136.0);
168 auto possibleWindow = static_cast<int>(floor(sigma * 3 * sqrt(2 * kPi) / 4 + 0.5));
169 return std::max(1, possibleWindow);
170 }
171
172 // Calculating the border is tricky. The border is the distance in pixels between the first dst
173 // pixel and the first src pixel (or the last src pixel and the last dst pixel).
174 // I will go through the odd case which is simpler, and then through the even case. Given a
175 // stack of filters seven wide for the odd case of three passes.
176 //
177 // S
178 // aaaAaaa
179 // bbbBbbb
180 // cccCccc
181 // D
182 //
183 // The furthest changed pixel is when the filters are in the following configuration.
184 //
185 // S
186 // aaaAaaa
187 // bbbBbbb
188 // cccCccc
189 // D
190 //
191 // The A pixel is calculated using the value S, the B uses A, and the C uses B, and
192 // finally D is C. So, with a window size of seven the border is nine. In the odd case, the
193 // border is 3*((window - 1)/2).
194 //
195 // For even cases the filter stack is more complicated. The spec specifies two passes
196 // of even filters and a final pass of odd filters. A stack for a width of six looks like
197 // this.
198 //
199 // S
200 // aaaAaa
201 // bbBbbb
202 // cccCccc
203 // D
204 //
205 // The furthest pixel looks like this.
206 //
207 // S
208 // aaaAaa
209 // bbBbbb
210 // cccCccc
211 // D
212 //
213 // For a window of six, the border value is eight. In the even case the border is 3 *
214 // (window/2) - 1.
calculate_border(int window)215 static int calculate_border(int window) {
216 return (window & 1) == 1 ? 3 * ((window - 1) / 2) : 3 * (window / 2) - 1;
217 }
218
calculate_buffer(int window)219 static int calculate_buffer(int window) {
220 int bufferSize = window - 1;
221 return (window & 1) == 1 ? 3 * bufferSize : 3 * bufferSize + 1;
222 }
223
224 // blur_one_direction implements the common three pass box filter approximation of Gaussian blur,
225 // but combines all three passes into a single pass. This approach is facilitated by three circular
226 // buffers the width of the window which track values for trailing edges of each of the three
227 // passes. This allows the algorithm to use more precision in the calculation because the values
228 // are not rounded each pass. And this implementation also avoids a trap that's easy to fall
229 // into resulting in blending in too many zeroes near the edge.
230 //
231 // In general, a window sum has the form:
232 // sum_n+1 = sum_n + leading_edge - trailing_edge.
233 // If instead we do the subtraction at the end of the previous iteration, we can just
234 // calculate the sums instead of having to do the subtractions too.
235 //
236 // In previous iteration:
237 // sum_n+1 = sum_n - trailing_edge.
238 //
239 // In this iteration:
240 // sum_n+1 = sum_n + leading_edge.
241 //
242 // Now we can stack all three sums and do them at once. Sum0 gets its leading edge from the
243 // actual data. Sum1's leading edge is just Sum0, and Sum2's leading edge is Sum1. So, doing the
244 // three passes at the same time has the form:
245 //
246 // sum0_n+1 = sum0_n + leading edge
247 // sum1_n+1 = sum1_n + sum0_n+1
248 // sum2_n+1 = sum2_n + sum1_n+1
249 //
250 // sum2_n+1 / window^3 is the new value of the destination pixel.
251 //
252 // Reduce the sums by the trailing edges which were stored in the circular buffers,
253 // for the next go around. This is the case for odd sized windows, even windows the the third
254 // circular buffer is one larger then the first two circular buffers.
255 //
256 // sum2_n+2 = sum2_n+1 - buffer2[i];
257 // buffer2[i] = sum1;
258 // sum1_n+2 = sum1_n+1 - buffer1[i];
259 // buffer1[i] = sum0;
260 // sum0_n+2 = sum0_n+1 - buffer0[i];
261 // buffer0[i] = leading edge
262 //
263 // This is all encapsulated in the processValue function below.
264 //
265 using Pass0And1 = Sk4u[2];
266 // The would be dLeft parameter is assumed to be 0.
blur_one_direction(Sk4u * buffer,int window,int srcLeft,int srcRight,int dstRight,const uint32_t * src,int srcXStride,int srcYStride,int srcH,uint32_t * dst,int dstXStride,int dstYStride)267 static void blur_one_direction(Sk4u* buffer, int window,
268 int srcLeft, int srcRight, int dstRight,
269 const uint32_t* src, int srcXStride, int srcYStride, int srcH,
270 uint32_t* dst, int dstXStride, int dstYStride) {
271
272 // The circular buffers are one less than the window.
273 auto pass0Count = window - 1,
274 pass1Count = window - 1,
275 pass2Count = (window & 1) == 1 ? window - 1 : window;
276
277 Pass0And1* buffer01Start = (Pass0And1*)buffer;
278 Sk4u* buffer2Start = buffer + pass0Count + pass1Count;
279 Pass0And1* buffer01End = (Pass0And1*)buffer2Start;
280 Sk4u* buffer2End = buffer2Start + pass2Count;
281
282 // If the window is odd then the divisor is just window ^ 3 otherwise,
283 // it is window * window * (window + 1) = window ^ 3 + window ^ 2;
284 auto window2 = window * window;
285 auto window3 = window2 * window;
286 auto divisor = (window & 1) == 1 ? window3 : window3 + window2;
287
288 // NB the sums in the blur code use the following technique to avoid
289 // adding 1/2 to round the divide.
290 //
291 // Sum/d + 1/2 == (Sum + h) / d
292 // Sum + d(1/2) == Sum + h
293 // h == (1/2)d
294 //
295 // But the d/2 it self should be rounded.
296 // h == d/2 + 1/2 == (d + 1) / 2
297 //
298 // weight = 1 / d * 2 ^ 32
299 auto weight = static_cast<uint32_t>(round(1.0 / divisor * (1ull << 32)));
300 auto half = static_cast<uint32_t>((divisor + 1) / 2);
301
302 auto border = calculate_border(window);
303
304 // Calculate the start and end of the source pixels with respect to the destination start.
305 auto srcStart = srcLeft - border,
306 srcEnd = srcRight - border,
307 dstEnd = dstRight;
308
309 for (auto y = 0; y < srcH; y++) {
310 auto buffer01Cursor = buffer01Start;
311 auto buffer2Cursor = buffer2Start;
312
313 Sk4u sum0{0u};
314 Sk4u sum1{0u};
315 Sk4u sum2{half};
316
317 sk_bzero(buffer01Start, (buffer2End - (Sk4u *) (buffer01Start)) * sizeof(*buffer2Start));
318
319 // Given an expanded input pixel, move the window ahead using the leadingEdge value.
320 auto processValue = [&](const Sk4u& leadingEdge) -> Sk4u {
321 sum0 += leadingEdge;
322 sum1 += sum0;
323 sum2 += sum1;
324
325 Sk4u value = sum2.mulHi(weight);
326
327 sum2 -= *buffer2Cursor;
328 *buffer2Cursor = sum1;
329 buffer2Cursor = (buffer2Cursor + 1) < buffer2End ? buffer2Cursor + 1 : buffer2Start;
330
331 sum1 -= (*buffer01Cursor)[1];
332 (*buffer01Cursor)[1] = sum0;
333 sum0 -= (*buffer01Cursor)[0];
334 (*buffer01Cursor)[0] = leadingEdge;
335 buffer01Cursor =
336 (buffer01Cursor + 1) < buffer01End ? buffer01Cursor + 1 : buffer01Start;
337
338 return value;
339 };
340
341 auto srcIdx = srcStart;
342 auto dstIdx = 0;
343 const uint32_t* srcCursor = src;
344 uint32_t* dstCursor = dst;
345
346 // The destination pixels are not effected by the src pixels,
347 // change to zero as per the spec.
348 // https://drafts.fxtf.org/filter-effects/#FilterPrimitivesOverviewIntro
349 while (dstIdx < srcIdx) {
350 *dstCursor = 0;
351 dstCursor += dstXStride;
352 SK_PREFETCH(dstCursor);
353 dstIdx++;
354 }
355
356 // The edge of the source is before the edge of the destination. Calculate the sums for
357 // the pixels before the start of the destination.
358 while (dstIdx > srcIdx) {
359 Sk4u leadingEdge = srcIdx < srcEnd ? SkNx_cast<uint32_t>(Sk4b::Load(srcCursor)) : 0;
360 (void) processValue(leadingEdge);
361 srcCursor += srcXStride;
362 srcIdx++;
363 }
364
365 // The dstIdx and srcIdx are in sync now; the code just uses the dstIdx for both now.
366 // Consume the source generating pixels to dst.
367 auto loopEnd = std::min(dstEnd, srcEnd);
368 while (dstIdx < loopEnd) {
369 Sk4u leadingEdge = SkNx_cast<uint32_t>(Sk4b::Load(srcCursor));
370 SkNx_cast<uint8_t>(processValue(leadingEdge)).store(dstCursor);
371 srcCursor += srcXStride;
372 dstCursor += dstXStride;
373 SK_PREFETCH(dstCursor);
374 dstIdx++;
375 }
376
377 // The leading edge is beyond the end of the source. Assume that the pixels
378 // are now 0x0000 until the end of the destination.
379 loopEnd = dstEnd;
380 while (dstIdx < loopEnd) {
381 SkNx_cast<uint8_t>(processValue(0u)).store(dstCursor);
382 dstCursor += dstXStride;
383 SK_PREFETCH(dstCursor);
384 dstIdx++;
385 }
386
387 src += srcYStride;
388 dst += dstYStride;
389 }
390 }
391
copy_image_with_bounds(SkSpecialImage * source,const sk_sp<SkSpecialImage> & input,SkIRect srcBounds,SkIRect dstBounds)392 static sk_sp<SkSpecialImage> copy_image_with_bounds(
393 SkSpecialImage *source, const sk_sp<SkSpecialImage> &input,
394 SkIRect srcBounds, SkIRect dstBounds) {
395 SkBitmap inputBM;
396 if (!input->getROPixels(&inputBM)) {
397 return nullptr;
398 }
399
400 if (inputBM.colorType() != kN32_SkColorType) {
401 return nullptr;
402 }
403
404 SkBitmap src;
405 inputBM.extractSubset(&src, srcBounds);
406
407 // Make everything relative to the destination bounds.
408 srcBounds.offset(-dstBounds.x(), -dstBounds.y());
409 dstBounds.offset(-dstBounds.x(), -dstBounds.y());
410
411 auto srcW = srcBounds.width(),
412 dstW = dstBounds.width(),
413 dstH = dstBounds.height();
414
415 SkImageInfo dstInfo = SkImageInfo::Make(dstW, dstH, inputBM.colorType(), inputBM.alphaType());
416
417 SkBitmap dst;
418 if (!dst.tryAllocPixels(dstInfo)) {
419 return nullptr;
420 }
421
422 // There is no blurring to do, but we still need to copy the source while accounting for the
423 // dstBounds. Remember that the src was intersected with the dst.
424 int y = 0;
425 size_t dstWBytes = dstW * sizeof(uint32_t);
426 for (;y < srcBounds.top(); y++) {
427 sk_bzero(dst.getAddr32(0, y), dstWBytes);
428 }
429
430 for (;y < srcBounds.bottom(); y++) {
431 int x = 0;
432 uint32_t* dstPtr = dst.getAddr32(0, y);
433 for (;x < srcBounds.left(); x++) {
434 *dstPtr++ = 0;
435 }
436
437 memcpy(dstPtr, src.getAddr32(x - srcBounds.left(), y - srcBounds.top()),
438 srcW * sizeof(uint32_t));
439
440 dstPtr += srcW;
441 x += srcW;
442
443 for (;x < dstBounds.right(); x++) {
444 *dstPtr++ = 0;
445 }
446 }
447
448 for (;y < dstBounds.bottom(); y++) {
449 sk_bzero(dst.getAddr32(0, y), dstWBytes);
450 }
451
452 return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(dstBounds.width(),
453 dstBounds.height()),
454 dst, &source->props());
455 }
456
457 // TODO: Implement CPU backend for different fTileMode.
cpu_blur(SkVector sigma,SkSpecialImage * source,const sk_sp<SkSpecialImage> & input,SkIRect srcBounds,SkIRect dstBounds)458 static sk_sp<SkSpecialImage> cpu_blur(
459 SkVector sigma,
460 SkSpecialImage *source, const sk_sp<SkSpecialImage> &input,
461 SkIRect srcBounds, SkIRect dstBounds) {
462 auto windowW = calculate_window(sigma.x()),
463 windowH = calculate_window(sigma.y());
464
465 if (windowW <= 1 && windowH <= 1) {
466 return copy_image_with_bounds(source, input, srcBounds, dstBounds);
467 }
468
469 SkBitmap inputBM;
470
471 if (!input->getROPixels(&inputBM)) {
472 return nullptr;
473 }
474
475 if (inputBM.colorType() != kN32_SkColorType) {
476 return nullptr;
477 }
478
479 SkBitmap src;
480 inputBM.extractSubset(&src, srcBounds);
481
482 // Make everything relative to the destination bounds.
483 srcBounds.offset(-dstBounds.x(), -dstBounds.y());
484 dstBounds.offset(-dstBounds.x(), -dstBounds.y());
485
486 auto srcW = srcBounds.width(),
487 srcH = srcBounds.height(),
488 dstW = dstBounds.width(),
489 dstH = dstBounds.height();
490
491 SkImageInfo dstInfo = SkImageInfo::Make(dstW, dstH, inputBM.colorType(), inputBM.alphaType());
492
493 SkBitmap dst;
494 if (!dst.tryAllocPixels(dstInfo)) {
495 return nullptr;
496 }
497
498 auto bufferSizeW = calculate_buffer(windowW),
499 bufferSizeH = calculate_buffer(windowH);
500
501 // The amount 1024 is enough for buffers up to 10 sigma. The tmp bitmap will be
502 // allocated on the heap.
503 SkSTArenaAlloc<1024> alloc;
504 Sk4u* buffer = alloc.makeArrayDefault<Sk4u>(std::max(bufferSizeW, bufferSizeH));
505
506 // Basic Plan: The three cases to handle
507 // * Horizontal and Vertical - blur horizontally while copying values from the source to
508 // the destination. Then, do an in-place vertical blur.
509 // * Horizontal only - blur horizontally copying values from the source to the destination.
510 // * Vertical only - blur vertically copying values from the source to the destination.
511
512 // Default to vertical only blur case. If a horizontal blur is needed, then these values
513 // will be adjusted while doing the horizontal blur.
514 auto intermediateSrc = static_cast<uint32_t *>(src.getPixels());
515 auto intermediateRowBytesAsPixels = src.rowBytesAsPixels();
516 auto intermediateWidth = srcW;
517
518 // Because the border is calculated before the fork of the GPU/CPU path. The border is
519 // the maximum of the two rendering methods. In the case where sigma is zero, then the
520 // src and dst left values are the same. If sigma is small resulting in a window size of
521 // 1, then border calculations add some pixels which will always be zero. Inset the
522 // destination by those zero pixels. This case is very rare.
523 auto intermediateDst = dst.getAddr32(srcBounds.left(), 0);
524
525 // The following code is executed very rarely, I have never seen it in a real web
526 // page. If sigma is small but not zero then shared GPU/CPU border calculation
527 // code adds extra pixels for the border. Just clear everything to clear those pixels.
528 // This solution is overkill, but very simple.
529 if (windowW == 1 || windowH == 1) {
530 dst.eraseColor(0);
531 }
532
533 if (windowW > 1) {
534 auto shift = srcBounds.top() - dstBounds.top();
535 // For the horizontal blur, starts part way down in anticipation of the vertical blur.
536 // For a vertical sigma of zero shift should be zero. But, for small sigma,
537 // shift may be > 0 but the vertical window could be 1.
538 intermediateSrc = static_cast<uint32_t *>(dst.getPixels())
539 + (shift > 0 ? shift * dst.rowBytesAsPixels() : 0);
540 intermediateRowBytesAsPixels = dst.rowBytesAsPixels();
541 intermediateWidth = dstW;
542 intermediateDst = static_cast<uint32_t *>(dst.getPixels());
543
544 blur_one_direction(
545 buffer, windowW,
546 srcBounds.left(), srcBounds.right(), dstBounds.right(),
547 static_cast<uint32_t *>(src.getPixels()), 1, src.rowBytesAsPixels(), srcH,
548 intermediateSrc, 1, intermediateRowBytesAsPixels);
549 }
550
551 if (windowH > 1) {
552 blur_one_direction(
553 buffer, windowH,
554 srcBounds.top(), srcBounds.bottom(), dstBounds.bottom(),
555 intermediateSrc, intermediateRowBytesAsPixels, 1, intermediateWidth,
556 intermediateDst, dst.rowBytesAsPixels(), 1);
557 }
558
559 return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(dstBounds.width(),
560 dstBounds.height()),
561 dst, &source->props());
562 }
563
onFilterImage(SkSpecialImage * source,const Context & ctx,SkIPoint * offset) const564 sk_sp<SkSpecialImage> SkBlurImageFilterImpl::onFilterImage(SkSpecialImage* source,
565 const Context& ctx,
566 SkIPoint* offset) const {
567 SkIPoint inputOffset = SkIPoint::Make(0, 0);
568
569 sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset));
570 if (!input) {
571 return nullptr;
572 }
573
574 SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.fX, inputOffset.fY,
575 input->width(), input->height());
576
577 // Calculate the destination bounds.
578 SkIRect dstBounds;
579 if (!this->applyCropRect(this->mapContext(ctx), inputBounds, &dstBounds)) {
580 return nullptr;
581 }
582 if (!inputBounds.intersect(dstBounds)) {
583 return nullptr;
584 }
585
586 // Save the offset in preparation to make all rectangles relative to the inputOffset.
587 SkIPoint resultOffset = SkIPoint::Make(dstBounds.fLeft, dstBounds.fTop);
588
589 // Make all bounds relative to the inputOffset.
590 inputBounds.offset(-inputOffset);
591 dstBounds.offset(-inputOffset);
592
593 const SkVector sigma = map_sigma(fSigma, ctx.ctm());
594 if (sigma.x() < 0 || sigma.y() < 0) {
595 return nullptr;
596 }
597
598 sk_sp<SkSpecialImage> result;
599 #if SK_SUPPORT_GPU
600 if (source->isTextureBacked()) {
601 // Ensure the input is in the destination's gamut. This saves us from having to do the
602 // xform during the filter itself.
603 input = ImageToColorSpace(input.get(), ctx.outputProperties());
604
605 result = this->gpuFilter(source, sigma, input, inputBounds, dstBounds,
606 ctx.outputProperties());
607 } else
608 #endif
609 {
610 result = cpu_blur(sigma, source, input, inputBounds, dstBounds);
611 }
612
613 // Return the resultOffset if the blur succeeded.
614 if (result != nullptr) {
615 *offset = resultOffset;
616 }
617 return result;
618 }
619
620 #if SK_SUPPORT_GPU
gpuFilter(SkSpecialImage * source,SkVector sigma,const sk_sp<SkSpecialImage> & input,SkIRect inputBounds,SkIRect dstBounds,const OutputProperties & outProps) const621 sk_sp<SkSpecialImage> SkBlurImageFilterImpl::gpuFilter(
622 SkSpecialImage *source,
623 SkVector sigma, const sk_sp<SkSpecialImage> &input,
624 SkIRect inputBounds, SkIRect dstBounds, const OutputProperties& outProps) const
625 {
626 // If both sigmas produce arms of the cross that are less than 1/2048, then they
627 // do not contribute to the sum of the filter in a way to change a gamma corrected result.
628 // Let s = 1/(2*sigma^2)
629 // The normalizing value n = 1 + 4*E^(-s) + 4*E^(-2s)
630 // The raw cross arm value c = E^-s
631 // The normalized cross arm value = c/n
632 // N[Solve[{c/n == 1/2048, sigma > 0}, sigma], 16]
633 static constexpr double kZeroWindowGPU = 0.2561130112451658;
634 if (sigma.x() < kZeroWindowGPU && sigma.y() < kZeroWindowGPU) {
635 return copy_image_with_bounds(source, input, inputBounds, dstBounds);
636 }
637
638 GrContext* context = source->getContext();
639
640 sk_sp<GrTextureProxy> inputTexture(input->asTextureProxyRef(context));
641 if (!inputTexture) {
642 return nullptr;
643 }
644
645 // Typically, we would create the RTC with the output's color space (from ctx), but we
646 // always blur in the PixelConfig of the *input*. Those might not be compatible (if they
647 // have different transfer functions). We've already guaranteed that those color spaces
648 // have the same gamut, so in this case, we do everything in the input's color space.
649 // ...
650 // Unless the output is legacy. In that case, the input could be almost anything (if we're
651 // using SkColorSpaceXformCanvas), but we can't make a corresponding RTC. We don't care to,
652 // either, we want to do our blending (and blurring) without any color correction, so pass
653 // nullptr here, causing us to operate entirely in the input's color space, with no decoding.
654 // Then, when we create the output image later, we tag it with the input's color space, so
655 // it will be tagged correctly, regardless of how we created the intermediate RTCs.
656 sk_sp<GrRenderTargetContext> renderTargetContext(SkGpuBlurUtils::GaussianBlur(
657 context,
658 std::move(inputTexture),
659 outProps.colorSpace() ? sk_ref_sp(input->getColorSpace()) : nullptr,
660 dstBounds,
661 inputBounds,
662 sigma.x(),
663 sigma.y(),
664 to_texture_domain_mode(fTileMode)));
665 if (!renderTargetContext) {
666 return nullptr;
667 }
668
669 return SkSpecialImage::MakeDeferredFromGpu(
670 context,
671 SkIRect::MakeWH(dstBounds.width(), dstBounds.height()),
672 kNeedNewImageUniqueID_SpecialImage,
673 renderTargetContext->asTextureProxyRef(),
674 sk_ref_sp(input->getColorSpace()),
675 &source->props());
676 }
677 #endif
678
onMakeColorSpace(SkColorSpaceXformer * xformer) const679 sk_sp<SkImageFilter> SkBlurImageFilterImpl::onMakeColorSpace(SkColorSpaceXformer* xformer)
680 const {
681 SkASSERT(1 == this->countInputs());
682
683 auto input = xformer->apply(this->getInput(0));
684 if (this->getInput(0) != input.get()) {
685 return SkBlurImageFilter::Make(fSigma.width(), fSigma.height(), std::move(input),
686 this->getCropRectIfSet(), fTileMode);
687 }
688 return this->refMe();
689 }
690
computeFastBounds(const SkRect & src) const691 SkRect SkBlurImageFilterImpl::computeFastBounds(const SkRect& src) const {
692 SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
693 bounds.outset(fSigma.width() * 3, fSigma.height() * 3);
694 return bounds;
695 }
696
onFilterNodeBounds(const SkIRect & src,const SkMatrix & ctm,MapDirection) const697 SkIRect SkBlurImageFilterImpl::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
698 MapDirection) const {
699 SkVector sigma = map_sigma(fSigma, ctm);
700 return src.makeOutset(SkScalarCeilToInt(sigma.x() * 3), SkScalarCeilToInt(sigma.y() * 3));
701 }
702
703 #ifndef SK_IGNORE_TO_STRING
toString(SkString * str) const704 void SkBlurImageFilterImpl::toString(SkString* str) const {
705 str->appendf("SkBlurImageFilterImpl: (");
706 str->appendf("sigma: (%f, %f) tileMode: %d input (", fSigma.fWidth, fSigma.fHeight,
707 static_cast<int>(fTileMode));
708
709 if (this->getInput(0)) {
710 this->getInput(0)->toString(str);
711 }
712
713 str->append("))");
714 }
715 #endif
716