Home | History | Annotate | Download | only in core
      1 /*
      2  * Copyright 2006 The Android Open Source Project
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "SkScan.h"
      9 #include "SkBlitter.h"
     10 #include "SkRasterClip.h"
     11 #include "SkFDot6.h"
     12 #include "SkLineClipper.h"
     13 
     14 static void horiline(int x, int stopx, SkFixed fy, SkFixed dy,
     15                      SkBlitter* blitter) {
     16     SkASSERT(x < stopx);
     17 
     18     do {
     19         blitter->blitH(x, fy >> 16, 1);
     20         fy += dy;
     21     } while (++x < stopx);
     22 }
     23 
     24 static void vertline(int y, int stopy, SkFixed fx, SkFixed dx,
     25                      SkBlitter* blitter) {
     26     SkASSERT(y < stopy);
     27 
     28     do {
     29         blitter->blitH(fx >> 16, y, 1);
     30         fx += dx;
     31     } while (++y < stopy);
     32 }
     33 
     34 #ifdef SK_DEBUG
     35 static bool canConvertFDot6ToFixed(SkFDot6 x) {
     36     const int maxDot6 = SK_MaxS32 >> (16 - 6);
     37     return SkAbs32(x) <= maxDot6;
     38 }
     39 #endif
     40 
     41 void SkScan::HairLineRgn(const SkPoint array[], int arrayCount, const SkRegion* clip,
     42                          SkBlitter* origBlitter) {
     43     SkBlitterClipper    clipper;
     44     SkIRect clipR, ptsR;
     45 
     46     const SkScalar max = SkIntToScalar(32767);
     47     const SkRect fixedBounds = SkRect::MakeLTRB(-max, -max, max, max);
     48 
     49     SkRect clipBounds;
     50     if (clip) {
     51         clipBounds.set(clip->getBounds());
     52     }
     53 
     54     for (int i = 0; i < arrayCount - 1; ++i) {
     55         SkBlitter* blitter = origBlitter;
     56 
     57         SkPoint pts[2];
     58 
     59         // We have to pre-clip the line to fit in a SkFixed, so we just chop
     60         // the line. TODO find a way to actually draw beyond that range.
     61         if (!SkLineClipper::IntersectLine(&array[i], fixedBounds, pts)) {
     62             continue;
     63         }
     64 
     65         // Perform a clip in scalar space, so we catch huge values which might
     66         // be missed after we convert to SkFDot6 (overflow)
     67         if (clip && !SkLineClipper::IntersectLine(pts, clipBounds, pts)) {
     68             continue;
     69         }
     70 
     71         SkFDot6 x0 = SkScalarToFDot6(pts[0].fX);
     72         SkFDot6 y0 = SkScalarToFDot6(pts[0].fY);
     73         SkFDot6 x1 = SkScalarToFDot6(pts[1].fX);
     74         SkFDot6 y1 = SkScalarToFDot6(pts[1].fY);
     75 
     76         SkASSERT(canConvertFDot6ToFixed(x0));
     77         SkASSERT(canConvertFDot6ToFixed(y0));
     78         SkASSERT(canConvertFDot6ToFixed(x1));
     79         SkASSERT(canConvertFDot6ToFixed(y1));
     80 
     81         if (clip) {
     82             // now perform clipping again, as the rounding to dot6 can wiggle us
     83             // our rects are really dot6 rects, but since we've already used
     84             // lineclipper, we know they will fit in 32bits (26.6)
     85             const SkIRect& bounds = clip->getBounds();
     86 
     87             clipR.set(SkIntToFDot6(bounds.fLeft), SkIntToFDot6(bounds.fTop),
     88                       SkIntToFDot6(bounds.fRight), SkIntToFDot6(bounds.fBottom));
     89             ptsR.set(x0, y0, x1, y1);
     90             ptsR.sort();
     91 
     92             // outset the right and bottom, to account for how hairlines are
     93             // actually drawn, which may hit the pixel to the right or below of
     94             // the coordinate
     95             ptsR.fRight += SK_FDot6One;
     96             ptsR.fBottom += SK_FDot6One;
     97 
     98             if (!SkIRect::Intersects(ptsR, clipR)) {
     99                 continue;
    100             }
    101             if (!clip->isRect() || !clipR.contains(ptsR)) {
    102                 blitter = clipper.apply(origBlitter, clip);
    103             }
    104         }
    105 
    106         SkFDot6 dx = x1 - x0;
    107         SkFDot6 dy = y1 - y0;
    108 
    109         if (SkAbs32(dx) > SkAbs32(dy)) { // mostly horizontal
    110             if (x0 > x1) {   // we want to go left-to-right
    111                 SkTSwap<SkFDot6>(x0, x1);
    112                 SkTSwap<SkFDot6>(y0, y1);
    113             }
    114             int ix0 = SkFDot6Round(x0);
    115             int ix1 = SkFDot6Round(x1);
    116             if (ix0 == ix1) {// too short to draw
    117                 continue;
    118             }
    119 
    120             SkFixed slope = SkFixedDiv(dy, dx);
    121             SkFixed startY = SkFDot6ToFixed(y0) + (slope * ((32 - x0) & 63) >> 6);
    122 
    123             horiline(ix0, ix1, startY, slope, blitter);
    124         } else {              // mostly vertical
    125             if (y0 > y1) {   // we want to go top-to-bottom
    126                 SkTSwap<SkFDot6>(x0, x1);
    127                 SkTSwap<SkFDot6>(y0, y1);
    128             }
    129             int iy0 = SkFDot6Round(y0);
    130             int iy1 = SkFDot6Round(y1);
    131             if (iy0 == iy1) { // too short to draw
    132                 continue;
    133             }
    134 
    135             SkFixed slope = SkFixedDiv(dx, dy);
    136             SkFixed startX = SkFDot6ToFixed(x0) + (slope * ((32 - y0) & 63) >> 6);
    137 
    138             vertline(iy0, iy1, startX, slope, blitter);
    139         }
    140     }
    141 }
    142 
    143 // we don't just draw 4 lines, 'cause that can leave a gap in the bottom-right
    144 // and double-hit the top-left.
    145 // TODO: handle huge coordinates on rect (before calling SkScalarToFixed)
    146 void SkScan::HairRect(const SkRect& rect, const SkRasterClip& clip,
    147                       SkBlitter* blitter) {
    148     SkAAClipBlitterWrapper wrapper;
    149     SkBlitterClipper    clipper;
    150     SkIRect             r;
    151 
    152     r.set(SkScalarToFixed(rect.fLeft) >> 16,
    153           SkScalarToFixed(rect.fTop) >> 16,
    154           (SkScalarToFixed(rect.fRight) >> 16) + 1,
    155           (SkScalarToFixed(rect.fBottom) >> 16) + 1);
    156 
    157     if (clip.quickReject(r)) {
    158         return;
    159     }
    160     if (!clip.quickContains(r)) {
    161         const SkRegion* clipRgn;
    162         if (clip.isBW()) {
    163             clipRgn = &clip.bwRgn();
    164         } else {
    165             wrapper.init(clip, blitter);
    166             clipRgn = &wrapper.getRgn();
    167             blitter = wrapper.getBlitter();
    168         }
    169         blitter = clipper.apply(blitter, clipRgn);
    170     }
    171 
    172     int width = r.width();
    173     int height = r.height();
    174 
    175     if ((width | height) == 0) {
    176         return;
    177     }
    178     if (width <= 2 || height <= 2) {
    179         blitter->blitRect(r.fLeft, r.fTop, width, height);
    180         return;
    181     }
    182     // if we get here, we know we have 4 segments to draw
    183     blitter->blitH(r.fLeft, r.fTop, width);                     // top
    184     blitter->blitRect(r.fLeft, r.fTop + 1, 1, height - 2);      // left
    185     blitter->blitRect(r.fRight - 1, r.fTop + 1, 1, height - 2); // right
    186     blitter->blitH(r.fLeft, r.fBottom - 1, width);              // bottom
    187 }
    188 
    189 ///////////////////////////////////////////////////////////////////////////////
    190 
    191 #include "SkPath.h"
    192 #include "SkGeometry.h"
    193 #include "SkNx.h"
    194 
    195 #define kMaxCubicSubdivideLevel 9
    196 #define kMaxQuadSubdivideLevel  5
    197 
    198 static int compute_int_quad_dist(const SkPoint pts[3]) {
    199     // compute the vector between the control point ([1]) and the middle of the
    200     // line connecting the start and end ([0] and [2])
    201     SkScalar dx = SkScalarHalf(pts[0].fX + pts[2].fX) - pts[1].fX;
    202     SkScalar dy = SkScalarHalf(pts[0].fY + pts[2].fY) - pts[1].fY;
    203     // we want everyone to be positive
    204     dx = SkScalarAbs(dx);
    205     dy = SkScalarAbs(dy);
    206     // convert to whole pixel values (use ceiling to be conservative)
    207     int idx = SkScalarCeilToInt(dx);
    208     int idy = SkScalarCeilToInt(dy);
    209     // use the cheap approx for distance
    210     if (idx > idy) {
    211         return idx + (idy >> 1);
    212     } else {
    213         return idy + (idx >> 1);
    214     }
    215 }
    216 
    217 static void hairquad(const SkPoint pts[3], const SkRegion* clip,
    218                      SkBlitter* blitter, int level, SkScan::HairRgnProc lineproc) {
    219     SkASSERT(level <= kMaxQuadSubdivideLevel);
    220 
    221     SkQuadCoeff coeff(pts);
    222 
    223     const int lines = 1 << level;
    224     Sk2s t(0);
    225     Sk2s dt(SK_Scalar1 / lines);
    226 
    227     SkPoint tmp[(1 << kMaxQuadSubdivideLevel) + 1];
    228     SkASSERT((unsigned)lines < SK_ARRAY_COUNT(tmp));
    229 
    230     tmp[0] = pts[0];
    231     Sk2s A = coeff.fA;
    232     Sk2s B = coeff.fB;
    233     Sk2s C = coeff.fC;
    234     for (int i = 1; i < lines; ++i) {
    235         t = t + dt;
    236         ((A * t + B) * t + C).store(&tmp[i]);
    237     }
    238     tmp[lines] = pts[2];
    239     lineproc(tmp, lines + 1, clip, blitter);
    240 }
    241 
    242 static inline Sk2s abs(const Sk2s& value) {
    243     return Sk2s::Max(value, Sk2s(0)-value);
    244 }
    245 
    246 static inline SkScalar max_component(const Sk2s& value) {
    247     SkScalar components[2];
    248     value.store(components);
    249     return SkTMax(components[0], components[1]);
    250 }
    251 
    252 static inline int compute_cubic_segs(const SkPoint pts[4]) {
    253     Sk2s p0 = from_point(pts[0]);
    254     Sk2s p1 = from_point(pts[1]);
    255     Sk2s p2 = from_point(pts[2]);
    256     Sk2s p3 = from_point(pts[3]);
    257 
    258     const Sk2s oneThird(1.0f / 3.0f);
    259     const Sk2s twoThird(2.0f / 3.0f);
    260 
    261     Sk2s p13 = oneThird * p3 + twoThird * p0;
    262     Sk2s p23 = oneThird * p0 + twoThird * p3;
    263 
    264     SkScalar diff = max_component(Sk2s::Max(abs(p1 - p13), abs(p2 - p23)));
    265     SkScalar tol = SK_Scalar1 / 8;
    266 
    267     for (int i = 0; i < kMaxCubicSubdivideLevel; ++i) {
    268         if (diff < tol) {
    269             return 1 << i;
    270         }
    271         tol *= 4;
    272     }
    273     return 1 << kMaxCubicSubdivideLevel;
    274 }
    275 
    276 static bool lt_90(SkPoint p0, SkPoint pivot, SkPoint p2) {
    277     return SkVector::DotProduct(p0 - pivot, p2 - pivot) >= 0;
    278 }
    279 
    280 // The off-curve points are "inside" the limits of the on-curve pts
    281 static bool quick_cubic_niceness_check(const SkPoint pts[4]) {
    282     return lt_90(pts[1], pts[0], pts[3]) &&
    283            lt_90(pts[2], pts[0], pts[3]) &&
    284            lt_90(pts[1], pts[3], pts[0]) &&
    285            lt_90(pts[2], pts[3], pts[0]);
    286 }
    287 
    288 static void hair_cubic(const SkPoint pts[4], const SkRegion* clip, SkBlitter* blitter,
    289                        SkScan::HairRgnProc lineproc) {
    290     const int lines = compute_cubic_segs(pts);
    291     SkASSERT(lines > 0);
    292     if (1 == lines) {
    293         SkPoint tmp[2] = { pts[0], pts[3] };
    294         lineproc(tmp, 2, clip, blitter);
    295         return;
    296     }
    297 
    298     SkCubicCoeff coeff(pts);
    299 
    300     const Sk2s dt(SK_Scalar1 / lines);
    301     Sk2s t(0);
    302 
    303     SkPoint tmp[(1 << kMaxCubicSubdivideLevel) + 1];
    304     SkASSERT((unsigned)lines < SK_ARRAY_COUNT(tmp));
    305 
    306     tmp[0] = pts[0];
    307     Sk2s A = coeff.fA;
    308     Sk2s B = coeff.fB;
    309     Sk2s C = coeff.fC;
    310     Sk2s D = coeff.fD;
    311     for (int i = 1; i < lines; ++i) {
    312         t = t + dt;
    313         (((A * t + B) * t + C) * t + D).store(&tmp[i]);
    314     }
    315     tmp[lines] = pts[3];
    316     lineproc(tmp, lines + 1, clip, blitter);
    317 }
    318 
    319 static SkRect compute_nocheck_cubic_bounds(const SkPoint pts[4]) {
    320     SkASSERT(SkScalarsAreFinite(&pts[0].fX, 8));
    321 
    322     Sk2s min = Sk2s::Load(pts);
    323     Sk2s max = min;
    324     for (int i = 1; i < 4; ++i) {
    325         Sk2s pair = Sk2s::Load(pts+i);
    326         min = Sk2s::Min(min, pair);
    327         max = Sk2s::Max(max, pair);
    328     }
    329     return { min[0], min[1], max[0], max[1] };
    330 }
    331 
    332 static bool is_inverted(const SkRect& r) {
    333     return r.fLeft > r.fRight || r.fTop > r.fBottom;
    334 }
    335 
    336 // Can't call SkRect::intersects, since it cares about empty, and we don't (since we tracking
    337 // something to be stroked, so empty can still draw something (e.g. horizontal line)
    338 static bool geometric_overlap(const SkRect& a, const SkRect& b) {
    339     SkASSERT(!is_inverted(a) && !is_inverted(b));
    340     return a.fLeft < b.fRight && b.fLeft < a.fRight &&
    341            a.fTop < b.fBottom && b.fTop < a.fBottom;
    342 }
    343 
    344 // Can't call SkRect::contains, since it cares about empty, and we don't (since we tracking
    345 // something to be stroked, so empty can still draw something (e.g. horizontal line)
    346 static bool geometric_contains(const SkRect& outer, const SkRect& inner) {
    347     SkASSERT(!is_inverted(outer) && !is_inverted(inner));
    348     return inner.fRight <= outer.fRight && inner.fLeft >= outer.fLeft &&
    349            inner.fBottom <= outer.fBottom && inner.fTop >= outer.fTop;
    350 }
    351 
    352 //#define SK_SHOW_HAIRCLIP_STATS
    353 #ifdef SK_SHOW_HAIRCLIP_STATS
    354 static int gKillClip, gRejectClip, gClipCount;
    355 #endif
    356 
    357 static inline void haircubic(const SkPoint pts[4], const SkRegion* clip, const SkRect* insetClip, const SkRect* outsetClip,
    358                       SkBlitter* blitter, int level, SkScan::HairRgnProc lineproc) {
    359     if (insetClip) {
    360         SkASSERT(outsetClip);
    361 #ifdef SK_SHOW_HAIRCLIP_STATS
    362         gClipCount += 1;
    363 #endif
    364         SkRect bounds = compute_nocheck_cubic_bounds(pts);
    365         if (!geometric_overlap(*outsetClip, bounds)) {
    366 #ifdef SK_SHOW_HAIRCLIP_STATS
    367             gRejectClip += 1;
    368 #endif
    369             return;
    370         } else if (geometric_contains(*insetClip, bounds)) {
    371             clip = nullptr;
    372 #ifdef SK_SHOW_HAIRCLIP_STATS
    373             gKillClip += 1;
    374 #endif
    375         }
    376 #ifdef SK_SHOW_HAIRCLIP_STATS
    377         if (0 == gClipCount % 256)
    378             SkDebugf("kill %g reject %g total %d\n", 1.0*gKillClip / gClipCount, 1.0*gRejectClip/gClipCount, gClipCount);
    379 #endif
    380     }
    381 
    382     if (quick_cubic_niceness_check(pts)) {
    383         hair_cubic(pts, clip, blitter, lineproc);
    384     } else {
    385         SkPoint  tmp[13];
    386         SkScalar tValues[3];
    387 
    388         int count = SkChopCubicAtMaxCurvature(pts, tmp, tValues);
    389         for (int i = 0; i < count; i++) {
    390             hair_cubic(&tmp[i * 3], clip, blitter, lineproc);
    391         }
    392     }
    393 }
    394 
    395 static int compute_quad_level(const SkPoint pts[3]) {
    396     int d = compute_int_quad_dist(pts);
    397     /*  quadratics approach the line connecting their start and end points
    398      4x closer with each subdivision, so we compute the number of
    399      subdivisions to be the minimum need to get that distance to be less
    400      than a pixel.
    401      */
    402     int level = (33 - SkCLZ(d)) >> 1;
    403     // sanity check on level (from the previous version)
    404     if (level > kMaxQuadSubdivideLevel) {
    405         level = kMaxQuadSubdivideLevel;
    406     }
    407     return level;
    408 }
    409 
    410 /* Extend the points in the direction of the starting or ending tangent by 1/2 unit to
    411    account for a round or square cap. If there's no distance between the end point and
    412    the control point, use the next control point to create a tangent. If the curve
    413    is degenerate, move the cap out 1/2 unit horizontally. */
    414 template <SkPaint::Cap capStyle>
    415 void extend_pts(SkPath::Verb prevVerb, SkPath::Verb nextVerb, SkPoint* pts, int ptCount) {
    416     SkASSERT(SkPaint::kSquare_Cap == capStyle || SkPaint::kRound_Cap == capStyle);
    417     // The area of a circle is PI*R*R. For a unit circle, R=1/2, and the cap covers half of that.
    418     const SkScalar capOutset = SkPaint::kSquare_Cap == capStyle ? 0.5f : SK_ScalarPI / 8;
    419     if (SkPath::kMove_Verb == prevVerb) {
    420         SkPoint* first = pts;
    421         SkPoint* ctrl = first;
    422         int controls = ptCount - 1;
    423         SkVector tangent;
    424         do {
    425             tangent = *first - *++ctrl;
    426         } while (tangent.isZero() && --controls > 0);
    427         if (tangent.isZero()) {
    428             tangent.set(1, 0);
    429             controls = ptCount - 1;  // If all points are equal, move all but one
    430         } else {
    431             tangent.normalize();
    432         }
    433         do {    // If the end point and control points are equal, loop to move them in tandem.
    434             first->fX += tangent.fX * capOutset;
    435             first->fY += tangent.fY * capOutset;
    436             ++first;
    437         } while (++controls < ptCount);
    438     }
    439     if (SkPath::kMove_Verb == nextVerb || SkPath::kDone_Verb == nextVerb) {
    440         SkPoint* last = &pts[ptCount - 1];
    441         SkPoint* ctrl = last;
    442         int controls = ptCount - 1;
    443         SkVector tangent;
    444         do {
    445             tangent = *last - *--ctrl;
    446         } while (tangent.isZero() && --controls > 0);
    447         if (tangent.isZero()) {
    448             tangent.set(-1, 0);
    449             controls = ptCount - 1;
    450         } else {
    451             tangent.normalize();
    452         }
    453         do {
    454             last->fX += tangent.fX * capOutset;
    455             last->fY += tangent.fY * capOutset;
    456             --last;
    457         } while (++controls < ptCount);
    458     }
    459 }
    460 
    461 template <SkPaint::Cap capStyle>
    462 void hair_path(const SkPath& path, const SkRasterClip& rclip, SkBlitter* blitter,
    463                       SkScan::HairRgnProc lineproc) {
    464     if (path.isEmpty()) {
    465         return;
    466     }
    467 
    468     SkAAClipBlitterWrapper wrap;
    469     const SkRegion* clip = nullptr;
    470     SkRect insetStorage, outsetStorage;
    471     const SkRect* insetClip = nullptr;
    472     const SkRect* outsetClip = nullptr;
    473 
    474     {
    475         const int capOut = SkPaint::kButt_Cap == capStyle ? 1 : 2;
    476         const SkIRect ibounds = path.getBounds().roundOut().makeOutset(capOut, capOut);
    477         if (rclip.quickReject(ibounds)) {
    478             return;
    479         }
    480         if (!rclip.quickContains(ibounds)) {
    481             if (rclip.isBW()) {
    482                 clip = &rclip.bwRgn();
    483             } else {
    484                 wrap.init(rclip, blitter);
    485                 blitter = wrap.getBlitter();
    486                 clip = &wrap.getRgn();
    487             }
    488 
    489             /*
    490              *  We now cache two scalar rects, to use for culling per-segment (e.g. cubic).
    491              *  Since we're hairlining, the "bounds" of the control points isn't necessairly the
    492              *  limit of where a segment can draw (it might draw up to 1 pixel beyond in aa-hairs).
    493              *
    494              *  Compute the pt-bounds per segment is easy, so we do that, and then inversely adjust
    495              *  the culling bounds so we can just do a straight compare per segment.
    496              *
    497              *  insetClip is use for quick-accept (i.e. the segment is not clipped), so we inset
    498              *  it from the clip-bounds (since segment bounds can be off by 1).
    499              *
    500              *  outsetClip is used for quick-reject (i.e. the segment is entirely outside), so we
    501              *  outset it from the clip-bounds.
    502              */
    503             insetStorage.set(clip->getBounds());
    504             outsetStorage = insetStorage.makeOutset(1, 1);
    505             insetStorage.inset(1, 1);
    506             if (is_inverted(insetStorage)) {
    507                 /*
    508                  *  our bounds checks assume the rects are never inverted. If insetting has
    509                  *  created that, we assume that the area is too small to safely perform a
    510                  *  quick-accept, so we just mark the rect as empty (so the quick-accept check
    511                  *  will always fail.
    512                  */
    513                 insetStorage.setEmpty();    // just so we don't pass an inverted rect
    514             }
    515             insetClip = &insetStorage;
    516             outsetClip = &outsetStorage;
    517         }
    518     }
    519 
    520     SkPath::RawIter     iter(path);
    521     SkPoint             pts[4], firstPt, lastPt;
    522     SkPath::Verb        verb, prevVerb;
    523     SkAutoConicToQuads  converter;
    524 
    525     if (SkPaint::kButt_Cap != capStyle) {
    526         prevVerb = SkPath::kDone_Verb;
    527     }
    528     while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
    529         switch (verb) {
    530             case SkPath::kMove_Verb:
    531                 firstPt = lastPt = pts[0];
    532                 break;
    533             case SkPath::kLine_Verb:
    534                 if (SkPaint::kButt_Cap != capStyle) {
    535                     extend_pts<capStyle>(prevVerb, iter.peek(), pts, 2);
    536                 }
    537                 lineproc(pts, 2, clip, blitter);
    538                 lastPt = pts[1];
    539                 break;
    540             case SkPath::kQuad_Verb:
    541                 if (SkPaint::kButt_Cap != capStyle) {
    542                     extend_pts<capStyle>(prevVerb, iter.peek(), pts, 3);
    543                 }
    544                 hairquad(pts, clip, blitter, compute_quad_level(pts), lineproc);
    545                 lastPt = pts[2];
    546                 break;
    547             case SkPath::kConic_Verb: {
    548                 if (SkPaint::kButt_Cap != capStyle) {
    549                     extend_pts<capStyle>(prevVerb, iter.peek(), pts, 3);
    550                 }
    551                 // how close should the quads be to the original conic?
    552                 const SkScalar tol = SK_Scalar1 / 4;
    553                 const SkPoint* quadPts = converter.computeQuads(pts,
    554                                                        iter.conicWeight(), tol);
    555                 for (int i = 0; i < converter.countQuads(); ++i) {
    556                     int level = compute_quad_level(quadPts);
    557                     hairquad(quadPts, clip, blitter, level, lineproc);
    558                     quadPts += 2;
    559                 }
    560                 lastPt = pts[2];
    561                 break;
    562             }
    563             case SkPath::kCubic_Verb: {
    564                 if (SkPaint::kButt_Cap != capStyle) {
    565                     extend_pts<capStyle>(prevVerb, iter.peek(), pts, 4);
    566                 }
    567                 haircubic(pts, clip, insetClip, outsetClip, blitter, kMaxCubicSubdivideLevel, lineproc);
    568                 lastPt = pts[3];
    569             } break;
    570             case SkPath::kClose_Verb:
    571                 pts[0] = lastPt;
    572                 pts[1] = firstPt;
    573                 if (SkPaint::kButt_Cap != capStyle && prevVerb == SkPath::kMove_Verb) {
    574                     // cap moveTo/close to match svg expectations for degenerate segments
    575                     extend_pts<capStyle>(prevVerb, iter.peek(), pts, 2);
    576                 }
    577                 lineproc(pts, 2, clip, blitter);
    578                 break;
    579             case SkPath::kDone_Verb:
    580                 break;
    581         }
    582         if (SkPaint::kButt_Cap != capStyle) {
    583             prevVerb = verb;
    584         }
    585     }
    586 }
    587 
    588 void SkScan::HairPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) {
    589     hair_path<SkPaint::kButt_Cap>(path, clip, blitter, SkScan::HairLineRgn);
    590 }
    591 
    592 void SkScan::AntiHairPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) {
    593     hair_path<SkPaint::kButt_Cap>(path, clip, blitter, SkScan::AntiHairLineRgn);
    594 }
    595 
    596 void SkScan::HairSquarePath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) {
    597     hair_path<SkPaint::kSquare_Cap>(path, clip, blitter, SkScan::HairLineRgn);
    598 }
    599 
    600 void SkScan::AntiHairSquarePath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) {
    601     hair_path<SkPaint::kSquare_Cap>(path, clip, blitter, SkScan::AntiHairLineRgn);
    602 }
    603 
    604 void SkScan::HairRoundPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) {
    605     hair_path<SkPaint::kRound_Cap>(path, clip, blitter, SkScan::HairLineRgn);
    606 }
    607 
    608 void SkScan::AntiHairRoundPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) {
    609     hair_path<SkPaint::kRound_Cap>(path, clip, blitter, SkScan::AntiHairLineRgn);
    610 }
    611 
    612 ///////////////////////////////////////////////////////////////////////////////
    613 
    614 void SkScan::FrameRect(const SkRect& r, const SkPoint& strokeSize,
    615                        const SkRasterClip& clip, SkBlitter* blitter) {
    616     SkASSERT(strokeSize.fX >= 0 && strokeSize.fY >= 0);
    617 
    618     if (strokeSize.fX < 0 || strokeSize.fY < 0) {
    619         return;
    620     }
    621 
    622     const SkScalar dx = strokeSize.fX;
    623     const SkScalar dy = strokeSize.fY;
    624     SkScalar rx = SkScalarHalf(dx);
    625     SkScalar ry = SkScalarHalf(dy);
    626     SkRect   outer, tmp;
    627 
    628     outer.set(r.fLeft - rx, r.fTop - ry,
    629                 r.fRight + rx, r.fBottom + ry);
    630 
    631     if (r.width() <= dx || r.height() <= dx) {
    632         SkScan::FillRect(outer, clip, blitter);
    633         return;
    634     }
    635 
    636     tmp.set(outer.fLeft, outer.fTop, outer.fRight, outer.fTop + dy);
    637     SkScan::FillRect(tmp, clip, blitter);
    638     tmp.fTop = outer.fBottom - dy;
    639     tmp.fBottom = outer.fBottom;
    640     SkScan::FillRect(tmp, clip, blitter);
    641 
    642     tmp.set(outer.fLeft, outer.fTop + dy, outer.fLeft + dx, outer.fBottom - dy);
    643     SkScan::FillRect(tmp, clip, blitter);
    644     tmp.fLeft = outer.fRight - dx;
    645     tmp.fRight = outer.fRight;
    646     SkScan::FillRect(tmp, clip, blitter);
    647 }
    648 
    649 void SkScan::HairLine(const SkPoint pts[], int count, const SkRasterClip& clip,
    650                       SkBlitter* blitter) {
    651     if (clip.isBW()) {
    652         HairLineRgn(pts, count, &clip.bwRgn(), blitter);
    653     } else {
    654         const SkRegion* clipRgn = nullptr;
    655 
    656         SkRect r;
    657         r.set(pts, count);
    658         r.outset(SK_ScalarHalf, SK_ScalarHalf);
    659 
    660         SkAAClipBlitterWrapper wrap;
    661         if (!clip.quickContains(r.roundOut())) {
    662             wrap.init(clip, blitter);
    663             blitter = wrap.getBlitter();
    664             clipRgn = &wrap.getRgn();
    665         }
    666         HairLineRgn(pts, count, clipRgn, blitter);
    667     }
    668 }
    669 
    670 void SkScan::AntiHairLine(const SkPoint pts[], int count, const SkRasterClip& clip,
    671                           SkBlitter* blitter) {
    672     if (clip.isBW()) {
    673         AntiHairLineRgn(pts, count, &clip.bwRgn(), blitter);
    674     } else {
    675         const SkRegion* clipRgn = nullptr;
    676 
    677         SkRect r;
    678         r.set(pts, count);
    679 
    680         SkAAClipBlitterWrapper wrap;
    681         if (!clip.quickContains(r.roundOut().makeOutset(1, 1))) {
    682             wrap.init(clip, blitter);
    683             blitter = wrap.getBlitter();
    684             clipRgn = &wrap.getRgn();
    685         }
    686         AntiHairLineRgn(pts, count, clipRgn, blitter);
    687     }
    688 }
    689