blob: f57a1776af15a56ab7af8dd9b882ec21b9eb60e3 [file] [log] [blame]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001/* libs/graphics/effects/SkBlurMask.cpp
2**
3** Copyright 2006, The Android Open Source Project
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9** http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17
18#include "SkBlurMask.h"
19#include "SkTemplates.h"
20
reed@android.com45607672009-09-21 00:27:08 +000021#if 0
22static void dump_sum_buffer(const uint32_t sum[], const int w, const int h) {
23 printf("---- sum buffer\n");
24 for (int y = 0; y <= h; y++) {
25 for (int x = 0; x <= w; x++) {
26 printf(" %5d", sum[x]);
27 }
28 printf("\n");
29 sum += w+1;
30 }
31}
32#else
33#define dump_sum_buffer(sum, w, h)
34#endif
35
36/** The sum buffer is an array of u32 to hold the accumulated sum of all of the
37 src values at their position, plus all values above and to the left.
38 When we sample into this buffer, we need an initial row and column of 0s,
39 so we have an index correspondence as follows:
40
41 src[i, j] == sum[i+1, j+1]
42 sum[0, j] == sum[i, 0] == 0
43
44 We assume that the sum buffer's stride == its width
45 */
46static void build_sum_buffer(uint32_t sum[], int srcW, int srcH, const uint8_t src[], int srcRB) {
47 int sumW = srcW + 1;
48
49 SkASSERT(srcRB >= srcW);
reed@android.com8a1c16f2008-12-17 15:59:43 +000050 // mod srcRB so we can apply it after each row
reed@android.com45607672009-09-21 00:27:08 +000051 srcRB -= srcW;
reed@android.com8a1c16f2008-12-17 15:59:43 +000052
53 int x, y;
54
reed@android.com45607672009-09-21 00:27:08 +000055 // zero out the top row and column
56 memset(sum, 0, sumW * sizeof(sum[0]));
57 sum += sumW;
58
reed@android.com8a1c16f2008-12-17 15:59:43 +000059 // special case first row
60 uint32_t X = 0;
reed@android.com45607672009-09-21 00:27:08 +000061 *sum++ = 0; // initialze the first column to 0
62 for (x = srcW - 1; x >= 0; --x)
reed@android.com8a1c16f2008-12-17 15:59:43 +000063 {
64 X = *src++ + X;
reed@android.com45607672009-09-21 00:27:08 +000065 *sum++ = X;
reed@android.com8a1c16f2008-12-17 15:59:43 +000066 }
67 src += srcRB;
68
69 // now do the rest of the rows
reed@android.com45607672009-09-21 00:27:08 +000070 for (y = srcH - 1; y > 0; --y)
reed@android.com8a1c16f2008-12-17 15:59:43 +000071 {
72 uint32_t L = 0;
73 uint32_t C = 0;
reed@android.com45607672009-09-21 00:27:08 +000074 *sum++ = 0; // initialze the first column to 0
75 for (x = srcW - 1; x >= 0; --x)
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 {
reed@android.com45607672009-09-21 00:27:08 +000077 uint32_t T = sum[-sumW];
reed@android.com8a1c16f2008-12-17 15:59:43 +000078 X = *src++ + L + T - C;
reed@android.com45607672009-09-21 00:27:08 +000079 *sum++ = X;
reed@android.com8a1c16f2008-12-17 15:59:43 +000080 L = X;
81 C = T;
82 }
83 src += srcRB;
84 }
85}
86
reed@android.com45607672009-09-21 00:27:08 +000087/* sw and sh are the width and height of the src. Since the sum buffer
88 matches that, but has an extra row and col at the beginning (with zeros),
89 we can just use sw and sh as our "max" values for pinning coordinates
90 when sampling into sum[][]
91 */
92static void apply_kernel(uint8_t dst[], int rx, int ry, const uint32_t sum[],
93 int sw, int sh) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000094 uint32_t scale = (1 << 24) / ((2*rx + 1)*(2*ry + 1));
95
reed@android.com45607672009-09-21 00:27:08 +000096 int sumStride = sw + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +000097
98 int dw = sw + 2*rx;
99 int dh = sh + 2*ry;
100
reed@android.com45607672009-09-21 00:27:08 +0000101 int prev_y = -2*ry;
102 int next_y = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000103
reed@android.com45607672009-09-21 00:27:08 +0000104 for (int y = 0; y < dh; y++) {
105 int py = SkClampPos(prev_y) * sumStride;
106 int ny = SkFastMin32(next_y, sh) * sumStride;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107
reed@android.com45607672009-09-21 00:27:08 +0000108 int prev_x = -2*rx;
109 int next_x = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000110
reed@android.com45607672009-09-21 00:27:08 +0000111 for (int x = 0; x < dw; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112 int px = SkClampPos(prev_x);
113 int nx = SkFastMin32(next_x, sw);
114
reed@android.com45607672009-09-21 00:27:08 +0000115 uint32_t tmp = sum[px+py] + sum[nx+ny] - sum[nx+py] - sum[px+ny];
116 *dst++ = SkToU8(tmp * scale >> 24);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117
118 prev_x += 1;
119 next_x += 1;
120 }
121 prev_y += 1;
122 next_y += 1;
123 }
124}
125
reed@android.com45607672009-09-21 00:27:08 +0000126/* sw and sh are the width and height of the src. Since the sum buffer
127 matches that, but has an extra row and col at the beginning (with zeros),
128 we can just use sw and sh as our "max" values for pinning coordinates
129 when sampling into sum[][]
130 */
131static void apply_kernel_interp(uint8_t dst[], int rx, int ry,
132 const uint32_t sum[], int sw, int sh, U8CPU outer_weight) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000133 SkASSERT(rx > 0 && ry > 0);
134 SkASSERT(outer_weight <= 255);
135
136 int inner_weight = 255 - outer_weight;
137
138 // round these guys up if they're bigger than 127
139 outer_weight += outer_weight >> 7;
140 inner_weight += inner_weight >> 7;
141
142 uint32_t outer_scale = (outer_weight << 16) / ((2*rx + 1)*(2*ry + 1));
143 uint32_t inner_scale = (inner_weight << 16) / ((2*rx - 1)*(2*ry - 1));
144
reed@android.com45607672009-09-21 00:27:08 +0000145 int sumStride = sw + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000146
147 int dw = sw + 2*rx;
148 int dh = sh + 2*ry;
149
reed@android.com45607672009-09-21 00:27:08 +0000150 int prev_y = -2*ry;
151 int next_y = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000152
reed@android.com45607672009-09-21 00:27:08 +0000153 for (int y = 0; y < dh; y++) {
154 int py = SkClampPos(prev_y) * sumStride;
155 int ny = SkFastMin32(next_y, sh) * sumStride;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156
reed@android.com45607672009-09-21 00:27:08 +0000157 int ipy = SkClampPos(prev_y + 1) * sumStride;
158 int iny = SkClampMax(next_y - 1, sh) * sumStride;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000159
reed@android.com45607672009-09-21 00:27:08 +0000160 int prev_x = -2*rx;
161 int next_x = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162
reed@android.com45607672009-09-21 00:27:08 +0000163 for (int x = 0; x < dw; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000164 int px = SkClampPos(prev_x);
165 int nx = SkFastMin32(next_x, sw);
166
167 int ipx = SkClampPos(prev_x + 1);
168 int inx = SkClampMax(next_x - 1, sw);
169
reed@android.com45607672009-09-21 00:27:08 +0000170 uint32_t outer_sum = sum[px+py] + sum[nx+ny] - sum[nx+py] - sum[px+ny];
171 uint32_t inner_sum = sum[ipx+ipy] + sum[inx+iny] - sum[inx+ipy] - sum[ipx+iny];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172 *dst++ = SkToU8((outer_sum * outer_scale + inner_sum * inner_scale) >> 24);
173
174 prev_x += 1;
175 next_x += 1;
176 }
177 prev_y += 1;
178 next_y += 1;
179 }
180}
181
182#include "SkColorPriv.h"
183
reed@android.com0e3c6642009-09-18 13:41:56 +0000184static void merge_src_with_blur(uint8_t dst[], int dstRB,
185 const uint8_t src[], int srcRB,
186 const uint8_t blur[], int blurRB,
187 int sw, int sh) {
188 dstRB -= sw;
189 srcRB -= sw;
190 blurRB -= sw;
191 while (--sh >= 0) {
192 for (int x = sw - 1; x >= 0; --x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 *dst = SkToU8(SkAlphaMul(*blur, SkAlpha255To256(*src)));
194 dst += 1;
195 src += 1;
196 blur += 1;
197 }
reed@android.com0e3c6642009-09-18 13:41:56 +0000198 dst += dstRB;
199 src += srcRB;
200 blur += blurRB;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000201 }
202}
203
204static void clamp_with_orig(uint8_t dst[], int dstRowBytes,
reed@android.com0e3c6642009-09-18 13:41:56 +0000205 const uint8_t src[], int srcRowBytes,
206 int sw, int sh,
reed@android.com45607672009-09-21 00:27:08 +0000207 SkBlurMask::Style style) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000208 int x;
reed@android.com0e3c6642009-09-18 13:41:56 +0000209 while (--sh >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000210 switch (style) {
211 case SkBlurMask::kSolid_Style:
reed@android.com0e3c6642009-09-18 13:41:56 +0000212 for (x = sw - 1; x >= 0; --x) {
213 int s = *src;
214 int d = *dst;
215 *dst = SkToU8(s + d - SkMulDiv255Round(s, d));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000216 dst += 1;
217 src += 1;
218 }
219 break;
220 case SkBlurMask::kOuter_Style:
reed@android.com0e3c6642009-09-18 13:41:56 +0000221 for (x = sw - 1; x >= 0; --x) {
222 if (*src) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000223 *dst = SkToU8(SkAlphaMul(*dst, SkAlpha255To256(255 - *src)));
reed@android.com0e3c6642009-09-18 13:41:56 +0000224 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000225 dst += 1;
226 src += 1;
227 }
228 break;
229 default:
230 SkASSERT(!"Unexpected blur style here");
231 break;
232 }
233 dst += dstRowBytes - sw;
reed@android.com0e3c6642009-09-18 13:41:56 +0000234 src += srcRowBytes - sw;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000235 }
236}
237
238////////////////////////////////////////////////////////////////////////
239
240// we use a local funciton to wrap the class static method to work around
241// a bug in gcc98
242void SkMask_FreeImage(uint8_t* image);
243void SkMask_FreeImage(uint8_t* image)
244{
245 SkMask::FreeImage(image);
246}
247
248bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000249 SkScalar radius, Style style, Quality quality)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000250{
251 if (src.fFormat != SkMask::kA8_Format)
252 return false;
253
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000254 // Force high quality off for small radii (performance)
255 if (radius < SkIntToScalar(3)) quality = kLow_Quality;
256
257 // highQuality: use three box blur passes as a cheap way to approximate a Gaussian blur
258 int passCount = (quality == kHigh_Quality) ? 3 : 1;
259 SkScalar passRadius = SkScalarDiv(radius, SkScalarSqrt(SkIntToScalar(passCount)));
260
261 int rx = SkScalarCeil(passRadius);
262 int outer_weight = 255 - SkScalarRound((SkIntToScalar(rx) - passRadius) * 255);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000263
264 SkASSERT(rx >= 0);
265 SkASSERT((unsigned)outer_weight <= 255);
reed@android.com0e3c6642009-09-18 13:41:56 +0000266 if (rx <= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000267 return false;
reed@android.com0e3c6642009-09-18 13:41:56 +0000268 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000269
270 int ry = rx; // only do square blur for now
271
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000272 int padx = passCount * rx;
273 int pady = passCount * ry;
274 dst->fBounds.set(src.fBounds.fLeft - padx, src.fBounds.fTop - pady,
275 src.fBounds.fRight + padx, src.fBounds.fBottom + pady);
reed@android.com49f0ff22009-03-19 21:52:42 +0000276 dst->fRowBytes = dst->fBounds.width();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000277 dst->fFormat = SkMask::kA8_Format;
278 dst->fImage = NULL;
279
reed@android.com0e3c6642009-09-18 13:41:56 +0000280 if (src.fImage) {
reed@android.com543ed932009-04-24 12:43:40 +0000281 size_t dstSize = dst->computeImageSize();
282 if (0 == dstSize) {
283 return false; // too big to allocate, abort
284 }
285
reed@android.com8a1c16f2008-12-17 15:59:43 +0000286 int sw = src.fBounds.width();
287 int sh = src.fBounds.height();
288 const uint8_t* sp = src.fImage;
reed@android.com543ed932009-04-24 12:43:40 +0000289 uint8_t* dp = SkMask::AllocImage(dstSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000290
291 SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dp);
292
293 // build the blurry destination
294 {
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000295 SkAutoTMalloc<uint32_t> storage((sw + 2 * (passCount - 1) * rx + 1) * (sh + 2 * (passCount - 1) * ry + 1));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000296 uint32_t* sumBuffer = storage.get();
297
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000298 //pass1: sp is source, dp is destination
reed@android.com8a1c16f2008-12-17 15:59:43 +0000299 build_sum_buffer(sumBuffer, sw, sh, sp, src.fRowBytes);
reed@android.com45607672009-09-21 00:27:08 +0000300 dump_sum_buffer(sumBuffer, sw, sh);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000301 if (outer_weight == 255)
302 apply_kernel(dp, rx, ry, sumBuffer, sw, sh);
303 else
304 apply_kernel_interp(dp, rx, ry, sumBuffer, sw, sh, outer_weight);
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000305
306 if (quality == kHigh_Quality)
307 {
308 //pass2: dp is source, tmpBuffer is destination
309 int tmp_sw = sw + 2 * rx;
310 int tmp_sh = sh + 2 * ry;
311 SkAutoTMalloc<uint8_t> tmpBuffer(dstSize);
312 build_sum_buffer(sumBuffer, tmp_sw, tmp_sh, dp, tmp_sw);
313 if (outer_weight == 255)
314 apply_kernel(tmpBuffer.get(), rx, ry, sumBuffer, tmp_sw, tmp_sh);
315 else
316 apply_kernel_interp(tmpBuffer.get(), rx, ry, sumBuffer, tmp_sw, tmp_sh, outer_weight);
317
318 //pass3: tmpBuffer is source, dp is destination
319 tmp_sw += 2 * rx;
320 tmp_sh += 2 * ry;
321 build_sum_buffer(sumBuffer, tmp_sw, tmp_sh, tmpBuffer.get(), tmp_sw);
322 if (outer_weight == 255)
323 apply_kernel(dp, rx, ry, sumBuffer, tmp_sw, tmp_sh);
324 else
325 apply_kernel_interp(dp, rx, ry, sumBuffer, tmp_sw, tmp_sh, outer_weight);
326 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000327 }
328
329 dst->fImage = dp;
330 // if need be, alloc the "real" dst (same size as src) and copy/merge
331 // the blur into it (applying the src)
reed@android.com0e3c6642009-09-18 13:41:56 +0000332 if (style == kInner_Style) {
333 // now we allocate the "real" dst, mirror the size of src
reed@android.com543ed932009-04-24 12:43:40 +0000334 size_t srcSize = src.computeImageSize();
335 if (0 == srcSize) {
336 return false; // too big to allocate, abort
337 }
338 dst->fImage = SkMask::AllocImage(srcSize);
reed@android.com0e3c6642009-09-18 13:41:56 +0000339 merge_src_with_blur(dst->fImage, src.fRowBytes,
340 sp, src.fRowBytes,
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000341 dp + passCount * (rx + ry * dst->fRowBytes), dst->fRowBytes,
reed@android.com0e3c6642009-09-18 13:41:56 +0000342 sw, sh);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000343 SkMask::FreeImage(dp);
reed@android.com0e3c6642009-09-18 13:41:56 +0000344 } else if (style != kNormal_Style) {
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000345 clamp_with_orig(dp + passCount * (rx + ry * dst->fRowBytes), dst->fRowBytes,
reed@android.com0e3c6642009-09-18 13:41:56 +0000346 sp, src.fRowBytes, sw, sh,
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347 style);
348 }
349 (void)autoCall.detach();
350 }
351
reed@android.com0e3c6642009-09-18 13:41:56 +0000352 if (style == kInner_Style) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000353 dst->fBounds = src.fBounds; // restore trimmed bounds
reed@android.com0e3c6642009-09-18 13:41:56 +0000354 dst->fRowBytes = src.fRowBytes;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355 }
356
357#if 0
reed@android.com0e3c6642009-09-18 13:41:56 +0000358 if (gamma && dst->fImage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000359 uint8_t* image = dst->fImage;
360 uint8_t* stop = image + dst->computeImageSize();
361
reed@android.com0e3c6642009-09-18 13:41:56 +0000362 for (; image < stop; image += 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000363 *image = gamma[*image];
reed@android.com0e3c6642009-09-18 13:41:56 +0000364 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000365 }
366#endif
367 return true;
368}
369
370#if 0
371void SkBlurMask::BuildSqrtGamma(uint8_t gamma[256], SkScalar percent)
372{
373 SkASSERT(gamma);
374 SkASSERT(percent >= 0 && percent <= SK_Scalar1);
375
376 int scale = SkScalarRound(percent * 256);
377
378 for (int i = 0; i < 256; i++)
379 {
380 SkFixed n = i * 257;
381 n += n >> 15;
382 SkASSERT(n >= 0 && n <= SK_Fixed1);
383 n = SkFixedSqrt(n);
384 n = n * 255 >> 16;
385 n = SkAlphaBlend(n, i, scale);
386 gamma[i] = SkToU8(n);
387 }
388}
389
390void SkBlurMask::BuildSqrGamma(uint8_t gamma[256], SkScalar percent)
391{
392 SkASSERT(gamma);
393 SkASSERT(percent >= 0 && percent <= SK_Scalar1);
394
395 int scale = SkScalarRound(percent * 256);
396 SkFixed div255 = SK_Fixed1 / 255;
397
398 for (int i = 0; i < 256; i++)
399 {
400 int square = i * i;
401 int linear = i * 255;
402 int n = SkAlphaBlend(square, linear, scale);
403 gamma[i] = SkToU8(n * div255 >> 16);
404 }
405}
406#endif