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