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