blob: bf50845ab6cb1d308562ea44fa279133bcafd46f [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkBlurMask.h"
tomhudson@google.com889bd8b2011-09-27 17:38:17 +000011#include "SkMath.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkTemplates.h"
tomhudson@google.com01224d52011-11-28 18:22:01 +000013#include "SkEndian.h"
14
robertphillips@google.com7ce661d2013-08-27 16:14:03 +000015
reed@google.comdaaafa62014-04-29 15:20:16 +000016// This constant approximates the scaling done in the software path's
17// "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)).
18// IMHO, it actually should be 1: we blur "less" than we should do
19// according to the CSS and canvas specs, simply because Safari does the same.
20// Firefox used to do the same too, until 4.0 where they fixed it. So at some
21// point we should probably get rid of these scaling constants and rebaseline
22// all the blur tests.
23static const SkScalar kBLUR_SIGMA_SCALE = 0.57735f;
robertphillips@google.com7ce661d2013-08-27 16:14:03 +000024
reed@google.comdaaafa62014-04-29 15:20:16 +000025SkScalar SkBlurMask::ConvertRadiusToSigma(SkScalar radius) {
26 return radius > 0 ? kBLUR_SIGMA_SCALE * radius + 0.5f : 0.0f;
27}
28
29SkScalar SkBlurMask::ConvertSigmaToRadius(SkScalar sigma) {
30 return sigma > 0.5f ? (sigma - 0.5f) / kBLUR_SIGMA_SCALE : 0.0f;
robertphillips@google.com7ce661d2013-08-27 16:14:03 +000031}
humper@google.com7c7292c2013-01-04 20:29:03 +000032
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +000033#define UNROLL_SEPARABLE_LOOPS
34
senorblanco@chromium.org908276b2012-11-15 20:27:35 +000035/**
36 * This function performs a box blur in X, of the given radius. If the
skia.committer@gmail.com884e60b2012-11-16 02:01:17 +000037 * "transpose" parameter is true, it will transpose the pixels on write,
senorblanco@chromium.org908276b2012-11-15 20:27:35 +000038 * such that X and Y are swapped. Reads are always performed from contiguous
39 * memory in X, for speed. The destination buffer (dst) must be at least
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +000040 * (width + leftRadius + rightRadius) * height bytes in size.
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +000041 *
42 * This is what the inner loop looks like before unrolling, and with the two
43 * cases broken out separately (width < diameter, width >= diameter):
skia.committer@gmail.com76bf70d2013-02-20 07:02:30 +000044 *
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +000045 * if (width < diameter) {
46 * for (int x = 0; x < width; ++x) {
47 * sum += *right++;
skia.committer@gmail.com76bf70d2013-02-20 07:02:30 +000048 * *dptr = (sum * scale + half) >> 24;
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +000049 * dptr += dst_x_stride;
50 * }
51 * for (int x = width; x < diameter; ++x) {
52 * *dptr = (sum * scale + half) >> 24;
53 * dptr += dst_x_stride;
54 * }
55 * for (int x = 0; x < width; ++x) {
56 * *dptr = (sum * scale + half) >> 24;
57 * sum -= *left++;
58 * dptr += dst_x_stride;
59 * }
60 * } else {
61 * for (int x = 0; x < diameter; ++x) {
62 * sum += *right++;
63 * *dptr = (sum * scale + half) >> 24;
64 * dptr += dst_x_stride;
65 * }
66 * for (int x = diameter; x < width; ++x) {
67 * sum += *right++;
68 * *dptr = (sum * scale + half) >> 24;
69 * sum -= *left++;
70 * dptr += dst_x_stride;
71 * }
72 * for (int x = 0; x < diameter; ++x) {
73 * *dptr = (sum * scale + half) >> 24;
74 * sum -= *left++;
75 * dptr += dst_x_stride;
76 * }
77 * }
senorblanco@chromium.org908276b2012-11-15 20:27:35 +000078 */
79static int boxBlur(const uint8_t* src, int src_y_stride, uint8_t* dst,
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +000080 int leftRadius, int rightRadius, int width, int height,
81 bool transpose)
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +000082{
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +000083 int diameter = leftRadius + rightRadius;
84 int kernelSize = diameter + 1;
85 int border = SkMin32(width, diameter);
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +000086 uint32_t scale = (1 << 24) / kernelSize;
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +000087 int new_width = width + SkMax32(leftRadius, rightRadius) * 2;
senorblanco@chromium.org908276b2012-11-15 20:27:35 +000088 int dst_x_stride = transpose ? height : 1;
89 int dst_y_stride = transpose ? 1 : new_width;
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +000090 uint32_t half = 1 << 23;
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +000091 for (int y = 0; y < height; ++y) {
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +000092 uint32_t sum = 0;
senorblanco@chromium.org908276b2012-11-15 20:27:35 +000093 uint8_t* dptr = dst + y * dst_y_stride;
94 const uint8_t* right = src + y * src_y_stride;
95 const uint8_t* left = right;
senorblanco@chromium.org336b4da2012-11-20 17:09:40 +000096 for (int x = 0; x < rightRadius - leftRadius; x++) {
97 *dptr = 0;
98 dptr += dst_x_stride;
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +000099 }
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000100#define LEFT_BORDER_ITER \
101 sum += *right++; \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000102 *dptr = (sum * scale + half) >> 24; \
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000103 dptr += dst_x_stride;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000104
105 int x = 0;
106#ifdef UNROLL_SEPARABLE_LOOPS
107 for (; x < border - 16; x += 16) {
108 LEFT_BORDER_ITER
109 LEFT_BORDER_ITER
110 LEFT_BORDER_ITER
111 LEFT_BORDER_ITER
112 LEFT_BORDER_ITER
113 LEFT_BORDER_ITER
114 LEFT_BORDER_ITER
115 LEFT_BORDER_ITER
116 LEFT_BORDER_ITER
117 LEFT_BORDER_ITER
118 LEFT_BORDER_ITER
119 LEFT_BORDER_ITER
120 LEFT_BORDER_ITER
121 LEFT_BORDER_ITER
122 LEFT_BORDER_ITER
123 LEFT_BORDER_ITER
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000124 }
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000125#endif
126 for (; x < border; ++x) {
127 LEFT_BORDER_ITER
128 }
129#undef LEFT_BORDER_ITER
130#define TRIVIAL_ITER \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000131 *dptr = (sum * scale + half) >> 24; \
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000132 dptr += dst_x_stride;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000133 x = width;
134#ifdef UNROLL_SEPARABLE_LOOPS
135 for (; x < diameter - 16; x += 16) {
136 TRIVIAL_ITER
137 TRIVIAL_ITER
138 TRIVIAL_ITER
139 TRIVIAL_ITER
140 TRIVIAL_ITER
141 TRIVIAL_ITER
142 TRIVIAL_ITER
143 TRIVIAL_ITER
144 TRIVIAL_ITER
145 TRIVIAL_ITER
146 TRIVIAL_ITER
147 TRIVIAL_ITER
148 TRIVIAL_ITER
149 TRIVIAL_ITER
150 TRIVIAL_ITER
151 TRIVIAL_ITER
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000152 }
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000153#endif
154 for (; x < diameter; ++x) {
155 TRIVIAL_ITER
156 }
157#undef TRIVIAL_ITER
158#define CENTER_ITER \
159 sum += *right++; \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000160 *dptr = (sum * scale + half) >> 24; \
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000161 sum -= *left++; \
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000162 dptr += dst_x_stride;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000163
164 x = diameter;
165#ifdef UNROLL_SEPARABLE_LOOPS
166 for (; x < width - 16; x += 16) {
167 CENTER_ITER
168 CENTER_ITER
169 CENTER_ITER
170 CENTER_ITER
171 CENTER_ITER
172 CENTER_ITER
173 CENTER_ITER
174 CENTER_ITER
175 CENTER_ITER
176 CENTER_ITER
177 CENTER_ITER
178 CENTER_ITER
179 CENTER_ITER
180 CENTER_ITER
181 CENTER_ITER
182 CENTER_ITER
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000183 }
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000184#endif
185 for (; x < width; ++x) {
186 CENTER_ITER
187 }
188#undef CENTER_ITER
189#define RIGHT_BORDER_ITER \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000190 *dptr = (sum * scale + half) >> 24; \
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000191 sum -= *left++; \
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000192 dptr += dst_x_stride;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000193
194 x = 0;
195#ifdef UNROLL_SEPARABLE_LOOPS
196 for (; x < border - 16; x += 16) {
197 RIGHT_BORDER_ITER
198 RIGHT_BORDER_ITER
199 RIGHT_BORDER_ITER
200 RIGHT_BORDER_ITER
201 RIGHT_BORDER_ITER
202 RIGHT_BORDER_ITER
203 RIGHT_BORDER_ITER
204 RIGHT_BORDER_ITER
205 RIGHT_BORDER_ITER
206 RIGHT_BORDER_ITER
207 RIGHT_BORDER_ITER
208 RIGHT_BORDER_ITER
209 RIGHT_BORDER_ITER
210 RIGHT_BORDER_ITER
211 RIGHT_BORDER_ITER
212 RIGHT_BORDER_ITER
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000213 }
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000214#endif
215 for (; x < border; ++x) {
216 RIGHT_BORDER_ITER
217 }
218#undef RIGHT_BORDER_ITER
humper@google.coma99a92c2013-02-20 16:42:06 +0000219 for (int x = 0; x < leftRadius - rightRadius; ++x) {
senorblanco@chromium.org336b4da2012-11-20 17:09:40 +0000220 *dptr = 0;
221 dptr += dst_x_stride;
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +0000222 }
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000223 SkASSERT(sum == 0);
224 }
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000225 return new_width;
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000226}
227
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000228/**
229 * This variant of the box blur handles blurring of non-integer radii. It
230 * keeps two running sums: an outer sum for the rounded-up kernel radius, and
231 * an inner sum for the rounded-down kernel radius. For each pixel, it linearly
232 * interpolates between them. In float this would be:
233 * outer_weight * outer_sum / kernelSize +
234 * (1.0 - outer_weight) * innerSum / (kernelSize - 2)
skia.committer@gmail.com76bf70d2013-02-20 07:02:30 +0000235 *
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000236 * This is what the inner loop looks like before unrolling, and with the two
237 * cases broken out separately (width < diameter, width >= diameter):
skia.committer@gmail.com76bf70d2013-02-20 07:02:30 +0000238 *
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000239 * if (width < diameter) {
240 * for (int x = 0; x < width; x++) {
241 * inner_sum = outer_sum;
242 * outer_sum += *right++;
243 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
244 * dptr += dst_x_stride;
245 * }
246 * for (int x = width; x < diameter; ++x) {
247 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
248 * dptr += dst_x_stride;
249 * }
250 * for (int x = 0; x < width; x++) {
251 * inner_sum = outer_sum - *left++;
252 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
253 * dptr += dst_x_stride;
254 * outer_sum = inner_sum;
255 * }
256 * } else {
257 * for (int x = 0; x < diameter; x++) {
258 * inner_sum = outer_sum;
259 * outer_sum += *right++;
260 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
261 * dptr += dst_x_stride;
262 * }
263 * for (int x = diameter; x < width; ++x) {
264 * inner_sum = outer_sum - *left;
265 * outer_sum += *right++;
266 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
267 * dptr += dst_x_stride;
268 * outer_sum -= *left++;
269 * }
270 * for (int x = 0; x < diameter; x++) {
271 * inner_sum = outer_sum - *left++;
272 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
273 * dptr += dst_x_stride;
274 * outer_sum = inner_sum;
275 * }
276 * }
277 * }
278 * return new_width;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000279 */
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000280
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000281static int boxBlurInterp(const uint8_t* src, int src_y_stride, uint8_t* dst,
282 int radius, int width, int height,
283 bool transpose, uint8_t outer_weight)
284{
285 int diameter = radius * 2;
286 int kernelSize = diameter + 1;
287 int border = SkMin32(width, diameter);
288 int inner_weight = 255 - outer_weight;
289 outer_weight += outer_weight >> 7;
290 inner_weight += inner_weight >> 7;
291 uint32_t outer_scale = (outer_weight << 16) / kernelSize;
292 uint32_t inner_scale = (inner_weight << 16) / (kernelSize - 2);
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000293 uint32_t half = 1 << 23;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000294 int new_width = width + diameter;
295 int dst_x_stride = transpose ? height : 1;
296 int dst_y_stride = transpose ? 1 : new_width;
297 for (int y = 0; y < height; ++y) {
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000298 uint32_t outer_sum = 0, inner_sum = 0;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000299 uint8_t* dptr = dst + y * dst_y_stride;
300 const uint8_t* right = src + y * src_y_stride;
301 const uint8_t* left = right;
302 int x = 0;
303
304#define LEFT_BORDER_ITER \
305 inner_sum = outer_sum; \
306 outer_sum += *right++; \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000307 *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24; \
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000308 dptr += dst_x_stride;
309
310#ifdef UNROLL_SEPARABLE_LOOPS
311 for (;x < border - 16; x += 16) {
312 LEFT_BORDER_ITER
313 LEFT_BORDER_ITER
314 LEFT_BORDER_ITER
315 LEFT_BORDER_ITER
316 LEFT_BORDER_ITER
317 LEFT_BORDER_ITER
318 LEFT_BORDER_ITER
319 LEFT_BORDER_ITER
320 LEFT_BORDER_ITER
321 LEFT_BORDER_ITER
322 LEFT_BORDER_ITER
323 LEFT_BORDER_ITER
324 LEFT_BORDER_ITER
325 LEFT_BORDER_ITER
326 LEFT_BORDER_ITER
327 LEFT_BORDER_ITER
328 }
329#endif
330
humper@google.coma99a92c2013-02-20 16:42:06 +0000331 for (;x < border; ++x) {
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000332 LEFT_BORDER_ITER
333 }
334#undef LEFT_BORDER_ITER
335 for (int x = width; x < diameter; ++x) {
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000336 *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000337 dptr += dst_x_stride;
338 }
339 x = diameter;
340
341#define CENTER_ITER \
342 inner_sum = outer_sum - *left; \
343 outer_sum += *right++; \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000344 *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24; \
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000345 dptr += dst_x_stride; \
346 outer_sum -= *left++;
347
348#ifdef UNROLL_SEPARABLE_LOOPS
349 for (; x < width - 16; x += 16) {
350 CENTER_ITER
351 CENTER_ITER
352 CENTER_ITER
353 CENTER_ITER
354 CENTER_ITER
355 CENTER_ITER
356 CENTER_ITER
357 CENTER_ITER
358 CENTER_ITER
359 CENTER_ITER
360 CENTER_ITER
361 CENTER_ITER
362 CENTER_ITER
363 CENTER_ITER
364 CENTER_ITER
365 CENTER_ITER
366 }
367#endif
368 for (; x < width; ++x) {
369 CENTER_ITER
370 }
371#undef CENTER_ITER
372
373 #define RIGHT_BORDER_ITER \
374 inner_sum = outer_sum - *left++; \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000375 *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24; \
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000376 dptr += dst_x_stride; \
377 outer_sum = inner_sum;
378
379 x = 0;
380#ifdef UNROLL_SEPARABLE_LOOPS
381 for (; x < border - 16; x += 16) {
382 RIGHT_BORDER_ITER
383 RIGHT_BORDER_ITER
384 RIGHT_BORDER_ITER
385 RIGHT_BORDER_ITER
386 RIGHT_BORDER_ITER
387 RIGHT_BORDER_ITER
388 RIGHT_BORDER_ITER
389 RIGHT_BORDER_ITER
390 RIGHT_BORDER_ITER
391 RIGHT_BORDER_ITER
392 RIGHT_BORDER_ITER
393 RIGHT_BORDER_ITER
394 RIGHT_BORDER_ITER
395 RIGHT_BORDER_ITER
396 RIGHT_BORDER_ITER
397 RIGHT_BORDER_ITER
398 }
399#endif
humper@google.coma99a92c2013-02-20 16:42:06 +0000400 for (; x < border; ++x) {
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000401 RIGHT_BORDER_ITER
402 }
403#undef RIGHT_BORDER_ITER
404 SkASSERT(outer_sum == 0 && inner_sum == 0);
405 }
406 return new_width;
407}
408
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +0000409static void get_adjusted_radii(SkScalar passRadius, int *loRadius, int *hiRadius)
410{
reed@google.come1ca7052013-12-17 19:22:07 +0000411 *loRadius = *hiRadius = SkScalarCeilToInt(passRadius);
commit-bot@chromium.org4b413c82013-11-25 19:44:07 +0000412 if (SkIntToScalar(*hiRadius) - passRadius > 0.5f) {
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +0000413 *loRadius = *hiRadius - 1;
414 }
415}
416
reed@android.com8a1c16f2008-12-17 15:59:43 +0000417#include "SkColorPriv.h"
418
reed@android.com0e3c6642009-09-18 13:41:56 +0000419static void merge_src_with_blur(uint8_t dst[], int dstRB,
420 const uint8_t src[], int srcRB,
421 const uint8_t blur[], int blurRB,
422 int sw, int sh) {
423 dstRB -= sw;
424 srcRB -= sw;
425 blurRB -= sw;
426 while (--sh >= 0) {
427 for (int x = sw - 1; x >= 0; --x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000428 *dst = SkToU8(SkAlphaMul(*blur, SkAlpha255To256(*src)));
429 dst += 1;
430 src += 1;
431 blur += 1;
432 }
reed@android.com0e3c6642009-09-18 13:41:56 +0000433 dst += dstRB;
434 src += srcRB;
435 blur += blurRB;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000436 }
437}
438
439static void clamp_with_orig(uint8_t dst[], int dstRowBytes,
reed@android.com0e3c6642009-09-18 13:41:56 +0000440 const uint8_t src[], int srcRowBytes,
441 int sw, int sh,
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000442 SkBlurStyle style) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000443 int x;
reed@android.com0e3c6642009-09-18 13:41:56 +0000444 while (--sh >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000445 switch (style) {
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000446 case kSolid_SkBlurStyle:
reed@android.com0e3c6642009-09-18 13:41:56 +0000447 for (x = sw - 1; x >= 0; --x) {
448 int s = *src;
449 int d = *dst;
450 *dst = SkToU8(s + d - SkMulDiv255Round(s, d));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000451 dst += 1;
452 src += 1;
453 }
454 break;
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000455 case kOuter_SkBlurStyle:
reed@android.com0e3c6642009-09-18 13:41:56 +0000456 for (x = sw - 1; x >= 0; --x) {
457 if (*src) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000458 *dst = SkToU8(SkAlphaMul(*dst, SkAlpha255To256(255 - *src)));
reed@android.com0e3c6642009-09-18 13:41:56 +0000459 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000460 dst += 1;
461 src += 1;
462 }
463 break;
464 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000465 SkDEBUGFAIL("Unexpected blur style here");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000466 break;
467 }
468 dst += dstRowBytes - sw;
reed@android.com0e3c6642009-09-18 13:41:56 +0000469 src += srcRowBytes - sw;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000470 }
471}
472
reed@google.com03016a32011-08-12 14:59:59 +0000473///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000474
bsalomon@google.com33cdbde2013-01-11 20:54:44 +0000475// we use a local function to wrap the class static method to work around
reed@android.com8a1c16f2008-12-17 15:59:43 +0000476// a bug in gcc98
477void SkMask_FreeImage(uint8_t* image);
reed@google.com03016a32011-08-12 14:59:59 +0000478void SkMask_FreeImage(uint8_t* image) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000479 SkMask::FreeImage(image);
480}
481
commit-bot@chromium.org3d8bf232014-04-28 19:49:24 +0000482bool SkBlurMask::BoxBlur(SkMask* dst, const SkMask& src,
483 SkScalar sigma, SkBlurStyle style, SkBlurQuality quality,
484 SkIPoint* margin, bool force_quality) {
485
reed@google.com03016a32011-08-12 14:59:59 +0000486 if (src.fFormat != SkMask::kA8_Format) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000487 return false;
reed@google.com03016a32011-08-12 14:59:59 +0000488 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000489
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000490 // Force high quality off for small radii (performance)
commit-bot@chromium.org3d8bf232014-04-28 19:49:24 +0000491 if (!force_quality && sigma <= SkIntToScalar(2)) {
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000492 quality = kLow_SkBlurQuality;
senorblanco@chromium.org91f489a2012-11-29 17:09:27 +0000493 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000494
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000495 SkScalar passRadius;
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000496 if (kHigh_SkBlurQuality == quality) {
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000497 // For the high quality path the 3 pass box blur kernel width is
498 // 6*rad+1 while the full Gaussian width is 6*sigma.
499 passRadius = sigma - (1/6.0f);
500 } else {
skia.committer@gmail.com7bd141d2013-08-28 07:01:18 +0000501 // For the low quality path we only attempt to cover 3*sigma of the
502 // Gaussian blur area (1.5*sigma on each side). The single pass box
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000503 // blur's kernel size is 2*rad+1.
504 passRadius = 1.5f*sigma - 0.5f;
505 }
506
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000507 // highQuality: use three box blur passes as a cheap way
humper@google.coma99a92c2013-02-20 16:42:06 +0000508 // to approximate a Gaussian blur
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000509 int passCount = (kHigh_SkBlurQuality == quality) ? 3 : 1;
humper@google.coma99a92c2013-02-20 16:42:06 +0000510
reed@google.come1ca7052013-12-17 19:22:07 +0000511 int rx = SkScalarCeilToInt(passRadius);
512 int outerWeight = 255 - SkScalarRoundToInt((SkIntToScalar(rx) - passRadius) * 255);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000513
514 SkASSERT(rx >= 0);
humper@google.coma99a92c2013-02-20 16:42:06 +0000515 SkASSERT((unsigned)outerWeight <= 255);
reed@android.com0e3c6642009-09-18 13:41:56 +0000516 if (rx <= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000517 return false;
reed@android.com0e3c6642009-09-18 13:41:56 +0000518 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000519
520 int ry = rx; // only do square blur for now
521
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000522 int padx = passCount * rx;
523 int pady = passCount * ry;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000524
bungeman@google.com5af16f82011-09-02 15:06:44 +0000525 if (margin) {
526 margin->set(padx, pady);
527 }
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000528 dst->fBounds.set(src.fBounds.fLeft - padx, src.fBounds.fTop - pady,
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000529 src.fBounds.fRight + padx, src.fBounds.fBottom + pady);
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000530
reed@android.com49f0ff22009-03-19 21:52:42 +0000531 dst->fRowBytes = dst->fBounds.width();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000532 dst->fFormat = SkMask::kA8_Format;
533 dst->fImage = NULL;
534
reed@android.com0e3c6642009-09-18 13:41:56 +0000535 if (src.fImage) {
reed@android.com543ed932009-04-24 12:43:40 +0000536 size_t dstSize = dst->computeImageSize();
537 if (0 == dstSize) {
538 return false; // too big to allocate, abort
539 }
540
reed@android.com8a1c16f2008-12-17 15:59:43 +0000541 int sw = src.fBounds.width();
542 int sh = src.fBounds.height();
543 const uint8_t* sp = src.fImage;
reed@android.com543ed932009-04-24 12:43:40 +0000544 uint8_t* dp = SkMask::AllocImage(dstSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000545 SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dp);
546
547 // build the blurry destination
commit-bot@chromium.org0a1c3872013-06-11 15:23:42 +0000548 SkAutoTMalloc<uint8_t> tmpBuffer(dstSize);
549 uint8_t* tp = tmpBuffer.get();
550 int w = sw, h = sh;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000551
commit-bot@chromium.org0a1c3872013-06-11 15:23:42 +0000552 if (outerWeight == 255) {
553 int loRadius, hiRadius;
554 get_adjusted_radii(passRadius, &loRadius, &hiRadius);
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000555 if (kHigh_SkBlurQuality == quality) {
commit-bot@chromium.org0a1c3872013-06-11 15:23:42 +0000556 // Do three X blurs, with a transpose on the final one.
557 w = boxBlur(sp, src.fRowBytes, tp, loRadius, hiRadius, w, h, false);
558 w = boxBlur(tp, w, dp, hiRadius, loRadius, w, h, false);
559 w = boxBlur(dp, w, tp, hiRadius, hiRadius, w, h, true);
560 // Do three Y blurs, with a transpose on the final one.
561 h = boxBlur(tp, h, dp, loRadius, hiRadius, h, w, false);
562 h = boxBlur(dp, h, tp, hiRadius, loRadius, h, w, false);
563 h = boxBlur(tp, h, dp, hiRadius, hiRadius, h, w, true);
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000564 } else {
commit-bot@chromium.org0a1c3872013-06-11 15:23:42 +0000565 w = boxBlur(sp, src.fRowBytes, tp, rx, rx, w, h, true);
566 h = boxBlur(tp, h, dp, ry, ry, h, w, true);
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000567 }
568 } else {
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000569 if (kHigh_SkBlurQuality == quality) {
commit-bot@chromium.org0a1c3872013-06-11 15:23:42 +0000570 // Do three X blurs, with a transpose on the final one.
571 w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, false, outerWeight);
572 w = boxBlurInterp(tp, w, dp, rx, w, h, false, outerWeight);
573 w = boxBlurInterp(dp, w, tp, rx, w, h, true, outerWeight);
574 // Do three Y blurs, with a transpose on the final one.
575 h = boxBlurInterp(tp, h, dp, ry, h, w, false, outerWeight);
576 h = boxBlurInterp(dp, h, tp, ry, h, w, false, outerWeight);
577 h = boxBlurInterp(tp, h, dp, ry, h, w, true, outerWeight);
578 } else {
579 w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, true, outerWeight);
580 h = boxBlurInterp(tp, h, dp, ry, h, w, true, outerWeight);
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000581 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000582 }
583
584 dst->fImage = dp;
585 // if need be, alloc the "real" dst (same size as src) and copy/merge
586 // the blur into it (applying the src)
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000587 if (style == kInner_SkBlurStyle) {
reed@android.com0e3c6642009-09-18 13:41:56 +0000588 // now we allocate the "real" dst, mirror the size of src
reed@android.com543ed932009-04-24 12:43:40 +0000589 size_t srcSize = src.computeImageSize();
590 if (0 == srcSize) {
591 return false; // too big to allocate, abort
592 }
593 dst->fImage = SkMask::AllocImage(srcSize);
reed@android.com0e3c6642009-09-18 13:41:56 +0000594 merge_src_with_blur(dst->fImage, src.fRowBytes,
595 sp, src.fRowBytes,
reed@google.com03016a32011-08-12 14:59:59 +0000596 dp + passCount * (rx + ry * dst->fRowBytes),
597 dst->fRowBytes, sw, sh);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000598 SkMask::FreeImage(dp);
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000599 } else if (style != kNormal_SkBlurStyle) {
reed@google.com03016a32011-08-12 14:59:59 +0000600 clamp_with_orig(dp + passCount * (rx + ry * dst->fRowBytes),
601 dst->fRowBytes, sp, src.fRowBytes, sw, sh, style);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000602 }
603 (void)autoCall.detach();
604 }
605
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000606 if (style == kInner_SkBlurStyle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000607 dst->fBounds = src.fBounds; // restore trimmed bounds
reed@android.com0e3c6642009-09-18 13:41:56 +0000608 dst->fRowBytes = src.fRowBytes;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000609 }
610
reed@android.com8a1c16f2008-12-17 15:59:43 +0000611 return true;
612}
613
humper@google.com7c7292c2013-01-04 20:29:03 +0000614/* Convolving a box with itself three times results in a piecewise
615 quadratic function:
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000616
humper@google.com7c7292c2013-01-04 20:29:03 +0000617 0 x <= -1.5
humper@google.coma99a92c2013-02-20 16:42:06 +0000618 9/8 + 3/2 x + 1/2 x^2 -1.5 < x <= -.5
humper@google.com7c7292c2013-01-04 20:29:03 +0000619 3/4 - x^2 -.5 < x <= .5
620 9/8 - 3/2 x + 1/2 x^2 0.5 < x <= 1.5
621 0 1.5 < x
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000622
humper@google.coma99a92c2013-02-20 16:42:06 +0000623 Mathematica:
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000624
humper@google.coma99a92c2013-02-20 16:42:06 +0000625 g[x_] := Piecewise [ {
626 {9/8 + 3/2 x + 1/2 x^2 , -1.5 < x <= -.5},
627 {3/4 - x^2 , -.5 < x <= .5},
628 {9/8 - 3/2 x + 1/2 x^2 , 0.5 < x <= 1.5}
629 }, 0]
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000630
humper@google.com7c7292c2013-01-04 20:29:03 +0000631 To get the profile curve of the blurred step function at the rectangle
632 edge, we evaluate the indefinite integral, which is piecewise cubic:
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000633
humper@google.com7c7292c2013-01-04 20:29:03 +0000634 0 x <= -1.5
humper@google.coma99a92c2013-02-20 16:42:06 +0000635 9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3 -1.5 < x <= -0.5
humper@google.com7c7292c2013-01-04 20:29:03 +0000636 1/2 + 3/4 x - 1/3 x^3 -.5 < x <= .5
humper@google.coma99a92c2013-02-20 16:42:06 +0000637 7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3 .5 < x <= 1.5
humper@google.com7c7292c2013-01-04 20:29:03 +0000638 1 1.5 < x
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000639
humper@google.coma99a92c2013-02-20 16:42:06 +0000640 in Mathematica code:
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000641
humper@google.coma99a92c2013-02-20 16:42:06 +0000642 gi[x_] := Piecewise[ {
643 { 0 , x <= -1.5 },
644 { 9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3, -1.5 < x <= -0.5 },
645 { 1/2 + 3/4 x - 1/3 x^3 , -.5 < x <= .5},
646 { 7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3, .5 < x <= 1.5}
647 },1]
humper@google.com7c7292c2013-01-04 20:29:03 +0000648*/
649
humper@google.coma99a92c2013-02-20 16:42:06 +0000650static float gaussianIntegral(float x) {
651 if (x > 1.5f) {
humper@google.com7c7292c2013-01-04 20:29:03 +0000652 return 0.0f;
653 }
humper@google.coma99a92c2013-02-20 16:42:06 +0000654 if (x < -1.5f) {
humper@google.com7c7292c2013-01-04 20:29:03 +0000655 return 1.0f;
656 }
657
658 float x2 = x*x;
659 float x3 = x2*x;
660
jvanverth@google.com9c4e5ac2013-01-07 18:41:28 +0000661 if ( x > 0.5f ) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000662 return 0.5625f - (x3 / 6.0f - 3.0f * x2 * 0.25f + 1.125f * x);
humper@google.com7c7292c2013-01-04 20:29:03 +0000663 }
jvanverth@google.com9c4e5ac2013-01-07 18:41:28 +0000664 if ( x > -0.5f ) {
665 return 0.5f - (0.75f * x - x3 / 3.0f);
humper@google.com7c7292c2013-01-04 20:29:03 +0000666 }
jvanverth@google.com9c4e5ac2013-01-07 18:41:28 +0000667 return 0.4375f + (-x3 / 6.0f - 3.0f * x2 * 0.25f - 1.125f * x);
humper@google.com7c7292c2013-01-04 20:29:03 +0000668}
669
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000670/* ComputeBlurProfile allocates and fills in an array of floating
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000671 point values between 0 and 255 for the profile signature of
humper@google.com7c7292c2013-01-04 20:29:03 +0000672 a blurred half-plane with the given blur radius. Since we're
673 going to be doing screened multiplications (i.e., 1 - (1-x)(1-y))
674 all the time, we actually fill in the profile pre-inverted
675 (already done 255-x).
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000676
humper@google.com7c5d7b72013-03-11 20:16:28 +0000677 It's the responsibility of the caller to delete the
humper@google.com7c7292c2013-01-04 20:29:03 +0000678 memory returned in profile_out.
679*/
680
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000681void SkBlurMask::ComputeBlurProfile(SkScalar sigma, uint8_t **profile_out) {
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000682 int size = SkScalarCeilToInt(6*sigma);
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000683
humper@google.com7c7292c2013-01-04 20:29:03 +0000684 int center = size >> 1;
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000685 uint8_t *profile = SkNEW_ARRAY(uint8_t, size);
humper@google.com7c7292c2013-01-04 20:29:03 +0000686
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000687 float invr = 1.f/(2*sigma);
humper@google.com7c7292c2013-01-04 20:29:03 +0000688
689 profile[0] = 255;
humper@google.coma99a92c2013-02-20 16:42:06 +0000690 for (int x = 1 ; x < size ; ++x) {
jvanverth@google.comd98df1a2013-02-20 19:02:34 +0000691 float scaled_x = (center - x - .5f) * invr;
humper@google.coma99a92c2013-02-20 16:42:06 +0000692 float gi = gaussianIntegral(scaled_x);
693 profile[x] = 255 - (uint8_t) (255.f * gi);
humper@google.com7c7292c2013-01-04 20:29:03 +0000694 }
695
696 *profile_out = profile;
humper@google.com7c7292c2013-01-04 20:29:03 +0000697}
698
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000699// TODO MAYBE: Maintain a profile cache to avoid recomputing this for
humper@google.com7c7292c2013-01-04 20:29:03 +0000700// commonly used radii. Consider baking some of the most common blur radii
701// directly in as static data?
702
703// Implementation adapted from Michael Herf's approach:
704// http://stereopsis.com/shadowrect/
705
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000706uint8_t SkBlurMask::ProfileLookup(const uint8_t *profile, int loc, int blurred_width, int sharp_width) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000707 int dx = SkAbs32(((loc << 1) + 1) - blurred_width) - sharp_width; // how far are we from the original edge?
708 int ox = dx >> 1;
709 if (ox < 0) {
710 ox = 0;
711 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000712
humper@google.coma99a92c2013-02-20 16:42:06 +0000713 return profile[ox];
714}
715
skia.committer@gmail.com4c18e9f2014-01-31 03:01:59 +0000716void SkBlurMask::ComputeBlurredScanline(uint8_t *pixels, const uint8_t *profile,
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000717 unsigned int width, SkScalar sigma) {
718
719 unsigned int profile_size = SkScalarCeilToInt(6*sigma);
720 SkAutoTMalloc<uint8_t> horizontalScanline(width);
721
722 unsigned int sw = width - profile_size;
723 // nearest odd number less than the profile size represents the center
724 // of the (2x scaled) profile
725 int center = ( profile_size & ~1 ) - 1;
726
727 int w = sw - center;
728
729 for (unsigned int x = 0 ; x < width ; ++x) {
730 if (profile_size <= sw) {
731 pixels[x] = ProfileLookup(profile, x, width, w);
732 } else {
733 float span = float(sw)/(2*sigma);
734 float giX = 1.5f - (x+.5f)/(2*sigma);
735 pixels[x] = (uint8_t) (255 * (gaussianIntegral(giX) - gaussianIntegral(giX + span)));
736 }
skia.committer@gmail.com4c18e9f2014-01-31 03:01:59 +0000737 }
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000738}
739
skia.committer@gmail.com7bd141d2013-08-28 07:01:18 +0000740bool SkBlurMask::BlurRect(SkScalar sigma, SkMask *dst,
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000741 const SkRect &src, SkBlurStyle style,
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000742 SkIPoint *margin, SkMask::CreateMode createMode) {
743 int profile_size = SkScalarCeilToInt(6*sigma);
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000744
humper@google.coma99a92c2013-02-20 16:42:06 +0000745 int pad = profile_size/2;
humper@google.com7c7292c2013-01-04 20:29:03 +0000746 if (margin) {
747 margin->set( pad, pad );
748 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000749
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000750 dst->fBounds.set(SkScalarRoundToInt(src.fLeft - pad),
751 SkScalarRoundToInt(src.fTop - pad),
752 SkScalarRoundToInt(src.fRight + pad),
humper@google.com68a690c2013-03-11 21:16:20 +0000753 SkScalarRoundToInt(src.fBottom + pad));
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000754
humper@google.com7c7292c2013-01-04 20:29:03 +0000755 dst->fRowBytes = dst->fBounds.width();
756 dst->fFormat = SkMask::kA8_Format;
757 dst->fImage = NULL;
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000758
humper@google.com7c5d7b72013-03-11 20:16:28 +0000759 int sw = SkScalarFloorToInt(src.width());
760 int sh = SkScalarFloorToInt(src.height());
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000761
humper@google.com7c5d7b72013-03-11 20:16:28 +0000762 if (createMode == SkMask::kJustComputeBounds_CreateMode) {
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000763 if (style == kInner_SkBlurStyle) {
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000764 dst->fBounds.set(SkScalarRoundToInt(src.fLeft),
765 SkScalarRoundToInt(src.fTop),
766 SkScalarRoundToInt(src.fRight),
humper@google.com68a690c2013-03-11 21:16:20 +0000767 SkScalarRoundToInt(src.fBottom)); // restore trimmed bounds
humper@google.com7c5d7b72013-03-11 20:16:28 +0000768 dst->fRowBytes = sw;
769 }
770 return true;
771 }
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000772 uint8_t *profile = NULL;
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000773
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000774 ComputeBlurProfile(sigma, &profile);
775 SkAutoTDeleteArray<uint8_t> ada(profile);
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000776
humper@google.com7c7292c2013-01-04 20:29:03 +0000777 size_t dstSize = dst->computeImageSize();
778 if (0 == dstSize) {
779 return false; // too big to allocate, abort
780 }
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000781
humper@google.com7c7292c2013-01-04 20:29:03 +0000782 uint8_t* dp = SkMask::AllocImage(dstSize);
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000783
humper@google.com7c7292c2013-01-04 20:29:03 +0000784 dst->fImage = dp;
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000785
humper@google.coma99a92c2013-02-20 16:42:06 +0000786 int dstHeight = dst->fBounds.height();
787 int dstWidth = dst->fBounds.width();
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000788
humper@google.com7c7292c2013-01-04 20:29:03 +0000789 uint8_t *outptr = dp;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000790
humper@google.coma99a92c2013-02-20 16:42:06 +0000791 SkAutoTMalloc<uint8_t> horizontalScanline(dstWidth);
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000792 SkAutoTMalloc<uint8_t> verticalScanline(dstHeight);
skia.committer@gmail.com4c18e9f2014-01-31 03:01:59 +0000793
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000794 ComputeBlurredScanline(horizontalScanline, profile, dstWidth, sigma);
795 ComputeBlurredScanline(verticalScanline, profile, dstHeight, sigma);
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000796
humper@google.coma99a92c2013-02-20 16:42:06 +0000797 for (int y = 0 ; y < dstHeight ; ++y) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000798 for (int x = 0 ; x < dstWidth ; x++) {
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000799 unsigned int maskval = SkMulDiv255Round(horizontalScanline[x], verticalScanline[y]);
humper@google.com7c7292c2013-01-04 20:29:03 +0000800 *(outptr++) = maskval;
801 }
802 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000803
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000804 if (style == kInner_SkBlurStyle) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000805 // now we allocate the "real" dst, mirror the size of src
jvanverth@google.comd98df1a2013-02-20 19:02:34 +0000806 size_t srcSize = (size_t)(src.width() * src.height());
humper@google.coma99a92c2013-02-20 16:42:06 +0000807 if (0 == srcSize) {
808 return false; // too big to allocate, abort
809 }
810 dst->fImage = SkMask::AllocImage(srcSize);
811 for (int y = 0 ; y < sh ; y++) {
812 uint8_t *blur_scanline = dp + (y+pad)*dstWidth + pad;
813 uint8_t *inner_scanline = dst->fImage + y*sw;
814 memcpy(inner_scanline, blur_scanline, sw);
815 }
816 SkMask::FreeImage(dp);
817
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000818 dst->fBounds.set(SkScalarRoundToInt(src.fLeft),
819 SkScalarRoundToInt(src.fTop),
820 SkScalarRoundToInt(src.fRight),
humper@google.com68a690c2013-03-11 21:16:20 +0000821 SkScalarRoundToInt(src.fBottom)); // restore trimmed bounds
humper@google.coma99a92c2013-02-20 16:42:06 +0000822 dst->fRowBytes = sw;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000823
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000824 } else if (style == kOuter_SkBlurStyle) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000825 for (int y = pad ; y < dstHeight-pad ; y++) {
826 uint8_t *dst_scanline = dp + y*dstWidth + pad;
827 memset(dst_scanline, 0, sw);
828 }
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000829 } else if (style == kSolid_SkBlurStyle) {
humper@google.comd4d57302013-03-11 22:18:54 +0000830 for (int y = pad ; y < dstHeight-pad ; y++) {
831 uint8_t *dst_scanline = dp + y*dstWidth + pad;
832 memset(dst_scanline, 0xff, sw);
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000833 }
humper@google.coma99a92c2013-02-20 16:42:06 +0000834 }
835 // normal and solid styles are the same for analytic rect blurs, so don't
836 // need to handle solid specially.
837
838 return true;
839}
840
commit-bot@chromium.orga4771542014-03-10 21:42:06 +0000841bool SkBlurMask::BlurRRect(SkScalar sigma, SkMask *dst,
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000842 const SkRRect &src, SkBlurStyle style,
commit-bot@chromium.orga4771542014-03-10 21:42:06 +0000843 SkIPoint *margin, SkMask::CreateMode createMode) {
844 // Temporary for now -- always fail, should cause caller to fall back
845 // to old path. Plumbing just to land API and parallelize effort.
846
847 return false;
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000848}
commit-bot@chromium.orga4771542014-03-10 21:42:06 +0000849
humper@google.coma99a92c2013-02-20 16:42:06 +0000850// The "simple" blur is a direct implementation of separable convolution with a discrete
851// gaussian kernel. It's "ground truth" in a sense; too slow to be used, but very
852// useful for correctness comparisons.
853
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000854bool SkBlurMask::BlurGroundTruth(SkScalar sigma, SkMask* dst, const SkMask& src,
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000855 SkBlurStyle style, SkIPoint* margin) {
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000856
humper@google.coma99a92c2013-02-20 16:42:06 +0000857 if (src.fFormat != SkMask::kA8_Format) {
858 return false;
859 }
860
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000861 float variance = sigma * sigma;
humper@google.coma99a92c2013-02-20 16:42:06 +0000862
reed@google.come1ca7052013-12-17 19:22:07 +0000863 int windowSize = SkScalarCeilToInt(sigma*6);
humper@google.coma99a92c2013-02-20 16:42:06 +0000864 // round window size up to nearest odd number
865 windowSize |= 1;
866
867 SkAutoTMalloc<float> gaussWindow(windowSize);
868
869 int halfWindow = windowSize >> 1;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000870
humper@google.coma99a92c2013-02-20 16:42:06 +0000871 gaussWindow[halfWindow] = 1;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000872
humper@google.coma99a92c2013-02-20 16:42:06 +0000873 float windowSum = 1;
874 for (int x = 1 ; x <= halfWindow ; ++x) {
robertphillips@google.com3dfa4cc2013-09-05 16:39:03 +0000875 float gaussian = expf(-x*x / (2*variance));
humper@google.coma99a92c2013-02-20 16:42:06 +0000876 gaussWindow[halfWindow + x] = gaussWindow[halfWindow-x] = gaussian;
877 windowSum += 2*gaussian;
878 }
879
880 // leave the filter un-normalized for now; we will divide by the normalization
881 // sum later;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000882
humper@google.coma99a92c2013-02-20 16:42:06 +0000883 int pad = halfWindow;
884 if (margin) {
885 margin->set( pad, pad );
886 }
887
888 dst->fBounds = src.fBounds;
889 dst->fBounds.outset(pad, pad);
890
891 dst->fRowBytes = dst->fBounds.width();
892 dst->fFormat = SkMask::kA8_Format;
893 dst->fImage = NULL;
894
895 if (src.fImage) {
896
897 size_t dstSize = dst->computeImageSize();
898 if (0 == dstSize) {
899 return false; // too big to allocate, abort
900 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000901
humper@google.coma99a92c2013-02-20 16:42:06 +0000902 int srcWidth = src.fBounds.width();
903 int srcHeight = src.fBounds.height();
904 int dstWidth = dst->fBounds.width();
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000905
humper@google.coma99a92c2013-02-20 16:42:06 +0000906 const uint8_t* srcPixels = src.fImage;
907 uint8_t* dstPixels = SkMask::AllocImage(dstSize);
908 SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dstPixels);
909
910 // do the actual blur. First, make a padded copy of the source.
911 // use double pad so we never have to check if we're outside anything
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000912
humper@google.coma99a92c2013-02-20 16:42:06 +0000913 int padWidth = srcWidth + 4*pad;
914 int padHeight = srcHeight;
915 int padSize = padWidth * padHeight;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000916
humper@google.coma99a92c2013-02-20 16:42:06 +0000917 SkAutoTMalloc<uint8_t> padPixels(padSize);
918 memset(padPixels, 0, padSize);
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000919
humper@google.coma99a92c2013-02-20 16:42:06 +0000920 for (int y = 0 ; y < srcHeight; ++y) {
921 uint8_t* padptr = padPixels + y * padWidth + 2*pad;
922 const uint8_t* srcptr = srcPixels + y * srcWidth;
923 memcpy(padptr, srcptr, srcWidth);
924 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000925
humper@google.coma99a92c2013-02-20 16:42:06 +0000926 // blur in X, transposing the result into a temporary floating point buffer.
927 // also double-pad the intermediate result so that the second blur doesn't
928 // have to do extra conditionals.
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000929
humper@google.coma99a92c2013-02-20 16:42:06 +0000930 int tmpWidth = padHeight + 4*pad;
931 int tmpHeight = padWidth - 2*pad;
932 int tmpSize = tmpWidth * tmpHeight;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000933
humper@google.coma99a92c2013-02-20 16:42:06 +0000934 SkAutoTMalloc<float> tmpImage(tmpSize);
935 memset(tmpImage, 0, tmpSize*sizeof(tmpImage[0]));
936
937 for (int y = 0 ; y < padHeight ; ++y) {
938 uint8_t *srcScanline = padPixels + y*padWidth;
939 for (int x = pad ; x < padWidth - pad ; ++x) {
940 float *outPixel = tmpImage + (x-pad)*tmpWidth + y + 2*pad; // transposed output
941 uint8_t *windowCenter = srcScanline + x;
942 for (int i = -pad ; i <= pad ; ++i) {
943 *outPixel += gaussWindow[pad+i]*windowCenter[i];
944 }
945 *outPixel /= windowSum;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000946 }
humper@google.coma99a92c2013-02-20 16:42:06 +0000947 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000948
humper@google.coma99a92c2013-02-20 16:42:06 +0000949 // blur in Y; now filling in the actual desired destination. We have to do
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000950 // the transpose again; these transposes guarantee that we read memory in
humper@google.coma99a92c2013-02-20 16:42:06 +0000951 // linear order.
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000952
humper@google.coma99a92c2013-02-20 16:42:06 +0000953 for (int y = 0 ; y < tmpHeight ; ++y) {
954 float *srcScanline = tmpImage + y*tmpWidth;
955 for (int x = pad ; x < tmpWidth - pad ; ++x) {
956 float *windowCenter = srcScanline + x;
957 float finalValue = 0;
958 for (int i = -pad ; i <= pad ; ++i) {
959 finalValue += gaussWindow[pad+i]*windowCenter[i];
960 }
961 finalValue /= windowSum;
962 uint8_t *outPixel = dstPixels + (x-pad)*dstWidth + y; // transposed output
963 int integerPixel = int(finalValue + 0.5f);
964 *outPixel = SkClampMax( SkClampPos(integerPixel), 255 );
965 }
966 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000967
humper@google.coma99a92c2013-02-20 16:42:06 +0000968 dst->fImage = dstPixels;
969 // if need be, alloc the "real" dst (same size as src) and copy/merge
970 // the blur into it (applying the src)
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000971 if (style == kInner_SkBlurStyle) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000972 // now we allocate the "real" dst, mirror the size of src
973 size_t srcSize = src.computeImageSize();
974 if (0 == srcSize) {
975 return false; // too big to allocate, abort
976 }
977 dst->fImage = SkMask::AllocImage(srcSize);
978 merge_src_with_blur(dst->fImage, src.fRowBytes,
979 srcPixels, src.fRowBytes,
980 dstPixels + pad*dst->fRowBytes + pad,
981 dst->fRowBytes, srcWidth, srcHeight);
982 SkMask::FreeImage(dstPixels);
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000983 } else if (style != kNormal_SkBlurStyle) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000984 clamp_with_orig(dstPixels + pad*dst->fRowBytes + pad,
985 dst->fRowBytes, srcPixels, src.fRowBytes, srcWidth, srcHeight, style);
986 }
987 (void)autoCall.detach();
988 }
989
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000990 if (style == kInner_SkBlurStyle) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000991 dst->fBounds = src.fBounds; // restore trimmed bounds
992 dst->fRowBytes = src.fRowBytes;
993 }
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000994
humper@google.com7c7292c2013-01-04 20:29:03 +0000995 return true;
996}