blob: a6492c3a026fb57827abe7dd4c90bdf3126cce72 [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,
249 SkScalar radius, Style style)
250{
251 if (src.fFormat != SkMask::kA8_Format)
252 return false;
253
254 int rx = SkScalarCeil(radius);
255 int outer_weight = 255 - SkScalarRound((SkIntToScalar(rx) - radius) * 255);
256
257 SkASSERT(rx >= 0);
258 SkASSERT((unsigned)outer_weight <= 255);
reed@android.com0e3c6642009-09-18 13:41:56 +0000259 if (rx <= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000260 return false;
reed@android.com0e3c6642009-09-18 13:41:56 +0000261 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000262
263 int ry = rx; // only do square blur for now
264
265 dst->fBounds.set(src.fBounds.fLeft - rx, src.fBounds.fTop - ry,
266 src.fBounds.fRight + rx, src.fBounds.fBottom + ry);
reed@android.com49f0ff22009-03-19 21:52:42 +0000267 dst->fRowBytes = dst->fBounds.width();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000268 dst->fFormat = SkMask::kA8_Format;
269 dst->fImage = NULL;
270
reed@android.com0e3c6642009-09-18 13:41:56 +0000271 if (src.fImage) {
reed@android.com543ed932009-04-24 12:43:40 +0000272 size_t dstSize = dst->computeImageSize();
273 if (0 == dstSize) {
274 return false; // too big to allocate, abort
275 }
276
reed@android.com8a1c16f2008-12-17 15:59:43 +0000277 int sw = src.fBounds.width();
278 int sh = src.fBounds.height();
279 const uint8_t* sp = src.fImage;
reed@android.com543ed932009-04-24 12:43:40 +0000280 uint8_t* dp = SkMask::AllocImage(dstSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000281
282 SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dp);
283
284 // build the blurry destination
285 {
reed@android.com45607672009-09-21 00:27:08 +0000286 SkAutoTMalloc<uint32_t> storage((sw + 1) * (sh + 1));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000287 uint32_t* sumBuffer = storage.get();
288
289 build_sum_buffer(sumBuffer, sw, sh, sp, src.fRowBytes);
reed@android.com45607672009-09-21 00:27:08 +0000290 dump_sum_buffer(sumBuffer, sw, sh);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000291 if (outer_weight == 255)
292 apply_kernel(dp, rx, ry, sumBuffer, sw, sh);
293 else
294 apply_kernel_interp(dp, rx, ry, sumBuffer, sw, sh, outer_weight);
295 }
296
297 dst->fImage = dp;
298 // if need be, alloc the "real" dst (same size as src) and copy/merge
299 // the blur into it (applying the src)
reed@android.com0e3c6642009-09-18 13:41:56 +0000300 if (style == kInner_Style) {
301 // now we allocate the "real" dst, mirror the size of src
reed@android.com543ed932009-04-24 12:43:40 +0000302 size_t srcSize = src.computeImageSize();
303 if (0 == srcSize) {
304 return false; // too big to allocate, abort
305 }
306 dst->fImage = SkMask::AllocImage(srcSize);
reed@android.com0e3c6642009-09-18 13:41:56 +0000307 merge_src_with_blur(dst->fImage, src.fRowBytes,
308 sp, src.fRowBytes,
309 dp + rx + ry*dst->fRowBytes, dst->fRowBytes,
310 sw, sh);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311 SkMask::FreeImage(dp);
reed@android.com0e3c6642009-09-18 13:41:56 +0000312 } else if (style != kNormal_Style) {
313 clamp_with_orig(dp + rx + ry*dst->fRowBytes, dst->fRowBytes,
314 sp, src.fRowBytes, sw, sh,
reed@android.com8a1c16f2008-12-17 15:59:43 +0000315 style);
316 }
317 (void)autoCall.detach();
318 }
319
reed@android.com0e3c6642009-09-18 13:41:56 +0000320 if (style == kInner_Style) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000321 dst->fBounds = src.fBounds; // restore trimmed bounds
reed@android.com0e3c6642009-09-18 13:41:56 +0000322 dst->fRowBytes = src.fRowBytes;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000323 }
324
325#if 0
reed@android.com0e3c6642009-09-18 13:41:56 +0000326 if (gamma && dst->fImage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000327 uint8_t* image = dst->fImage;
328 uint8_t* stop = image + dst->computeImageSize();
329
reed@android.com0e3c6642009-09-18 13:41:56 +0000330 for (; image < stop; image += 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 *image = gamma[*image];
reed@android.com0e3c6642009-09-18 13:41:56 +0000332 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000333 }
334#endif
335 return true;
336}
337
338#if 0
339void SkBlurMask::BuildSqrtGamma(uint8_t gamma[256], SkScalar percent)
340{
341 SkASSERT(gamma);
342 SkASSERT(percent >= 0 && percent <= SK_Scalar1);
343
344 int scale = SkScalarRound(percent * 256);
345
346 for (int i = 0; i < 256; i++)
347 {
348 SkFixed n = i * 257;
349 n += n >> 15;
350 SkASSERT(n >= 0 && n <= SK_Fixed1);
351 n = SkFixedSqrt(n);
352 n = n * 255 >> 16;
353 n = SkAlphaBlend(n, i, scale);
354 gamma[i] = SkToU8(n);
355 }
356}
357
358void SkBlurMask::BuildSqrGamma(uint8_t gamma[256], SkScalar percent)
359{
360 SkASSERT(gamma);
361 SkASSERT(percent >= 0 && percent <= SK_Scalar1);
362
363 int scale = SkScalarRound(percent * 256);
364 SkFixed div255 = SK_Fixed1 / 255;
365
366 for (int i = 0; i < 256; i++)
367 {
368 int square = i * i;
369 int linear = i * 255;
370 int n = SkAlphaBlend(square, linear, scale);
371 gamma[i] = SkToU8(n * div255 >> 16);
372 }
373}
374#endif