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