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 
horiline(int x,int stopx,SkFixed fy,SkFixed dy,SkBlitter * blitter)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 
vertline(int y,int stopy,SkFixed fx,SkFixed dx,SkBlitter * blitter)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
canConvertFDot6ToFixed(SkFDot6 x)35 static bool canConvertFDot6ToFixed(SkFDot6 x) {
36     const int maxDot6 = SK_MaxS32 >> (16 - 6);
37     return SkAbs32(x) <= maxDot6;
38 }
39 #endif
40 
HairLineRgn(const SkPoint array[],int arrayCount,const SkRegion * clip,SkBlitter * origBlitter)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)
HairRect(const SkRect & rect,const SkRasterClip & clip,SkBlitter * blitter)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 
compute_int_quad_dist(const SkPoint pts[3])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 
hairquad(const SkPoint pts[3],const SkRegion * clip,SkBlitter * blitter,int level,SkScan::HairRgnProc lineproc)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 
abs(const Sk2s & value)242 static inline Sk2s abs(const Sk2s& value) {
243     return Sk2s::Max(value, Sk2s(0)-value);
244 }
245 
max_component(const Sk2s & value)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 
compute_cubic_segs(const SkPoint pts[4])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 
lt_90(SkPoint p0,SkPoint pivot,SkPoint p2)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
quick_cubic_niceness_check(const SkPoint pts[4])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 
hair_cubic(const SkPoint pts[4],const SkRegion * clip,SkBlitter * blitter,SkScan::HairRgnProc lineproc)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 
compute_nocheck_cubic_bounds(const SkPoint pts[4])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 
is_inverted(const SkRect & r)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)
geometric_overlap(const SkRect & a,const SkRect & b)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)
geometric_contains(const SkRect & outer,const SkRect & inner)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 
haircubic(const SkPoint pts[4],const SkRegion * clip,const SkRect * insetClip,const SkRect * outsetClip,SkBlitter * blitter,int level,SkScan::HairRgnProc lineproc)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 
compute_quad_level(const SkPoint pts[3])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>
extend_pts(SkPath::Verb prevVerb,SkPath::Verb nextVerb,SkPoint * pts,int ptCount)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>
hair_path(const SkPath & path,const SkRasterClip & rclip,SkBlitter * blitter,SkScan::HairRgnProc lineproc)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 
HairPath(const SkPath & path,const SkRasterClip & clip,SkBlitter * blitter)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 
AntiHairPath(const SkPath & path,const SkRasterClip & clip,SkBlitter * blitter)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 
HairSquarePath(const SkPath & path,const SkRasterClip & clip,SkBlitter * blitter)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 
AntiHairSquarePath(const SkPath & path,const SkRasterClip & clip,SkBlitter * blitter)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 
HairRoundPath(const SkPath & path,const SkRasterClip & clip,SkBlitter * blitter)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 
AntiHairRoundPath(const SkPath & path,const SkRasterClip & clip,SkBlitter * blitter)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 
FrameRect(const SkRect & r,const SkPoint & strokeSize,const SkRasterClip & clip,SkBlitter * blitter)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 
HairLine(const SkPoint pts[],int count,const SkRasterClip & clip,SkBlitter * blitter)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 
AntiHairLine(const SkPoint pts[],int count,const SkRasterClip & clip,SkBlitter * blitter)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