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