xref: /aosp_15_r20/external/skia/src/core/SkBlurMask.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2006 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 "src/core/SkBlurMask.h"
9 
10 #include "include/core/SkBlurTypes.h"
11 #include "include/core/SkColorPriv.h"
12 #include "include/core/SkPoint.h"
13 #include "include/core/SkRect.h"
14 #include "include/private/base/SkMath.h"
15 #include "include/private/base/SkSafe32.h"
16 #include "include/private/base/SkTPin.h"
17 #include "include/private/base/SkTemplates.h"
18 #include "include/private/base/SkTo.h"
19 #include "src/base/SkMathPriv.h"
20 #include "src/core/SkMaskBlurFilter.h"
21 
22 #include <cmath>
23 #include <cstring>
24 #include <utility>
25 
26 class SkRRect;
27 
28 using namespace skia_private;
29 
30 // This constant approximates the scaling done in the software path's
31 // "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)).
32 // IMHO, it actually should be 1:  we blur "less" than we should do
33 // according to the CSS and canvas specs, simply because Safari does the same.
34 // Firefox used to do the same too, until 4.0 where they fixed it.  So at some
35 // point we should probably get rid of these scaling constants and rebaseline
36 // all the blur tests.
37 static const SkScalar kBLUR_SIGMA_SCALE = 0.57735f;
38 
ConvertRadiusToSigma(SkScalar radius)39 SkScalar SkBlurMask::ConvertRadiusToSigma(SkScalar radius) {
40     return radius > 0 ? kBLUR_SIGMA_SCALE * radius + 0.5f : 0.0f;
41 }
42 
ConvertSigmaToRadius(SkScalar sigma)43 SkScalar SkBlurMask::ConvertSigmaToRadius(SkScalar sigma) {
44     return sigma > 0.5f ? (sigma - 0.5f) / kBLUR_SIGMA_SCALE : 0.0f;
45 }
46 
47 
48 template <typename AlphaIter>
merge_src_with_blur(uint8_t dst[],int dstRB,AlphaIter src,int srcRB,const uint8_t blur[],int blurRB,int sw,int sh)49 static void merge_src_with_blur(uint8_t dst[], int dstRB,
50                                 AlphaIter src, int srcRB,
51                                 const uint8_t blur[], int blurRB,
52                                 int sw, int sh) {
53     dstRB -= sw;
54     blurRB -= sw;
55     while (--sh >= 0) {
56         AlphaIter rowSrc(src);
57         for (int x = sw - 1; x >= 0; --x) {
58             *dst = SkToU8(SkAlphaMul(*blur, SkAlpha255To256(*rowSrc)));
59             ++dst;
60             ++rowSrc;
61             ++blur;
62         }
63         dst += dstRB;
64         src >>= srcRB;
65         blur += blurRB;
66     }
67 }
68 
69 template <typename AlphaIter>
clamp_solid_with_orig(uint8_t dst[],int dstRowBytes,AlphaIter src,int srcRowBytes,int sw,int sh)70 static void clamp_solid_with_orig(uint8_t dst[], int dstRowBytes,
71                                   AlphaIter src, int srcRowBytes,
72                                   int sw, int sh) {
73     int x;
74     while (--sh >= 0) {
75         AlphaIter rowSrc(src);
76         for (x = sw - 1; x >= 0; --x) {
77             int s = *rowSrc;
78             int d = *dst;
79             *dst = SkToU8(s + d - SkMulDiv255Round(s, d));
80             ++dst;
81             ++rowSrc;
82         }
83         dst += dstRowBytes - sw;
84         src >>= srcRowBytes;
85     }
86 }
87 
88 template <typename AlphaIter>
clamp_outer_with_orig(uint8_t dst[],int dstRowBytes,AlphaIter src,int srcRowBytes,int sw,int sh)89 static void clamp_outer_with_orig(uint8_t dst[], int dstRowBytes,
90                                   AlphaIter src, int srcRowBytes,
91                                   int sw, int sh) {
92     int x;
93     while (--sh >= 0) {
94         AlphaIter rowSrc(src);
95         for (x = sw - 1; x >= 0; --x) {
96             int srcValue = *rowSrc;
97             if (srcValue) {
98                 *dst = SkToU8(SkAlphaMul(*dst, SkAlpha255To256(255 - srcValue)));
99             }
100             ++dst;
101             ++rowSrc;
102         }
103         dst += dstRowBytes - sw;
104         src >>= srcRowBytes;
105     }
106 }
107 ///////////////////////////////////////////////////////////////////////////////
108 
109 // we use a local function to wrap the class static method to work around
110 // a bug in gcc98
111 void SkMask_FreeImage(uint8_t* image);
SkMask_FreeImage(uint8_t * image)112 void SkMask_FreeImage(uint8_t* image) {
113     SkMaskBuilder::FreeImage(image);
114 }
115 
BoxBlur(SkMaskBuilder * dst,const SkMask & src,SkScalar sigma,SkBlurStyle style,SkIPoint * margin)116 bool SkBlurMask::BoxBlur(SkMaskBuilder* dst, const SkMask& src, SkScalar sigma, SkBlurStyle style,
117                          SkIPoint* margin) {
118     if (src.fFormat != SkMask::kBW_Format &&
119         src.fFormat != SkMask::kA8_Format &&
120         src.fFormat != SkMask::kARGB32_Format &&
121         src.fFormat != SkMask::kLCD16_Format)
122     {
123         return false;
124     }
125 
126     SkMaskBlurFilter blurFilter{sigma, sigma};
127     if (blurFilter.hasNoBlur()) {
128         // If there is no effective blur most styles will just produce the original mask.
129         // However, kOuter_SkBlurStyle will produce an empty mask.
130         if (style == kOuter_SkBlurStyle) {
131             dst->image() = nullptr;
132             dst->bounds() = SkIRect::MakeEmpty();
133             dst->rowBytes() = dst->fBounds.width();
134             dst->format() = SkMask::kA8_Format;
135             if (margin != nullptr) {
136                 // This filter will disregard the src.fImage completely.
137                 // The margin is actually {-(src.fBounds.width() / 2), -(src.fBounds.height() / 2)}
138                 // but it is not clear if callers will fall over with negative margins.
139                 *margin = SkIPoint{0,0};
140             }
141             return true;
142         }
143         return false;
144     }
145     const SkIPoint border = blurFilter.blur(src, dst);
146     // If src.fImage is null, then this call is only to calculate the border.
147     if (src.fImage != nullptr && dst->fImage == nullptr) {
148         return false;
149     }
150 
151     if (margin != nullptr) {
152         *margin = border;
153     }
154 
155     if (src.fImage == nullptr) {
156         if (style == kInner_SkBlurStyle) {
157             dst->bounds() = src.fBounds; // restore trimmed bounds
158             dst->rowBytes() = dst->fBounds.width();
159         }
160         return true;
161     }
162 
163     switch (style) {
164         case kNormal_SkBlurStyle:
165             break;
166         case kSolid_SkBlurStyle: {
167             auto dstStart = &dst->image()[border.x() + border.y() * dst->fRowBytes];
168             switch (src.fFormat) {
169                 case SkMask::kBW_Format:
170                     clamp_solid_with_orig(
171                             dstStart, dst->fRowBytes,
172                             SkMask::AlphaIter<SkMask::kBW_Format>(src.fImage, 0), src.fRowBytes,
173                             src.fBounds.width(), src.fBounds.height());
174                     break;
175                 case SkMask::kA8_Format:
176                     clamp_solid_with_orig(
177                             dstStart, dst->fRowBytes,
178                             SkMask::AlphaIter<SkMask::kA8_Format>(src.fImage), src.fRowBytes,
179                             src.fBounds.width(), src.fBounds.height());
180                     break;
181                 case SkMask::kARGB32_Format: {
182                     const uint32_t* srcARGB = reinterpret_cast<const uint32_t*>(src.fImage);
183                     clamp_solid_with_orig(
184                             dstStart, dst->fRowBytes,
185                             SkMask::AlphaIter<SkMask::kARGB32_Format>(srcARGB), src.fRowBytes,
186                             src.fBounds.width(), src.fBounds.height());
187                 } break;
188                 case SkMask::kLCD16_Format: {
189                     const uint16_t* srcLCD = reinterpret_cast<const uint16_t*>(src.fImage);
190                     clamp_solid_with_orig(
191                             dstStart, dst->fRowBytes,
192                             SkMask::AlphaIter<SkMask::kLCD16_Format>(srcLCD), src.fRowBytes,
193                             src.fBounds.width(), src.fBounds.height());
194                 } break;
195                 default:
196                     SK_ABORT("Unhandled format.");
197             }
198         } break;
199         case kOuter_SkBlurStyle: {
200             auto dstStart = &dst->image()[border.x() + border.y() * dst->fRowBytes];
201             switch (src.fFormat) {
202                 case SkMask::kBW_Format:
203                     clamp_outer_with_orig(
204                             dstStart, dst->fRowBytes,
205                             SkMask::AlphaIter<SkMask::kBW_Format>(src.fImage, 0), src.fRowBytes,
206                             src.fBounds.width(), src.fBounds.height());
207                     break;
208                 case SkMask::kA8_Format:
209                     clamp_outer_with_orig(
210                             dstStart, dst->fRowBytes,
211                             SkMask::AlphaIter<SkMask::kA8_Format>(src.fImage), src.fRowBytes,
212                             src.fBounds.width(), src.fBounds.height());
213                     break;
214                 case SkMask::kARGB32_Format: {
215                     const uint32_t* srcARGB = reinterpret_cast<const uint32_t*>(src.fImage);
216                     clamp_outer_with_orig(
217                             dstStart, dst->fRowBytes,
218                             SkMask::AlphaIter<SkMask::kARGB32_Format>(srcARGB), src.fRowBytes,
219                             src.fBounds.width(), src.fBounds.height());
220                 } break;
221                 case SkMask::kLCD16_Format: {
222                     const uint16_t* srcLCD = reinterpret_cast<const uint16_t*>(src.fImage);
223                     clamp_outer_with_orig(
224                             dstStart, dst->fRowBytes,
225                             SkMask::AlphaIter<SkMask::kLCD16_Format>(srcLCD), src.fRowBytes,
226                             src.fBounds.width(), src.fBounds.height());
227                 } break;
228                 default:
229                     SK_ABORT("Unhandled format.");
230             }
231         } break;
232         case kInner_SkBlurStyle: {
233             // now we allocate the "real" dst, mirror the size of src
234             SkMaskBuilder blur = std::move(*dst);
235             SkAutoMaskFreeImage autoFreeBlurMask(blur.image());
236 
237             *dst = SkMaskBuilder(nullptr, src.fBounds, src.fBounds.width(), blur.format());
238             size_t dstSize = dst->computeImageSize();
239             if (0 == dstSize) {
240                 return false;   // too big to allocate, abort
241             }
242             dst->image() = SkMaskBuilder::AllocImage(dstSize);
243 
244             auto blurStart = &blur.image()[border.x() + border.y() * blur.fRowBytes];
245             switch (src.fFormat) {
246                 case SkMask::kBW_Format:
247                     merge_src_with_blur(
248                             dst->image(), dst->fRowBytes,
249                             SkMask::AlphaIter<SkMask::kBW_Format>(src.fImage, 0), src.fRowBytes,
250                             blurStart, blur.fRowBytes,
251                             src.fBounds.width(), src.fBounds.height());
252                     break;
253                 case SkMask::kA8_Format:
254                     merge_src_with_blur(
255                             dst->image(), dst->fRowBytes,
256                             SkMask::AlphaIter<SkMask::kA8_Format>(src.fImage), src.fRowBytes,
257                             blurStart, blur.fRowBytes,
258                             src.fBounds.width(), src.fBounds.height());
259                     break;
260                 case SkMask::kARGB32_Format: {
261                     const uint32_t* srcARGB = reinterpret_cast<const uint32_t*>(src.fImage);
262                     merge_src_with_blur(
263                             dst->image(), dst->fRowBytes,
264                             SkMask::AlphaIter<SkMask::kARGB32_Format>(srcARGB), src.fRowBytes,
265                             blurStart, blur.fRowBytes,
266                             src.fBounds.width(), src.fBounds.height());
267                 } break;
268                 case SkMask::kLCD16_Format: {
269                     const uint16_t* srcLCD = reinterpret_cast<const uint16_t*>(src.fImage);
270                     merge_src_with_blur(
271                             dst->image(), dst->fRowBytes,
272                             SkMask::AlphaIter<SkMask::kLCD16_Format>(srcLCD), src.fRowBytes,
273                             blurStart, blur.fRowBytes,
274                             src.fBounds.width(), src.fBounds.height());
275                 } break;
276                 default:
277                     SK_ABORT("Unhandled format.");
278             }
279         } break;
280     }
281 
282     return true;
283 }
284 
285 /* Convolving a box with itself three times results in a piecewise
286    quadratic function:
287 
288    0                              x <= -1.5
289    9/8 + 3/2 x + 1/2 x^2   -1.5 < x <= -.5
290    3/4 - x^2                -.5 < x <= .5
291    9/8 - 3/2 x + 1/2 x^2    0.5 < x <= 1.5
292    0                        1.5 < x
293 
294    Mathematica:
295 
296    g[x_] := Piecewise [ {
297      {9/8 + 3/2 x + 1/2 x^2 ,  -1.5 < x <= -.5},
298      {3/4 - x^2             ,   -.5 < x <= .5},
299      {9/8 - 3/2 x + 1/2 x^2 ,   0.5 < x <= 1.5}
300    }, 0]
301 
302    To get the profile curve of the blurred step function at the rectangle
303    edge, we evaluate the indefinite integral, which is piecewise cubic:
304 
305    0                                        x <= -1.5
306    9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3   -1.5 < x <= -0.5
307    1/2 + 3/4 x - 1/3 x^3              -.5 < x <= .5
308    7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3     .5 < x <= 1.5
309    1                                  1.5 < x
310 
311    in Mathematica code:
312 
313    gi[x_] := Piecewise[ {
314      { 0 , x <= -1.5 },
315      { 9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3, -1.5 < x <= -0.5 },
316      { 1/2 + 3/4 x - 1/3 x^3          ,  -.5 < x <= .5},
317      { 7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3,   .5 < x <= 1.5}
318    },1]
319 */
320 
gaussianIntegral(float x)321 static float gaussianIntegral(float x) {
322     if (x > 1.5f) {
323         return 0.0f;
324     }
325     if (x < -1.5f) {
326         return 1.0f;
327     }
328 
329     float x2 = x*x;
330     float x3 = x2*x;
331 
332     if ( x > 0.5f ) {
333         return 0.5625f - (x3 / 6.0f - 3.0f * x2 * 0.25f + 1.125f * x);
334     }
335     if ( x > -0.5f ) {
336         return 0.5f - (0.75f * x - x3 / 3.0f);
337     }
338     return 0.4375f + (-x3 / 6.0f - 3.0f * x2 * 0.25f - 1.125f * x);
339 }
340 
341 /*  ComputeBlurProfile fills in an array of floating
342     point values between 0 and 255 for the profile signature of
343     a blurred half-plane with the given blur radius.  Since we're
344     going to be doing screened multiplications (i.e., 1 - (1-x)(1-y))
345     all the time, we actually fill in the profile pre-inverted
346     (already done 255-x).
347 */
348 
ComputeBlurProfile(uint8_t * profile,int size,SkScalar sigma)349 void SkBlurMask::ComputeBlurProfile(uint8_t* profile, int size, SkScalar sigma) {
350     SkASSERT(SkScalarCeilToInt(6*sigma) == size);
351 
352     int center = size >> 1;
353 
354     float invr = 1.f/(2*sigma);
355 
356     profile[0] = 255;
357     for (int x = 1 ; x < size ; ++x) {
358         float scaled_x = (center - x - .5f) * invr;
359         float gi = gaussianIntegral(scaled_x);
360         profile[x] = 255 - (uint8_t) (255.f * gi);
361     }
362 }
363 
364 // TODO MAYBE: Maintain a profile cache to avoid recomputing this for
365 // commonly used radii.  Consider baking some of the most common blur radii
366 // directly in as static data?
367 
368 // Implementation adapted from Michael Herf's approach:
369 // http://stereopsis.com/shadowrect/
370 
ProfileLookup(const uint8_t * profile,int loc,int blurredWidth,int sharpWidth)371 uint8_t SkBlurMask::ProfileLookup(const uint8_t *profile, int loc,
372                                   int blurredWidth, int sharpWidth) {
373     // how far are we from the original edge?
374     int dx = SkAbs32(((loc << 1) + 1) - blurredWidth) - sharpWidth;
375     int ox = dx >> 1;
376     if (ox < 0) {
377         ox = 0;
378     }
379 
380     return profile[ox];
381 }
382 
ComputeBlurredScanline(uint8_t * pixels,const uint8_t * profile,unsigned int width,SkScalar sigma)383 void SkBlurMask::ComputeBlurredScanline(uint8_t *pixels, const uint8_t *profile,
384                                         unsigned int width, SkScalar sigma) {
385 
386     unsigned int profile_size = SkScalarCeilToInt(6*sigma);
387     skia_private::AutoTMalloc<uint8_t> horizontalScanline(width);
388 
389     unsigned int sw = width - profile_size;
390     // nearest odd number less than the profile size represents the center
391     // of the (2x scaled) profile
392     int center = ( profile_size & ~1 ) - 1;
393 
394     int w = sw - center;
395 
396     for (unsigned int x = 0 ; x < width ; ++x) {
397        if (profile_size <= sw) {
398            pixels[x] = ProfileLookup(profile, x, width, w);
399        } else {
400            float span = float(sw)/(2*sigma);
401            float giX = 1.5f - (x+.5f)/(2*sigma);
402            pixels[x] = (uint8_t) (255 * (gaussianIntegral(giX) - gaussianIntegral(giX + span)));
403        }
404     }
405 }
406 
BlurRect(SkScalar sigma,SkMaskBuilder * dst,const SkRect & src,SkBlurStyle style,SkIPoint * margin,SkMaskBuilder::CreateMode createMode)407 bool SkBlurMask::BlurRect(SkScalar sigma, SkMaskBuilder *dst,
408                           const SkRect &src, SkBlurStyle style,
409                           SkIPoint *margin, SkMaskBuilder::CreateMode createMode) {
410     int profileSize = SkScalarCeilToInt(6*sigma);
411     if (profileSize <= 0) {
412         return false;   // no blur to compute
413     }
414 
415     int pad = profileSize/2;
416     if (margin) {
417         margin->set( pad, pad );
418     }
419 
420     dst->bounds().setLTRB(SkScalarRoundToInt(src.fLeft - pad),
421                          SkScalarRoundToInt(src.fTop - pad),
422                          SkScalarRoundToInt(src.fRight + pad),
423                          SkScalarRoundToInt(src.fBottom + pad));
424 
425     dst->rowBytes() = dst->fBounds.width();
426     dst->format() = SkMask::kA8_Format;
427     dst->image() = nullptr;
428 
429     int             sw = SkScalarFloorToInt(src.width());
430     int             sh = SkScalarFloorToInt(src.height());
431 
432     if (createMode == SkMaskBuilder::kJustComputeBounds_CreateMode) {
433         if (style == kInner_SkBlurStyle) {
434             dst->bounds() = src.round(); // restore trimmed bounds
435             dst->rowBytes() = sw;
436         }
437         return true;
438     }
439 
440     AutoTMalloc<uint8_t> profile(profileSize);
441 
442     ComputeBlurProfile(profile, profileSize, sigma);
443 
444     size_t dstSize = dst->computeImageSize();
445     if (0 == dstSize) {
446         return false;   // too big to allocate, abort
447     }
448 
449     uint8_t* dp = SkMaskBuilder::AllocImage(dstSize);
450     dst->image() = dp;
451 
452     int dstHeight = dst->fBounds.height();
453     int dstWidth = dst->fBounds.width();
454 
455     uint8_t *outptr = dp;
456 
457     AutoTMalloc<uint8_t> horizontalScanline(dstWidth);
458     AutoTMalloc<uint8_t> verticalScanline(dstHeight);
459 
460     ComputeBlurredScanline(horizontalScanline, profile, dstWidth, sigma);
461     ComputeBlurredScanline(verticalScanline, profile, dstHeight, sigma);
462 
463     for (int y = 0 ; y < dstHeight ; ++y) {
464         for (int x = 0 ; x < dstWidth ; x++) {
465             unsigned int maskval = SkMulDiv255Round(horizontalScanline[x], verticalScanline[y]);
466             *(outptr++) = maskval;
467         }
468     }
469 
470     if (style == kInner_SkBlurStyle) {
471         // now we allocate the "real" dst, mirror the size of src
472         size_t srcSize = (size_t)(src.width() * src.height());
473         if (0 == srcSize) {
474             return false;   // too big to allocate, abort
475         }
476         dst->image() = SkMaskBuilder::AllocImage(srcSize);
477         for (int y = 0 ; y < sh ; y++) {
478             uint8_t *blur_scanline = dp + (y+pad)*dstWidth + pad;
479             uint8_t *inner_scanline = dst->image() + y*sw;
480             memcpy(inner_scanline, blur_scanline, sw);
481         }
482         SkMaskBuilder::FreeImage(dp);
483 
484         dst->bounds() = src.round(); // restore trimmed bounds
485         dst->rowBytes() = sw;
486 
487     } else if (style == kOuter_SkBlurStyle) {
488         for (int y = pad ; y < dstHeight-pad ; y++) {
489             uint8_t *dst_scanline = dp + y*dstWidth + pad;
490             memset(dst_scanline, 0, sw);
491         }
492     } else if (style == kSolid_SkBlurStyle) {
493         for (int y = pad ; y < dstHeight-pad ; y++) {
494             uint8_t *dst_scanline = dp + y*dstWidth + pad;
495             memset(dst_scanline, 0xff, sw);
496         }
497     }
498     // normal and solid styles are the same for analytic rect blurs, so don't
499     // need to handle solid specially.
500 
501     return true;
502 }
503 
BlurRRect(SkScalar sigma,SkMaskBuilder * dst,const SkRRect & src,SkBlurStyle style,SkIPoint * margin,SkMaskBuilder::CreateMode createMode)504 bool SkBlurMask::BlurRRect(SkScalar sigma, SkMaskBuilder *dst,
505                            const SkRRect &src, SkBlurStyle style,
506                            SkIPoint *margin, SkMaskBuilder::CreateMode createMode) {
507     // Temporary for now -- always fail, should cause caller to fall back
508     // to old path.  Plumbing just to land API and parallelize effort.
509 
510     return false;
511 }
512 
513 // The "simple" blur is a direct implementation of separable convolution with a discrete
514 // gaussian kernel.  It's "ground truth" in a sense; too slow to be used, but very
515 // useful for correctness comparisons.
516 
BlurGroundTruth(SkScalar sigma,SkMaskBuilder * dst,const SkMask & src,SkBlurStyle style,SkIPoint * margin)517 bool SkBlurMask::BlurGroundTruth(SkScalar sigma, SkMaskBuilder* dst, const SkMask& src,
518                                  SkBlurStyle style, SkIPoint* margin) {
519 
520     if (src.fFormat != SkMask::kA8_Format) {
521         return false;
522     }
523 
524     float variance = sigma * sigma;
525 
526     int windowSize = SkScalarCeilToInt(sigma*6);
527     // round window size up to nearest odd number
528     windowSize |= 1;
529 
530     AutoTMalloc<float> gaussWindow(windowSize);
531 
532     int halfWindow = windowSize >> 1;
533 
534     gaussWindow[halfWindow] = 1;
535 
536     float windowSum = 1;
537     for (int x = 1 ; x <= halfWindow ; ++x) {
538         float gaussian = expf(-x*x / (2*variance));
539         gaussWindow[halfWindow + x] = gaussWindow[halfWindow-x] = gaussian;
540         windowSum += 2*gaussian;
541     }
542 
543     // leave the filter un-normalized for now; we will divide by the normalization
544     // sum later;
545 
546     int pad = halfWindow;
547     if (margin) {
548         margin->set( pad, pad );
549     }
550 
551     dst->bounds() = src.fBounds;
552     dst->bounds().outset(pad, pad);
553 
554     dst->rowBytes() = dst->fBounds.width();
555     dst->format() = SkMask::kA8_Format;
556     dst->image() = nullptr;
557 
558     if (src.fImage) {
559 
560         size_t dstSize = dst->computeImageSize();
561         if (0 == dstSize) {
562             return false;   // too big to allocate, abort
563         }
564 
565         int             srcWidth = src.fBounds.width();
566         int             srcHeight = src.fBounds.height();
567         int             dstWidth = dst->fBounds.width();
568 
569         const uint8_t*  srcPixels = src.fImage;
570         uint8_t*        dstPixels = SkMaskBuilder::AllocImage(dstSize);
571         SkAutoMaskFreeImage autoFreeDstPixels(dstPixels);
572 
573         // do the actual blur.  First, make a padded copy of the source.
574         // use double pad so we never have to check if we're outside anything
575 
576         int padWidth = srcWidth + 4*pad;
577         int padHeight = srcHeight;
578         int padSize = padWidth * padHeight;
579 
580         AutoTMalloc<uint8_t> padPixels(padSize);
581         memset(padPixels, 0, padSize);
582 
583         for (int y = 0 ; y < srcHeight; ++y) {
584             uint8_t* padptr = padPixels + y * padWidth + 2*pad;
585             const uint8_t* srcptr = srcPixels + y * srcWidth;
586             memcpy(padptr, srcptr, srcWidth);
587         }
588 
589         // blur in X, transposing the result into a temporary floating point buffer.
590         // also double-pad the intermediate result so that the second blur doesn't
591         // have to do extra conditionals.
592 
593         int tmpWidth = padHeight + 4*pad;
594         int tmpHeight = padWidth - 2*pad;
595         int tmpSize = tmpWidth * tmpHeight;
596 
597         AutoTMalloc<float> tmpImage(tmpSize);
598         memset(tmpImage, 0, tmpSize*sizeof(tmpImage[0]));
599 
600         for (int y = 0 ; y < padHeight ; ++y) {
601             uint8_t *srcScanline = padPixels + y*padWidth;
602             for (int x = pad ; x < padWidth - pad ; ++x) {
603                 float *outPixel = tmpImage + (x-pad)*tmpWidth + y + 2*pad; // transposed output
604                 uint8_t *windowCenter = srcScanline + x;
605                 for (int i = -pad ; i <= pad ; ++i) {
606                     *outPixel += gaussWindow[pad+i]*windowCenter[i];
607                 }
608                 *outPixel /= windowSum;
609             }
610         }
611 
612         // blur in Y; now filling in the actual desired destination.  We have to do
613         // the transpose again; these transposes guarantee that we read memory in
614         // linear order.
615 
616         for (int y = 0 ; y < tmpHeight ; ++y) {
617             float *srcScanline = tmpImage + y*tmpWidth;
618             for (int x = pad ; x < tmpWidth - pad ; ++x) {
619                 float *windowCenter = srcScanline + x;
620                 float finalValue = 0;
621                 for (int i = -pad ; i <= pad ; ++i) {
622                     finalValue += gaussWindow[pad+i]*windowCenter[i];
623                 }
624                 finalValue /= windowSum;
625                 uint8_t *outPixel = dstPixels + (x-pad)*dstWidth + y; // transposed output
626                 int integerPixel = int(finalValue + 0.5f);
627                 *outPixel = SkTPin(SkClampPos(integerPixel), 0, 255);
628             }
629         }
630 
631         dst->image() = dstPixels;
632         switch (style) {
633             case kNormal_SkBlurStyle:
634                 break;
635             case kSolid_SkBlurStyle: {
636                 clamp_solid_with_orig(
637                         dstPixels + pad*dst->fRowBytes + pad, dst->fRowBytes,
638                         SkMask::AlphaIter<SkMask::kA8_Format>(srcPixels), src.fRowBytes,
639                         srcWidth, srcHeight);
640             } break;
641             case kOuter_SkBlurStyle: {
642                 clamp_outer_with_orig(
643                         dstPixels + pad*dst->fRowBytes + pad, dst->fRowBytes,
644                         SkMask::AlphaIter<SkMask::kA8_Format>(srcPixels), src.fRowBytes,
645                         srcWidth, srcHeight);
646             } break;
647             case kInner_SkBlurStyle: {
648                 // now we allocate the "real" dst, mirror the size of src
649                 size_t srcSize = src.computeImageSize();
650                 if (0 == srcSize) {
651                     return false;   // too big to allocate, abort
652                 }
653                 dst->image() = SkMaskBuilder::AllocImage(srcSize);
654                 merge_src_with_blur(dst->image(), src.fRowBytes,
655                     SkMask::AlphaIter<SkMask::kA8_Format>(srcPixels), src.fRowBytes,
656                     dstPixels + pad*dst->fRowBytes + pad,
657                     dst->fRowBytes, srcWidth, srcHeight);
658                 SkMaskBuilder::FreeImage(dstPixels);
659             } break;
660         }
661         autoFreeDstPixels.release();
662     }
663 
664     if (style == kInner_SkBlurStyle) {
665         dst->bounds() = src.fBounds; // restore trimmed bounds
666         dst->rowBytes() = src.fRowBytes;
667     }
668 
669     return true;
670 }
671