1 /* 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
21 #if 0
22 static 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 */
build_sum_buffer(uint32_t sum[],int srcW,int srcH,const uint8_t src[],int srcRB)46 static 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);
50 // mod srcRB so we can apply it after each row
51 srcRB -= srcW;
52
53 int x, y;
54
55 // zero out the top row and column
56 memset(sum, 0, sumW * sizeof(sum[0]));
57 sum += sumW;
58
59 // special case first row
60 uint32_t X = 0;
61 *sum++ = 0; // initialze the first column to 0
62 for (x = srcW - 1; x >= 0; --x)
63 {
64 X = *src++ + X;
65 *sum++ = X;
66 }
67 src += srcRB;
68
69 // now do the rest of the rows
70 for (y = srcH - 1; y > 0; --y)
71 {
72 uint32_t L = 0;
73 uint32_t C = 0;
74 *sum++ = 0; // initialze the first column to 0
75 for (x = srcW - 1; x >= 0; --x)
76 {
77 uint32_t T = sum[-sumW];
78 X = *src++ + L + T - C;
79 *sum++ = X;
80 L = X;
81 C = T;
82 }
83 src += srcRB;
84 }
85 }
86
87 /* 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 */
apply_kernel(uint8_t dst[],int rx,int ry,const uint32_t sum[],int sw,int sh)92 static void apply_kernel(uint8_t dst[], int rx, int ry, const uint32_t sum[],
93 int sw, int sh) {
94 uint32_t scale = (1 << 24) / ((2*rx + 1)*(2*ry + 1));
95
96 int sumStride = sw + 1;
97
98 int dw = sw + 2*rx;
99 int dh = sh + 2*ry;
100
101 int prev_y = -2*ry;
102 int next_y = 1;
103
104 for (int y = 0; y < dh; y++) {
105 int py = SkClampPos(prev_y) * sumStride;
106 int ny = SkFastMin32(next_y, sh) * sumStride;
107
108 int prev_x = -2*rx;
109 int next_x = 1;
110
111 for (int x = 0; x < dw; x++) {
112 int px = SkClampPos(prev_x);
113 int nx = SkFastMin32(next_x, sw);
114
115 uint32_t tmp = sum[px+py] + sum[nx+ny] - sum[nx+py] - sum[px+ny];
116 *dst++ = SkToU8(tmp * scale >> 24);
117
118 prev_x += 1;
119 next_x += 1;
120 }
121 prev_y += 1;
122 next_y += 1;
123 }
124 }
125
126 /* 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 */
apply_kernel_interp(uint8_t dst[],int rx,int ry,const uint32_t sum[],int sw,int sh,U8CPU outer_weight)131 static void apply_kernel_interp(uint8_t dst[], int rx, int ry,
132 const uint32_t sum[], int sw, int sh, U8CPU outer_weight) {
133 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
145 int sumStride = sw + 1;
146
147 int dw = sw + 2*rx;
148 int dh = sh + 2*ry;
149
150 int prev_y = -2*ry;
151 int next_y = 1;
152
153 for (int y = 0; y < dh; y++) {
154 int py = SkClampPos(prev_y) * sumStride;
155 int ny = SkFastMin32(next_y, sh) * sumStride;
156
157 int ipy = SkClampPos(prev_y + 1) * sumStride;
158 int iny = SkClampMax(next_y - 1, sh) * sumStride;
159
160 int prev_x = -2*rx;
161 int next_x = 1;
162
163 for (int x = 0; x < dw; x++) {
164 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
170 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];
172 *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
merge_src_with_blur(uint8_t dst[],int dstRB,const uint8_t src[],int srcRB,const uint8_t blur[],int blurRB,int sw,int sh)184 static 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) {
193 *dst = SkToU8(SkAlphaMul(*blur, SkAlpha255To256(*src)));
194 dst += 1;
195 src += 1;
196 blur += 1;
197 }
198 dst += dstRB;
199 src += srcRB;
200 blur += blurRB;
201 }
202 }
203
clamp_with_orig(uint8_t dst[],int dstRowBytes,const uint8_t src[],int srcRowBytes,int sw,int sh,SkBlurMask::Style style)204 static void clamp_with_orig(uint8_t dst[], int dstRowBytes,
205 const uint8_t src[], int srcRowBytes,
206 int sw, int sh,
207 SkBlurMask::Style style) {
208 int x;
209 while (--sh >= 0) {
210 switch (style) {
211 case SkBlurMask::kSolid_Style:
212 for (x = sw - 1; x >= 0; --x) {
213 int s = *src;
214 int d = *dst;
215 *dst = SkToU8(s + d - SkMulDiv255Round(s, d));
216 dst += 1;
217 src += 1;
218 }
219 break;
220 case SkBlurMask::kOuter_Style:
221 for (x = sw - 1; x >= 0; --x) {
222 if (*src) {
223 *dst = SkToU8(SkAlphaMul(*dst, SkAlpha255To256(255 - *src)));
224 }
225 dst += 1;
226 src += 1;
227 }
228 break;
229 default:
230 SkASSERT(!"Unexpected blur style here");
231 break;
232 }
233 dst += dstRowBytes - sw;
234 src += srcRowBytes - sw;
235 }
236 }
237
238 ////////////////////////////////////////////////////////////////////////
239
240 // we use a local funciton to wrap the class static method to work around
241 // a bug in gcc98
242 void SkMask_FreeImage(uint8_t* image);
SkMask_FreeImage(uint8_t * image)243 void SkMask_FreeImage(uint8_t* image)
244 {
245 SkMask::FreeImage(image);
246 }
247
Blur(SkMask * dst,const SkMask & src,SkScalar radius,Style style,Quality quality)248 bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
249 SkScalar radius, Style style, Quality quality)
250 {
251 if (src.fFormat != SkMask::kA8_Format)
252 return false;
253
254 // 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);
263
264 SkASSERT(rx >= 0);
265 SkASSERT((unsigned)outer_weight <= 255);
266 if (rx <= 0) {
267 return false;
268 }
269
270 int ry = rx; // only do square blur for now
271
272 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);
276 dst->fRowBytes = dst->fBounds.width();
277 dst->fFormat = SkMask::kA8_Format;
278 dst->fImage = NULL;
279
280 if (src.fImage) {
281 size_t dstSize = dst->computeImageSize();
282 if (0 == dstSize) {
283 return false; // too big to allocate, abort
284 }
285
286 int sw = src.fBounds.width();
287 int sh = src.fBounds.height();
288 const uint8_t* sp = src.fImage;
289 uint8_t* dp = SkMask::AllocImage(dstSize);
290
291 SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dp);
292
293 // build the blurry destination
294 {
295 SkAutoTMalloc<uint32_t> storage((sw + 2 * (passCount - 1) * rx + 1) * (sh + 2 * (passCount - 1) * ry + 1));
296 uint32_t* sumBuffer = storage.get();
297
298 //pass1: sp is source, dp is destination
299 build_sum_buffer(sumBuffer, sw, sh, sp, src.fRowBytes);
300 dump_sum_buffer(sumBuffer, sw, sh);
301 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);
305
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 }
327 }
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)
332 if (style == kInner_Style) {
333 // now we allocate the "real" dst, mirror the size of src
334 size_t srcSize = src.computeImageSize();
335 if (0 == srcSize) {
336 return false; // too big to allocate, abort
337 }
338 dst->fImage = SkMask::AllocImage(srcSize);
339 merge_src_with_blur(dst->fImage, src.fRowBytes,
340 sp, src.fRowBytes,
341 dp + passCount * (rx + ry * dst->fRowBytes), dst->fRowBytes,
342 sw, sh);
343 SkMask::FreeImage(dp);
344 } else if (style != kNormal_Style) {
345 clamp_with_orig(dp + passCount * (rx + ry * dst->fRowBytes), dst->fRowBytes,
346 sp, src.fRowBytes, sw, sh,
347 style);
348 }
349 (void)autoCall.detach();
350 }
351
352 if (style == kInner_Style) {
353 dst->fBounds = src.fBounds; // restore trimmed bounds
354 dst->fRowBytes = src.fRowBytes;
355 }
356
357 #if 0
358 if (gamma && dst->fImage) {
359 uint8_t* image = dst->fImage;
360 uint8_t* stop = image + dst->computeImageSize();
361
362 for (; image < stop; image += 1) {
363 *image = gamma[*image];
364 }
365 }
366 #endif
367 return true;
368 }
369
370 #if 0
371 void 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
390 void 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
407