blob: 0089bab96e7426cfe78c766fa6be24001f720031 [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
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +000015// scale factor for the blur radius to match the behavior of the all existing blur
humper@google.com7c7292c2013-01-04 20:29:03 +000016// code (both on the CPU and the GPU). This magic constant is 1/sqrt(3).
17
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +000018// TODO: get rid of this fudge factor and move any required fudging up into
humper@google.com7c7292c2013-01-04 20:29:03 +000019// the calling library
20
21#define kBlurRadiusFudgeFactor SkFloatToScalar( .57735f )
22
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +000023#define UNROLL_SEPARABLE_LOOPS
24
senorblanco@chromium.org908276b2012-11-15 20:27:35 +000025/**
26 * This function performs a box blur in X, of the given radius. If the
skia.committer@gmail.com884e60b2012-11-16 02:01:17 +000027 * "transpose" parameter is true, it will transpose the pixels on write,
senorblanco@chromium.org908276b2012-11-15 20:27:35 +000028 * such that X and Y are swapped. Reads are always performed from contiguous
29 * memory in X, for speed. The destination buffer (dst) must be at least
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +000030 * (width + leftRadius + rightRadius) * height bytes in size.
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +000031 *
32 * This is what the inner loop looks like before unrolling, and with the two
33 * cases broken out separately (width < diameter, width >= diameter):
skia.committer@gmail.com76bf70d2013-02-20 07:02:30 +000034 *
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +000035 * if (width < diameter) {
36 * for (int x = 0; x < width; ++x) {
37 * sum += *right++;
skia.committer@gmail.com76bf70d2013-02-20 07:02:30 +000038 * *dptr = (sum * scale + half) >> 24;
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +000039 * dptr += dst_x_stride;
40 * }
41 * for (int x = width; x < diameter; ++x) {
42 * *dptr = (sum * scale + half) >> 24;
43 * dptr += dst_x_stride;
44 * }
45 * for (int x = 0; x < width; ++x) {
46 * *dptr = (sum * scale + half) >> 24;
47 * sum -= *left++;
48 * dptr += dst_x_stride;
49 * }
50 * } else {
51 * for (int x = 0; x < diameter; ++x) {
52 * sum += *right++;
53 * *dptr = (sum * scale + half) >> 24;
54 * dptr += dst_x_stride;
55 * }
56 * for (int x = diameter; x < width; ++x) {
57 * sum += *right++;
58 * *dptr = (sum * scale + half) >> 24;
59 * sum -= *left++;
60 * dptr += dst_x_stride;
61 * }
62 * for (int x = 0; x < diameter; ++x) {
63 * *dptr = (sum * scale + half) >> 24;
64 * sum -= *left++;
65 * dptr += dst_x_stride;
66 * }
67 * }
senorblanco@chromium.org908276b2012-11-15 20:27:35 +000068 */
69static int boxBlur(const uint8_t* src, int src_y_stride, uint8_t* dst,
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +000070 int leftRadius, int rightRadius, int width, int height,
71 bool transpose)
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +000072{
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +000073 int diameter = leftRadius + rightRadius;
74 int kernelSize = diameter + 1;
75 int border = SkMin32(width, diameter);
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +000076 uint32_t scale = (1 << 24) / kernelSize;
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +000077 int new_width = width + SkMax32(leftRadius, rightRadius) * 2;
senorblanco@chromium.org908276b2012-11-15 20:27:35 +000078 int dst_x_stride = transpose ? height : 1;
79 int dst_y_stride = transpose ? 1 : new_width;
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +000080#ifndef SK_DISABLE_BLUR_ROUNDING
81 uint32_t half = 1 << 23;
82#else
83 uint32_t half = 0;
84#endif
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +000085 for (int y = 0; y < height; ++y) {
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +000086 uint32_t sum = 0;
senorblanco@chromium.org908276b2012-11-15 20:27:35 +000087 uint8_t* dptr = dst + y * dst_y_stride;
88 const uint8_t* right = src + y * src_y_stride;
89 const uint8_t* left = right;
senorblanco@chromium.org336b4da2012-11-20 17:09:40 +000090 for (int x = 0; x < rightRadius - leftRadius; x++) {
91 *dptr = 0;
92 dptr += dst_x_stride;
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +000093 }
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +000094#define LEFT_BORDER_ITER \
95 sum += *right++; \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +000096 *dptr = (sum * scale + half) >> 24; \
senorblanco@chromium.org908276b2012-11-15 20:27:35 +000097 dptr += dst_x_stride;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +000098
99 int x = 0;
100#ifdef UNROLL_SEPARABLE_LOOPS
101 for (; x < border - 16; x += 16) {
102 LEFT_BORDER_ITER
103 LEFT_BORDER_ITER
104 LEFT_BORDER_ITER
105 LEFT_BORDER_ITER
106 LEFT_BORDER_ITER
107 LEFT_BORDER_ITER
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
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000118 }
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000119#endif
120 for (; x < border; ++x) {
121 LEFT_BORDER_ITER
122 }
123#undef LEFT_BORDER_ITER
124#define TRIVIAL_ITER \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000125 *dptr = (sum * scale + half) >> 24; \
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000126 dptr += dst_x_stride;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000127 x = width;
128#ifdef UNROLL_SEPARABLE_LOOPS
129 for (; x < diameter - 16; x += 16) {
130 TRIVIAL_ITER
131 TRIVIAL_ITER
132 TRIVIAL_ITER
133 TRIVIAL_ITER
134 TRIVIAL_ITER
135 TRIVIAL_ITER
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
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000146 }
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000147#endif
148 for (; x < diameter; ++x) {
149 TRIVIAL_ITER
150 }
151#undef TRIVIAL_ITER
152#define CENTER_ITER \
153 sum += *right++; \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000154 *dptr = (sum * scale + half) >> 24; \
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000155 sum -= *left++; \
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000156 dptr += dst_x_stride;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000157
158 x = diameter;
159#ifdef UNROLL_SEPARABLE_LOOPS
160 for (; x < width - 16; x += 16) {
161 CENTER_ITER
162 CENTER_ITER
163 CENTER_ITER
164 CENTER_ITER
165 CENTER_ITER
166 CENTER_ITER
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
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000177 }
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000178#endif
179 for (; x < width; ++x) {
180 CENTER_ITER
181 }
182#undef CENTER_ITER
183#define RIGHT_BORDER_ITER \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000184 *dptr = (sum * scale + half) >> 24; \
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000185 sum -= *left++; \
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000186 dptr += dst_x_stride;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000187
188 x = 0;
189#ifdef UNROLL_SEPARABLE_LOOPS
190 for (; x < border - 16; x += 16) {
191 RIGHT_BORDER_ITER
192 RIGHT_BORDER_ITER
193 RIGHT_BORDER_ITER
194 RIGHT_BORDER_ITER
195 RIGHT_BORDER_ITER
196 RIGHT_BORDER_ITER
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
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000207 }
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000208#endif
209 for (; x < border; ++x) {
210 RIGHT_BORDER_ITER
211 }
212#undef RIGHT_BORDER_ITER
humper@google.coma99a92c2013-02-20 16:42:06 +0000213 for (int x = 0; x < leftRadius - rightRadius; ++x) {
senorblanco@chromium.org336b4da2012-11-20 17:09:40 +0000214 *dptr = 0;
215 dptr += dst_x_stride;
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +0000216 }
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000217 SkASSERT(sum == 0);
218 }
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000219 return new_width;
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000220}
221
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000222/**
223 * This variant of the box blur handles blurring of non-integer radii. It
224 * keeps two running sums: an outer sum for the rounded-up kernel radius, and
225 * an inner sum for the rounded-down kernel radius. For each pixel, it linearly
226 * interpolates between them. In float this would be:
227 * outer_weight * outer_sum / kernelSize +
228 * (1.0 - outer_weight) * innerSum / (kernelSize - 2)
skia.committer@gmail.com76bf70d2013-02-20 07:02:30 +0000229 *
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000230 * This is what the inner loop looks like before unrolling, and with the two
231 * cases broken out separately (width < diameter, width >= diameter):
skia.committer@gmail.com76bf70d2013-02-20 07:02:30 +0000232 *
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000233 * if (width < diameter) {
234 * for (int x = 0; x < width; x++) {
235 * inner_sum = outer_sum;
236 * outer_sum += *right++;
237 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
238 * dptr += dst_x_stride;
239 * }
240 * for (int x = width; x < diameter; ++x) {
241 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
242 * dptr += dst_x_stride;
243 * }
244 * for (int x = 0; x < width; x++) {
245 * inner_sum = outer_sum - *left++;
246 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
247 * dptr += dst_x_stride;
248 * outer_sum = inner_sum;
249 * }
250 * } else {
251 * for (int x = 0; x < diameter; x++) {
252 * inner_sum = outer_sum;
253 * outer_sum += *right++;
254 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
255 * dptr += dst_x_stride;
256 * }
257 * for (int x = diameter; x < width; ++x) {
258 * inner_sum = outer_sum - *left;
259 * outer_sum += *right++;
260 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
261 * dptr += dst_x_stride;
262 * outer_sum -= *left++;
263 * }
264 * for (int x = 0; x < diameter; x++) {
265 * inner_sum = outer_sum - *left++;
266 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
267 * dptr += dst_x_stride;
268 * outer_sum = inner_sum;
269 * }
270 * }
271 * }
272 * return new_width;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000273 */
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000274
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000275static int boxBlurInterp(const uint8_t* src, int src_y_stride, uint8_t* dst,
276 int radius, int width, int height,
277 bool transpose, uint8_t outer_weight)
278{
279 int diameter = radius * 2;
280 int kernelSize = diameter + 1;
281 int border = SkMin32(width, diameter);
282 int inner_weight = 255 - outer_weight;
283 outer_weight += outer_weight >> 7;
284 inner_weight += inner_weight >> 7;
285 uint32_t outer_scale = (outer_weight << 16) / kernelSize;
286 uint32_t inner_scale = (inner_weight << 16) / (kernelSize - 2);
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000287#ifndef SK_DISABLE_BLUR_ROUNDING
288 uint32_t half = 1 << 23;
289#else
290 uint32_t half = 0;
291#endif
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000292 int new_width = width + diameter;
293 int dst_x_stride = transpose ? height : 1;
294 int dst_y_stride = transpose ? 1 : new_width;
295 for (int y = 0; y < height; ++y) {
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000296 uint32_t outer_sum = 0, inner_sum = 0;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000297 uint8_t* dptr = dst + y * dst_y_stride;
298 const uint8_t* right = src + y * src_y_stride;
299 const uint8_t* left = right;
300 int x = 0;
301
302#define LEFT_BORDER_ITER \
303 inner_sum = outer_sum; \
304 outer_sum += *right++; \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000305 *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24; \
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000306 dptr += dst_x_stride;
307
308#ifdef UNROLL_SEPARABLE_LOOPS
309 for (;x < border - 16; x += 16) {
310 LEFT_BORDER_ITER
311 LEFT_BORDER_ITER
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 }
327#endif
328
humper@google.coma99a92c2013-02-20 16:42:06 +0000329 for (;x < border; ++x) {
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000330 LEFT_BORDER_ITER
331 }
332#undef LEFT_BORDER_ITER
333 for (int x = width; x < diameter; ++x) {
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000334 *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000335 dptr += dst_x_stride;
336 }
337 x = diameter;
338
339#define CENTER_ITER \
340 inner_sum = outer_sum - *left; \
341 outer_sum += *right++; \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000342 *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24; \
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000343 dptr += dst_x_stride; \
344 outer_sum -= *left++;
345
346#ifdef UNROLL_SEPARABLE_LOOPS
347 for (; x < width - 16; x += 16) {
348 CENTER_ITER
349 CENTER_ITER
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 }
365#endif
366 for (; x < width; ++x) {
367 CENTER_ITER
368 }
369#undef CENTER_ITER
370
371 #define RIGHT_BORDER_ITER \
372 inner_sum = outer_sum - *left++; \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000373 *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24; \
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000374 dptr += dst_x_stride; \
375 outer_sum = inner_sum;
376
377 x = 0;
378#ifdef UNROLL_SEPARABLE_LOOPS
379 for (; x < border - 16; x += 16) {
380 RIGHT_BORDER_ITER
381 RIGHT_BORDER_ITER
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 }
397#endif
humper@google.coma99a92c2013-02-20 16:42:06 +0000398 for (; x < border; ++x) {
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000399 RIGHT_BORDER_ITER
400 }
401#undef RIGHT_BORDER_ITER
402 SkASSERT(outer_sum == 0 && inner_sum == 0);
403 }
404 return new_width;
405}
406
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +0000407static void get_adjusted_radii(SkScalar passRadius, int *loRadius, int *hiRadius)
408{
409 *loRadius = *hiRadius = SkScalarCeil(passRadius);
410 if (SkIntToScalar(*hiRadius) - passRadius > SkFloatToScalar(0.5f)) {
411 *loRadius = *hiRadius - 1;
412 }
413}
414
reed@android.com8a1c16f2008-12-17 15:59:43 +0000415#include "SkColorPriv.h"
416
reed@android.com0e3c6642009-09-18 13:41:56 +0000417static void merge_src_with_blur(uint8_t dst[], int dstRB,
418 const uint8_t src[], int srcRB,
419 const uint8_t blur[], int blurRB,
420 int sw, int sh) {
421 dstRB -= sw;
422 srcRB -= sw;
423 blurRB -= sw;
424 while (--sh >= 0) {
425 for (int x = sw - 1; x >= 0; --x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000426 *dst = SkToU8(SkAlphaMul(*blur, SkAlpha255To256(*src)));
427 dst += 1;
428 src += 1;
429 blur += 1;
430 }
reed@android.com0e3c6642009-09-18 13:41:56 +0000431 dst += dstRB;
432 src += srcRB;
433 blur += blurRB;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000434 }
435}
436
437static void clamp_with_orig(uint8_t dst[], int dstRowBytes,
reed@android.com0e3c6642009-09-18 13:41:56 +0000438 const uint8_t src[], int srcRowBytes,
439 int sw, int sh,
reed@android.com45607672009-09-21 00:27:08 +0000440 SkBlurMask::Style style) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000441 int x;
reed@android.com0e3c6642009-09-18 13:41:56 +0000442 while (--sh >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000443 switch (style) {
444 case SkBlurMask::kSolid_Style:
reed@android.com0e3c6642009-09-18 13:41:56 +0000445 for (x = sw - 1; x >= 0; --x) {
446 int s = *src;
447 int d = *dst;
448 *dst = SkToU8(s + d - SkMulDiv255Round(s, d));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000449 dst += 1;
450 src += 1;
451 }
452 break;
453 case SkBlurMask::kOuter_Style:
reed@android.com0e3c6642009-09-18 13:41:56 +0000454 for (x = sw - 1; x >= 0; --x) {
455 if (*src) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000456 *dst = SkToU8(SkAlphaMul(*dst, SkAlpha255To256(255 - *src)));
reed@android.com0e3c6642009-09-18 13:41:56 +0000457 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000458 dst += 1;
459 src += 1;
460 }
461 break;
462 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000463 SkDEBUGFAIL("Unexpected blur style here");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000464 break;
465 }
466 dst += dstRowBytes - sw;
reed@android.com0e3c6642009-09-18 13:41:56 +0000467 src += srcRowBytes - sw;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000468 }
469}
470
reed@google.com03016a32011-08-12 14:59:59 +0000471///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000472
bsalomon@google.com33cdbde2013-01-11 20:54:44 +0000473// we use a local function to wrap the class static method to work around
reed@android.com8a1c16f2008-12-17 15:59:43 +0000474// a bug in gcc98
475void SkMask_FreeImage(uint8_t* image);
reed@google.com03016a32011-08-12 14:59:59 +0000476void SkMask_FreeImage(uint8_t* image) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000477 SkMask::FreeImage(image);
478}
479
480bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
bungeman@google.com5af16f82011-09-02 15:06:44 +0000481 SkScalar radius, Style style, Quality quality,
commit-bot@chromium.org0a1c3872013-06-11 15:23:42 +0000482 SkIPoint* margin)
bungeman@google.com5af16f82011-09-02 15:06:44 +0000483{
humper@google.coma99a92c2013-02-20 16:42:06 +0000484
reed@google.com03016a32011-08-12 14:59:59 +0000485 if (src.fFormat != SkMask::kA8_Format) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000486 return false;
reed@google.com03016a32011-08-12 14:59:59 +0000487 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000488
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000489 // Force high quality off for small radii (performance)
senorblanco@chromium.org91f489a2012-11-29 17:09:27 +0000490 if (radius < SkIntToScalar(3)) {
491 quality = kLow_Quality;
492 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000493
494 // highQuality: use three box blur passes as a cheap way
humper@google.coma99a92c2013-02-20 16:42:06 +0000495 // to approximate a Gaussian blur
senorblanco@chromium.org91f489a2012-11-29 17:09:27 +0000496 int passCount = (kHigh_Quality == quality) ? 3 : 1;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000497 SkScalar passRadius = (kHigh_Quality == quality) ?
498 SkScalarMul( radius, kBlurRadiusFudgeFactor):
humper@google.coma99a92c2013-02-20 16:42:06 +0000499 radius;
500
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000501 int rx = SkScalarCeil(passRadius);
humper@google.coma99a92c2013-02-20 16:42:06 +0000502 int outerWeight = 255 - SkScalarRound((SkIntToScalar(rx) - passRadius) * 255);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000503
504 SkASSERT(rx >= 0);
humper@google.coma99a92c2013-02-20 16:42:06 +0000505 SkASSERT((unsigned)outerWeight <= 255);
reed@android.com0e3c6642009-09-18 13:41:56 +0000506 if (rx <= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000507 return false;
reed@android.com0e3c6642009-09-18 13:41:56 +0000508 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000509
510 int ry = rx; // only do square blur for now
511
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000512 int padx = passCount * rx;
513 int pady = passCount * ry;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000514
bungeman@google.com5af16f82011-09-02 15:06:44 +0000515 if (margin) {
516 margin->set(padx, pady);
517 }
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000518 dst->fBounds.set(src.fBounds.fLeft - padx, src.fBounds.fTop - pady,
519 src.fBounds.fRight + padx, src.fBounds.fBottom + pady);
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000520
reed@android.com49f0ff22009-03-19 21:52:42 +0000521 dst->fRowBytes = dst->fBounds.width();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000522 dst->fFormat = SkMask::kA8_Format;
523 dst->fImage = NULL;
524
reed@android.com0e3c6642009-09-18 13:41:56 +0000525 if (src.fImage) {
reed@android.com543ed932009-04-24 12:43:40 +0000526 size_t dstSize = dst->computeImageSize();
527 if (0 == dstSize) {
528 return false; // too big to allocate, abort
529 }
530
reed@android.com8a1c16f2008-12-17 15:59:43 +0000531 int sw = src.fBounds.width();
532 int sh = src.fBounds.height();
533 const uint8_t* sp = src.fImage;
reed@android.com543ed932009-04-24 12:43:40 +0000534 uint8_t* dp = SkMask::AllocImage(dstSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000535 SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dp);
536
537 // build the blurry destination
commit-bot@chromium.org0a1c3872013-06-11 15:23:42 +0000538 SkAutoTMalloc<uint8_t> tmpBuffer(dstSize);
539 uint8_t* tp = tmpBuffer.get();
540 int w = sw, h = sh;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000541
commit-bot@chromium.org0a1c3872013-06-11 15:23:42 +0000542 if (outerWeight == 255) {
543 int loRadius, hiRadius;
544 get_adjusted_radii(passRadius, &loRadius, &hiRadius);
545 if (kHigh_Quality == quality) {
546 // Do three X blurs, with a transpose on the final one.
547 w = boxBlur(sp, src.fRowBytes, tp, loRadius, hiRadius, w, h, false);
548 w = boxBlur(tp, w, dp, hiRadius, loRadius, w, h, false);
549 w = boxBlur(dp, w, tp, hiRadius, hiRadius, w, h, true);
550 // Do three Y blurs, with a transpose on the final one.
551 h = boxBlur(tp, h, dp, loRadius, hiRadius, h, w, false);
552 h = boxBlur(dp, h, tp, hiRadius, loRadius, h, w, false);
553 h = boxBlur(tp, h, dp, hiRadius, hiRadius, h, w, true);
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000554 } else {
commit-bot@chromium.org0a1c3872013-06-11 15:23:42 +0000555 w = boxBlur(sp, src.fRowBytes, tp, rx, rx, w, h, true);
556 h = boxBlur(tp, h, dp, ry, ry, h, w, true);
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000557 }
558 } else {
senorblanco@chromium.org91f489a2012-11-29 17:09:27 +0000559 if (kHigh_Quality == quality) {
commit-bot@chromium.org0a1c3872013-06-11 15:23:42 +0000560 // Do three X blurs, with a transpose on the final one.
561 w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, false, outerWeight);
562 w = boxBlurInterp(tp, w, dp, rx, w, h, false, outerWeight);
563 w = boxBlurInterp(dp, w, tp, rx, w, h, true, outerWeight);
564 // Do three Y blurs, with a transpose on the final one.
565 h = boxBlurInterp(tp, h, dp, ry, h, w, false, outerWeight);
566 h = boxBlurInterp(dp, h, tp, ry, h, w, false, outerWeight);
567 h = boxBlurInterp(tp, h, dp, ry, h, w, true, outerWeight);
568 } else {
569 w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, true, outerWeight);
570 h = boxBlurInterp(tp, h, dp, ry, h, w, true, outerWeight);
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000571 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000572 }
573
574 dst->fImage = dp;
575 // if need be, alloc the "real" dst (same size as src) and copy/merge
576 // the blur into it (applying the src)
reed@android.com0e3c6642009-09-18 13:41:56 +0000577 if (style == kInner_Style) {
578 // now we allocate the "real" dst, mirror the size of src
reed@android.com543ed932009-04-24 12:43:40 +0000579 size_t srcSize = src.computeImageSize();
580 if (0 == srcSize) {
581 return false; // too big to allocate, abort
582 }
583 dst->fImage = SkMask::AllocImage(srcSize);
reed@android.com0e3c6642009-09-18 13:41:56 +0000584 merge_src_with_blur(dst->fImage, src.fRowBytes,
585 sp, src.fRowBytes,
reed@google.com03016a32011-08-12 14:59:59 +0000586 dp + passCount * (rx + ry * dst->fRowBytes),
587 dst->fRowBytes, sw, sh);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000588 SkMask::FreeImage(dp);
reed@android.com0e3c6642009-09-18 13:41:56 +0000589 } else if (style != kNormal_Style) {
reed@google.com03016a32011-08-12 14:59:59 +0000590 clamp_with_orig(dp + passCount * (rx + ry * dst->fRowBytes),
591 dst->fRowBytes, sp, src.fRowBytes, sw, sh, style);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000592 }
593 (void)autoCall.detach();
594 }
595
reed@android.com0e3c6642009-09-18 13:41:56 +0000596 if (style == kInner_Style) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000597 dst->fBounds = src.fBounds; // restore trimmed bounds
reed@android.com0e3c6642009-09-18 13:41:56 +0000598 dst->fRowBytes = src.fRowBytes;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000599 }
600
reed@android.com8a1c16f2008-12-17 15:59:43 +0000601 return true;
602}
603
humper@google.com7c7292c2013-01-04 20:29:03 +0000604/* Convolving a box with itself three times results in a piecewise
605 quadratic function:
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000606
humper@google.com7c7292c2013-01-04 20:29:03 +0000607 0 x <= -1.5
humper@google.coma99a92c2013-02-20 16:42:06 +0000608 9/8 + 3/2 x + 1/2 x^2 -1.5 < x <= -.5
humper@google.com7c7292c2013-01-04 20:29:03 +0000609 3/4 - x^2 -.5 < x <= .5
610 9/8 - 3/2 x + 1/2 x^2 0.5 < x <= 1.5
611 0 1.5 < x
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000612
humper@google.coma99a92c2013-02-20 16:42:06 +0000613 Mathematica:
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000614
humper@google.coma99a92c2013-02-20 16:42:06 +0000615 g[x_] := Piecewise [ {
616 {9/8 + 3/2 x + 1/2 x^2 , -1.5 < x <= -.5},
617 {3/4 - x^2 , -.5 < x <= .5},
618 {9/8 - 3/2 x + 1/2 x^2 , 0.5 < x <= 1.5}
619 }, 0]
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000620
humper@google.com7c7292c2013-01-04 20:29:03 +0000621 To get the profile curve of the blurred step function at the rectangle
622 edge, we evaluate the indefinite integral, which is piecewise cubic:
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000623
humper@google.com7c7292c2013-01-04 20:29:03 +0000624 0 x <= -1.5
humper@google.coma99a92c2013-02-20 16:42:06 +0000625 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 +0000626 1/2 + 3/4 x - 1/3 x^3 -.5 < x <= .5
humper@google.coma99a92c2013-02-20 16:42:06 +0000627 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 +0000628 1 1.5 < x
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000629
humper@google.coma99a92c2013-02-20 16:42:06 +0000630 in Mathematica code:
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000631
humper@google.coma99a92c2013-02-20 16:42:06 +0000632 gi[x_] := Piecewise[ {
633 { 0 , x <= -1.5 },
634 { 9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3, -1.5 < x <= -0.5 },
635 { 1/2 + 3/4 x - 1/3 x^3 , -.5 < x <= .5},
636 { 7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3, .5 < x <= 1.5}
637 },1]
humper@google.com7c7292c2013-01-04 20:29:03 +0000638*/
639
humper@google.coma99a92c2013-02-20 16:42:06 +0000640static float gaussianIntegral(float x) {
641 if (x > 1.5f) {
humper@google.com7c7292c2013-01-04 20:29:03 +0000642 return 0.0f;
643 }
humper@google.coma99a92c2013-02-20 16:42:06 +0000644 if (x < -1.5f) {
humper@google.com7c7292c2013-01-04 20:29:03 +0000645 return 1.0f;
646 }
647
648 float x2 = x*x;
649 float x3 = x2*x;
650
jvanverth@google.com9c4e5ac2013-01-07 18:41:28 +0000651 if ( x > 0.5f ) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000652 return 0.5625f - (x3 / 6.0f - 3.0f * x2 * 0.25f + 1.125f * x);
humper@google.com7c7292c2013-01-04 20:29:03 +0000653 }
jvanverth@google.com9c4e5ac2013-01-07 18:41:28 +0000654 if ( x > -0.5f ) {
655 return 0.5f - (0.75f * x - x3 / 3.0f);
humper@google.com7c7292c2013-01-04 20:29:03 +0000656 }
jvanverth@google.com9c4e5ac2013-01-07 18:41:28 +0000657 return 0.4375f + (-x3 / 6.0f - 3.0f * x2 * 0.25f - 1.125f * x);
humper@google.com7c7292c2013-01-04 20:29:03 +0000658}
659
humper@google.com7c5d7b72013-03-11 20:16:28 +0000660// Compute the size of the array allocated for the profile.
661
662static int compute_profile_size(SkScalar radius) {
663 return SkScalarRoundToInt(radius * 3);
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000664
665}
humper@google.com7c5d7b72013-03-11 20:16:28 +0000666
667/* compute_profile allocates and fills in an array of floating
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000668 point values between 0 and 255 for the profile signature of
humper@google.com7c7292c2013-01-04 20:29:03 +0000669 a blurred half-plane with the given blur radius. Since we're
670 going to be doing screened multiplications (i.e., 1 - (1-x)(1-y))
671 all the time, we actually fill in the profile pre-inverted
672 (already done 255-x).
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000673
humper@google.com7c5d7b72013-03-11 20:16:28 +0000674 It's the responsibility of the caller to delete the
humper@google.com7c7292c2013-01-04 20:29:03 +0000675 memory returned in profile_out.
676*/
677
humper@google.com7c5d7b72013-03-11 20:16:28 +0000678static void compute_profile(SkScalar radius, unsigned int **profile_out) {
679 int size = compute_profile_size(radius);
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000680
humper@google.com7c7292c2013-01-04 20:29:03 +0000681 int center = size >> 1;
bsalomon@google.com33cdbde2013-01-11 20:54:44 +0000682 unsigned int *profile = SkNEW_ARRAY(unsigned int, size);
humper@google.com7c7292c2013-01-04 20:29:03 +0000683
humper@google.coma99a92c2013-02-20 16:42:06 +0000684 float invr = 1.f/radius;
humper@google.com7c7292c2013-01-04 20:29:03 +0000685
686 profile[0] = 255;
humper@google.coma99a92c2013-02-20 16:42:06 +0000687 for (int x = 1 ; x < size ; ++x) {
jvanverth@google.comd98df1a2013-02-20 19:02:34 +0000688 float scaled_x = (center - x - .5f) * invr;
humper@google.coma99a92c2013-02-20 16:42:06 +0000689 float gi = gaussianIntegral(scaled_x);
690 profile[x] = 255 - (uint8_t) (255.f * gi);
humper@google.com7c7292c2013-01-04 20:29:03 +0000691 }
692
693 *profile_out = profile;
humper@google.com7c7292c2013-01-04 20:29:03 +0000694}
695
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000696// TODO MAYBE: Maintain a profile cache to avoid recomputing this for
humper@google.com7c7292c2013-01-04 20:29:03 +0000697// commonly used radii. Consider baking some of the most common blur radii
698// directly in as static data?
699
700// Implementation adapted from Michael Herf's approach:
701// http://stereopsis.com/shadowrect/
702
humper@google.coma99a92c2013-02-20 16:42:06 +0000703static inline unsigned int profile_lookup( unsigned int *profile, int loc, int blurred_width, int sharp_width ) {
704 int dx = SkAbs32(((loc << 1) + 1) - blurred_width) - sharp_width; // how far are we from the original edge?
705 int ox = dx >> 1;
706 if (ox < 0) {
707 ox = 0;
708 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000709
humper@google.coma99a92c2013-02-20 16:42:06 +0000710 return profile[ox];
711}
712
humper@google.com7c7292c2013-01-04 20:29:03 +0000713bool SkBlurMask::BlurRect(SkMask *dst, const SkRect &src,
humper@google.coma99a92c2013-02-20 16:42:06 +0000714 SkScalar provided_radius, Style style,
humper@google.com7c5d7b72013-03-11 20:16:28 +0000715 SkIPoint *margin, SkMask::CreateMode createMode) {
humper@google.com7c7292c2013-01-04 20:29:03 +0000716 int profile_size;
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000717
humper@google.com7c5d7b72013-03-11 20:16:28 +0000718 float radius = SkScalarToFloat(SkScalarMul(provided_radius, kBlurRadiusFudgeFactor));
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000719
humper@google.com1e1a24e2013-02-20 18:35:40 +0000720 // adjust blur radius to match interpretation from boxfilter code
humper@google.com7c5d7b72013-03-11 20:16:28 +0000721 radius = (radius + .5f) * 2.f;
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000722
humper@google.com7c5d7b72013-03-11 20:16:28 +0000723 profile_size = compute_profile_size(radius);
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000724
humper@google.coma99a92c2013-02-20 16:42:06 +0000725 int pad = profile_size/2;
humper@google.com7c7292c2013-01-04 20:29:03 +0000726 if (margin) {
727 margin->set( pad, pad );
728 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000729
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000730 dst->fBounds.set(SkScalarRoundToInt(src.fLeft - pad),
731 SkScalarRoundToInt(src.fTop - pad),
732 SkScalarRoundToInt(src.fRight + pad),
humper@google.com68a690c2013-03-11 21:16:20 +0000733 SkScalarRoundToInt(src.fBottom + pad));
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000734
humper@google.com7c7292c2013-01-04 20:29:03 +0000735 dst->fRowBytes = dst->fBounds.width();
736 dst->fFormat = SkMask::kA8_Format;
737 dst->fImage = NULL;
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000738
humper@google.com7c5d7b72013-03-11 20:16:28 +0000739 int sw = SkScalarFloorToInt(src.width());
740 int sh = SkScalarFloorToInt(src.height());
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000741
humper@google.com7c5d7b72013-03-11 20:16:28 +0000742 if (createMode == SkMask::kJustComputeBounds_CreateMode) {
743 if (style == kInner_Style) {
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000744 dst->fBounds.set(SkScalarRoundToInt(src.fLeft),
745 SkScalarRoundToInt(src.fTop),
746 SkScalarRoundToInt(src.fRight),
humper@google.com68a690c2013-03-11 21:16:20 +0000747 SkScalarRoundToInt(src.fBottom)); // restore trimmed bounds
humper@google.com7c5d7b72013-03-11 20:16:28 +0000748 dst->fRowBytes = sw;
749 }
750 return true;
751 }
752 unsigned int *profile = NULL;
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000753
humper@google.com7c5d7b72013-03-11 20:16:28 +0000754 compute_profile(radius, &profile);
755 SkAutoTDeleteArray<unsigned int> ada(profile);
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000756
humper@google.com7c7292c2013-01-04 20:29:03 +0000757 size_t dstSize = dst->computeImageSize();
758 if (0 == dstSize) {
759 return false; // too big to allocate, abort
760 }
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000761
humper@google.com7c7292c2013-01-04 20:29:03 +0000762 uint8_t* dp = SkMask::AllocImage(dstSize);
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000763
humper@google.com7c7292c2013-01-04 20:29:03 +0000764 dst->fImage = dp;
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000765
humper@google.coma99a92c2013-02-20 16:42:06 +0000766 int dstHeight = dst->fBounds.height();
767 int dstWidth = dst->fBounds.width();
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000768
humper@google.com7c7292c2013-01-04 20:29:03 +0000769 // nearest odd number less than the profile size represents the center
770 // of the (2x scaled) profile
771 int center = ( profile_size & ~1 ) - 1;
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000772
humper@google.com7c7292c2013-01-04 20:29:03 +0000773 int w = sw - center;
774 int h = sh - center;
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000775
humper@google.com7c7292c2013-01-04 20:29:03 +0000776 uint8_t *outptr = dp;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000777
humper@google.coma99a92c2013-02-20 16:42:06 +0000778 SkAutoTMalloc<uint8_t> horizontalScanline(dstWidth);
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000779
humper@google.coma99a92c2013-02-20 16:42:06 +0000780 for (int x = 0 ; x < dstWidth ; ++x) {
781 if (profile_size <= sw) {
782 horizontalScanline[x] = profile_lookup(profile, x, dstWidth, w);
783 } else {
784 float span = float(sw)/radius;
jvanverth@google.comd98df1a2013-02-20 19:02:34 +0000785 float giX = 1.5f - (x+.5f)/radius;
humper@google.coma99a92c2013-02-20 16:42:06 +0000786 horizontalScanline[x] = (uint8_t) (255 * (gaussianIntegral(giX) - gaussianIntegral(giX + span)));
787 }
788 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000789
humper@google.coma99a92c2013-02-20 16:42:06 +0000790 for (int y = 0 ; y < dstHeight ; ++y) {
791 unsigned int profile_y;
792 if (profile_size <= sh) {
793 profile_y = profile_lookup(profile, y, dstHeight, h);
794 } else {
795 float span = float(sh)/radius;
jvanverth@google.comd98df1a2013-02-20 19:02:34 +0000796 float giY = 1.5f - (y+.5f)/radius;
humper@google.coma99a92c2013-02-20 16:42:06 +0000797 profile_y = (uint8_t) (255 * (gaussianIntegral(giY) - gaussianIntegral(giY + span)));
798 }
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000799
humper@google.coma99a92c2013-02-20 16:42:06 +0000800 for (int x = 0 ; x < dstWidth ; x++) {
801 unsigned int maskval = SkMulDiv255Round(horizontalScanline[x], profile_y);
humper@google.com7c7292c2013-01-04 20:29:03 +0000802 *(outptr++) = maskval;
803 }
804 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000805
humper@google.coma99a92c2013-02-20 16:42:06 +0000806 if (style == kInner_Style) {
807 // now we allocate the "real" dst, mirror the size of src
jvanverth@google.comd98df1a2013-02-20 19:02:34 +0000808 size_t srcSize = (size_t)(src.width() * src.height());
humper@google.coma99a92c2013-02-20 16:42:06 +0000809 if (0 == srcSize) {
810 return false; // too big to allocate, abort
811 }
812 dst->fImage = SkMask::AllocImage(srcSize);
813 for (int y = 0 ; y < sh ; y++) {
814 uint8_t *blur_scanline = dp + (y+pad)*dstWidth + pad;
815 uint8_t *inner_scanline = dst->fImage + y*sw;
816 memcpy(inner_scanline, blur_scanline, sw);
817 }
818 SkMask::FreeImage(dp);
819
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000820 dst->fBounds.set(SkScalarRoundToInt(src.fLeft),
821 SkScalarRoundToInt(src.fTop),
822 SkScalarRoundToInt(src.fRight),
humper@google.com68a690c2013-03-11 21:16:20 +0000823 SkScalarRoundToInt(src.fBottom)); // restore trimmed bounds
humper@google.coma99a92c2013-02-20 16:42:06 +0000824 dst->fRowBytes = sw;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000825
humper@google.coma99a92c2013-02-20 16:42:06 +0000826 } else if (style == kOuter_Style) {
827 for (int y = pad ; y < dstHeight-pad ; y++) {
828 uint8_t *dst_scanline = dp + y*dstWidth + pad;
829 memset(dst_scanline, 0, sw);
830 }
humper@google.comd4d57302013-03-11 22:18:54 +0000831 } else if (style == kSolid_Style) {
832 for (int y = pad ; y < dstHeight-pad ; y++) {
833 uint8_t *dst_scanline = dp + y*dstWidth + pad;
834 memset(dst_scanline, 0xff, sw);
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000835 }
humper@google.coma99a92c2013-02-20 16:42:06 +0000836 }
837 // normal and solid styles are the same for analytic rect blurs, so don't
838 // need to handle solid specially.
839
840 return true;
841}
842
843// The "simple" blur is a direct implementation of separable convolution with a discrete
844// gaussian kernel. It's "ground truth" in a sense; too slow to be used, but very
845// useful for correctness comparisons.
846
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000847bool SkBlurMask::BlurGroundTruth(SkMask* dst, const SkMask& src, SkScalar provided_radius,
humper@google.coma99a92c2013-02-20 16:42:06 +0000848 Style style, SkIPoint* margin) {
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000849
humper@google.coma99a92c2013-02-20 16:42:06 +0000850 if (src.fFormat != SkMask::kA8_Format) {
851 return false;
852 }
853
854 float radius = SkScalarToFloat(SkScalarMul(provided_radius, kBlurRadiusFudgeFactor));
855 float stddev = SkScalarToFloat(radius) /2.0f;
856 float variance = stddev * stddev;
857
858 int windowSize = SkScalarCeil(stddev*4);
859 // round window size up to nearest odd number
860 windowSize |= 1;
861
862 SkAutoTMalloc<float> gaussWindow(windowSize);
863
864 int halfWindow = windowSize >> 1;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000865
humper@google.coma99a92c2013-02-20 16:42:06 +0000866 gaussWindow[halfWindow] = 1;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000867
humper@google.coma99a92c2013-02-20 16:42:06 +0000868 float windowSum = 1;
869 for (int x = 1 ; x <= halfWindow ; ++x) {
870 float gaussian = expf(-x*x / variance);
871 gaussWindow[halfWindow + x] = gaussWindow[halfWindow-x] = gaussian;
872 windowSum += 2*gaussian;
873 }
874
875 // leave the filter un-normalized for now; we will divide by the normalization
876 // sum later;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000877
humper@google.coma99a92c2013-02-20 16:42:06 +0000878 int pad = halfWindow;
879 if (margin) {
880 margin->set( pad, pad );
881 }
882
883 dst->fBounds = src.fBounds;
884 dst->fBounds.outset(pad, pad);
885
886 dst->fRowBytes = dst->fBounds.width();
887 dst->fFormat = SkMask::kA8_Format;
888 dst->fImage = NULL;
889
890 if (src.fImage) {
891
892 size_t dstSize = dst->computeImageSize();
893 if (0 == dstSize) {
894 return false; // too big to allocate, abort
895 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000896
humper@google.coma99a92c2013-02-20 16:42:06 +0000897 int srcWidth = src.fBounds.width();
898 int srcHeight = src.fBounds.height();
899 int dstWidth = dst->fBounds.width();
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000900
humper@google.coma99a92c2013-02-20 16:42:06 +0000901 const uint8_t* srcPixels = src.fImage;
902 uint8_t* dstPixels = SkMask::AllocImage(dstSize);
903 SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dstPixels);
904
905 // do the actual blur. First, make a padded copy of the source.
906 // use double pad so we never have to check if we're outside anything
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000907
humper@google.coma99a92c2013-02-20 16:42:06 +0000908 int padWidth = srcWidth + 4*pad;
909 int padHeight = srcHeight;
910 int padSize = padWidth * padHeight;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000911
humper@google.coma99a92c2013-02-20 16:42:06 +0000912 SkAutoTMalloc<uint8_t> padPixels(padSize);
913 memset(padPixels, 0, padSize);
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000914
humper@google.coma99a92c2013-02-20 16:42:06 +0000915 for (int y = 0 ; y < srcHeight; ++y) {
916 uint8_t* padptr = padPixels + y * padWidth + 2*pad;
917 const uint8_t* srcptr = srcPixels + y * srcWidth;
918 memcpy(padptr, srcptr, srcWidth);
919 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000920
humper@google.coma99a92c2013-02-20 16:42:06 +0000921 // blur in X, transposing the result into a temporary floating point buffer.
922 // also double-pad the intermediate result so that the second blur doesn't
923 // have to do extra conditionals.
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000924
humper@google.coma99a92c2013-02-20 16:42:06 +0000925 int tmpWidth = padHeight + 4*pad;
926 int tmpHeight = padWidth - 2*pad;
927 int tmpSize = tmpWidth * tmpHeight;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000928
humper@google.coma99a92c2013-02-20 16:42:06 +0000929 SkAutoTMalloc<float> tmpImage(tmpSize);
930 memset(tmpImage, 0, tmpSize*sizeof(tmpImage[0]));
931
932 for (int y = 0 ; y < padHeight ; ++y) {
933 uint8_t *srcScanline = padPixels + y*padWidth;
934 for (int x = pad ; x < padWidth - pad ; ++x) {
935 float *outPixel = tmpImage + (x-pad)*tmpWidth + y + 2*pad; // transposed output
936 uint8_t *windowCenter = srcScanline + x;
937 for (int i = -pad ; i <= pad ; ++i) {
938 *outPixel += gaussWindow[pad+i]*windowCenter[i];
939 }
940 *outPixel /= windowSum;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000941 }
humper@google.coma99a92c2013-02-20 16:42:06 +0000942 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000943
humper@google.coma99a92c2013-02-20 16:42:06 +0000944 // blur in Y; now filling in the actual desired destination. We have to do
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000945 // the transpose again; these transposes guarantee that we read memory in
humper@google.coma99a92c2013-02-20 16:42:06 +0000946 // linear order.
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000947
humper@google.coma99a92c2013-02-20 16:42:06 +0000948 for (int y = 0 ; y < tmpHeight ; ++y) {
949 float *srcScanline = tmpImage + y*tmpWidth;
950 for (int x = pad ; x < tmpWidth - pad ; ++x) {
951 float *windowCenter = srcScanline + x;
952 float finalValue = 0;
953 for (int i = -pad ; i <= pad ; ++i) {
954 finalValue += gaussWindow[pad+i]*windowCenter[i];
955 }
956 finalValue /= windowSum;
957 uint8_t *outPixel = dstPixels + (x-pad)*dstWidth + y; // transposed output
958 int integerPixel = int(finalValue + 0.5f);
959 *outPixel = SkClampMax( SkClampPos(integerPixel), 255 );
960 }
961 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000962
humper@google.coma99a92c2013-02-20 16:42:06 +0000963 dst->fImage = dstPixels;
964 // if need be, alloc the "real" dst (same size as src) and copy/merge
965 // the blur into it (applying the src)
966 if (style == kInner_Style) {
967 // now we allocate the "real" dst, mirror the size of src
968 size_t srcSize = src.computeImageSize();
969 if (0 == srcSize) {
970 return false; // too big to allocate, abort
971 }
972 dst->fImage = SkMask::AllocImage(srcSize);
973 merge_src_with_blur(dst->fImage, src.fRowBytes,
974 srcPixels, src.fRowBytes,
975 dstPixels + pad*dst->fRowBytes + pad,
976 dst->fRowBytes, srcWidth, srcHeight);
977 SkMask::FreeImage(dstPixels);
978 } else if (style != kNormal_Style) {
979 clamp_with_orig(dstPixels + pad*dst->fRowBytes + pad,
980 dst->fRowBytes, srcPixels, src.fRowBytes, srcWidth, srcHeight, style);
981 }
982 (void)autoCall.detach();
983 }
984
985 if (style == kInner_Style) {
986 dst->fBounds = src.fBounds; // restore trimmed bounds
987 dst->fRowBytes = src.fRowBytes;
988 }
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000989
humper@google.com7c7292c2013-01-04 20:29:03 +0000990 return true;
991}