blob: ac1e78013032b2a5530496a7c2a0f4ef6695e4ee [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,
reed@google.com03016a32011-08-12 14:59:59 +0000225 SkScalar radius, Style style, Quality quality) {
226 if (src.fFormat != SkMask::kA8_Format) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000227 return false;
reed@google.com03016a32011-08-12 14:59:59 +0000228 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000229
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000230 // Force high quality off for small radii (performance)
231 if (radius < SkIntToScalar(3)) quality = kLow_Quality;
232
233 // highQuality: use three box blur passes as a cheap way to approximate a Gaussian blur
234 int passCount = (quality == kHigh_Quality) ? 3 : 1;
235 SkScalar passRadius = SkScalarDiv(radius, SkScalarSqrt(SkIntToScalar(passCount)));
236
237 int rx = SkScalarCeil(passRadius);
238 int outer_weight = 255 - SkScalarRound((SkIntToScalar(rx) - passRadius) * 255);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000239
240 SkASSERT(rx >= 0);
241 SkASSERT((unsigned)outer_weight <= 255);
reed@android.com0e3c6642009-09-18 13:41:56 +0000242 if (rx <= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000243 return false;
reed@android.com0e3c6642009-09-18 13:41:56 +0000244 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000245
246 int ry = rx; // only do square blur for now
247
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000248 int padx = passCount * rx;
249 int pady = passCount * ry;
250 dst->fBounds.set(src.fBounds.fLeft - padx, src.fBounds.fTop - pady,
251 src.fBounds.fRight + padx, src.fBounds.fBottom + pady);
reed@android.com49f0ff22009-03-19 21:52:42 +0000252 dst->fRowBytes = dst->fBounds.width();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000253 dst->fFormat = SkMask::kA8_Format;
254 dst->fImage = NULL;
255
reed@android.com0e3c6642009-09-18 13:41:56 +0000256 if (src.fImage) {
reed@android.com543ed932009-04-24 12:43:40 +0000257 size_t dstSize = dst->computeImageSize();
258 if (0 == dstSize) {
259 return false; // too big to allocate, abort
260 }
261
reed@android.com8a1c16f2008-12-17 15:59:43 +0000262 int sw = src.fBounds.width();
263 int sh = src.fBounds.height();
264 const uint8_t* sp = src.fImage;
reed@android.com543ed932009-04-24 12:43:40 +0000265 uint8_t* dp = SkMask::AllocImage(dstSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000266
267 SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dp);
268
269 // build the blurry destination
270 {
reed@google.com03016a32011-08-12 14:59:59 +0000271 const size_t storageW = sw + 2 * (passCount - 1) * rx + 1;
272 const size_t storageH = sh + 2 * (passCount - 1) * ry + 1;
273 SkAutoTMalloc<uint32_t> storage(storageW * storageH);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000274 uint32_t* sumBuffer = storage.get();
275
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000276 //pass1: sp is source, dp is destination
reed@android.com8a1c16f2008-12-17 15:59:43 +0000277 build_sum_buffer(sumBuffer, sw, sh, sp, src.fRowBytes);
reed@google.com03016a32011-08-12 14:59:59 +0000278 if (outer_weight == 255) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000279 apply_kernel(dp, rx, ry, sumBuffer, sw, sh);
reed@google.com03016a32011-08-12 14:59:59 +0000280 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000281 apply_kernel_interp(dp, rx, ry, sumBuffer, sw, sh, outer_weight);
reed@google.com03016a32011-08-12 14:59:59 +0000282 }
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000283
reed@google.com03016a32011-08-12 14:59:59 +0000284 if (quality == kHigh_Quality) {
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000285 //pass2: dp is source, tmpBuffer is destination
286 int tmp_sw = sw + 2 * rx;
287 int tmp_sh = sh + 2 * ry;
288 SkAutoTMalloc<uint8_t> tmpBuffer(dstSize);
289 build_sum_buffer(sumBuffer, tmp_sw, tmp_sh, dp, tmp_sw);
290 if (outer_weight == 255)
291 apply_kernel(tmpBuffer.get(), rx, ry, sumBuffer, tmp_sw, tmp_sh);
292 else
reed@google.com03016a32011-08-12 14:59:59 +0000293 apply_kernel_interp(tmpBuffer.get(), rx, ry, sumBuffer,
294 tmp_sw, tmp_sh, outer_weight);
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000295
296 //pass3: tmpBuffer is source, dp is destination
297 tmp_sw += 2 * rx;
298 tmp_sh += 2 * ry;
299 build_sum_buffer(sumBuffer, tmp_sw, tmp_sh, tmpBuffer.get(), tmp_sw);
300 if (outer_weight == 255)
301 apply_kernel(dp, rx, ry, sumBuffer, tmp_sw, tmp_sh);
302 else
reed@google.com03016a32011-08-12 14:59:59 +0000303 apply_kernel_interp(dp, rx, ry, sumBuffer, tmp_sw, tmp_sh,
304 outer_weight);
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000305 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000306 }
307
308 dst->fImage = dp;
309 // if need be, alloc the "real" dst (same size as src) and copy/merge
310 // the blur into it (applying the src)
reed@android.com0e3c6642009-09-18 13:41:56 +0000311 if (style == kInner_Style) {
312 // now we allocate the "real" dst, mirror the size of src
reed@android.com543ed932009-04-24 12:43:40 +0000313 size_t srcSize = src.computeImageSize();
314 if (0 == srcSize) {
315 return false; // too big to allocate, abort
316 }
317 dst->fImage = SkMask::AllocImage(srcSize);
reed@android.com0e3c6642009-09-18 13:41:56 +0000318 merge_src_with_blur(dst->fImage, src.fRowBytes,
319 sp, src.fRowBytes,
reed@google.com03016a32011-08-12 14:59:59 +0000320 dp + passCount * (rx + ry * dst->fRowBytes),
321 dst->fRowBytes, sw, sh);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322 SkMask::FreeImage(dp);
reed@android.com0e3c6642009-09-18 13:41:56 +0000323 } else if (style != kNormal_Style) {
reed@google.com03016a32011-08-12 14:59:59 +0000324 clamp_with_orig(dp + passCount * (rx + ry * dst->fRowBytes),
325 dst->fRowBytes, sp, src.fRowBytes, sw, sh, style);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326 }
327 (void)autoCall.detach();
328 }
329
reed@android.com0e3c6642009-09-18 13:41:56 +0000330 if (style == kInner_Style) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 dst->fBounds = src.fBounds; // restore trimmed bounds
reed@android.com0e3c6642009-09-18 13:41:56 +0000332 dst->fRowBytes = src.fRowBytes;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000333 }
334
reed@android.com8a1c16f2008-12-17 15:59:43 +0000335 return true;
336}
337