Home | History | Annotate | Download | only in core
      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)
     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 
     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 
    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 
    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 
    125 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
    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
    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.
    215 static int calculate_border(int window) {
    216     return (window & 1) == 1 ? 3 * ((window - 1) / 2) : 3 * (window / 2) - 1;
    217 }
    218 
    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.
    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 
    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.
    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 
    564 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
    621 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 
    679 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 
    691 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 
    697 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
    704 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