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