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