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