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 32static constexpr double kPi = 3.14159265358979323846264338327950288; 33 34class SkBlurImageFilterImpl final : public SkImageFilter { 35public: 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 47protected: 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 54private: 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 69SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkImageFilter) 70 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurImageFilterImpl) 71SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END 72 73/////////////////////////////////////////////////////////////////////////////// 74 75sk_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 93static 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 101SkBlurImageFilterImpl::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 108sk_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 125void 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 137static 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 154static 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. 215static int calculate_border(int window) { 216 return (window & 1) == 1 ? 3 * ((window - 1) / 2) : 3 * (window / 2) - 1; 217} 218 219static 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// 265using Pass0And1 = Sk4u[2]; 266// The would be dLeft parameter is assumed to be 0. 267static 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 392static 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. 458static 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 564sk_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 621sk_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 679sk_sp<SkImageFilter> SkBlurImageFilterImpl::onMakeColorSpace(SkColorSpaceXformer* xformer) 680const { 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 691SkRect 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 697SkIRect 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 704void 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