blob: b139714a3d6439ab38dc547c124526675498e02d [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkBlurMask.h"
tomhudson@google.com889bd8b2011-09-27 17:38:17 +000011#include "SkMath.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkTemplates.h"
13
reed@android.com45607672009-09-21 00:27:08 +000014/** The sum buffer is an array of u32 to hold the accumulated sum of all of the
15 src values at their position, plus all values above and to the left.
16 When we sample into this buffer, we need an initial row and column of 0s,
17 so we have an index correspondence as follows:
18
19 src[i, j] == sum[i+1, j+1]
20 sum[0, j] == sum[i, 0] == 0
21
22 We assume that the sum buffer's stride == its width
23 */
reed@google.com03016a32011-08-12 14:59:59 +000024static void build_sum_buffer(uint32_t sum[], int srcW, int srcH,
25 const uint8_t src[], int srcRB) {
reed@android.com45607672009-09-21 00:27:08 +000026 int sumW = srcW + 1;
27
28 SkASSERT(srcRB >= srcW);
reed@android.com8a1c16f2008-12-17 15:59:43 +000029 // mod srcRB so we can apply it after each row
reed@android.com45607672009-09-21 00:27:08 +000030 srcRB -= srcW;
reed@android.com8a1c16f2008-12-17 15:59:43 +000031
32 int x, y;
33
reed@android.com45607672009-09-21 00:27:08 +000034 // zero out the top row and column
35 memset(sum, 0, sumW * sizeof(sum[0]));
36 sum += sumW;
37
reed@android.com8a1c16f2008-12-17 15:59:43 +000038 // special case first row
39 uint32_t X = 0;
reed@android.com45607672009-09-21 00:27:08 +000040 *sum++ = 0; // initialze the first column to 0
reed@google.com03016a32011-08-12 14:59:59 +000041 for (x = srcW - 1; x >= 0; --x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000042 X = *src++ + X;
reed@android.com45607672009-09-21 00:27:08 +000043 *sum++ = X;
reed@android.com8a1c16f2008-12-17 15:59:43 +000044 }
45 src += srcRB;
46
47 // now do the rest of the rows
reed@google.com03016a32011-08-12 14:59:59 +000048 for (y = srcH - 1; y > 0; --y) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000049 uint32_t L = 0;
50 uint32_t C = 0;
reed@android.com45607672009-09-21 00:27:08 +000051 *sum++ = 0; // initialze the first column to 0
reed@google.com03016a32011-08-12 14:59:59 +000052 for (x = srcW - 1; x >= 0; --x) {
reed@android.com45607672009-09-21 00:27:08 +000053 uint32_t T = sum[-sumW];
reed@android.com8a1c16f2008-12-17 15:59:43 +000054 X = *src++ + L + T - C;
reed@android.com45607672009-09-21 00:27:08 +000055 *sum++ = X;
reed@android.com8a1c16f2008-12-17 15:59:43 +000056 L = X;
57 C = T;
58 }
59 src += srcRB;
60 }
61}
62
reed@google.com03016a32011-08-12 14:59:59 +000063/**
64 * sw and sh are the width and height of the src. Since the sum buffer
65 * matches that, but has an extra row and col at the beginning (with zeros),
66 * we can just use sw and sh as our "max" values for pinning coordinates
67 * when sampling into sum[][]
reed@android.com45607672009-09-21 00:27:08 +000068 */
69static void apply_kernel(uint8_t dst[], int rx, int ry, const uint32_t sum[],
70 int sw, int sh) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000071 uint32_t scale = (1 << 24) / ((2*rx + 1)*(2*ry + 1));
72
reed@android.com45607672009-09-21 00:27:08 +000073 int sumStride = sw + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +000074
75 int dw = sw + 2*rx;
76 int dh = sh + 2*ry;
77
reed@android.com45607672009-09-21 00:27:08 +000078 int prev_y = -2*ry;
79 int next_y = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +000080
reed@android.com45607672009-09-21 00:27:08 +000081 for (int y = 0; y < dh; y++) {
82 int py = SkClampPos(prev_y) * sumStride;
83 int ny = SkFastMin32(next_y, sh) * sumStride;
reed@android.com8a1c16f2008-12-17 15:59:43 +000084
reed@android.com45607672009-09-21 00:27:08 +000085 int prev_x = -2*rx;
86 int next_x = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +000087
reed@android.com45607672009-09-21 00:27:08 +000088 for (int x = 0; x < dw; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000089 int px = SkClampPos(prev_x);
90 int nx = SkFastMin32(next_x, sw);
91
reed@android.com45607672009-09-21 00:27:08 +000092 uint32_t tmp = sum[px+py] + sum[nx+ny] - sum[nx+py] - sum[px+ny];
93 *dst++ = SkToU8(tmp * scale >> 24);
reed@android.com8a1c16f2008-12-17 15:59:43 +000094
95 prev_x += 1;
96 next_x += 1;
97 }
98 prev_y += 1;
99 next_y += 1;
100 }
101}
102
reed@google.com03016a32011-08-12 14:59:59 +0000103/**
104 * sw and sh are the width and height of the src. Since the sum buffer
105 * matches that, but has an extra row and col at the beginning (with zeros),
106 * we can just use sw and sh as our "max" values for pinning coordinates
107 * when sampling into sum[][]
reed@android.com45607672009-09-21 00:27:08 +0000108 */
109static void apply_kernel_interp(uint8_t dst[], int rx, int ry,
110 const uint32_t sum[], int sw, int sh, U8CPU outer_weight) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000111 SkASSERT(rx > 0 && ry > 0);
112 SkASSERT(outer_weight <= 255);
113
114 int inner_weight = 255 - outer_weight;
115
116 // round these guys up if they're bigger than 127
117 outer_weight += outer_weight >> 7;
118 inner_weight += inner_weight >> 7;
119
120 uint32_t outer_scale = (outer_weight << 16) / ((2*rx + 1)*(2*ry + 1));
121 uint32_t inner_scale = (inner_weight << 16) / ((2*rx - 1)*(2*ry - 1));
122
reed@android.com45607672009-09-21 00:27:08 +0000123 int sumStride = sw + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000124
125 int dw = sw + 2*rx;
126 int dh = sh + 2*ry;
127
reed@android.com45607672009-09-21 00:27:08 +0000128 int prev_y = -2*ry;
129 int next_y = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000130
reed@android.com45607672009-09-21 00:27:08 +0000131 for (int y = 0; y < dh; y++) {
132 int py = SkClampPos(prev_y) * sumStride;
133 int ny = SkFastMin32(next_y, sh) * sumStride;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000134
reed@android.com45607672009-09-21 00:27:08 +0000135 int ipy = SkClampPos(prev_y + 1) * sumStride;
136 int iny = SkClampMax(next_y - 1, sh) * sumStride;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000137
reed@android.com45607672009-09-21 00:27:08 +0000138 int prev_x = -2*rx;
139 int next_x = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000140
reed@android.com45607672009-09-21 00:27:08 +0000141 for (int x = 0; x < dw; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000142 int px = SkClampPos(prev_x);
143 int nx = SkFastMin32(next_x, sw);
144
145 int ipx = SkClampPos(prev_x + 1);
146 int inx = SkClampMax(next_x - 1, sw);
147
reed@android.com45607672009-09-21 00:27:08 +0000148 uint32_t outer_sum = sum[px+py] + sum[nx+ny] - sum[nx+py] - sum[px+ny];
149 uint32_t inner_sum = sum[ipx+ipy] + sum[inx+iny] - sum[inx+ipy] - sum[ipx+iny];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000150 *dst++ = SkToU8((outer_sum * outer_scale + inner_sum * inner_scale) >> 24);
151
152 prev_x += 1;
153 next_x += 1;
154 }
155 prev_y += 1;
156 next_y += 1;
157 }
158}
159
160#include "SkColorPriv.h"
161
reed@android.com0e3c6642009-09-18 13:41:56 +0000162static void merge_src_with_blur(uint8_t dst[], int dstRB,
163 const uint8_t src[], int srcRB,
164 const uint8_t blur[], int blurRB,
165 int sw, int sh) {
166 dstRB -= sw;
167 srcRB -= sw;
168 blurRB -= sw;
169 while (--sh >= 0) {
170 for (int x = sw - 1; x >= 0; --x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000171 *dst = SkToU8(SkAlphaMul(*blur, SkAlpha255To256(*src)));
172 dst += 1;
173 src += 1;
174 blur += 1;
175 }
reed@android.com0e3c6642009-09-18 13:41:56 +0000176 dst += dstRB;
177 src += srcRB;
178 blur += blurRB;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000179 }
180}
181
182static void clamp_with_orig(uint8_t dst[], int dstRowBytes,
reed@android.com0e3c6642009-09-18 13:41:56 +0000183 const uint8_t src[], int srcRowBytes,
184 int sw, int sh,
reed@android.com45607672009-09-21 00:27:08 +0000185 SkBlurMask::Style style) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000186 int x;
reed@android.com0e3c6642009-09-18 13:41:56 +0000187 while (--sh >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000188 switch (style) {
189 case SkBlurMask::kSolid_Style:
reed@android.com0e3c6642009-09-18 13:41:56 +0000190 for (x = sw - 1; x >= 0; --x) {
191 int s = *src;
192 int d = *dst;
193 *dst = SkToU8(s + d - SkMulDiv255Round(s, d));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000194 dst += 1;
195 src += 1;
196 }
197 break;
198 case SkBlurMask::kOuter_Style:
reed@android.com0e3c6642009-09-18 13:41:56 +0000199 for (x = sw - 1; x >= 0; --x) {
200 if (*src) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000201 *dst = SkToU8(SkAlphaMul(*dst, SkAlpha255To256(255 - *src)));
reed@android.com0e3c6642009-09-18 13:41:56 +0000202 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000203 dst += 1;
204 src += 1;
205 }
206 break;
207 default:
208 SkASSERT(!"Unexpected blur style here");
209 break;
210 }
211 dst += dstRowBytes - sw;
reed@android.com0e3c6642009-09-18 13:41:56 +0000212 src += srcRowBytes - sw;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000213 }
214}
215
reed@google.com03016a32011-08-12 14:59:59 +0000216///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000217
218// we use a local funciton to wrap the class static method to work around
219// a bug in gcc98
220void SkMask_FreeImage(uint8_t* image);
reed@google.com03016a32011-08-12 14:59:59 +0000221void SkMask_FreeImage(uint8_t* image) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000222 SkMask::FreeImage(image);
223}
224
225bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
bungeman@google.com5af16f82011-09-02 15:06:44 +0000226 SkScalar radius, Style style, Quality quality,
227 SkIPoint* margin)
228{
reed@google.com03016a32011-08-12 14:59:59 +0000229 if (src.fFormat != SkMask::kA8_Format) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000230 return false;
reed@google.com03016a32011-08-12 14:59:59 +0000231 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000232
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000233 // Force high quality off for small radii (performance)
234 if (radius < SkIntToScalar(3)) quality = kLow_Quality;
235
236 // highQuality: use three box blur passes as a cheap way to approximate a Gaussian blur
237 int passCount = (quality == kHigh_Quality) ? 3 : 1;
238 SkScalar passRadius = SkScalarDiv(radius, SkScalarSqrt(SkIntToScalar(passCount)));
239
240 int rx = SkScalarCeil(passRadius);
241 int outer_weight = 255 - SkScalarRound((SkIntToScalar(rx) - passRadius) * 255);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000242
243 SkASSERT(rx >= 0);
244 SkASSERT((unsigned)outer_weight <= 255);
reed@android.com0e3c6642009-09-18 13:41:56 +0000245 if (rx <= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000246 return false;
reed@android.com0e3c6642009-09-18 13:41:56 +0000247 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000248
249 int ry = rx; // only do square blur for now
250
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000251 int padx = passCount * rx;
252 int pady = passCount * ry;
bungeman@google.com5af16f82011-09-02 15:06:44 +0000253 if (margin) {
254 margin->set(padx, pady);
255 }
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000256 dst->fBounds.set(src.fBounds.fLeft - padx, src.fBounds.fTop - pady,
257 src.fBounds.fRight + padx, src.fBounds.fBottom + pady);
reed@android.com49f0ff22009-03-19 21:52:42 +0000258 dst->fRowBytes = dst->fBounds.width();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000259 dst->fFormat = SkMask::kA8_Format;
260 dst->fImage = NULL;
261
reed@android.com0e3c6642009-09-18 13:41:56 +0000262 if (src.fImage) {
reed@android.com543ed932009-04-24 12:43:40 +0000263 size_t dstSize = dst->computeImageSize();
264 if (0 == dstSize) {
265 return false; // too big to allocate, abort
266 }
267
reed@android.com8a1c16f2008-12-17 15:59:43 +0000268 int sw = src.fBounds.width();
269 int sh = src.fBounds.height();
270 const uint8_t* sp = src.fImage;
reed@android.com543ed932009-04-24 12:43:40 +0000271 uint8_t* dp = SkMask::AllocImage(dstSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000272
273 SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dp);
274
275 // build the blurry destination
276 {
reed@google.com03016a32011-08-12 14:59:59 +0000277 const size_t storageW = sw + 2 * (passCount - 1) * rx + 1;
278 const size_t storageH = sh + 2 * (passCount - 1) * ry + 1;
279 SkAutoTMalloc<uint32_t> storage(storageW * storageH);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000280 uint32_t* sumBuffer = storage.get();
281
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000282 //pass1: sp is source, dp is destination
reed@android.com8a1c16f2008-12-17 15:59:43 +0000283 build_sum_buffer(sumBuffer, sw, sh, sp, src.fRowBytes);
reed@google.com03016a32011-08-12 14:59:59 +0000284 if (outer_weight == 255) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000285 apply_kernel(dp, rx, ry, sumBuffer, sw, sh);
reed@google.com03016a32011-08-12 14:59:59 +0000286 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000287 apply_kernel_interp(dp, rx, ry, sumBuffer, sw, sh, outer_weight);
reed@google.com03016a32011-08-12 14:59:59 +0000288 }
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000289
reed@google.com03016a32011-08-12 14:59:59 +0000290 if (quality == kHigh_Quality) {
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000291 //pass2: dp is source, tmpBuffer is destination
292 int tmp_sw = sw + 2 * rx;
293 int tmp_sh = sh + 2 * ry;
294 SkAutoTMalloc<uint8_t> tmpBuffer(dstSize);
295 build_sum_buffer(sumBuffer, tmp_sw, tmp_sh, dp, tmp_sw);
296 if (outer_weight == 255)
297 apply_kernel(tmpBuffer.get(), rx, ry, sumBuffer, tmp_sw, tmp_sh);
298 else
reed@google.com03016a32011-08-12 14:59:59 +0000299 apply_kernel_interp(tmpBuffer.get(), rx, ry, sumBuffer,
300 tmp_sw, tmp_sh, outer_weight);
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000301
302 //pass3: tmpBuffer is source, dp is destination
303 tmp_sw += 2 * rx;
304 tmp_sh += 2 * ry;
305 build_sum_buffer(sumBuffer, tmp_sw, tmp_sh, tmpBuffer.get(), tmp_sw);
306 if (outer_weight == 255)
307 apply_kernel(dp, rx, ry, sumBuffer, tmp_sw, tmp_sh);
308 else
reed@google.com03016a32011-08-12 14:59:59 +0000309 apply_kernel_interp(dp, rx, ry, sumBuffer, tmp_sw, tmp_sh,
310 outer_weight);
senorblanco@chromium.org4868e6b2011-02-18 19:03:01 +0000311 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000312 }
313
314 dst->fImage = dp;
315 // if need be, alloc the "real" dst (same size as src) and copy/merge
316 // the blur into it (applying the src)
reed@android.com0e3c6642009-09-18 13:41:56 +0000317 if (style == kInner_Style) {
318 // now we allocate the "real" dst, mirror the size of src
reed@android.com543ed932009-04-24 12:43:40 +0000319 size_t srcSize = src.computeImageSize();
320 if (0 == srcSize) {
321 return false; // too big to allocate, abort
322 }
323 dst->fImage = SkMask::AllocImage(srcSize);
reed@android.com0e3c6642009-09-18 13:41:56 +0000324 merge_src_with_blur(dst->fImage, src.fRowBytes,
325 sp, src.fRowBytes,
reed@google.com03016a32011-08-12 14:59:59 +0000326 dp + passCount * (rx + ry * dst->fRowBytes),
327 dst->fRowBytes, sw, sh);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000328 SkMask::FreeImage(dp);
reed@android.com0e3c6642009-09-18 13:41:56 +0000329 } else if (style != kNormal_Style) {
reed@google.com03016a32011-08-12 14:59:59 +0000330 clamp_with_orig(dp + passCount * (rx + ry * dst->fRowBytes),
331 dst->fRowBytes, sp, src.fRowBytes, sw, sh, style);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000332 }
333 (void)autoCall.detach();
334 }
335
reed@android.com0e3c6642009-09-18 13:41:56 +0000336 if (style == kInner_Style) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000337 dst->fBounds = src.fBounds; // restore trimmed bounds
reed@android.com0e3c6642009-09-18 13:41:56 +0000338 dst->fRowBytes = src.fRowBytes;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339 }
340
reed@android.com8a1c16f2008-12-17 15:59:43 +0000341 return true;
342}
343