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 "SkPaint.h"
     12 #include "SkRasterClip.h"
     13 #include "SkFDot6.h"
     14 #include "SkLineClipper.h"
     15 
     16 static void horiline(int x, int stopx, SkFixed fy, SkFixed dy,
     17                      SkBlitter* blitter) {
     18     SkASSERT(x < stopx);
     19 
     20     do {
     21         blitter->blitH(x, fy >> 16, 1);
     22         fy += dy;
     23     } while (++x < stopx);
     24 }
     25 
     26 static void vertline(int y, int stopy, SkFixed fx, SkFixed dx,
     27                      SkBlitter* blitter) {
     28     SkASSERT(y < stopy);
     29 
     30     do {
     31         blitter->blitH(fx >> 16, y, 1);
     32         fx += dx;
     33     } while (++y < stopy);
     34 }
     35 
     36 #ifdef SK_DEBUG
     37 static bool canConvertFDot6ToFixed(SkFDot6 x) {
     38     const int maxDot6 = SK_MaxS32 >> (16 - 6);
     39     return SkAbs32(x) <= maxDot6;
     40 }
     41 #endif
     42 
     43 void SkScan::HairLineRgn(const SkPoint array[], int arrayCount, const SkRegion* clip,
     44                          SkBlitter* origBlitter) {
     45     SkBlitterClipper    clipper;
     46     SkIRect clipR, ptsR;
     47 
     48     const SkScalar max = SkIntToScalar(32767);
     49     const SkRect fixedBounds = SkRect::MakeLTRB(-max, -max, max, max);
     50 
     51     SkRect clipBounds;
     52     if (clip) {
     53         clipBounds.set(clip->getBounds());
     54     }
     55 
     56     for (int i = 0; i < arrayCount - 1; ++i) {
     57         SkBlitter* blitter = origBlitter;
     58 
     59         SkPoint pts[2];
     60 
     61         // We have to pre-clip the line to fit in a SkFixed, so we just chop
     62         // the line. TODO find a way to actually draw beyond that range.
     63         if (!SkLineClipper::IntersectLine(&array[i], fixedBounds, pts)) {
     64             continue;
     65         }
     66 
     67         // Perform a clip in scalar space, so we catch huge values which might
     68         // be missed after we convert to SkFDot6 (overflow)
     69         if (clip && !SkLineClipper::IntersectLine(pts, clipBounds, pts)) {
     70             continue;
     71         }
     72 
     73         SkFDot6 x0 = SkScalarToFDot6(pts[0].fX);
     74         SkFDot6 y0 = SkScalarToFDot6(pts[0].fY);
     75         SkFDot6 x1 = SkScalarToFDot6(pts[1].fX);
     76         SkFDot6 y1 = SkScalarToFDot6(pts[1].fY);
     77 
     78         SkASSERT(canConvertFDot6ToFixed(x0));
     79         SkASSERT(canConvertFDot6ToFixed(y0));
     80         SkASSERT(canConvertFDot6ToFixed(x1));
     81         SkASSERT(canConvertFDot6ToFixed(y1));
     82 
     83         if (clip) {
     84             // now perform clipping again, as the rounding to dot6 can wiggle us
     85             // our rects are really dot6 rects, but since we've already used
     86             // lineclipper, we know they will fit in 32bits (26.6)
     87             const SkIRect& bounds = clip->getBounds();
     88 
     89             clipR.set(SkIntToFDot6(bounds.fLeft), SkIntToFDot6(bounds.fTop),
     90                       SkIntToFDot6(bounds.fRight), SkIntToFDot6(bounds.fBottom));
     91             ptsR.set(x0, y0, x1, y1);
     92             ptsR.sort();
     93 
     94             // outset the right and bottom, to account for how hairlines are
     95             // actually drawn, which may hit the pixel to the right or below of
     96             // the coordinate
     97             ptsR.fRight += SK_FDot6One;
     98             ptsR.fBottom += SK_FDot6One;
     99 
    100             if (!SkIRect::Intersects(ptsR, clipR)) {
    101                 continue;
    102             }
    103             if (!clip->isRect() || !clipR.contains(ptsR)) {
    104                 blitter = clipper.apply(origBlitter, clip);
    105             }
    106         }
    107 
    108         SkFDot6 dx = x1 - x0;
    109         SkFDot6 dy = y1 - y0;
    110 
    111         if (SkAbs32(dx) > SkAbs32(dy)) { // mostly horizontal
    112             if (x0 > x1) {   // we want to go left-to-right
    113                 SkTSwap<SkFDot6>(x0, x1);
    114                 SkTSwap<SkFDot6>(y0, y1);
    115             }
    116             int ix0 = SkFDot6Round(x0);
    117             int ix1 = SkFDot6Round(x1);
    118             if (ix0 == ix1) {// too short to draw
    119                 continue;
    120             }
    121 
    122             SkFixed slope = SkFixedDiv(dy, dx);
    123             SkFixed startY = SkFDot6ToFixed(y0) + (slope * ((32 - x0) & 63) >> 6);
    124 
    125             horiline(ix0, ix1, startY, slope, blitter);
    126         } else {              // mostly vertical
    127             if (y0 > y1) {   // we want to go top-to-bottom
    128                 SkTSwap<SkFDot6>(x0, x1);
    129                 SkTSwap<SkFDot6>(y0, y1);
    130             }
    131             int iy0 = SkFDot6Round(y0);
    132             int iy1 = SkFDot6Round(y1);
    133             if (iy0 == iy1) { // too short to draw
    134                 continue;
    135             }
    136 
    137             SkFixed slope = SkFixedDiv(dx, dy);
    138             SkFixed startX = SkFDot6ToFixed(x0) + (slope * ((32 - y0) & 63) >> 6);
    139 
    140             vertline(iy0, iy1, startX, slope, blitter);
    141         }
    142     }
    143 }
    144 
    145 // we don't just draw 4 lines, 'cause that can leave a gap in the bottom-right
    146 // and double-hit the top-left.
    147 void SkScan::HairRect(const SkRect& rect, const SkRasterClip& clip,
    148                       SkBlitter* blitter) {
    149     SkAAClipBlitterWrapper wrapper;
    150     SkBlitterClipper clipper;
    151     const SkIRect r = SkIRect::MakeLTRB(SkScalarFloorToInt(rect.fLeft),
    152                                         SkScalarFloorToInt(rect.fTop),
    153                                         SkScalarFloorToInt(rect.fRight) + 1,
    154                                         SkScalarFloorToInt(rect.fBottom) + 1);
    155 
    156     if (clip.quickReject(r)) {
    157         return;
    158     }
    159     if (!clip.quickContains(r)) {
    160         const SkRegion* clipRgn;
    161         if (clip.isBW()) {
    162             clipRgn = &clip.bwRgn();
    163         } else {
    164             wrapper.init(clip, blitter);
    165             clipRgn = &wrapper.getRgn();
    166             blitter = wrapper.getBlitter();
    167         }
    168         blitter = clipper.apply(blitter, clipRgn);
    169     }
    170 
    171     int width = r.width();
    172     int height = r.height();
    173 
    174     if ((width | height) == 0) {
    175         return;
    176     }
    177     if (width <= 2 || height <= 2) {
    178         blitter->blitRect(r.fLeft, r.fTop, width, height);
    179         return;
    180     }
    181     // if we get here, we know we have 4 segments to draw
    182     blitter->blitH(r.fLeft, r.fTop, width);                     // top
    183     blitter->blitRect(r.fLeft, r.fTop + 1, 1, height - 2);      // left
    184     blitter->blitRect(r.fRight - 1, r.fTop + 1, 1, height - 2); // right
    185     blitter->blitH(r.fLeft, r.fBottom - 1, width);              // bottom
    186 }
    187 
    188 ///////////////////////////////////////////////////////////////////////////////
    189 
    190 #include "SkPath.h"
    191 #include "SkGeometry.h"
    192 #include "SkNx.h"
    193 
    194 #define kMaxCubicSubdivideLevel 9
    195 #define kMaxQuadSubdivideLevel  5
    196 
    197 static uint32_t compute_int_quad_dist(const SkPoint pts[3]) {
    198     // compute the vector between the control point ([1]) and the middle of the
    199     // line connecting the start and end ([0] and [2])
    200     SkScalar dx = SkScalarHalf(pts[0].fX + pts[2].fX) - pts[1].fX;
    201     SkScalar dy = SkScalarHalf(pts[0].fY + pts[2].fY) - pts[1].fY;
    202     // we want everyone to be positive
    203     dx = SkScalarAbs(dx);
    204     dy = SkScalarAbs(dy);
    205     // convert to whole pixel values (use ceiling to be conservative).
    206     // assign to unsigned so we can safely add 1/2 of the smaller and still fit in
    207     // uint32_t, since SkScalarCeilToInt() returns 31 bits at most.
    208     uint32_t idx = SkScalarCeilToInt(dx);
    209     uint32_t 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     uint32_t 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             || SkPath::kClose_Verb == nextVerb) {
    452         SkPoint* last = &pts[ptCount - 1];
    453         SkPoint* ctrl = last;
    454         int controls = ptCount - 1;
    455         SkVector tangent;
    456         do {
    457             tangent = *last - *--ctrl;
    458         } while (tangent.isZero() && --controls > 0);
    459         if (tangent.isZero()) {
    460             tangent.set(-1, 0);
    461             controls = ptCount - 1;
    462         } else {
    463             tangent.normalize();
    464         }
    465         do {
    466             last->fX += tangent.fX * capOutset;
    467             last->fY += tangent.fY * capOutset;
    468             --last;
    469         } while (++controls < ptCount);
    470     }
    471 }
    472 
    473 template <SkPaint::Cap capStyle>
    474 void hair_path(const SkPath& path, const SkRasterClip& rclip, SkBlitter* blitter,
    475                       SkScan::HairRgnProc lineproc) {
    476     if (path.isEmpty()) {
    477         return;
    478     }
    479 
    480     SkAAClipBlitterWrapper wrap;
    481     const SkRegion* clip = nullptr;
    482     SkRect insetStorage, outsetStorage;
    483     const SkRect* insetClip = nullptr;
    484     const SkRect* outsetClip = nullptr;
    485 
    486     {
    487         const int capOut = SkPaint::kButt_Cap == capStyle ? 1 : 2;
    488         const SkIRect ibounds = path.getBounds().roundOut().makeOutset(capOut, capOut);
    489         if (rclip.quickReject(ibounds)) {
    490             return;
    491         }
    492         if (!rclip.quickContains(ibounds)) {
    493             if (rclip.isBW()) {
    494                 clip = &rclip.bwRgn();
    495             } else {
    496                 wrap.init(rclip, blitter);
    497                 blitter = wrap.getBlitter();
    498                 clip = &wrap.getRgn();
    499             }
    500 
    501             /*
    502              *  We now cache two scalar rects, to use for culling per-segment (e.g. cubic).
    503              *  Since we're hairlining, the "bounds" of the control points isn't necessairly the
    504              *  limit of where a segment can draw (it might draw up to 1 pixel beyond in aa-hairs).
    505              *
    506              *  Compute the pt-bounds per segment is easy, so we do that, and then inversely adjust
    507              *  the culling bounds so we can just do a straight compare per segment.
    508              *
    509              *  insetClip is use for quick-accept (i.e. the segment is not clipped), so we inset
    510              *  it from the clip-bounds (since segment bounds can be off by 1).
    511              *
    512              *  outsetClip is used for quick-reject (i.e. the segment is entirely outside), so we
    513              *  outset it from the clip-bounds.
    514              */
    515             insetStorage.set(clip->getBounds());
    516             outsetStorage = insetStorage.makeOutset(1, 1);
    517             insetStorage.inset(1, 1);
    518             if (is_inverted(insetStorage)) {
    519                 /*
    520                  *  our bounds checks assume the rects are never inverted. If insetting has
    521                  *  created that, we assume that the area is too small to safely perform a
    522                  *  quick-accept, so we just mark the rect as empty (so the quick-accept check
    523                  *  will always fail.
    524                  */
    525                 insetStorage.setEmpty();    // just so we don't pass an inverted rect
    526             }
    527             if (rclip.isRect()) {
    528                 insetClip = &insetStorage;
    529             }
    530             outsetClip = &outsetStorage;
    531         }
    532     }
    533 
    534     SkPath::RawIter     iter(path);
    535     SkPoint             pts[4], firstPt, lastPt;
    536     SkPath::Verb        verb, prevVerb;
    537     SkAutoConicToQuads  converter;
    538 
    539     if (SkPaint::kButt_Cap != capStyle) {
    540         prevVerb = SkPath::kDone_Verb;
    541     }
    542     while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
    543         switch (verb) {
    544             case SkPath::kMove_Verb:
    545                 firstPt = lastPt = pts[0];
    546                 break;
    547             case SkPath::kLine_Verb:
    548                 if (SkPaint::kButt_Cap != capStyle) {
    549                     extend_pts<capStyle>(prevVerb, iter.peek(), pts, 2);
    550                 }
    551                 lineproc(pts, 2, clip, blitter);
    552                 lastPt = pts[1];
    553                 break;
    554             case SkPath::kQuad_Verb:
    555                 if (SkPaint::kButt_Cap != capStyle) {
    556                     extend_pts<capStyle>(prevVerb, iter.peek(), pts, 3);
    557                 }
    558                 hairquad(pts, clip, insetClip, outsetClip, blitter, compute_quad_level(pts), lineproc);
    559                 lastPt = pts[2];
    560                 break;
    561             case SkPath::kConic_Verb: {
    562                 if (SkPaint::kButt_Cap != capStyle) {
    563                     extend_pts<capStyle>(prevVerb, iter.peek(), pts, 3);
    564                 }
    565                 // how close should the quads be to the original conic?
    566                 const SkScalar tol = SK_Scalar1 / 4;
    567                 const SkPoint* quadPts = converter.computeQuads(pts,
    568                                                        iter.conicWeight(), tol);
    569                 for (int i = 0; i < converter.countQuads(); ++i) {
    570                     int level = compute_quad_level(quadPts);
    571                     hairquad(quadPts, clip, insetClip, outsetClip, blitter, level, lineproc);
    572                     quadPts += 2;
    573                 }
    574                 lastPt = pts[2];
    575                 break;
    576             }
    577             case SkPath::kCubic_Verb: {
    578                 if (SkPaint::kButt_Cap != capStyle) {
    579                     extend_pts<capStyle>(prevVerb, iter.peek(), pts, 4);
    580                 }
    581                 haircubic(pts, clip, insetClip, outsetClip, blitter, kMaxCubicSubdivideLevel, lineproc);
    582                 lastPt = pts[3];
    583             } break;
    584             case SkPath::kClose_Verb:
    585                 pts[0] = lastPt;
    586                 pts[1] = firstPt;
    587                 if (SkPaint::kButt_Cap != capStyle && prevVerb == SkPath::kMove_Verb) {
    588                     // cap moveTo/close to match svg expectations for degenerate segments
    589                     extend_pts<capStyle>(prevVerb, iter.peek(), pts, 2);
    590                 }
    591                 lineproc(pts, 2, clip, blitter);
    592                 break;
    593             case SkPath::kDone_Verb:
    594                 break;
    595         }
    596         if (SkPaint::kButt_Cap != capStyle) {
    597             if (prevVerb == SkPath::kMove_Verb &&
    598                     verb >= SkPath::kLine_Verb && verb <= SkPath::kCubic_Verb) {
    599                 firstPt = pts[0];  // the curve moved the initial point, so close to it instead
    600             }
    601             prevVerb = verb;
    602         }
    603     }
    604 }
    605 
    606 void SkScan::HairPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) {
    607     hair_path<SkPaint::kButt_Cap>(path, clip, blitter, SkScan::HairLineRgn);
    608 }
    609 
    610 void SkScan::AntiHairPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) {
    611     hair_path<SkPaint::kButt_Cap>(path, clip, blitter, SkScan::AntiHairLineRgn);
    612 }
    613 
    614 void SkScan::HairSquarePath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) {
    615     hair_path<SkPaint::kSquare_Cap>(path, clip, blitter, SkScan::HairLineRgn);
    616 }
    617 
    618 void SkScan::AntiHairSquarePath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) {
    619     hair_path<SkPaint::kSquare_Cap>(path, clip, blitter, SkScan::AntiHairLineRgn);
    620 }
    621 
    622 void SkScan::HairRoundPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) {
    623     hair_path<SkPaint::kRound_Cap>(path, clip, blitter, SkScan::HairLineRgn);
    624 }
    625 
    626 void SkScan::AntiHairRoundPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) {
    627     hair_path<SkPaint::kRound_Cap>(path, clip, blitter, SkScan::AntiHairLineRgn);
    628 }
    629 
    630 ///////////////////////////////////////////////////////////////////////////////
    631 
    632 void SkScan::FrameRect(const SkRect& r, const SkPoint& strokeSize,
    633                        const SkRasterClip& clip, SkBlitter* blitter) {
    634     SkASSERT(strokeSize.fX >= 0 && strokeSize.fY >= 0);
    635 
    636     if (strokeSize.fX < 0 || strokeSize.fY < 0) {
    637         return;
    638     }
    639 
    640     const SkScalar dx = strokeSize.fX;
    641     const SkScalar dy = strokeSize.fY;
    642     SkScalar rx = SkScalarHalf(dx);
    643     SkScalar ry = SkScalarHalf(dy);
    644     SkRect   outer, tmp;
    645 
    646     outer.set(r.fLeft - rx, r.fTop - ry,
    647                 r.fRight + rx, r.fBottom + ry);
    648 
    649     if (r.width() <= dx || r.height() <= dy) {
    650         SkScan::FillRect(outer, clip, blitter);
    651         return;
    652     }
    653 
    654     tmp.set(outer.fLeft, outer.fTop, outer.fRight, outer.fTop + dy);
    655     SkScan::FillRect(tmp, clip, blitter);
    656     tmp.fTop = outer.fBottom - dy;
    657     tmp.fBottom = outer.fBottom;
    658     SkScan::FillRect(tmp, clip, blitter);
    659 
    660     tmp.set(outer.fLeft, outer.fTop + dy, outer.fLeft + dx, outer.fBottom - dy);
    661     SkScan::FillRect(tmp, clip, blitter);
    662     tmp.fLeft = outer.fRight - dx;
    663     tmp.fRight = outer.fRight;
    664     SkScan::FillRect(tmp, clip, blitter);
    665 }
    666 
    667 void SkScan::HairLine(const SkPoint pts[], int count, const SkRasterClip& clip,
    668                       SkBlitter* blitter) {
    669     if (clip.isBW()) {
    670         HairLineRgn(pts, count, &clip.bwRgn(), blitter);
    671     } else {
    672         const SkRegion* clipRgn = nullptr;
    673 
    674         SkRect r;
    675         r.set(pts, count);
    676         r.outset(SK_ScalarHalf, SK_ScalarHalf);
    677 
    678         SkAAClipBlitterWrapper wrap;
    679         if (!clip.quickContains(r.roundOut())) {
    680             wrap.init(clip, blitter);
    681             blitter = wrap.getBlitter();
    682             clipRgn = &wrap.getRgn();
    683         }
    684         HairLineRgn(pts, count, clipRgn, blitter);
    685     }
    686 }
    687 
    688 void SkScan::AntiHairLine(const SkPoint pts[], int count, const SkRasterClip& clip,
    689                           SkBlitter* blitter) {
    690     if (clip.isBW()) {
    691         AntiHairLineRgn(pts, count, &clip.bwRgn(), blitter);
    692     } else {
    693         const SkRegion* clipRgn = nullptr;
    694 
    695         SkRect r;
    696         r.set(pts, count);
    697 
    698         SkAAClipBlitterWrapper wrap;
    699         if (!clip.quickContains(r.roundOut().makeOutset(1, 1))) {
    700             wrap.init(clip, blitter);
    701             blitter = wrap.getBlitter();
    702             clipRgn = &wrap.getRgn();
    703         }
    704         AntiHairLineRgn(pts, count, clipRgn, blitter);
    705     }
    706 }
    707