blob: ce3c504759764bd43479a091c8ec350ef265fd5f [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
reed@android.com8a1c16f2008-12-17 15:59:43 +00008
9#include "SkBlurMask.h"
tomhudson@google.com889bd8b2011-09-27 17:38:17 +000010#include "SkMath.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000011#include "SkTemplates.h"
tomhudson@google.com01224d52011-11-28 18:22:01 +000012#include "SkEndian.h"
13
robertphillips@google.com7ce661d2013-08-27 16:14:03 +000014
reed@google.comdaaafa62014-04-29 15:20:16 +000015// This constant approximates the scaling done in the software path's
16// "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)).
17// IMHO, it actually should be 1: we blur "less" than we should do
18// according to the CSS and canvas specs, simply because Safari does the same.
19// Firefox used to do the same too, until 4.0 where they fixed it. So at some
20// point we should probably get rid of these scaling constants and rebaseline
21// all the blur tests.
22static const SkScalar kBLUR_SIGMA_SCALE = 0.57735f;
robertphillips@google.com7ce661d2013-08-27 16:14:03 +000023
reed@google.comdaaafa62014-04-29 15:20:16 +000024SkScalar SkBlurMask::ConvertRadiusToSigma(SkScalar radius) {
25 return radius > 0 ? kBLUR_SIGMA_SCALE * radius + 0.5f : 0.0f;
26}
27
28SkScalar SkBlurMask::ConvertSigmaToRadius(SkScalar sigma) {
29 return sigma > 0.5f ? (sigma - 0.5f) / kBLUR_SIGMA_SCALE : 0.0f;
robertphillips@google.com7ce661d2013-08-27 16:14:03 +000030}
humper@google.com7c7292c2013-01-04 20:29:03 +000031
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +000032#define UNROLL_SEPARABLE_LOOPS
33
senorblanco@chromium.org908276b2012-11-15 20:27:35 +000034/**
35 * This function performs a box blur in X, of the given radius. If the
skia.committer@gmail.com884e60b2012-11-16 02:01:17 +000036 * "transpose" parameter is true, it will transpose the pixels on write,
senorblanco@chromium.org908276b2012-11-15 20:27:35 +000037 * such that X and Y are swapped. Reads are always performed from contiguous
38 * memory in X, for speed. The destination buffer (dst) must be at least
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +000039 * (width + leftRadius + rightRadius) * height bytes in size.
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +000040 *
41 * This is what the inner loop looks like before unrolling, and with the two
42 * cases broken out separately (width < diameter, width >= diameter):
skia.committer@gmail.com76bf70d2013-02-20 07:02:30 +000043 *
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +000044 * if (width < diameter) {
45 * for (int x = 0; x < width; ++x) {
46 * sum += *right++;
skia.committer@gmail.com76bf70d2013-02-20 07:02:30 +000047 * *dptr = (sum * scale + half) >> 24;
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +000048 * dptr += dst_x_stride;
49 * }
50 * for (int x = width; x < diameter; ++x) {
51 * *dptr = (sum * scale + half) >> 24;
52 * dptr += dst_x_stride;
53 * }
54 * for (int x = 0; x < width; ++x) {
55 * *dptr = (sum * scale + half) >> 24;
56 * sum -= *left++;
57 * dptr += dst_x_stride;
58 * }
59 * } else {
60 * for (int x = 0; x < diameter; ++x) {
61 * sum += *right++;
62 * *dptr = (sum * scale + half) >> 24;
63 * dptr += dst_x_stride;
64 * }
65 * for (int x = diameter; x < width; ++x) {
66 * sum += *right++;
67 * *dptr = (sum * scale + half) >> 24;
68 * sum -= *left++;
69 * dptr += dst_x_stride;
70 * }
71 * for (int x = 0; x < diameter; ++x) {
72 * *dptr = (sum * scale + half) >> 24;
73 * sum -= *left++;
74 * dptr += dst_x_stride;
75 * }
76 * }
senorblanco@chromium.org908276b2012-11-15 20:27:35 +000077 */
78static int boxBlur(const uint8_t* src, int src_y_stride, uint8_t* dst,
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +000079 int leftRadius, int rightRadius, int width, int height,
80 bool transpose)
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +000081{
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +000082 int diameter = leftRadius + rightRadius;
83 int kernelSize = diameter + 1;
84 int border = SkMin32(width, diameter);
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +000085 uint32_t scale = (1 << 24) / kernelSize;
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +000086 int new_width = width + SkMax32(leftRadius, rightRadius) * 2;
senorblanco@chromium.org908276b2012-11-15 20:27:35 +000087 int dst_x_stride = transpose ? height : 1;
88 int dst_y_stride = transpose ? 1 : new_width;
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +000089 uint32_t half = 1 << 23;
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +000090 for (int y = 0; y < height; ++y) {
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +000091 uint32_t sum = 0;
senorblanco@chromium.org908276b2012-11-15 20:27:35 +000092 uint8_t* dptr = dst + y * dst_y_stride;
93 const uint8_t* right = src + y * src_y_stride;
94 const uint8_t* left = right;
senorblanco@chromium.org336b4da2012-11-20 17:09:40 +000095 for (int x = 0; x < rightRadius - leftRadius; x++) {
96 *dptr = 0;
97 dptr += dst_x_stride;
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +000098 }
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +000099#define LEFT_BORDER_ITER \
100 sum += *right++; \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000101 *dptr = (sum * scale + half) >> 24; \
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000102 dptr += dst_x_stride;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000103
104 int x = 0;
105#ifdef UNROLL_SEPARABLE_LOOPS
106 for (; x < border - 16; x += 16) {
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
118 LEFT_BORDER_ITER
119 LEFT_BORDER_ITER
120 LEFT_BORDER_ITER
121 LEFT_BORDER_ITER
122 LEFT_BORDER_ITER
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000123 }
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000124#endif
125 for (; x < border; ++x) {
126 LEFT_BORDER_ITER
127 }
128#undef LEFT_BORDER_ITER
129#define TRIVIAL_ITER \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000130 *dptr = (sum * scale + half) >> 24; \
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000131 dptr += dst_x_stride;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000132 x = width;
133#ifdef UNROLL_SEPARABLE_LOOPS
134 for (; x < diameter - 16; x += 16) {
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
146 TRIVIAL_ITER
147 TRIVIAL_ITER
148 TRIVIAL_ITER
149 TRIVIAL_ITER
150 TRIVIAL_ITER
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000151 }
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000152#endif
153 for (; x < diameter; ++x) {
154 TRIVIAL_ITER
155 }
156#undef TRIVIAL_ITER
157#define CENTER_ITER \
158 sum += *right++; \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000159 *dptr = (sum * scale + half) >> 24; \
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000160 sum -= *left++; \
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000161 dptr += dst_x_stride;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000162
163 x = diameter;
164#ifdef UNROLL_SEPARABLE_LOOPS
165 for (; x < width - 16; x += 16) {
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
177 CENTER_ITER
178 CENTER_ITER
179 CENTER_ITER
180 CENTER_ITER
181 CENTER_ITER
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000182 }
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000183#endif
184 for (; x < width; ++x) {
185 CENTER_ITER
186 }
187#undef CENTER_ITER
188#define RIGHT_BORDER_ITER \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000189 *dptr = (sum * scale + half) >> 24; \
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000190 sum -= *left++; \
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000191 dptr += dst_x_stride;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000192
193 x = 0;
194#ifdef UNROLL_SEPARABLE_LOOPS
195 for (; x < border - 16; x += 16) {
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
207 RIGHT_BORDER_ITER
208 RIGHT_BORDER_ITER
209 RIGHT_BORDER_ITER
210 RIGHT_BORDER_ITER
211 RIGHT_BORDER_ITER
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000212 }
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000213#endif
214 for (; x < border; ++x) {
215 RIGHT_BORDER_ITER
216 }
217#undef RIGHT_BORDER_ITER
humper@google.coma99a92c2013-02-20 16:42:06 +0000218 for (int x = 0; x < leftRadius - rightRadius; ++x) {
senorblanco@chromium.org336b4da2012-11-20 17:09:40 +0000219 *dptr = 0;
220 dptr += dst_x_stride;
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +0000221 }
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000222 SkASSERT(sum == 0);
223 }
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000224 return new_width;
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000225}
226
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000227/**
228 * This variant of the box blur handles blurring of non-integer radii. It
229 * keeps two running sums: an outer sum for the rounded-up kernel radius, and
230 * an inner sum for the rounded-down kernel radius. For each pixel, it linearly
231 * interpolates between them. In float this would be:
232 * outer_weight * outer_sum / kernelSize +
233 * (1.0 - outer_weight) * innerSum / (kernelSize - 2)
skia.committer@gmail.com76bf70d2013-02-20 07:02:30 +0000234 *
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000235 * This is what the inner loop looks like before unrolling, and with the two
236 * cases broken out separately (width < diameter, width >= diameter):
skia.committer@gmail.com76bf70d2013-02-20 07:02:30 +0000237 *
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000238 * if (width < diameter) {
239 * for (int x = 0; x < width; x++) {
240 * inner_sum = outer_sum;
241 * outer_sum += *right++;
242 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
243 * dptr += dst_x_stride;
244 * }
245 * for (int x = width; x < diameter; ++x) {
246 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
247 * dptr += dst_x_stride;
248 * }
249 * for (int x = 0; x < width; x++) {
250 * inner_sum = outer_sum - *left++;
251 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
252 * dptr += dst_x_stride;
253 * outer_sum = inner_sum;
254 * }
255 * } else {
256 * for (int x = 0; x < diameter; x++) {
257 * inner_sum = outer_sum;
258 * outer_sum += *right++;
259 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
260 * dptr += dst_x_stride;
261 * }
262 * for (int x = diameter; x < width; ++x) {
263 * inner_sum = outer_sum - *left;
264 * outer_sum += *right++;
265 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
266 * dptr += dst_x_stride;
267 * outer_sum -= *left++;
268 * }
269 * for (int x = 0; x < diameter; x++) {
270 * inner_sum = outer_sum - *left++;
271 * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
272 * dptr += dst_x_stride;
273 * outer_sum = inner_sum;
274 * }
275 * }
276 * }
277 * return new_width;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000278 */
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000279
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000280static int boxBlurInterp(const uint8_t* src, int src_y_stride, uint8_t* dst,
281 int radius, int width, int height,
282 bool transpose, uint8_t outer_weight)
283{
284 int diameter = radius * 2;
285 int kernelSize = diameter + 1;
286 int border = SkMin32(width, diameter);
287 int inner_weight = 255 - outer_weight;
288 outer_weight += outer_weight >> 7;
289 inner_weight += inner_weight >> 7;
290 uint32_t outer_scale = (outer_weight << 16) / kernelSize;
291 uint32_t inner_scale = (inner_weight << 16) / (kernelSize - 2);
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000292 uint32_t half = 1 << 23;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000293 int new_width = width + diameter;
294 int dst_x_stride = transpose ? height : 1;
295 int dst_y_stride = transpose ? 1 : new_width;
296 for (int y = 0; y < height; ++y) {
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000297 uint32_t outer_sum = 0, inner_sum = 0;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000298 uint8_t* dptr = dst + y * dst_y_stride;
299 const uint8_t* right = src + y * src_y_stride;
300 const uint8_t* left = right;
301 int x = 0;
302
303#define LEFT_BORDER_ITER \
304 inner_sum = outer_sum; \
305 outer_sum += *right++; \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000306 *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24; \
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000307 dptr += dst_x_stride;
308
309#ifdef UNROLL_SEPARABLE_LOOPS
310 for (;x < border - 16; x += 16) {
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 LEFT_BORDER_ITER
327 }
328#endif
329
humper@google.coma99a92c2013-02-20 16:42:06 +0000330 for (;x < border; ++x) {
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000331 LEFT_BORDER_ITER
332 }
333#undef LEFT_BORDER_ITER
334 for (int x = width; x < diameter; ++x) {
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000335 *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000336 dptr += dst_x_stride;
337 }
338 x = diameter;
339
340#define CENTER_ITER \
341 inner_sum = outer_sum - *left; \
342 outer_sum += *right++; \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000343 *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24; \
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000344 dptr += dst_x_stride; \
345 outer_sum -= *left++;
346
347#ifdef UNROLL_SEPARABLE_LOOPS
348 for (; x < width - 16; x += 16) {
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 CENTER_ITER
365 }
366#endif
367 for (; x < width; ++x) {
368 CENTER_ITER
369 }
370#undef CENTER_ITER
371
372 #define RIGHT_BORDER_ITER \
373 inner_sum = outer_sum - *left++; \
senorblanco@chromium.org4a525d72013-02-19 16:09:10 +0000374 *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24; \
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000375 dptr += dst_x_stride; \
376 outer_sum = inner_sum;
377
378 x = 0;
379#ifdef UNROLL_SEPARABLE_LOOPS
380 for (; x < border - 16; x += 16) {
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 RIGHT_BORDER_ITER
397 }
398#endif
humper@google.coma99a92c2013-02-20 16:42:06 +0000399 for (; x < border; ++x) {
senorblanco@chromium.org9b0d4d72012-11-27 22:57:41 +0000400 RIGHT_BORDER_ITER
401 }
402#undef RIGHT_BORDER_ITER
403 SkASSERT(outer_sum == 0 && inner_sum == 0);
404 }
405 return new_width;
406}
407
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +0000408static void get_adjusted_radii(SkScalar passRadius, int *loRadius, int *hiRadius)
409{
reed@google.come1ca7052013-12-17 19:22:07 +0000410 *loRadius = *hiRadius = SkScalarCeilToInt(passRadius);
commit-bot@chromium.org4b413c82013-11-25 19:44:07 +0000411 if (SkIntToScalar(*hiRadius) - passRadius > 0.5f) {
senorblanco@chromium.orgc4381302012-11-16 17:22:33 +0000412 *loRadius = *hiRadius - 1;
413 }
414}
415
reed@android.com8a1c16f2008-12-17 15:59:43 +0000416#include "SkColorPriv.h"
417
reed@android.com0e3c6642009-09-18 13:41:56 +0000418static void merge_src_with_blur(uint8_t dst[], int dstRB,
419 const uint8_t src[], int srcRB,
420 const uint8_t blur[], int blurRB,
421 int sw, int sh) {
422 dstRB -= sw;
423 srcRB -= sw;
424 blurRB -= sw;
425 while (--sh >= 0) {
426 for (int x = sw - 1; x >= 0; --x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000427 *dst = SkToU8(SkAlphaMul(*blur, SkAlpha255To256(*src)));
428 dst += 1;
429 src += 1;
430 blur += 1;
431 }
reed@android.com0e3c6642009-09-18 13:41:56 +0000432 dst += dstRB;
433 src += srcRB;
434 blur += blurRB;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000435 }
436}
437
438static void clamp_with_orig(uint8_t dst[], int dstRowBytes,
reed@android.com0e3c6642009-09-18 13:41:56 +0000439 const uint8_t src[], int srcRowBytes,
440 int sw, int sh,
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000441 SkBlurStyle style) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000442 int x;
reed@android.com0e3c6642009-09-18 13:41:56 +0000443 while (--sh >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000444 switch (style) {
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000445 case kSolid_SkBlurStyle:
reed@android.com0e3c6642009-09-18 13:41:56 +0000446 for (x = sw - 1; x >= 0; --x) {
447 int s = *src;
448 int d = *dst;
449 *dst = SkToU8(s + d - SkMulDiv255Round(s, d));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000450 dst += 1;
451 src += 1;
452 }
453 break;
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000454 case kOuter_SkBlurStyle:
reed@android.com0e3c6642009-09-18 13:41:56 +0000455 for (x = sw - 1; x >= 0; --x) {
456 if (*src) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000457 *dst = SkToU8(SkAlphaMul(*dst, SkAlpha255To256(255 - *src)));
reed@android.com0e3c6642009-09-18 13:41:56 +0000458 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000459 dst += 1;
460 src += 1;
461 }
462 break;
463 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000464 SkDEBUGFAIL("Unexpected blur style here");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000465 break;
466 }
467 dst += dstRowBytes - sw;
reed@android.com0e3c6642009-09-18 13:41:56 +0000468 src += srcRowBytes - sw;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000469 }
470}
471
reed@google.com03016a32011-08-12 14:59:59 +0000472///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000473
bsalomon@google.com33cdbde2013-01-11 20:54:44 +0000474// we use a local function to wrap the class static method to work around
reed@android.com8a1c16f2008-12-17 15:59:43 +0000475// a bug in gcc98
476void SkMask_FreeImage(uint8_t* image);
reed@google.com03016a32011-08-12 14:59:59 +0000477void SkMask_FreeImage(uint8_t* image) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000478 SkMask::FreeImage(image);
479}
480
commit-bot@chromium.org3d8bf232014-04-28 19:49:24 +0000481bool SkBlurMask::BoxBlur(SkMask* dst, const SkMask& src,
482 SkScalar sigma, SkBlurStyle style, SkBlurQuality quality,
483 SkIPoint* margin, bool force_quality) {
484
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)
commit-bot@chromium.org3d8bf232014-04-28 19:49:24 +0000490 if (!force_quality && sigma <= SkIntToScalar(2)) {
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000491 quality = kLow_SkBlurQuality;
senorblanco@chromium.org91f489a2012-11-29 17:09:27 +0000492 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000493
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000494 SkScalar passRadius;
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000495 if (kHigh_SkBlurQuality == quality) {
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000496 // For the high quality path the 3 pass box blur kernel width is
497 // 6*rad+1 while the full Gaussian width is 6*sigma.
498 passRadius = sigma - (1/6.0f);
499 } else {
skia.committer@gmail.com7bd141d2013-08-28 07:01:18 +0000500 // For the low quality path we only attempt to cover 3*sigma of the
501 // Gaussian blur area (1.5*sigma on each side). The single pass box
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000502 // blur's kernel size is 2*rad+1.
503 passRadius = 1.5f*sigma - 0.5f;
504 }
505
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000506 // highQuality: use three box blur passes as a cheap way
humper@google.coma99a92c2013-02-20 16:42:06 +0000507 // to approximate a Gaussian blur
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000508 int passCount = (kHigh_SkBlurQuality == quality) ? 3 : 1;
humper@google.coma99a92c2013-02-20 16:42:06 +0000509
reed@google.come1ca7052013-12-17 19:22:07 +0000510 int rx = SkScalarCeilToInt(passRadius);
511 int outerWeight = 255 - SkScalarRoundToInt((SkIntToScalar(rx) - passRadius) * 255);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000512
513 SkASSERT(rx >= 0);
humper@google.coma99a92c2013-02-20 16:42:06 +0000514 SkASSERT((unsigned)outerWeight <= 255);
reed@android.com0e3c6642009-09-18 13:41:56 +0000515 if (rx <= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000516 return false;
reed@android.com0e3c6642009-09-18 13:41:56 +0000517 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000518
519 int ry = rx; // only do square blur for now
520
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000521 int padx = passCount * rx;
522 int pady = passCount * ry;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000523
bungeman@google.com5af16f82011-09-02 15:06:44 +0000524 if (margin) {
525 margin->set(padx, pady);
526 }
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000527 dst->fBounds.set(src.fBounds.fLeft - padx, src.fBounds.fTop - pady,
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000528 src.fBounds.fRight + padx, src.fBounds.fBottom + pady);
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000529
reed@android.com49f0ff22009-03-19 21:52:42 +0000530 dst->fRowBytes = dst->fBounds.width();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000531 dst->fFormat = SkMask::kA8_Format;
halcanary96fcdcc2015-08-27 07:41:13 -0700532 dst->fImage = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000533
reed@android.com0e3c6642009-09-18 13:41:56 +0000534 if (src.fImage) {
reed@android.com543ed932009-04-24 12:43:40 +0000535 size_t dstSize = dst->computeImageSize();
536 if (0 == dstSize) {
537 return false; // too big to allocate, abort
538 }
539
reed@android.com8a1c16f2008-12-17 15:59:43 +0000540 int sw = src.fBounds.width();
541 int sh = src.fBounds.height();
542 const uint8_t* sp = src.fImage;
reed@android.com543ed932009-04-24 12:43:40 +0000543 uint8_t* dp = SkMask::AllocImage(dstSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000544 SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dp);
545
546 // build the blurry destination
commit-bot@chromium.org0a1c3872013-06-11 15:23:42 +0000547 SkAutoTMalloc<uint8_t> tmpBuffer(dstSize);
548 uint8_t* tp = tmpBuffer.get();
549 int w = sw, h = sh;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000550
commit-bot@chromium.org0a1c3872013-06-11 15:23:42 +0000551 if (outerWeight == 255) {
552 int loRadius, hiRadius;
553 get_adjusted_radii(passRadius, &loRadius, &hiRadius);
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000554 if (kHigh_SkBlurQuality == quality) {
commit-bot@chromium.org0a1c3872013-06-11 15:23:42 +0000555 // Do three X blurs, with a transpose on the final one.
556 w = boxBlur(sp, src.fRowBytes, tp, loRadius, hiRadius, w, h, false);
557 w = boxBlur(tp, w, dp, hiRadius, loRadius, w, h, false);
558 w = boxBlur(dp, w, tp, hiRadius, hiRadius, w, h, true);
559 // Do three Y blurs, with a transpose on the final one.
560 h = boxBlur(tp, h, dp, loRadius, hiRadius, h, w, false);
561 h = boxBlur(dp, h, tp, hiRadius, loRadius, h, w, false);
562 h = boxBlur(tp, h, dp, hiRadius, hiRadius, h, w, true);
senorblanco@chromium.org908276b2012-11-15 20:27:35 +0000563 } else {
commit-bot@chromium.org0a1c3872013-06-11 15:23:42 +0000564 w = boxBlur(sp, src.fRowBytes, tp, rx, rx, w, h, true);
565 h = boxBlur(tp, h, dp, ry, ry, h, w, true);
senorblanco@chromium.org71f0f342012-11-13 20:35:21 +0000566 }
567 } else {
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000568 if (kHigh_SkBlurQuality == quality) {
commit-bot@chromium.org0a1c3872013-06-11 15:23:42 +0000569 // Do three X blurs, with a transpose on the final one.
570 w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, false, outerWeight);
571 w = boxBlurInterp(tp, w, dp, rx, w, h, false, outerWeight);
572 w = boxBlurInterp(dp, w, tp, rx, w, h, true, outerWeight);
573 // Do three Y blurs, with a transpose on the final one.
574 h = boxBlurInterp(tp, h, dp, ry, h, w, false, outerWeight);
575 h = boxBlurInterp(dp, h, tp, ry, h, w, false, outerWeight);
576 h = boxBlurInterp(tp, h, dp, ry, h, w, true, outerWeight);
577 } else {
578 w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, true, outerWeight);
579 h = boxBlurInterp(tp, h, dp, ry, h, w, true, outerWeight);
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000580 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000581 }
582
583 dst->fImage = dp;
584 // if need be, alloc the "real" dst (same size as src) and copy/merge
585 // the blur into it (applying the src)
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000586 if (style == kInner_SkBlurStyle) {
reed@android.com0e3c6642009-09-18 13:41:56 +0000587 // now we allocate the "real" dst, mirror the size of src
reed@android.com543ed932009-04-24 12:43:40 +0000588 size_t srcSize = src.computeImageSize();
589 if (0 == srcSize) {
590 return false; // too big to allocate, abort
591 }
592 dst->fImage = SkMask::AllocImage(srcSize);
reed@android.com0e3c6642009-09-18 13:41:56 +0000593 merge_src_with_blur(dst->fImage, src.fRowBytes,
594 sp, src.fRowBytes,
reed@google.com03016a32011-08-12 14:59:59 +0000595 dp + passCount * (rx + ry * dst->fRowBytes),
596 dst->fRowBytes, sw, sh);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000597 SkMask::FreeImage(dp);
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000598 } else if (style != kNormal_SkBlurStyle) {
reed@google.com03016a32011-08-12 14:59:59 +0000599 clamp_with_orig(dp + passCount * (rx + ry * dst->fRowBytes),
600 dst->fRowBytes, sp, src.fRowBytes, sw, sh, style);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000601 }
mtklein18300a32016-03-16 13:53:35 -0700602 (void)autoCall.release();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000603 }
604
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000605 if (style == kInner_SkBlurStyle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000606 dst->fBounds = src.fBounds; // restore trimmed bounds
reed@android.com0e3c6642009-09-18 13:41:56 +0000607 dst->fRowBytes = src.fRowBytes;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000608 }
609
reed@android.com8a1c16f2008-12-17 15:59:43 +0000610 return true;
611}
612
humper@google.com7c7292c2013-01-04 20:29:03 +0000613/* Convolving a box with itself three times results in a piecewise
614 quadratic function:
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000615
humper@google.com7c7292c2013-01-04 20:29:03 +0000616 0 x <= -1.5
humper@google.coma99a92c2013-02-20 16:42:06 +0000617 9/8 + 3/2 x + 1/2 x^2 -1.5 < x <= -.5
humper@google.com7c7292c2013-01-04 20:29:03 +0000618 3/4 - x^2 -.5 < x <= .5
619 9/8 - 3/2 x + 1/2 x^2 0.5 < x <= 1.5
620 0 1.5 < x
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000621
humper@google.coma99a92c2013-02-20 16:42:06 +0000622 Mathematica:
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000623
humper@google.coma99a92c2013-02-20 16:42:06 +0000624 g[x_] := Piecewise [ {
625 {9/8 + 3/2 x + 1/2 x^2 , -1.5 < x <= -.5},
626 {3/4 - x^2 , -.5 < x <= .5},
627 {9/8 - 3/2 x + 1/2 x^2 , 0.5 < x <= 1.5}
628 }, 0]
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000629
humper@google.com7c7292c2013-01-04 20:29:03 +0000630 To get the profile curve of the blurred step function at the rectangle
631 edge, we evaluate the indefinite integral, which is piecewise cubic:
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000632
humper@google.com7c7292c2013-01-04 20:29:03 +0000633 0 x <= -1.5
humper@google.coma99a92c2013-02-20 16:42:06 +0000634 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 +0000635 1/2 + 3/4 x - 1/3 x^3 -.5 < x <= .5
humper@google.coma99a92c2013-02-20 16:42:06 +0000636 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 +0000637 1 1.5 < x
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000638
humper@google.coma99a92c2013-02-20 16:42:06 +0000639 in Mathematica code:
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000640
humper@google.coma99a92c2013-02-20 16:42:06 +0000641 gi[x_] := Piecewise[ {
642 { 0 , x <= -1.5 },
643 { 9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3, -1.5 < x <= -0.5 },
644 { 1/2 + 3/4 x - 1/3 x^3 , -.5 < x <= .5},
645 { 7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3, .5 < x <= 1.5}
646 },1]
humper@google.com7c7292c2013-01-04 20:29:03 +0000647*/
648
humper@google.coma99a92c2013-02-20 16:42:06 +0000649static float gaussianIntegral(float x) {
650 if (x > 1.5f) {
humper@google.com7c7292c2013-01-04 20:29:03 +0000651 return 0.0f;
652 }
humper@google.coma99a92c2013-02-20 16:42:06 +0000653 if (x < -1.5f) {
humper@google.com7c7292c2013-01-04 20:29:03 +0000654 return 1.0f;
655 }
656
657 float x2 = x*x;
658 float x3 = x2*x;
659
jvanverth@google.com9c4e5ac2013-01-07 18:41:28 +0000660 if ( x > 0.5f ) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000661 return 0.5625f - (x3 / 6.0f - 3.0f * x2 * 0.25f + 1.125f * x);
humper@google.com7c7292c2013-01-04 20:29:03 +0000662 }
jvanverth@google.com9c4e5ac2013-01-07 18:41:28 +0000663 if ( x > -0.5f ) {
664 return 0.5f - (0.75f * x - x3 / 3.0f);
humper@google.com7c7292c2013-01-04 20:29:03 +0000665 }
jvanverth@google.com9c4e5ac2013-01-07 18:41:28 +0000666 return 0.4375f + (-x3 / 6.0f - 3.0f * x2 * 0.25f - 1.125f * x);
humper@google.com7c7292c2013-01-04 20:29:03 +0000667}
668
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000669/* ComputeBlurProfile allocates and fills in an array of floating
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000670 point values between 0 and 255 for the profile signature of
humper@google.com7c7292c2013-01-04 20:29:03 +0000671 a blurred half-plane with the given blur radius. Since we're
672 going to be doing screened multiplications (i.e., 1 - (1-x)(1-y))
673 all the time, we actually fill in the profile pre-inverted
674 (already done 255-x).
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000675
humper@google.com7c5d7b72013-03-11 20:16:28 +0000676 It's the responsibility of the caller to delete the
humper@google.com7c7292c2013-01-04 20:29:03 +0000677 memory returned in profile_out.
678*/
679
robertphillips30c4cae2015-09-15 10:20:55 -0700680uint8_t* SkBlurMask::ComputeBlurProfile(SkScalar sigma) {
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000681 int size = SkScalarCeilToInt(6*sigma);
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000682
robertphillipsd8982d72015-10-04 12:21:33 -0700683 int center = size >> 1;
halcanary385fe4d2015-08-26 13:07:48 -0700684 uint8_t* profile = new uint8_t[size];
humper@google.com7c7292c2013-01-04 20:29:03 +0000685
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000686 float invr = 1.f/(2*sigma);
humper@google.com7c7292c2013-01-04 20:29:03 +0000687
688 profile[0] = 255;
humper@google.coma99a92c2013-02-20 16:42:06 +0000689 for (int x = 1 ; x < size ; ++x) {
jvanverth@google.comd98df1a2013-02-20 19:02:34 +0000690 float scaled_x = (center - x - .5f) * invr;
humper@google.coma99a92c2013-02-20 16:42:06 +0000691 float gi = gaussianIntegral(scaled_x);
692 profile[x] = 255 - (uint8_t) (255.f * gi);
humper@google.com7c7292c2013-01-04 20:29:03 +0000693 }
694
robertphillips30c4cae2015-09-15 10:20:55 -0700695 return profile;
humper@google.com7c7292c2013-01-04 20:29:03 +0000696}
697
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000698// TODO MAYBE: Maintain a profile cache to avoid recomputing this for
humper@google.com7c7292c2013-01-04 20:29:03 +0000699// commonly used radii. Consider baking some of the most common blur radii
700// directly in as static data?
701
702// Implementation adapted from Michael Herf's approach:
703// http://stereopsis.com/shadowrect/
704
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000705uint8_t SkBlurMask::ProfileLookup(const uint8_t *profile, int loc, int blurred_width, int sharp_width) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000706 int dx = SkAbs32(((loc << 1) + 1) - blurred_width) - sharp_width; // how far are we from the original edge?
707 int ox = dx >> 1;
708 if (ox < 0) {
709 ox = 0;
710 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000711
humper@google.coma99a92c2013-02-20 16:42:06 +0000712 return profile[ox];
713}
714
skia.committer@gmail.com4c18e9f2014-01-31 03:01:59 +0000715void SkBlurMask::ComputeBlurredScanline(uint8_t *pixels, const uint8_t *profile,
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000716 unsigned int width, SkScalar sigma) {
717
718 unsigned int profile_size = SkScalarCeilToInt(6*sigma);
719 SkAutoTMalloc<uint8_t> horizontalScanline(width);
720
721 unsigned int sw = width - profile_size;
722 // nearest odd number less than the profile size represents the center
723 // of the (2x scaled) profile
724 int center = ( profile_size & ~1 ) - 1;
725
726 int w = sw - center;
727
728 for (unsigned int x = 0 ; x < width ; ++x) {
729 if (profile_size <= sw) {
730 pixels[x] = ProfileLookup(profile, x, width, w);
731 } else {
732 float span = float(sw)/(2*sigma);
733 float giX = 1.5f - (x+.5f)/(2*sigma);
734 pixels[x] = (uint8_t) (255 * (gaussianIntegral(giX) - gaussianIntegral(giX + span)));
735 }
skia.committer@gmail.com4c18e9f2014-01-31 03:01:59 +0000736 }
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000737}
738
skia.committer@gmail.com7bd141d2013-08-28 07:01:18 +0000739bool SkBlurMask::BlurRect(SkScalar sigma, SkMask *dst,
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000740 const SkRect &src, SkBlurStyle style,
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000741 SkIPoint *margin, SkMask::CreateMode createMode) {
742 int profile_size = SkScalarCeilToInt(6*sigma);
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000743
humper@google.coma99a92c2013-02-20 16:42:06 +0000744 int pad = profile_size/2;
humper@google.com7c7292c2013-01-04 20:29:03 +0000745 if (margin) {
746 margin->set( pad, pad );
747 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000748
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000749 dst->fBounds.set(SkScalarRoundToInt(src.fLeft - pad),
750 SkScalarRoundToInt(src.fTop - pad),
751 SkScalarRoundToInt(src.fRight + pad),
humper@google.com68a690c2013-03-11 21:16:20 +0000752 SkScalarRoundToInt(src.fBottom + pad));
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000753
humper@google.com7c7292c2013-01-04 20:29:03 +0000754 dst->fRowBytes = dst->fBounds.width();
755 dst->fFormat = SkMask::kA8_Format;
halcanary96fcdcc2015-08-27 07:41:13 -0700756 dst->fImage = nullptr;
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000757
humper@google.com7c5d7b72013-03-11 20:16:28 +0000758 int sw = SkScalarFloorToInt(src.width());
759 int sh = SkScalarFloorToInt(src.height());
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000760
humper@google.com7c5d7b72013-03-11 20:16:28 +0000761 if (createMode == SkMask::kJustComputeBounds_CreateMode) {
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000762 if (style == kInner_SkBlurStyle) {
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000763 dst->fBounds.set(SkScalarRoundToInt(src.fLeft),
764 SkScalarRoundToInt(src.fTop),
765 SkScalarRoundToInt(src.fRight),
humper@google.com68a690c2013-03-11 21:16:20 +0000766 SkScalarRoundToInt(src.fBottom)); // restore trimmed bounds
humper@google.com7c5d7b72013-03-11 20:16:28 +0000767 dst->fRowBytes = sw;
768 }
769 return true;
770 }
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000771
Ben Wagner7ecc5962016-11-02 17:07:33 -0400772 std::unique_ptr<uint8_t[]> profile(ComputeBlurProfile(sigma));
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000773
humper@google.com7c7292c2013-01-04 20:29:03 +0000774 size_t dstSize = dst->computeImageSize();
775 if (0 == dstSize) {
776 return false; // too big to allocate, abort
777 }
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000778
humper@google.com7c7292c2013-01-04 20:29:03 +0000779 uint8_t* dp = SkMask::AllocImage(dstSize);
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000780
humper@google.com7c7292c2013-01-04 20:29:03 +0000781 dst->fImage = dp;
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000782
humper@google.coma99a92c2013-02-20 16:42:06 +0000783 int dstHeight = dst->fBounds.height();
784 int dstWidth = dst->fBounds.width();
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000785
humper@google.com7c7292c2013-01-04 20:29:03 +0000786 uint8_t *outptr = dp;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000787
humper@google.coma99a92c2013-02-20 16:42:06 +0000788 SkAutoTMalloc<uint8_t> horizontalScanline(dstWidth);
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000789 SkAutoTMalloc<uint8_t> verticalScanline(dstHeight);
skia.committer@gmail.com4c18e9f2014-01-31 03:01:59 +0000790
robertphillips30c4cae2015-09-15 10:20:55 -0700791 ComputeBlurredScanline(horizontalScanline, profile.get(), dstWidth, sigma);
792 ComputeBlurredScanline(verticalScanline, profile.get(), dstHeight, sigma);
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000793
humper@google.coma99a92c2013-02-20 16:42:06 +0000794 for (int y = 0 ; y < dstHeight ; ++y) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000795 for (int x = 0 ; x < dstWidth ; x++) {
commit-bot@chromium.orgcf34bc02014-01-30 15:34:43 +0000796 unsigned int maskval = SkMulDiv255Round(horizontalScanline[x], verticalScanline[y]);
humper@google.com7c7292c2013-01-04 20:29:03 +0000797 *(outptr++) = maskval;
798 }
799 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000800
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000801 if (style == kInner_SkBlurStyle) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000802 // now we allocate the "real" dst, mirror the size of src
jvanverth@google.comd98df1a2013-02-20 19:02:34 +0000803 size_t srcSize = (size_t)(src.width() * src.height());
humper@google.coma99a92c2013-02-20 16:42:06 +0000804 if (0 == srcSize) {
805 return false; // too big to allocate, abort
806 }
807 dst->fImage = SkMask::AllocImage(srcSize);
808 for (int y = 0 ; y < sh ; y++) {
809 uint8_t *blur_scanline = dp + (y+pad)*dstWidth + pad;
810 uint8_t *inner_scanline = dst->fImage + y*sw;
811 memcpy(inner_scanline, blur_scanline, sw);
812 }
813 SkMask::FreeImage(dp);
814
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000815 dst->fBounds.set(SkScalarRoundToInt(src.fLeft),
816 SkScalarRoundToInt(src.fTop),
817 SkScalarRoundToInt(src.fRight),
humper@google.com68a690c2013-03-11 21:16:20 +0000818 SkScalarRoundToInt(src.fBottom)); // restore trimmed bounds
humper@google.coma99a92c2013-02-20 16:42:06 +0000819 dst->fRowBytes = sw;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000820
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000821 } else if (style == kOuter_SkBlurStyle) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000822 for (int y = pad ; y < dstHeight-pad ; y++) {
823 uint8_t *dst_scanline = dp + y*dstWidth + pad;
824 memset(dst_scanline, 0, sw);
825 }
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000826 } else if (style == kSolid_SkBlurStyle) {
humper@google.comd4d57302013-03-11 22:18:54 +0000827 for (int y = pad ; y < dstHeight-pad ; y++) {
828 uint8_t *dst_scanline = dp + y*dstWidth + pad;
829 memset(dst_scanline, 0xff, sw);
skia.committer@gmail.com2e71f162013-03-12 07:12:32 +0000830 }
humper@google.coma99a92c2013-02-20 16:42:06 +0000831 }
832 // normal and solid styles are the same for analytic rect blurs, so don't
833 // need to handle solid specially.
834
835 return true;
836}
837
commit-bot@chromium.orga4771542014-03-10 21:42:06 +0000838bool SkBlurMask::BlurRRect(SkScalar sigma, SkMask *dst,
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000839 const SkRRect &src, SkBlurStyle style,
commit-bot@chromium.orga4771542014-03-10 21:42:06 +0000840 SkIPoint *margin, SkMask::CreateMode createMode) {
841 // Temporary for now -- always fail, should cause caller to fall back
842 // to old path. Plumbing just to land API and parallelize effort.
843
844 return false;
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000845}
commit-bot@chromium.orga4771542014-03-10 21:42:06 +0000846
humper@google.coma99a92c2013-02-20 16:42:06 +0000847// The "simple" blur is a direct implementation of separable convolution with a discrete
848// gaussian kernel. It's "ground truth" in a sense; too slow to be used, but very
849// useful for correctness comparisons.
850
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000851bool SkBlurMask::BlurGroundTruth(SkScalar sigma, SkMask* dst, const SkMask& src,
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000852 SkBlurStyle style, SkIPoint* margin) {
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000853
humper@google.coma99a92c2013-02-20 16:42:06 +0000854 if (src.fFormat != SkMask::kA8_Format) {
855 return false;
856 }
857
robertphillips@google.com7ce661d2013-08-27 16:14:03 +0000858 float variance = sigma * sigma;
humper@google.coma99a92c2013-02-20 16:42:06 +0000859
reed@google.come1ca7052013-12-17 19:22:07 +0000860 int windowSize = SkScalarCeilToInt(sigma*6);
humper@google.coma99a92c2013-02-20 16:42:06 +0000861 // round window size up to nearest odd number
862 windowSize |= 1;
863
864 SkAutoTMalloc<float> gaussWindow(windowSize);
865
866 int halfWindow = windowSize >> 1;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000867
humper@google.coma99a92c2013-02-20 16:42:06 +0000868 gaussWindow[halfWindow] = 1;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000869
humper@google.coma99a92c2013-02-20 16:42:06 +0000870 float windowSum = 1;
871 for (int x = 1 ; x <= halfWindow ; ++x) {
robertphillips@google.com3dfa4cc2013-09-05 16:39:03 +0000872 float gaussian = expf(-x*x / (2*variance));
humper@google.coma99a92c2013-02-20 16:42:06 +0000873 gaussWindow[halfWindow + x] = gaussWindow[halfWindow-x] = gaussian;
874 windowSum += 2*gaussian;
875 }
876
877 // leave the filter un-normalized for now; we will divide by the normalization
878 // sum later;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000879
humper@google.coma99a92c2013-02-20 16:42:06 +0000880 int pad = halfWindow;
881 if (margin) {
882 margin->set( pad, pad );
883 }
884
885 dst->fBounds = src.fBounds;
886 dst->fBounds.outset(pad, pad);
887
888 dst->fRowBytes = dst->fBounds.width();
889 dst->fFormat = SkMask::kA8_Format;
halcanary96fcdcc2015-08-27 07:41:13 -0700890 dst->fImage = nullptr;
humper@google.coma99a92c2013-02-20 16:42:06 +0000891
892 if (src.fImage) {
893
894 size_t dstSize = dst->computeImageSize();
895 if (0 == dstSize) {
896 return false; // too big to allocate, abort
897 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000898
humper@google.coma99a92c2013-02-20 16:42:06 +0000899 int srcWidth = src.fBounds.width();
900 int srcHeight = src.fBounds.height();
901 int dstWidth = dst->fBounds.width();
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000902
humper@google.coma99a92c2013-02-20 16:42:06 +0000903 const uint8_t* srcPixels = src.fImage;
904 uint8_t* dstPixels = SkMask::AllocImage(dstSize);
905 SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dstPixels);
906
907 // do the actual blur. First, make a padded copy of the source.
908 // use double pad so we never have to check if we're outside anything
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000909
humper@google.coma99a92c2013-02-20 16:42:06 +0000910 int padWidth = srcWidth + 4*pad;
911 int padHeight = srcHeight;
912 int padSize = padWidth * padHeight;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000913
humper@google.coma99a92c2013-02-20 16:42:06 +0000914 SkAutoTMalloc<uint8_t> padPixels(padSize);
915 memset(padPixels, 0, padSize);
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000916
humper@google.coma99a92c2013-02-20 16:42:06 +0000917 for (int y = 0 ; y < srcHeight; ++y) {
918 uint8_t* padptr = padPixels + y * padWidth + 2*pad;
919 const uint8_t* srcptr = srcPixels + y * srcWidth;
920 memcpy(padptr, srcptr, srcWidth);
921 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000922
humper@google.coma99a92c2013-02-20 16:42:06 +0000923 // blur in X, transposing the result into a temporary floating point buffer.
924 // also double-pad the intermediate result so that the second blur doesn't
925 // have to do extra conditionals.
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000926
humper@google.coma99a92c2013-02-20 16:42:06 +0000927 int tmpWidth = padHeight + 4*pad;
928 int tmpHeight = padWidth - 2*pad;
929 int tmpSize = tmpWidth * tmpHeight;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000930
humper@google.coma99a92c2013-02-20 16:42:06 +0000931 SkAutoTMalloc<float> tmpImage(tmpSize);
932 memset(tmpImage, 0, tmpSize*sizeof(tmpImage[0]));
933
934 for (int y = 0 ; y < padHeight ; ++y) {
935 uint8_t *srcScanline = padPixels + y*padWidth;
936 for (int x = pad ; x < padWidth - pad ; ++x) {
937 float *outPixel = tmpImage + (x-pad)*tmpWidth + y + 2*pad; // transposed output
938 uint8_t *windowCenter = srcScanline + x;
939 for (int i = -pad ; i <= pad ; ++i) {
940 *outPixel += gaussWindow[pad+i]*windowCenter[i];
941 }
942 *outPixel /= windowSum;
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000943 }
humper@google.coma99a92c2013-02-20 16:42:06 +0000944 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000945
humper@google.coma99a92c2013-02-20 16:42:06 +0000946 // blur in Y; now filling in the actual desired destination. We have to do
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000947 // the transpose again; these transposes guarantee that we read memory in
humper@google.coma99a92c2013-02-20 16:42:06 +0000948 // linear order.
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000949
humper@google.coma99a92c2013-02-20 16:42:06 +0000950 for (int y = 0 ; y < tmpHeight ; ++y) {
951 float *srcScanline = tmpImage + y*tmpWidth;
952 for (int x = pad ; x < tmpWidth - pad ; ++x) {
953 float *windowCenter = srcScanline + x;
954 float finalValue = 0;
955 for (int i = -pad ; i <= pad ; ++i) {
956 finalValue += gaussWindow[pad+i]*windowCenter[i];
957 }
958 finalValue /= windowSum;
959 uint8_t *outPixel = dstPixels + (x-pad)*dstWidth + y; // transposed output
960 int integerPixel = int(finalValue + 0.5f);
961 *outPixel = SkClampMax( SkClampPos(integerPixel), 255 );
962 }
963 }
skia.committer@gmail.comd454ec12013-02-21 07:15:03 +0000964
humper@google.coma99a92c2013-02-20 16:42:06 +0000965 dst->fImage = dstPixels;
966 // if need be, alloc the "real" dst (same size as src) and copy/merge
967 // the blur into it (applying the src)
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000968 if (style == kInner_SkBlurStyle) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000969 // now we allocate the "real" dst, mirror the size of src
970 size_t srcSize = src.computeImageSize();
971 if (0 == srcSize) {
972 return false; // too big to allocate, abort
973 }
974 dst->fImage = SkMask::AllocImage(srcSize);
975 merge_src_with_blur(dst->fImage, src.fRowBytes,
976 srcPixels, src.fRowBytes,
977 dstPixels + pad*dst->fRowBytes + pad,
978 dst->fRowBytes, srcWidth, srcHeight);
979 SkMask::FreeImage(dstPixels);
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000980 } else if (style != kNormal_SkBlurStyle) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000981 clamp_with_orig(dstPixels + pad*dst->fRowBytes + pad,
982 dst->fRowBytes, srcPixels, src.fRowBytes, srcWidth, srcHeight, style);
983 }
mtklein18300a32016-03-16 13:53:35 -0700984 (void)autoCall.release();
humper@google.coma99a92c2013-02-20 16:42:06 +0000985 }
986
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000987 if (style == kInner_SkBlurStyle) {
humper@google.coma99a92c2013-02-20 16:42:06 +0000988 dst->fBounds = src.fBounds; // restore trimmed bounds
989 dst->fRowBytes = src.fRowBytes;
990 }
skia.committer@gmail.com8ae714b2013-01-05 02:02:05 +0000991
humper@google.com7c7292c2013-01-04 20:29:03 +0000992 return true;
993}