• 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/SkBlurTypes.h"
11 #include "include/core/SkPoint.h"
12 #include "include/core/SkRect.h"
13 #include "include/private/base/SkMath.h"
14 #include "include/private/base/SkSafe32.h"
15 #include "include/private/base/SkTPin.h"
16 #include "include/private/base/SkTemplates.h"
17 #include "include/private/base/SkTo.h"
18 #include "src/base/SkMathPriv.h"
19 #include "src/core/SkColorPriv.h"
20 #include "src/core/SkMaskBlurFilter.h"
21 
22 #include <cmath>
23 #include <cstring>
24 #include <utility>
25 
26 using namespace skia_private;
27 
28 // This constant approximates the scaling done in the software path's
29 // "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)).
30 // IMHO, it actually should be 1:  we blur "less" than we should do
31 // according to the CSS and canvas specs, simply because Safari does the same.
32 // Firefox used to do the same too, until 4.0 where they fixed it.  So at some
33 // point we should probably get rid of these scaling constants and rebaseline
34 // all the blur tests.
35 static const SkScalar kBLUR_SIGMA_SCALE = 0.57735f;
36 
ConvertRadiusToSigma(SkScalar radius)37 SkScalar SkBlurMask::ConvertRadiusToSigma(SkScalar radius) {
38     return radius > 0 ? kBLUR_SIGMA_SCALE * radius + 0.5f : 0.0f;
39 }
40 
ConvertSigmaToRadius(SkScalar sigma)41 SkScalar SkBlurMask::ConvertSigmaToRadius(SkScalar sigma) {
42     return sigma > 0.5f ? (sigma - 0.5f) / kBLUR_SIGMA_SCALE : 0.0f;
43 }
44 
45 
46 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)47 static void merge_src_with_blur(uint8_t dst[], int dstRB,
48                                 AlphaIter src, int srcRB,
49                                 const uint8_t blur[], int blurRB,
50                                 int sw, int sh) {
51     dstRB -= sw;
52     blurRB -= sw;
53     while (--sh >= 0) {
54         AlphaIter rowSrc(src);
55         for (int x = sw - 1; x >= 0; --x) {
56             *dst = SkToU8(SkAlphaMul(*blur, SkAlpha255To256(*rowSrc)));
57             ++dst;
58             ++rowSrc;
59             ++blur;
60         }
61         dst += dstRB;
62         src >>= srcRB;
63         blur += blurRB;
64     }
65 }
66 
67 template <typename AlphaIter>
clamp_solid_with_orig(uint8_t dst[],int dstRowBytes,AlphaIter src,int srcRowBytes,int sw,int sh)68 static void clamp_solid_with_orig(uint8_t dst[], int dstRowBytes,
69                                   AlphaIter src, int srcRowBytes,
70                                   int sw, int sh) {
71     int x;
72     while (--sh >= 0) {
73         AlphaIter rowSrc(src);
74         for (x = sw - 1; x >= 0; --x) {
75             int s = *rowSrc;
76             int d = *dst;
77             *dst = SkToU8(s + d - SkMulDiv255Round(s, d));
78             ++dst;
79             ++rowSrc;
80         }
81         dst += dstRowBytes - sw;
82         src >>= srcRowBytes;
83     }
84 }
85 
86 template <typename AlphaIter>
clamp_outer_with_orig(uint8_t dst[],int dstRowBytes,AlphaIter src,int srcRowBytes,int sw,int sh)87 static void clamp_outer_with_orig(uint8_t dst[], int dstRowBytes,
88                                   AlphaIter src, int srcRowBytes,
89                                   int sw, int sh) {
90     int x;
91     while (--sh >= 0) {
92         AlphaIter rowSrc(src);
93         for (x = sw - 1; x >= 0; --x) {
94             int srcValue = *rowSrc;
95             if (srcValue) {
96                 *dst = SkToU8(SkAlphaMul(*dst, SkAlpha255To256(255 - srcValue)));
97             }
98             ++dst;
99             ++rowSrc;
100         }
101         dst += dstRowBytes - sw;
102         src >>= srcRowBytes;
103     }
104 }
105 ///////////////////////////////////////////////////////////////////////////////
106 
BoxBlur(SkMaskBuilder * dst,const SkMask & src,SkScalar sigma,SkBlurStyle style,SkIVector * margin)107 bool SkBlurMask::BoxBlur(SkMaskBuilder* dst,
108                          const SkMask& src,
109                          SkScalar sigma,
110                          SkBlurStyle style,
111                          SkIVector* margin) {
112     SkASSERT(dst);
113     if (src.fFormat != SkMask::kBW_Format &&
114         src.fFormat != SkMask::kA8_Format &&
115         src.fFormat != SkMask::kARGB32_Format &&
116         src.fFormat != SkMask::kLCD16_Format)
117     {
118         return false;
119     }
120 
121     SkMaskBlurFilter blurFilter{sigma, sigma};
122     if (blurFilter.hasNoBlur()) {
123         // If there is no effective blur most styles will just produce the original mask.
124         // However, kOuter_SkBlurStyle will produce an empty mask.
125         if (style == kOuter_SkBlurStyle) {
126             dst->image() = nullptr;
127             dst->bounds() = SkIRect::MakeEmpty();
128             dst->rowBytes() = dst->fBounds.width();
129             dst->format() = SkMask::kA8_Format;
130             if (margin != nullptr) {
131                 // This filter will disregard the src.fImage completely.
132                 // The margin is actually {-(src.fBounds.width() / 2), -(src.fBounds.height() / 2)}
133                 // but it is not clear if callers will fall over with negative margins.
134                 *margin = SkIVector{0, 0};
135             }
136             return true;
137         }
138         return false;
139     }
140     const SkIVector border = blurFilter.blur(src, dst);
141 
142     if (src.fImage != nullptr && dst->fImage == nullptr) {
143         // The call to blur() failed to set our destination image up (e.g. an overflow).
144         // Note that if src.fImage was null, dst->fImage will also be null and that's
145         // *not* an error case - the code should continue to calculate the border.
146         return false;
147     }
148 
149     if (margin != nullptr) {
150         *margin = border;
151     }
152 
153     if (src.fImage == nullptr) {
154         if (style == kInner_SkBlurStyle) {
155             dst->bounds() = src.fBounds; // restore trimmed bounds
156             dst->rowBytes() = dst->fBounds.width();
157         }
158         return true;
159     }
160 
161     switch (style) {
162         case kNormal_SkBlurStyle:
163             break;
164         case kSolid_SkBlurStyle: {
165             auto dstStart = &dst->image()[border.x() + border.y() * dst->fRowBytes];
166             switch (src.fFormat) {
167                 case SkMask::kBW_Format:
168                     clamp_solid_with_orig(
169                             dstStart, dst->fRowBytes,
170                             SkMask::AlphaIter<SkMask::kBW_Format>(src.fImage, 0), src.fRowBytes,
171                             src.fBounds.width(), src.fBounds.height());
172                     break;
173                 case SkMask::kA8_Format:
174                     clamp_solid_with_orig(
175                             dstStart, dst->fRowBytes,
176                             SkMask::AlphaIter<SkMask::kA8_Format>(src.fImage), src.fRowBytes,
177                             src.fBounds.width(), src.fBounds.height());
178                     break;
179                 case SkMask::kARGB32_Format: {
180                     const uint32_t* srcARGB = reinterpret_cast<const uint32_t*>(src.fImage);
181                     clamp_solid_with_orig(
182                             dstStart, dst->fRowBytes,
183                             SkMask::AlphaIter<SkMask::kARGB32_Format>(srcARGB), src.fRowBytes,
184                             src.fBounds.width(), src.fBounds.height());
185                 } break;
186                 case SkMask::kLCD16_Format: {
187                     const uint16_t* srcLCD = reinterpret_cast<const uint16_t*>(src.fImage);
188                     clamp_solid_with_orig(
189                             dstStart, dst->fRowBytes,
190                             SkMask::AlphaIter<SkMask::kLCD16_Format>(srcLCD), src.fRowBytes,
191                             src.fBounds.width(), src.fBounds.height());
192                 } break;
193                 default:
194                     SK_ABORT("Unhandled format.");
195             }
196         } break;
197         case kOuter_SkBlurStyle: {
198             auto dstStart = &dst->image()[border.x() + border.y() * dst->fRowBytes];
199             switch (src.fFormat) {
200                 case SkMask::kBW_Format:
201                     clamp_outer_with_orig(
202                             dstStart, dst->fRowBytes,
203                             SkMask::AlphaIter<SkMask::kBW_Format>(src.fImage, 0), src.fRowBytes,
204                             src.fBounds.width(), src.fBounds.height());
205                     break;
206                 case SkMask::kA8_Format:
207                     clamp_outer_with_orig(
208                             dstStart, dst->fRowBytes,
209                             SkMask::AlphaIter<SkMask::kA8_Format>(src.fImage), src.fRowBytes,
210                             src.fBounds.width(), src.fBounds.height());
211                     break;
212                 case SkMask::kARGB32_Format: {
213                     const uint32_t* srcARGB = reinterpret_cast<const uint32_t*>(src.fImage);
214                     clamp_outer_with_orig(
215                             dstStart, dst->fRowBytes,
216                             SkMask::AlphaIter<SkMask::kARGB32_Format>(srcARGB), src.fRowBytes,
217                             src.fBounds.width(), src.fBounds.height());
218                 } break;
219                 case SkMask::kLCD16_Format: {
220                     const uint16_t* srcLCD = reinterpret_cast<const uint16_t*>(src.fImage);
221                     clamp_outer_with_orig(
222                             dstStart, dst->fRowBytes,
223                             SkMask::AlphaIter<SkMask::kLCD16_Format>(srcLCD), src.fRowBytes,
224                             src.fBounds.width(), src.fBounds.height());
225                 } break;
226                 default:
227                     SK_ABORT("Unhandled format.");
228             }
229         } break;
230         case kInner_SkBlurStyle: {
231             // now we allocate the "real" dst, mirror the size of src
232             SkMaskBuilder blur = std::move(*dst);
233             SkAutoMaskFreeImage autoFreeBlurMask(blur.image());
234 
235             *dst = SkMaskBuilder(nullptr, src.fBounds, src.fBounds.width(), blur.format());
236             size_t dstSize = dst->computeImageSize();
237             if (0 == dstSize) {
238                 return false;   // too big to allocate, abort
239             }
240             dst->image() = SkMaskBuilder::AllocImage(dstSize);
241 
242             auto blurStart = &blur.image()[border.x() + border.y() * blur.fRowBytes];
243             switch (src.fFormat) {
244                 case SkMask::kBW_Format:
245                     merge_src_with_blur(
246                             dst->image(), dst->fRowBytes,
247                             SkMask::AlphaIter<SkMask::kBW_Format>(src.fImage, 0), src.fRowBytes,
248                             blurStart, blur.fRowBytes,
249                             src.fBounds.width(), src.fBounds.height());
250                     break;
251                 case SkMask::kA8_Format:
252                     merge_src_with_blur(
253                             dst->image(), dst->fRowBytes,
254                             SkMask::AlphaIter<SkMask::kA8_Format>(src.fImage), src.fRowBytes,
255                             blurStart, blur.fRowBytes,
256                             src.fBounds.width(), src.fBounds.height());
257                     break;
258                 case SkMask::kARGB32_Format: {
259                     const uint32_t* srcARGB = reinterpret_cast<const uint32_t*>(src.fImage);
260                     merge_src_with_blur(
261                             dst->image(), dst->fRowBytes,
262                             SkMask::AlphaIter<SkMask::kARGB32_Format>(srcARGB), src.fRowBytes,
263                             blurStart, blur.fRowBytes,
264                             src.fBounds.width(), src.fBounds.height());
265                 } break;
266                 case SkMask::kLCD16_Format: {
267                     const uint16_t* srcLCD = reinterpret_cast<const uint16_t*>(src.fImage);
268                     merge_src_with_blur(
269                             dst->image(), dst->fRowBytes,
270                             SkMask::AlphaIter<SkMask::kLCD16_Format>(srcLCD), src.fRowBytes,
271                             blurStart, blur.fRowBytes,
272                             src.fBounds.width(), src.fBounds.height());
273                 } break;
274                 default:
275                     SK_ABORT("Unhandled format.");
276             }
277         } break;
278     }
279 
280     return true;
281 }
282 
283 /* Convolving a box with itself three times results in a piecewise
284    quadratic function:
285 
286    0                              x <= -1.5
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                        1.5 < x
291 
292    Mathematica:
293 
294    g[x_] := Piecewise [ {
295      {9/8 + 3/2 x + 1/2 x^2 ,  -1.5 < x <= -.5},
296      {3/4 - x^2             ,   -.5 < x <= .5},
297      {9/8 - 3/2 x + 1/2 x^2 ,   0.5 < x <= 1.5}
298    }, 0]
299 
300    To get the profile curve of the blurred step function at the rectangle
301    edge, we evaluate the indefinite integral, which is piecewise cubic:
302 
303    0                                        x <= -1.5
304    9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3   -1.5 < x <= -0.5
305    1/2 + 3/4 x - 1/3 x^3              -.5 < x <= .5
306    7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3     .5 < x <= 1.5
307    1                                  1.5 < x
308 
309    in Mathematica code:
310 
311    gi[x_] := Piecewise[ {
312      { 0 , x <= -1.5 },
313      { 9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3, -1.5 < x <= -0.5 },
314      { 1/2 + 3/4 x - 1/3 x^3          ,  -.5 < x <= .5},
315      { 7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3,   .5 < x <= 1.5}
316    },1]
317 */
318 
gaussianIntegral(float x)319 static float gaussianIntegral(float x) {
320     if (x > 1.5f) {
321         return 0.0f;
322     }
323     if (x < -1.5f) {
324         return 1.0f;
325     }
326 
327     float x2 = x*x;
328     float x3 = x2*x;
329 
330     if ( x > 0.5f ) {
331         return 0.5625f - (x3 / 6.0f - 3.0f * x2 * 0.25f + 1.125f * x);
332     }
333     if ( x > -0.5f ) {
334         return 0.5f - (0.75f * x - x3 / 3.0f);
335     }
336     return 0.4375f + (-x3 / 6.0f - 3.0f * x2 * 0.25f - 1.125f * x);
337 }
338 
339 /*  ComputeBlurProfile fills in an array of floating
340     point values between 0 and 255 for the profile signature of
341     a blurred half-plane with the given blur radius.  Since we're
342     going to be doing screened multiplications (i.e., 1 - (1-x)(1-y))
343     all the time, we actually fill in the profile pre-inverted
344     (already done 255-x).
345 */
346 
ComputeBlurProfile(uint8_t * profile,int size,SkScalar sigma)347 void SkBlurMask::ComputeBlurProfile(uint8_t* profile, int size, SkScalar sigma) {
348     SkASSERT(SkScalarCeilToInt(6*sigma) == size);
349 
350     int center = size >> 1;
351 
352     float invr = 1.f/(2*sigma);
353 
354     profile[0] = 255;
355     for (int x = 1 ; x < size ; ++x) {
356         float scaled_x = (center - x - .5f) * invr;
357         float gi = gaussianIntegral(scaled_x);
358         profile[x] = 255 - (uint8_t) (255.f * gi);
359     }
360 }
361 
362 // TODO MAYBE: Maintain a profile cache to avoid recomputing this for
363 // commonly used radii.  Consider baking some of the most common blur radii
364 // directly in as static data?
365 
366 // Implementation adapted from Michael Herf's approach:
367 // http://stereopsis.com/shadowrect/
368 
ProfileLookup(const uint8_t * profile,int loc,int blurredWidth,int sharpWidth)369 uint8_t SkBlurMask::ProfileLookup(const uint8_t *profile, int loc,
370                                   int blurredWidth, int sharpWidth) {
371     // how far are we from the original edge?
372     int dx = SkAbs32(((loc << 1) + 1) - blurredWidth) - sharpWidth;
373     int ox = dx >> 1;
374     if (ox < 0) {
375         ox = 0;
376     }
377 
378     return profile[ox];
379 }
380 
ComputeBlurredScanline(uint8_t * pixels,const uint8_t * profile,unsigned int width,SkScalar sigma)381 void SkBlurMask::ComputeBlurredScanline(uint8_t *pixels, const uint8_t *profile,
382                                         unsigned int width, SkScalar sigma) {
383 
384     unsigned int profile_size = SkScalarCeilToInt(6*sigma);
385     skia_private::AutoTMalloc<uint8_t> horizontalScanline(width);
386 
387     unsigned int sw = width - profile_size;
388     // nearest odd number less than the profile size represents the center
389     // of the (2x scaled) profile
390     int center = ( profile_size & ~1 ) - 1;
391 
392     int w = sw - center;
393 
394     for (unsigned int x = 0 ; x < width ; ++x) {
395        if (profile_size <= sw) {
396            pixels[x] = ProfileLookup(profile, x, width, w);
397        } else {
398            float span = float(sw)/(2*sigma);
399            float giX = 1.5f - (x+.5f)/(2*sigma);
400            pixels[x] = (uint8_t) (255 * (gaussianIntegral(giX) - gaussianIntegral(giX + span)));
401        }
402     }
403 }
404 
BlurRect(SkScalar sigma,SkMaskBuilder * dst,const SkRect & src,SkBlurStyle style,SkIVector * margin,SkMaskBuilder::CreateMode createMode)405 bool SkBlurMask::BlurRect(SkScalar sigma,
406                           SkMaskBuilder* dst,
407                           const SkRect& src,
408                           SkBlurStyle style,
409                           SkIVector* margin,
410                           SkMaskBuilder::CreateMode createMode) {
411     int profileSize = SkScalarCeilToInt(6*sigma);
412     if (profileSize <= 0) {
413         return false;   // no blur to compute
414     }
415 
416     int pad = profileSize/2;
417     if (margin) {
418         margin->set( pad, pad );
419     }
420 
421     dst->bounds().setLTRB(SkScalarRoundToInt(src.fLeft - pad),
422                          SkScalarRoundToInt(src.fTop - pad),
423                          SkScalarRoundToInt(src.fRight + pad),
424                          SkScalarRoundToInt(src.fBottom + pad));
425 
426     dst->rowBytes() = dst->fBounds.width();
427     dst->format() = SkMask::kA8_Format;
428     dst->image() = nullptr;
429 
430     int             sw = SkScalarFloorToInt(src.width());
431     int             sh = SkScalarFloorToInt(src.height());
432 
433     if (createMode == SkMaskBuilder::kJustComputeBounds_CreateMode) {
434         if (style == kInner_SkBlurStyle) {
435             dst->bounds() = src.round(); // restore trimmed bounds
436             dst->rowBytes() = sw;
437         }
438         return true;
439     }
440 
441     AutoTMalloc<uint8_t> profile(profileSize);
442 
443     ComputeBlurProfile(profile, profileSize, sigma);
444 
445     size_t dstSize = dst->computeImageSize();
446     if (0 == dstSize) {
447         return false;   // too big to allocate, abort
448     }
449 
450     uint8_t* dp = SkMaskBuilder::AllocImage(dstSize);
451     dst->image() = dp;
452 
453     int dstHeight = dst->fBounds.height();
454     int dstWidth = dst->fBounds.width();
455 
456     uint8_t *outptr = dp;
457 
458     AutoTMalloc<uint8_t> horizontalScanline(dstWidth);
459     AutoTMalloc<uint8_t> verticalScanline(dstHeight);
460 
461     ComputeBlurredScanline(horizontalScanline, profile, dstWidth, sigma);
462     ComputeBlurredScanline(verticalScanline, profile, dstHeight, sigma);
463 
464     for (int y = 0 ; y < dstHeight ; ++y) {
465         for (int x = 0 ; x < dstWidth ; x++) {
466             unsigned int maskval = SkMulDiv255Round(horizontalScanline[x], verticalScanline[y]);
467             *(outptr++) = maskval;
468         }
469     }
470 
471     if (style == kInner_SkBlurStyle) {
472         // now we allocate the "real" dst, mirror the size of src
473         size_t srcSize = (size_t)(src.width() * src.height());
474         if (0 == srcSize) {
475             return false;   // too big to allocate, abort
476         }
477         dst->image() = SkMaskBuilder::AllocImage(srcSize);
478         for (int y = 0 ; y < sh ; y++) {
479             uint8_t *blur_scanline = dp + (y+pad)*dstWidth + pad;
480             uint8_t *inner_scanline = dst->image() + y*sw;
481             memcpy(inner_scanline, blur_scanline, sw);
482         }
483         SkMaskBuilder::FreeImage(dp);
484 
485         dst->bounds() = src.round(); // restore trimmed bounds
486         dst->rowBytes() = sw;
487 
488     } else if (style == kOuter_SkBlurStyle) {
489         for (int y = pad ; y < dstHeight-pad ; y++) {
490             uint8_t *dst_scanline = dp + y*dstWidth + pad;
491             memset(dst_scanline, 0, sw);
492         }
493     } else if (style == kSolid_SkBlurStyle) {
494         for (int y = pad ; y < dstHeight-pad ; y++) {
495             uint8_t *dst_scanline = dp + y*dstWidth + pad;
496             memset(dst_scanline, 0xff, sw);
497         }
498     }
499     // normal and solid styles are the same for analytic rect blurs, so don't
500     // need to handle solid specially.
501 
502     return true;
503 }
504 
505 // The "simple" blur is a direct implementation of separable convolution with a discrete
506 // gaussian kernel.  It's "ground truth" in a sense; too slow to be used, but very
507 // useful for correctness comparisons.
508 
BlurGroundTruth(SkScalar sigma,SkMaskBuilder * dst,const SkMask & src,SkBlurStyle style,SkIVector * margin)509 bool SkBlurMask::BlurGroundTruth(SkScalar sigma,
510                                  SkMaskBuilder* dst,
511                                  const SkMask& src,
512                                  SkBlurStyle style,
513                                  SkIVector* margin) {
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     AutoTMalloc<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->bounds() = src.fBounds;
546     dst->bounds().outset(pad, pad);
547 
548     dst->rowBytes() = dst->fBounds.width();
549     dst->format() = SkMask::kA8_Format;
550     dst->image() = 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 = SkMaskBuilder::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         AutoTMalloc<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         AutoTMalloc<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 = SkTPin(SkClampPos(integerPixel), 0, 255);
622             }
623         }
624 
625         dst->image() = 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->image() = SkMaskBuilder::AllocImage(srcSize);
648                 merge_src_with_blur(dst->image(), src.fRowBytes,
649                     SkMask::AlphaIter<SkMask::kA8_Format>(srcPixels), src.fRowBytes,
650                     dstPixels + pad*dst->fRowBytes + pad,
651                     dst->fRowBytes, srcWidth, srcHeight);
652                 SkMaskBuilder::FreeImage(dstPixels);
653             } break;
654         }
655         autoFreeDstPixels.release();
656     }
657 
658     if (style == kInner_SkBlurStyle) {
659         dst->bounds() = src.fBounds; // restore trimmed bounds
660         dst->rowBytes() = src.fRowBytes;
661     }
662 
663     return true;
664 }
665