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