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