blob: 5dc9f448c345f7404d9db210de7fad92d97a6e7c [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"
11#include "SkTemplates.h"
12
reed@android.com45607672009-09-21 00:27:08 +000013/** The sum buffer is an array of u32 to hold the accumulated sum of all of the
14 src values at their position, plus all values above and to the left.
15 When we sample into this buffer, we need an initial row and column of 0s,
16 so we have an index correspondence as follows:
17
18 src[i, j] == sum[i+1, j+1]
19 sum[0, j] == sum[i, 0] == 0
20
21 We assume that the sum buffer's stride == its width
22 */
reed@google.com03016a32011-08-12 14:59:59 +000023static void build_sum_buffer(uint32_t sum[], int srcW, int srcH,
24 const uint8_t src[], int srcRB) {
reed@android.com45607672009-09-21 00:27:08 +000025 int sumW = srcW + 1;
26
27 SkASSERT(srcRB >= srcW);
reed@android.com8a1c16f2008-12-17 15:59:43 +000028 // mod srcRB so we can apply it after each row
reed@android.com45607672009-09-21 00:27:08 +000029 srcRB -= srcW;
reed@android.com8a1c16f2008-12-17 15:59:43 +000030
31 int x, y;
32
reed@android.com45607672009-09-21 00:27:08 +000033 // zero out the top row and column
34 memset(sum, 0, sumW * sizeof(sum[0]));
35 sum += sumW;
36
reed@android.com8a1c16f2008-12-17 15:59:43 +000037 // special case first row
38 uint32_t X = 0;
reed@android.com45607672009-09-21 00:27:08 +000039 *sum++ = 0; // initialze the first column to 0
reed@google.com03016a32011-08-12 14:59:59 +000040 for (x = srcW - 1; x >= 0; --x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000041 X = *src++ + X;
reed@android.com45607672009-09-21 00:27:08 +000042 *sum++ = X;
reed@android.com8a1c16f2008-12-17 15:59:43 +000043 }
44 src += srcRB;
45
46 // now do the rest of the rows
reed@google.com03016a32011-08-12 14:59:59 +000047 for (y = srcH - 1; y > 0; --y) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000048 uint32_t L = 0;
49 uint32_t C = 0;
reed@android.com45607672009-09-21 00:27:08 +000050 *sum++ = 0; // initialze the first column to 0
reed@google.com03016a32011-08-12 14:59:59 +000051 for (x = srcW - 1; x >= 0; --x) {
reed@android.com45607672009-09-21 00:27:08 +000052 uint32_t T = sum[-sumW];
reed@android.com8a1c16f2008-12-17 15:59:43 +000053 X = *src++ + L + T - C;
reed@android.com45607672009-09-21 00:27:08 +000054 *sum++ = X;
reed@android.com8a1c16f2008-12-17 15:59:43 +000055 L = X;
56 C = T;
57 }
58 src += srcRB;
59 }
60}
61
reed@google.com03016a32011-08-12 14:59:59 +000062/**
63 * sw and sh are the width and height of the src. Since the sum buffer
64 * matches that, but has an extra row and col at the beginning (with zeros),
65 * we can just use sw and sh as our "max" values for pinning coordinates
66 * when sampling into sum[][]
reed@android.com45607672009-09-21 00:27:08 +000067 */
68static void apply_kernel(uint8_t dst[], int rx, int ry, const uint32_t sum[],
69 int sw, int sh) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000070 uint32_t scale = (1 << 24) / ((2*rx + 1)*(2*ry + 1));
71
reed@android.com45607672009-09-21 00:27:08 +000072 int sumStride = sw + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +000073
74 int dw = sw + 2*rx;
75 int dh = sh + 2*ry;
76
reed@android.com45607672009-09-21 00:27:08 +000077 int prev_y = -2*ry;
78 int next_y = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +000079
reed@android.com45607672009-09-21 00:27:08 +000080 for (int y = 0; y < dh; y++) {
81 int py = SkClampPos(prev_y) * sumStride;
82 int ny = SkFastMin32(next_y, sh) * sumStride;
reed@android.com8a1c16f2008-12-17 15:59:43 +000083
reed@android.com45607672009-09-21 00:27:08 +000084 int prev_x = -2*rx;
85 int next_x = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +000086
reed@android.com45607672009-09-21 00:27:08 +000087 for (int x = 0; x < dw; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000088 int px = SkClampPos(prev_x);
89 int nx = SkFastMin32(next_x, sw);
90
reed@android.com45607672009-09-21 00:27:08 +000091 uint32_t tmp = sum[px+py] + sum[nx+ny] - sum[nx+py] - sum[px+ny];
92 *dst++ = SkToU8(tmp * scale >> 24);
reed@android.com8a1c16f2008-12-17 15:59:43 +000093
94 prev_x += 1;
95 next_x += 1;
96 }
97 prev_y += 1;
98 next_y += 1;
99 }
100}
101
reed@google.com03016a32011-08-12 14:59:59 +0000102/**
103 * sw and sh are the width and height of the src. Since the sum buffer
104 * matches that, but has an extra row and col at the beginning (with zeros),
105 * we can just use sw and sh as our "max" values for pinning coordinates
106 * when sampling into sum[][]
reed@android.com45607672009-09-21 00:27:08 +0000107 */
108static void apply_kernel_interp(uint8_t dst[], int rx, int ry,
109 const uint32_t sum[], int sw, int sh, U8CPU outer_weight) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000110 SkASSERT(rx > 0 && ry > 0);
111 SkASSERT(outer_weight <= 255);
112
113 int inner_weight = 255 - outer_weight;
114
115 // round these guys up if they're bigger than 127
116 outer_weight += outer_weight >> 7;
117 inner_weight += inner_weight >> 7;
118
119 uint32_t outer_scale = (outer_weight << 16) / ((2*rx + 1)*(2*ry + 1));
120 uint32_t inner_scale = (inner_weight << 16) / ((2*rx - 1)*(2*ry - 1));
121
reed@android.com45607672009-09-21 00:27:08 +0000122 int sumStride = sw + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000123
124 int dw = sw + 2*rx;
125 int dh = sh + 2*ry;
126
reed@android.com45607672009-09-21 00:27:08 +0000127 int prev_y = -2*ry;
128 int next_y = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129
reed@android.com45607672009-09-21 00:27:08 +0000130 for (int y = 0; y < dh; y++) {
131 int py = SkClampPos(prev_y) * sumStride;
132 int ny = SkFastMin32(next_y, sh) * sumStride;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000133
reed@android.com45607672009-09-21 00:27:08 +0000134 int ipy = SkClampPos(prev_y + 1) * sumStride;
135 int iny = SkClampMax(next_y - 1, sh) * sumStride;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000136
reed@android.com45607672009-09-21 00:27:08 +0000137 int prev_x = -2*rx;
138 int next_x = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000139
reed@android.com45607672009-09-21 00:27:08 +0000140 for (int x = 0; x < dw; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000141 int px = SkClampPos(prev_x);
142 int nx = SkFastMin32(next_x, sw);
143
144 int ipx = SkClampPos(prev_x + 1);
145 int inx = SkClampMax(next_x - 1, sw);
146
reed@android.com45607672009-09-21 00:27:08 +0000147 uint32_t outer_sum = sum[px+py] + sum[nx+ny] - sum[nx+py] - sum[px+ny];
148 uint32_t inner_sum = sum[ipx+ipy] + sum[inx+iny] - sum[inx+ipy] - sum[ipx+iny];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000149 *dst++ = SkToU8((outer_sum * outer_scale + inner_sum * inner_scale) >> 24);
150
151 prev_x += 1;
152 next_x += 1;
153 }
154 prev_y += 1;
155 next_y += 1;
156 }
157}
158
159#include "SkColorPriv.h"
160
reed@android.com0e3c6642009-09-18 13:41:56 +0000161static void merge_src_with_blur(uint8_t dst[], int dstRB,
162 const uint8_t src[], int srcRB,
163 const uint8_t blur[], int blurRB,
164 int sw, int sh) {
165 dstRB -= sw;
166 srcRB -= sw;
167 blurRB -= sw;
168 while (--sh >= 0) {
169 for (int x = sw - 1; x >= 0; --x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170 *dst = SkToU8(SkAlphaMul(*blur, SkAlpha255To256(*src)));
171 dst += 1;
172 src += 1;
173 blur += 1;
174 }
reed@android.com0e3c6642009-09-18 13:41:56 +0000175 dst += dstRB;
176 src += srcRB;
177 blur += blurRB;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000178 }
179}
180
181static void clamp_with_orig(uint8_t dst[], int dstRowBytes,
reed@android.com0e3c6642009-09-18 13:41:56 +0000182 const uint8_t src[], int srcRowBytes,
183 int sw, int sh,
reed@android.com45607672009-09-21 00:27:08 +0000184 SkBlurMask::Style style) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000185 int x;
reed@android.com0e3c6642009-09-18 13:41:56 +0000186 while (--sh >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000187 switch (style) {
188 case SkBlurMask::kSolid_Style:
reed@android.com0e3c6642009-09-18 13:41:56 +0000189 for (x = sw - 1; x >= 0; --x) {
190 int s = *src;
191 int d = *dst;
192 *dst = SkToU8(s + d - SkMulDiv255Round(s, d));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 dst += 1;
194 src += 1;
195 }
196 break;
197 case SkBlurMask::kOuter_Style:
reed@android.com0e3c6642009-09-18 13:41:56 +0000198 for (x = sw - 1; x >= 0; --x) {
199 if (*src) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000200 *dst = SkToU8(SkAlphaMul(*dst, SkAlpha255To256(255 - *src)));
reed@android.com0e3c6642009-09-18 13:41:56 +0000201 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202 dst += 1;
203 src += 1;
204 }
205 break;
206 default:
207 SkASSERT(!"Unexpected blur style here");
208 break;
209 }
210 dst += dstRowBytes - sw;
reed@android.com0e3c6642009-09-18 13:41:56 +0000211 src += srcRowBytes - sw;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212 }
213}
214
reed@google.com03016a32011-08-12 14:59:59 +0000215///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000216
217// we use a local funciton to wrap the class static method to work around
218// a bug in gcc98
219void SkMask_FreeImage(uint8_t* image);
reed@google.com03016a32011-08-12 14:59:59 +0000220void SkMask_FreeImage(uint8_t* image) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000221 SkMask::FreeImage(image);
222}
223
224bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
bungeman@google.com5af16f82011-09-02 15:06:44 +0000225 SkScalar radius, Style style, Quality quality,
226 SkIPoint* margin)
227{
reed@google.com03016a32011-08-12 14:59:59 +0000228 if (src.fFormat != SkMask::kA8_Format) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000229 return false;
reed@google.com03016a32011-08-12 14:59:59 +0000230 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000231
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000232 // Force high quality off for small radii (performance)
233 if (radius < SkIntToScalar(3)) quality = kLow_Quality;
234
235 // highQuality: use three box blur passes as a cheap way to approximate a Gaussian blur
236 int passCount = (quality == kHigh_Quality) ? 3 : 1;
237 SkScalar passRadius = SkScalarDiv(radius, SkScalarSqrt(SkIntToScalar(passCount)));
238
239 int rx = SkScalarCeil(passRadius);
240 int outer_weight = 255 - SkScalarRound((SkIntToScalar(rx) - passRadius) * 255);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000241
242 SkASSERT(rx >= 0);
243 SkASSERT((unsigned)outer_weight <= 255);
reed@android.com0e3c6642009-09-18 13:41:56 +0000244 if (rx <= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000245 return false;
reed@android.com0e3c6642009-09-18 13:41:56 +0000246 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000247
248 int ry = rx; // only do square blur for now
249
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000250 int padx = passCount * rx;
251 int pady = passCount * ry;
bungeman@google.com5af16f82011-09-02 15:06:44 +0000252 if (margin) {
253 margin->set(padx, pady);
254 }
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000255 dst->fBounds.set(src.fBounds.fLeft - padx, src.fBounds.fTop - pady,
256 src.fBounds.fRight + padx, src.fBounds.fBottom + pady);
reed@android.com49f0ff22009-03-19 21:52:42 +0000257 dst->fRowBytes = dst->fBounds.width();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000258 dst->fFormat = SkMask::kA8_Format;
259 dst->fImage = NULL;
260
reed@android.com0e3c6642009-09-18 13:41:56 +0000261 if (src.fImage) {
reed@android.com543ed932009-04-24 12:43:40 +0000262 size_t dstSize = dst->computeImageSize();
263 if (0 == dstSize) {
264 return false; // too big to allocate, abort
265 }
266
reed@android.com8a1c16f2008-12-17 15:59:43 +0000267 int sw = src.fBounds.width();
268 int sh = src.fBounds.height();
269 const uint8_t* sp = src.fImage;
reed@android.com543ed932009-04-24 12:43:40 +0000270 uint8_t* dp = SkMask::AllocImage(dstSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000271
272 SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dp);
273
274 // build the blurry destination
275 {
reed@google.com03016a32011-08-12 14:59:59 +0000276 const size_t storageW = sw + 2 * (passCount - 1) * rx + 1;
277 const size_t storageH = sh + 2 * (passCount - 1) * ry + 1;
278 SkAutoTMalloc<uint32_t> storage(storageW * storageH);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000279 uint32_t* sumBuffer = storage.get();
280
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000281 //pass1: sp is source, dp is destination
reed@android.com8a1c16f2008-12-17 15:59:43 +0000282 build_sum_buffer(sumBuffer, sw, sh, sp, src.fRowBytes);
reed@google.com03016a32011-08-12 14:59:59 +0000283 if (outer_weight == 255) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000284 apply_kernel(dp, rx, ry, sumBuffer, sw, sh);
reed@google.com03016a32011-08-12 14:59:59 +0000285 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000286 apply_kernel_interp(dp, rx, ry, sumBuffer, sw, sh, outer_weight);
reed@google.com03016a32011-08-12 14:59:59 +0000287 }
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000288
reed@google.com03016a32011-08-12 14:59:59 +0000289 if (quality == kHigh_Quality) {
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000290 //pass2: dp is source, tmpBuffer is destination
291 int tmp_sw = sw + 2 * rx;
292 int tmp_sh = sh + 2 * ry;
293 SkAutoTMalloc<uint8_t> tmpBuffer(dstSize);
294 build_sum_buffer(sumBuffer, tmp_sw, tmp_sh, dp, tmp_sw);
295 if (outer_weight == 255)
296 apply_kernel(tmpBuffer.get(), rx, ry, sumBuffer, tmp_sw, tmp_sh);
297 else
reed@google.com03016a32011-08-12 14:59:59 +0000298 apply_kernel_interp(tmpBuffer.get(), rx, ry, sumBuffer,
299 tmp_sw, tmp_sh, outer_weight);
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000300
301 //pass3: tmpBuffer is source, dp is destination
302 tmp_sw += 2 * rx;
303 tmp_sh += 2 * ry;
304 build_sum_buffer(sumBuffer, tmp_sw, tmp_sh, tmpBuffer.get(), tmp_sw);
305 if (outer_weight == 255)
306 apply_kernel(dp, rx, ry, sumBuffer, tmp_sw, tmp_sh);
307 else
reed@google.com03016a32011-08-12 14:59:59 +0000308 apply_kernel_interp(dp, rx, ry, sumBuffer, tmp_sw, tmp_sh,
309 outer_weight);
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000310 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311 }
312
313 dst->fImage = dp;
314 // if need be, alloc the "real" dst (same size as src) and copy/merge
315 // the blur into it (applying the src)
reed@android.com0e3c6642009-09-18 13:41:56 +0000316 if (style == kInner_Style) {
317 // now we allocate the "real" dst, mirror the size of src
reed@android.com543ed932009-04-24 12:43:40 +0000318 size_t srcSize = src.computeImageSize();
319 if (0 == srcSize) {
320 return false; // too big to allocate, abort
321 }
322 dst->fImage = SkMask::AllocImage(srcSize);
reed@android.com0e3c6642009-09-18 13:41:56 +0000323 merge_src_with_blur(dst->fImage, src.fRowBytes,
324 sp, src.fRowBytes,
reed@google.com03016a32011-08-12 14:59:59 +0000325 dp + passCount * (rx + ry * dst->fRowBytes),
326 dst->fRowBytes, sw, sh);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000327 SkMask::FreeImage(dp);
reed@android.com0e3c6642009-09-18 13:41:56 +0000328 } else if (style != kNormal_Style) {
reed@google.com03016a32011-08-12 14:59:59 +0000329 clamp_with_orig(dp + passCount * (rx + ry * dst->fRowBytes),
330 dst->fRowBytes, sp, src.fRowBytes, sw, sh, style);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 }
332 (void)autoCall.detach();
333 }
334
reed@android.com0e3c6642009-09-18 13:41:56 +0000335 if (style == kInner_Style) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000336 dst->fBounds = src.fBounds; // restore trimmed bounds
reed@android.com0e3c6642009-09-18 13:41:56 +0000337 dst->fRowBytes = src.fRowBytes;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000338 }
339
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340 return true;
341}
342