1 /* libs/graphics/sgl/SkScan_Antihair.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 "SkScan.h"
19 #include "SkBlitter.h"
20 #include "SkColorPriv.h"
21 #include "SkRegion.h"
22 #include "SkFDot6.h"
23
24 /* Our attempt to compute the worst case "bounds" for the horizontal and
25 vertical cases has some numerical bug in it, and we sometimes undervalue
26 our extends. The bug is that when this happens, we will set the clip to
27 NULL (for speed), and thus draw outside of the clip by a pixel, which might
28 only look bad, but it might also access memory outside of the valid range
29 allcoated for the device bitmap.
30
31 This define enables our fix to outset our "bounds" by 1, thus avoiding the
32 chance of the bug, but at the cost of sometimes taking the rectblitter
33 case (i.e. not setting the clip to NULL) when we might not actually need
34 to. If we can improve/fix the actual calculations, then we can remove this
35 step.
36 */
37 #define OUTSET_BEFORE_CLIP_TEST true
38
39 #define HLINE_STACK_BUFFER 100
40
SmallDot6Scale(int value,int dot6)41 static inline int SmallDot6Scale(int value, int dot6) {
42 SkASSERT((int16_t)value == value);
43 SkASSERT((unsigned)dot6 <= 64);
44 return SkMulS16(value, dot6) >> 6;
45 }
46
47 //#define TEST_GAMMA
48
49 #ifdef TEST_GAMMA
50 static uint8_t gGammaTable[256];
51 #define ApplyGamma(table, alpha) (table)[alpha]
52
build_gamma_table()53 static void build_gamma_table()
54 {
55 static bool gInit = false;
56
57 if (gInit == false)
58 {
59 for (int i = 0; i < 256; i++)
60 {
61 SkFixed n = i * 257;
62 n += n >> 15;
63 SkASSERT(n >= 0 && n <= SK_Fixed1);
64 n = SkFixedSqrt(n);
65 n = n * 255 >> 16;
66 // SkDebugf("morph %d -> %d\n", i, n);
67 gGammaTable[i] = SkToU8(n);
68 }
69 gInit = true;
70 }
71 }
72 #else
73 #define ApplyGamma(table, alpha) SkToU8(alpha)
74 #endif
75
76 ///////////////////////////////////////////////////////////////////////////////
77
call_hline_blitter(SkBlitter * blitter,int x,int y,int count,U8CPU alpha)78 static void call_hline_blitter(SkBlitter* blitter, int x, int y, int count, U8CPU alpha)
79 {
80 SkASSERT(count > 0);
81
82 int16_t runs[HLINE_STACK_BUFFER + 1];
83 uint8_t aa[HLINE_STACK_BUFFER];
84
85 aa[0] = ApplyGamma(gGammaTable, alpha);
86 do {
87 int n = count;
88 if (n > HLINE_STACK_BUFFER)
89 n = HLINE_STACK_BUFFER;
90
91 runs[0] = SkToS16(n);
92 runs[n] = 0;
93 blitter->blitAntiH(x, y, aa, runs);
94 x += n;
95 count -= n;
96 } while (count > 0);
97 }
98
hline(int x,int stopx,SkFixed fy,SkFixed,SkBlitter * blitter,int mod64)99 static SkFixed hline(int x, int stopx, SkFixed fy, SkFixed /*slope*/, SkBlitter* blitter, int mod64)
100 {
101 SkASSERT(x < stopx);
102 int count = stopx - x;
103 fy += SK_Fixed1/2;
104
105 int y = fy >> 16;
106 uint8_t a = (uint8_t)(fy >> 8);
107
108 // lower line
109 unsigned ma = SmallDot6Scale(a, mod64);
110 if (ma) {
111 call_hline_blitter(blitter, x, y, count, ma);
112 }
113
114 // upper line
115 ma = SmallDot6Scale(255 - a, mod64);
116 if (ma) {
117 call_hline_blitter(blitter, x, y - 1, count, ma);
118 }
119
120 return fy - SK_Fixed1/2;
121 }
122
horish(int x,int stopx,SkFixed fy,SkFixed dy,SkBlitter * blitter,int mod64)123 static SkFixed horish(int x, int stopx, SkFixed fy, SkFixed dy, SkBlitter* blitter, int mod64)
124 {
125 SkASSERT(x < stopx);
126
127 #ifdef TEST_GAMMA
128 const uint8_t* gamma = gGammaTable;
129 #endif
130 int16_t runs[2];
131 uint8_t aa[1];
132
133 runs[0] = 1;
134 runs[1] = 0;
135
136 fy += SK_Fixed1/2;
137 do {
138 int lower_y = fy >> 16;
139 uint8_t a = (uint8_t)(fy >> 8);
140 unsigned ma = SmallDot6Scale(a, mod64);
141 if (ma)
142 {
143 aa[0] = ApplyGamma(gamma, ma);
144 blitter->blitAntiH(x, lower_y, aa, runs);
145 // the clipping blitters might edit runs, but should not affect us
146 SkASSERT(runs[0] == 1);
147 SkASSERT(runs[1] == 0);
148 }
149 ma = SmallDot6Scale(255 - a, mod64);
150 if (ma)
151 {
152 aa[0] = ApplyGamma(gamma, ma);
153 blitter->blitAntiH(x, lower_y - 1, aa, runs);
154 // the clipping blitters might edit runs, but should not affect us
155 SkASSERT(runs[0] == 1);
156 SkASSERT(runs[1] == 0);
157 }
158 fy += dy;
159 } while (++x < stopx);
160
161 return fy - SK_Fixed1/2;
162 }
163
vline(int y,int stopy,SkFixed fx,SkFixed,SkBlitter * blitter,int mod64)164 static SkFixed vline(int y, int stopy, SkFixed fx, SkFixed /*slope*/, SkBlitter* blitter, int mod64)
165 {
166 SkASSERT(y < stopy);
167 fx += SK_Fixed1/2;
168
169 int x = fx >> 16;
170 int a = (uint8_t)(fx >> 8);
171
172 unsigned ma = SmallDot6Scale(a, mod64);
173 if (ma)
174 blitter->blitV(x, y, stopy - y, ApplyGamma(gGammaTable, ma));
175 ma = SmallDot6Scale(255 - a, mod64);
176 if (ma)
177 blitter->blitV(x - 1, y, stopy - y, ApplyGamma(gGammaTable, ma));
178
179 return fx - SK_Fixed1/2;
180 }
181
vertish(int y,int stopy,SkFixed fx,SkFixed dx,SkBlitter * blitter,int mod64)182 static SkFixed vertish(int y, int stopy, SkFixed fx, SkFixed dx, SkBlitter* blitter, int mod64)
183 {
184 SkASSERT(y < stopy);
185 #ifdef TEST_GAMMA
186 const uint8_t* gamma = gGammaTable;
187 #endif
188 int16_t runs[3];
189 uint8_t aa[2];
190
191 runs[0] = 1;
192 runs[2] = 0;
193
194 fx += SK_Fixed1/2;
195 do {
196 int x = fx >> 16;
197 uint8_t a = (uint8_t)(fx >> 8);
198
199 aa[0] = ApplyGamma(gamma, SmallDot6Scale(255 - a, mod64));
200 aa[1] = ApplyGamma(gamma, SmallDot6Scale(a, mod64));
201 // the clippng blitters might overwrite this guy, so we have to reset it each time
202 runs[1] = 1;
203 blitter->blitAntiH(x - 1, y, aa, runs);
204 // the clipping blitters might edit runs, but should not affect us
205 SkASSERT(runs[0] == 1);
206 SkASSERT(runs[2] == 0);
207 fx += dx;
208 } while (++y < stopy);
209
210 return fx - SK_Fixed1/2;
211 }
212
213 typedef SkFixed (*LineProc)(int istart, int istop, SkFixed fstart, SkFixed slope, SkBlitter*, int);
214
fastfixdiv(SkFDot6 a,SkFDot6 b)215 static inline SkFixed fastfixdiv(SkFDot6 a, SkFDot6 b)
216 {
217 SkASSERT((a << 16 >> 16) == a);
218 SkASSERT(b != 0);
219 return (a << 16) / b;
220 }
221
do_anti_hairline(SkFDot6 x0,SkFDot6 y0,SkFDot6 x1,SkFDot6 y1,const SkIRect * clip,SkBlitter * blitter)222 static void do_anti_hairline(SkFDot6 x0, SkFDot6 y0, SkFDot6 x1, SkFDot6 y1,
223 const SkIRect* clip, SkBlitter* blitter)
224 {
225 // check that we're no larger than 511 pixels (so we can do a faster div).
226 // if we are, subdivide and call again
227
228 if (SkAbs32(x1 - x0) > SkIntToFDot6(511) || SkAbs32(y1 - y0) > SkIntToFDot6(511))
229 {
230 /* instead of (x0 + x1) >> 1, we shift each separately. This is less
231 precise, but avoids overflowing the intermediate result if the
232 values are huge. A better fix might be to clip the original pts
233 directly (i.e. do the divide), so we don't spend time subdividing
234 huge lines at all.
235 */
236 int hx = (x0 >> 1) + (x1 >> 1);
237 int hy = (y0 >> 1) + (y1 >> 1);
238 do_anti_hairline(x0, y0, hx, hy, clip, blitter);
239 do_anti_hairline(hx, hy, x1, y1, clip, blitter);
240 return;
241 }
242
243 int scaleStart, scaleStop;
244 int istart, istop;
245 SkFixed fstart, slope;
246 LineProc proc;
247
248 if (SkAbs32(x1 - x0) > SkAbs32(y1 - y0)) // mostly horizontal
249 {
250 if (x0 > x1) { // we want to go left-to-right
251 SkTSwap<SkFDot6>(x0, x1);
252 SkTSwap<SkFDot6>(y0, y1);
253 }
254
255 istart = SkFDot6Floor(x0);
256 istop = SkFDot6Ceil(x1);
257 fstart = SkFDot6ToFixed(y0);
258 if (y0 == y1) { // completely horizontal, take fast case
259 slope = 0;
260 proc = hline;
261 } else {
262 slope = fastfixdiv(y1 - y0, x1 - x0);
263 SkASSERT(slope >= -SK_Fixed1 && slope <= SK_Fixed1);
264 fstart += (slope * (32 - (x0 & 63)) + 32) >> 6;
265 proc = horish;
266 }
267
268 SkASSERT(istop > istart);
269 if (istop - istart == 1) {
270 scaleStart = x1 - x0;
271 SkASSERT(scaleStart >= 0 && scaleStart <= 64);
272 scaleStop = 0;
273 } else {
274 scaleStart = 64 - (x0 & 63);
275 scaleStop = x1 & 63;
276 }
277
278 if (clip)
279 {
280 if (istart >= clip->fRight || istop <= clip->fLeft)
281 return;
282 if (istart < clip->fLeft)
283 {
284 fstart += slope * (clip->fLeft - istart);
285 istart = clip->fLeft;
286 scaleStart = 64;
287 }
288 if (istop > clip->fRight) {
289 istop = clip->fRight;
290 scaleStop = 64;
291 }
292 SkASSERT(istart <= istop);
293 if (istart == istop)
294 return;
295
296 // now test if our Y values are completely inside the clip
297 int top, bottom;
298 if (slope >= 0) // T2B
299 {
300 top = SkFixedFloor(fstart - SK_FixedHalf);
301 bottom = SkFixedCeil(fstart + (istop - istart - 1) * slope + SK_FixedHalf);
302 }
303 else // B2T
304 {
305 bottom = SkFixedCeil(fstart + SK_FixedHalf);
306 top = SkFixedFloor(fstart + (istop - istart - 1) * slope - SK_FixedHalf);
307 }
308 #ifdef OUTSET_BEFORE_CLIP_TEST
309 top -= 1;
310 bottom += 1;
311 #endif
312 if (top >= clip->fBottom || bottom <= clip->fTop)
313 return;
314 if (clip->fTop <= top && clip->fBottom >= bottom)
315 clip = NULL;
316 }
317 }
318 else // mostly vertical
319 {
320 if (y0 > y1) // we want to go top-to-bottom
321 {
322 SkTSwap<SkFDot6>(x0, x1);
323 SkTSwap<SkFDot6>(y0, y1);
324 }
325
326 istart = SkFDot6Floor(y0);
327 istop = SkFDot6Ceil(y1);
328 fstart = SkFDot6ToFixed(x0);
329 if (x0 == x1)
330 {
331 if (y0 == y1) { // are we zero length?
332 return; // nothing to do
333 }
334 slope = 0;
335 proc = vline;
336 }
337 else
338 {
339 slope = fastfixdiv(x1 - x0, y1 - y0);
340 SkASSERT(slope <= SK_Fixed1 && slope >= -SK_Fixed1);
341 fstart += (slope * (32 - (y0 & 63)) + 32) >> 6;
342 proc = vertish;
343 }
344
345 SkASSERT(istop > istart);
346 if (istop - istart == 1) {
347 scaleStart = y1 - y0;
348 SkASSERT(scaleStart >= 0 && scaleStart <= 64);
349 scaleStop = 0;
350 } else {
351 scaleStart = 64 - (y0 & 63);
352 scaleStop = y1 & 63;
353 }
354
355 if (clip)
356 {
357 if (istart >= clip->fBottom || istop <= clip->fTop)
358 return;
359 if (istart < clip->fTop)
360 {
361 fstart += slope * (clip->fTop - istart);
362 istart = clip->fTop;
363 scaleStart = 64;
364 }
365 if (istop > clip->fBottom) {
366 istop = clip->fBottom;
367 scaleStop = 64;
368 }
369 SkASSERT(istart <= istop);
370 if (istart == istop)
371 return;
372
373 // now test if our X values are completely inside the clip
374 int left, right;
375 if (slope >= 0) // L2R
376 {
377 left = SkFixedFloor(fstart - SK_FixedHalf);
378 right = SkFixedCeil(fstart + (istop - istart - 1) * slope + SK_FixedHalf);
379 }
380 else // R2L
381 {
382 right = SkFixedCeil(fstart + SK_FixedHalf);
383 left = SkFixedFloor(fstart + (istop - istart - 1) * slope - SK_FixedHalf);
384 }
385 #ifdef OUTSET_BEFORE_CLIP_TEST
386 left -= 1;
387 right += 1;
388 #endif
389 if (left >= clip->fRight || right <= clip->fLeft)
390 return;
391 if (clip->fLeft <= left && clip->fRight >= right)
392 clip = NULL;
393 }
394 }
395
396 SkRectClipBlitter rectClipper;
397 if (clip)
398 {
399 rectClipper.init(blitter, *clip);
400 blitter = &rectClipper;
401 }
402
403 fstart = proc(istart, istart + 1, fstart, slope, blitter, scaleStart);
404 istart += 1;
405 int fullSpans = istop - istart - (scaleStop > 0);
406 if (fullSpans > 0) {
407 fstart = proc(istart, istart + fullSpans, fstart, slope, blitter, 64);
408 }
409 if (scaleStop > 0) {
410 proc(istop - 1, istop, fstart, slope, blitter, scaleStop);
411 }
412 }
413
AntiHairLine(const SkPoint & pt0,const SkPoint & pt1,const SkRegion * clip,SkBlitter * blitter)414 void SkScan::AntiHairLine(const SkPoint& pt0, const SkPoint& pt1,
415 const SkRegion* clip, SkBlitter* blitter)
416 {
417 if (clip && clip->isEmpty())
418 return;
419
420 SkASSERT(clip == NULL || !clip->getBounds().isEmpty());
421
422 #ifdef TEST_GAMMA
423 build_gamma_table();
424 #endif
425
426 SkFDot6 x0 = SkScalarToFDot6(pt0.fX);
427 SkFDot6 y0 = SkScalarToFDot6(pt0.fY);
428 SkFDot6 x1 = SkScalarToFDot6(pt1.fX);
429 SkFDot6 y1 = SkScalarToFDot6(pt1.fY);
430
431 if (clip)
432 {
433 SkFDot6 left = SkMin32(x0, x1);
434 SkFDot6 top = SkMin32(y0, y1);
435 SkFDot6 right = SkMax32(x0, x1);
436 SkFDot6 bottom = SkMax32(y0, y1);
437 SkIRect ir;
438
439 ir.set( SkFDot6Floor(left) - 1,
440 SkFDot6Floor(top) - 1,
441 SkFDot6Ceil(right) + 1,
442 SkFDot6Ceil(bottom) + 1);
443
444 if (clip->quickReject(ir))
445 return;
446 if (!clip->quickContains(ir))
447 {
448 SkRegion::Cliperator iter(*clip, ir);
449 const SkIRect* r = &iter.rect();
450
451 while (!iter.done())
452 {
453 do_anti_hairline(x0, y0, x1, y1, r, blitter);
454 iter.next();
455 }
456 return;
457 }
458 // fall through to no-clip case
459 }
460 do_anti_hairline(x0, y0, x1, y1, NULL, blitter);
461 }
462
AntiHairRect(const SkRect & rect,const SkRegion * clip,SkBlitter * blitter)463 void SkScan::AntiHairRect(const SkRect& rect, const SkRegion* clip, SkBlitter* blitter)
464 {
465 if (clip)
466 {
467 SkIRect ir;
468 SkRect r = rect;
469
470 r.inset(-SK_Scalar1/2, -SK_Scalar1/2);
471 r.roundOut(&ir);
472 if (clip->quickReject(ir))
473 return;
474 if (clip->quickContains(ir))
475 clip = NULL;
476 }
477
478 SkPoint p0, p1;
479
480 p0.set(rect.fLeft, rect.fTop);
481 p1.set(rect.fRight, rect.fTop);
482 SkScan::AntiHairLine(p0, p1, clip, blitter);
483 p0.set(rect.fRight, rect.fBottom);
484 SkScan::AntiHairLine(p0, p1, clip, blitter);
485 p1.set(rect.fLeft, rect.fBottom);
486 SkScan::AntiHairLine(p0, p1, clip, blitter);
487 p0.set(rect.fLeft, rect.fTop);
488 SkScan::AntiHairLine(p0, p1, clip, blitter);
489 }
490
491 //////////////////////////////////////////////////////////////////////////////////////////
492
493 typedef int FDot8; // 24.8 integer fixed point
494
SkFixedToFDot8(SkFixed x)495 static inline FDot8 SkFixedToFDot8(SkFixed x) {
496 return (x + 0x80) >> 8;
497 }
498
do_scanline(FDot8 L,int top,FDot8 R,U8CPU alpha,SkBlitter * blitter)499 static void do_scanline(FDot8 L, int top, FDot8 R, U8CPU alpha, SkBlitter* blitter)
500 {
501 SkASSERT(L < R);
502
503 if ((L >> 8) == ((R - 1) >> 8)) // 1x1 pixel
504 {
505 blitter->blitV(L >> 8, top, 1, SkAlphaMul(alpha, R - L));
506 return;
507 }
508
509 int left = L >> 8;
510
511 if (L & 0xFF)
512 {
513 blitter->blitV(left, top, 1, SkAlphaMul(alpha, 256 - (L & 0xFF)));
514 left += 1;
515 }
516
517 int rite = R >> 8;
518 int width = rite - left;
519 if (width > 0)
520 call_hline_blitter(blitter, left, top, width, alpha);
521
522 if (R & 0xFF)
523 blitter->blitV(rite, top, 1, SkAlphaMul(alpha, R & 0xFF));
524 }
525
antifillrect(const SkXRect & xr,SkBlitter * blitter)526 static void antifillrect(const SkXRect& xr, SkBlitter* blitter)
527 {
528 FDot8 L = SkFixedToFDot8(xr.fLeft);
529 FDot8 T = SkFixedToFDot8(xr.fTop);
530 FDot8 R = SkFixedToFDot8(xr.fRight);
531 FDot8 B = SkFixedToFDot8(xr.fBottom);
532
533 // check for empty now that we're in our reduced precision space
534 if (L >= R || T >= B)
535 return;
536
537 int top = T >> 8;
538 if (top == ((B - 1) >> 8)) // just one scanline high
539 {
540 do_scanline(L, top, R, B - T - 1, blitter);
541 return;
542 }
543
544 if (T & 0xFF)
545 {
546 do_scanline(L, top, R, 256 - (T & 0xFF), blitter);
547 top += 1;
548 }
549
550 int bot = B >> 8;
551 int height = bot - top;
552 if (height > 0)
553 {
554 int left = L >> 8;
555 if (L & 0xFF)
556 {
557 blitter->blitV(left, top, height, 256 - (L & 0xFF));
558 left += 1;
559 }
560 int rite = R >> 8;
561 int width = rite - left;
562 if (width > 0)
563 blitter->blitRect(left, top, width, height);
564 if (R & 0xFF)
565 blitter->blitV(rite, top, height, R & 0xFF);
566 }
567
568 if (B & 0xFF)
569 do_scanline(L, bot, R, B & 0xFF, blitter);
570 }
571
572 ///////////////////////////////////////////////////////////////////////////////
573
AntiFillXRect(const SkXRect & xr,const SkRegion * clip,SkBlitter * blitter)574 void SkScan::AntiFillXRect(const SkXRect& xr, const SkRegion* clip,
575 SkBlitter* blitter) {
576 if (clip) {
577 SkIRect outerBounds;
578 XRect_roundOut(xr, &outerBounds);
579
580 if (clip->isRect()) {
581 const SkIRect& clipBounds = clip->getBounds();
582
583 if (clipBounds.contains(outerBounds)) {
584 antifillrect(xr, blitter);
585 } else {
586 SkXRect tmpR;
587 // this keeps our original edges fractional
588 XRect_set(&tmpR, clipBounds);
589 if (tmpR.intersect(xr)) {
590 antifillrect(tmpR, blitter);
591 }
592 }
593 } else {
594 SkRegion::Cliperator clipper(*clip, outerBounds);
595 const SkIRect& rr = clipper.rect();
596
597 while (!clipper.done()) {
598 SkXRect tmpR;
599
600 // this keeps our original edges fractional
601 XRect_set(&tmpR, rr);
602 if (tmpR.intersect(xr)) {
603 antifillrect(tmpR, blitter);
604 }
605 clipper.next();
606 }
607 }
608 } else {
609 antifillrect(xr, blitter);
610 }
611 }
612
613 #ifdef SK_SCALAR_IS_FLOAT
614
615 /* This guy takes a float-rect, but with the key improvement that it has
616 already been clipped, so we know that it is safe to convert it into a
617 XRect (fixedpoint), as it won't overflow.
618 */
antifillrect(const SkRect & r,SkBlitter * blitter)619 static void antifillrect(const SkRect& r, SkBlitter* blitter) {
620 SkXRect xr;
621
622 XRect_set(&xr, r);
623 antifillrect(xr, blitter);
624 }
625
626 /* We repeat the clipping logic of AntiFillXRect because the float rect might
627 overflow if we blindly converted it to an XRect. This sucks that we have to
628 repeat the clipping logic, but I don't see how to share the code/logic.
629
630 We clip r (as needed) into one or more (smaller) float rects, and then pass
631 those to our version of antifillrect, which converts it into an XRect and
632 then calls the blit.
633 */
AntiFillRect(const SkRect & r,const SkRegion * clip,SkBlitter * blitter)634 void SkScan::AntiFillRect(const SkRect& r, const SkRegion* clip,
635 SkBlitter* blitter) {
636 if (clip) {
637 SkIRect outerBounds;
638 r.roundOut(&outerBounds);
639
640 if (clip->isRect()) {
641 const SkIRect& clipBounds = clip->getBounds();
642
643 if (clipBounds.contains(outerBounds)) {
644 antifillrect(r, blitter);
645 } else {
646 SkRect tmpR;
647 // this keeps our original edges fractional
648 tmpR.set(clipBounds);
649 if (tmpR.intersect(r)) {
650 antifillrect(tmpR, blitter);
651 }
652 }
653 } else {
654 SkRegion::Cliperator clipper(*clip, outerBounds);
655 const SkIRect& rr = clipper.rect();
656
657 while (!clipper.done()) {
658 SkRect tmpR;
659 // this keeps our original edges fractional
660 tmpR.set(rr);
661 if (tmpR.intersect(r)) {
662 antifillrect(tmpR, blitter);
663 }
664 clipper.next();
665 }
666 }
667 } else {
668 antifillrect(r, blitter);
669 }
670 }
671
672 #endif
673
674
675