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