xref: /aosp_15_r20/external/skia/src/core/SkEdgeClipper.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2009 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 "src/core/SkEdgeClipper.h"
9 
10 #include "include/core/SkRect.h"
11 #include "include/core/SkTypes.h"
12 #include "include/private/base/SkMacros.h"
13 #include "src/core/SkGeometry.h"
14 #include "src/core/SkLineClipper.h"
15 #include "src/core/SkPathPriv.h"
16 
17 #include <algorithm>
18 #include <cstring>
19 
quick_reject(const SkRect & bounds,const SkRect & clip)20 static bool quick_reject(const SkRect& bounds, const SkRect& clip) {
21     return bounds.fTop >= clip.fBottom || bounds.fBottom <= clip.fTop;
22 }
23 
clamp_le(SkScalar & value,SkScalar max)24 static inline void clamp_le(SkScalar& value, SkScalar max) {
25     if (value > max) {
26         value = max;
27     }
28 }
29 
clamp_ge(SkScalar & value,SkScalar min)30 static inline void clamp_ge(SkScalar& value, SkScalar min) {
31     if (value < min) {
32         value = min;
33     }
34 }
35 
36 /*  src[] must be monotonic in Y. This routine copies src into dst, and sorts
37  it to be increasing in Y. If it had to reverse the order of the points,
38  it returns true, otherwise it returns false
39  */
sort_increasing_Y(SkPoint dst[],const SkPoint src[],int count)40 static bool sort_increasing_Y(SkPoint dst[], const SkPoint src[], int count) {
41     // we need the data to be monotonically increasing in Y
42     if (src[0].fY > src[count - 1].fY) {
43         for (int i = 0; i < count; i++) {
44             dst[i] = src[count - i - 1];
45         }
46         return true;
47     } else {
48         memcpy(dst, src, count * sizeof(SkPoint));
49         return false;
50     }
51 }
52 
clipLine(SkPoint p0,SkPoint p1,const SkRect & clip)53 bool SkEdgeClipper::clipLine(SkPoint p0, SkPoint p1, const SkRect& clip) {
54     fCurrPoint = fPoints;
55     fCurrVerb = fVerbs;
56 
57     SkPoint lines[SkLineClipper::kMaxPoints];
58     const SkPoint pts[] = { p0, p1 };
59     int lineCount = SkLineClipper::ClipLine(pts, clip, lines, fCanCullToTheRight);
60     for (int i = 0; i < lineCount; i++) {
61         this->appendLine(lines[i], lines[i + 1]);
62     }
63 
64     *fCurrVerb = SkPath::kDone_Verb;
65     fCurrPoint = fPoints;
66     fCurrVerb = fVerbs;
67     return SkPath::kDone_Verb != fVerbs[0];
68 }
69 
70 ///////////////////////////////////////////////////////////////////////////////
71 
chopMonoQuadAt(SkScalar c0,SkScalar c1,SkScalar c2,SkScalar target,SkScalar * t)72 static bool chopMonoQuadAt(SkScalar c0, SkScalar c1, SkScalar c2,
73                            SkScalar target, SkScalar* t) {
74     /* Solve F(t) = y where F(t) := [0](1-t)^2 + 2[1]t(1-t) + [2]t^2
75      *  We solve for t, using quadratic equation, hence we have to rearrange
76      * our cooefficents to look like At^2 + Bt + C
77      */
78     SkScalar A = c0 - c1 - c1 + c2;
79     SkScalar B = 2*(c1 - c0);
80     SkScalar C = c0 - target;
81 
82     SkScalar roots[2];  // we only expect one, but make room for 2 for safety
83     int count = SkFindUnitQuadRoots(A, B, C, roots);
84     if (count) {
85         *t = roots[0];
86         return true;
87     }
88     return false;
89 }
90 
chopMonoQuadAtY(SkPoint pts[3],SkScalar y,SkScalar * t)91 static bool chopMonoQuadAtY(SkPoint pts[3], SkScalar y, SkScalar* t) {
92     return chopMonoQuadAt(pts[0].fY, pts[1].fY, pts[2].fY, y, t);
93 }
94 
chopMonoQuadAtX(SkPoint pts[3],SkScalar x,SkScalar * t)95 static bool chopMonoQuadAtX(SkPoint pts[3], SkScalar x, SkScalar* t) {
96     return chopMonoQuadAt(pts[0].fX, pts[1].fX, pts[2].fX, x, t);
97 }
98 
99 // Modify pts[] in place so that it is clipped in Y to the clip rect
chop_quad_in_Y(SkPoint pts[3],const SkRect & clip)100 static void chop_quad_in_Y(SkPoint pts[3], const SkRect& clip) {
101     SkScalar t;
102     SkPoint tmp[5]; // for SkChopQuadAt
103 
104     // are we partially above
105     if (pts[0].fY < clip.fTop) {
106         if (chopMonoQuadAtY(pts, clip.fTop, &t)) {
107             // take the 2nd chopped quad
108             SkChopQuadAt(pts, tmp, t);
109             // clamp to clean up imprecise numerics in the chop
110             tmp[2].fY = clip.fTop;
111             clamp_ge(tmp[3].fY, clip.fTop);
112 
113             pts[0] = tmp[2];
114             pts[1] = tmp[3];
115         } else {
116             // if chopMonoQuadAtY failed, then we may have hit inexact numerics
117             // so we just clamp against the top
118             for (int i = 0; i < 3; i++) {
119                 if (pts[i].fY < clip.fTop) {
120                     pts[i].fY = clip.fTop;
121                 }
122             }
123         }
124     }
125 
126     // are we partially below
127     if (pts[2].fY > clip.fBottom) {
128         if (chopMonoQuadAtY(pts, clip.fBottom, &t)) {
129             SkChopQuadAt(pts, tmp, t);
130             // clamp to clean up imprecise numerics in the chop
131             clamp_le(tmp[1].fY, clip.fBottom);
132             tmp[2].fY = clip.fBottom;
133 
134             pts[1] = tmp[1];
135             pts[2] = tmp[2];
136         } else {
137             // if chopMonoQuadAtY failed, then we may have hit inexact numerics
138             // so we just clamp against the bottom
139             for (int i = 0; i < 3; i++) {
140                 if (pts[i].fY > clip.fBottom) {
141                     pts[i].fY = clip.fBottom;
142                 }
143             }
144         }
145     }
146 }
147 
148 // srcPts[] must be monotonic in X and Y
clipMonoQuad(const SkPoint srcPts[3],const SkRect & clip)149 void SkEdgeClipper::clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip) {
150     SkPoint pts[3];
151     bool reverse = sort_increasing_Y(pts, srcPts, 3);
152 
153     // are we completely above or below
154     if (pts[2].fY <= clip.fTop || pts[0].fY >= clip.fBottom) {
155         return;
156     }
157 
158     // Now chop so that pts is contained within clip in Y
159     chop_quad_in_Y(pts, clip);
160 
161     if (pts[0].fX > pts[2].fX) {
162         using std::swap;
163         swap(pts[0], pts[2]);
164         reverse = !reverse;
165     }
166     SkASSERT(pts[0].fX <= pts[1].fX);
167     SkASSERT(pts[1].fX <= pts[2].fX);
168 
169     // Now chop in X has needed, and record the segments
170 
171     if (pts[2].fX <= clip.fLeft) {  // wholly to the left
172         this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse);
173         return;
174     }
175     if (pts[0].fX >= clip.fRight) {  // wholly to the right
176         if (!this->canCullToTheRight()) {
177             this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse);
178         }
179         return;
180     }
181 
182     SkScalar t;
183     SkPoint tmp[5]; // for SkChopQuadAt
184 
185     // are we partially to the left
186     if (pts[0].fX < clip.fLeft) {
187         if (chopMonoQuadAtX(pts, clip.fLeft, &t)) {
188             SkChopQuadAt(pts, tmp, t);
189             this->appendVLine(clip.fLeft, tmp[0].fY, tmp[2].fY, reverse);
190             // clamp to clean up imprecise numerics in the chop
191             tmp[2].fX = clip.fLeft;
192             clamp_ge(tmp[3].fX, clip.fLeft);
193 
194             pts[0] = tmp[2];
195             pts[1] = tmp[3];
196         } else {
197             // if chopMonoQuadAtY failed, then we may have hit inexact numerics
198             // so we just clamp against the left
199             this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse);
200             return;
201         }
202     }
203 
204     // are we partially to the right
205     if (pts[2].fX > clip.fRight) {
206         if (chopMonoQuadAtX(pts, clip.fRight, &t)) {
207             SkChopQuadAt(pts, tmp, t);
208             // clamp to clean up imprecise numerics in the chop
209             clamp_le(tmp[1].fX, clip.fRight);
210             tmp[2].fX = clip.fRight;
211 
212             this->appendQuad(tmp, reverse);
213             this->appendVLine(clip.fRight, tmp[2].fY, tmp[4].fY, reverse);
214         } else {
215             // if chopMonoQuadAtY failed, then we may have hit inexact numerics
216             // so we just clamp against the right
217             pts[1].fX = std::min(pts[1].fX, clip.fRight);
218             pts[2].fX = std::min(pts[2].fX, clip.fRight);
219             this->appendQuad(pts, reverse);
220         }
221     } else {    // wholly inside the clip
222         this->appendQuad(pts, reverse);
223     }
224 }
225 
clipQuad(const SkPoint srcPts[3],const SkRect & clip)226 bool SkEdgeClipper::clipQuad(const SkPoint srcPts[3], const SkRect& clip) {
227     fCurrPoint = fPoints;
228     fCurrVerb = fVerbs;
229 
230     SkRect  bounds;
231     bounds.setBounds(srcPts, 3);
232 
233     if (!quick_reject(bounds, clip)) {
234         SkPoint monoY[5];
235         int countY = SkChopQuadAtYExtrema(srcPts, monoY);
236         for (int y = 0; y <= countY; y++) {
237             SkPoint monoX[5];
238             int countX = SkChopQuadAtXExtrema(&monoY[y * 2], monoX);
239             for (int x = 0; x <= countX; x++) {
240                 this->clipMonoQuad(&monoX[x * 2], clip);
241                 SkASSERT(fCurrVerb - fVerbs < kMaxVerbs);
242                 SkASSERT(fCurrPoint - fPoints <= kMaxPoints);
243             }
244         }
245     }
246 
247     *fCurrVerb = SkPath::kDone_Verb;
248     fCurrPoint = fPoints;
249     fCurrVerb = fVerbs;
250     return SkPath::kDone_Verb != fVerbs[0];
251 }
252 
253 ///////////////////////////////////////////////////////////////////////////////
254 
mono_cubic_closestT(const SkScalar src[],SkScalar x)255 static SkScalar mono_cubic_closestT(const SkScalar src[], SkScalar x) {
256     SkScalar t = 0.5f;
257     SkScalar lastT;
258     SkScalar bestT  SK_INIT_TO_AVOID_WARNING;
259     SkScalar step = 0.25f;
260     SkScalar D = src[0];
261     SkScalar A = src[6] + 3*(src[2] - src[4]) - D;
262     SkScalar B = 3*(src[4] - src[2] - src[2] + D);
263     SkScalar C = 3*(src[2] - D);
264     x -= D;
265     SkScalar closest = SK_ScalarMax;
266     do {
267         SkScalar loc = ((A * t + B) * t + C) * t;
268         SkScalar dist = SkScalarAbs(loc - x);
269         if (closest > dist) {
270             closest = dist;
271             bestT = t;
272         }
273         lastT = t;
274         t += loc < x ? step : -step;
275         step *= 0.5f;
276     } while (closest > 0.25f && lastT != t);
277     return bestT;
278 }
279 
chop_mono_cubic_at_y(SkPoint src[4],SkScalar y,SkPoint dst[7])280 static void chop_mono_cubic_at_y(SkPoint src[4], SkScalar y, SkPoint dst[7]) {
281     if (SkChopMonoCubicAtY(src, y, dst)) {
282         return;
283     }
284     SkChopCubicAt(src, dst, mono_cubic_closestT(&src->fY, y));
285 }
286 
287 // Modify pts[] in place so that it is clipped in Y to the clip rect
chop_cubic_in_Y(SkPoint pts[4],const SkRect & clip)288 static void chop_cubic_in_Y(SkPoint pts[4], const SkRect& clip) {
289 
290     // are we partially above
291     if (pts[0].fY < clip.fTop) {
292         SkPoint tmp[7];
293         chop_mono_cubic_at_y(pts, clip.fTop, tmp);
294 
295         /*
296          *  For a large range in the points, we can do a poor job of chopping, such that the t
297          *  we computed resulted in the lower cubic still being partly above the clip.
298          *
299          *  If just the first or first 2 Y values are above the fTop, we can just smash them
300          *  down. If the first 3 Ys are above fTop, we can't smash all 3, as that can really
301          *  distort the cubic. In this case, we take the first output (tmp[3..6] and treat it as
302          *  a guess, and re-chop against fTop. Then we fall through to checking if we need to
303          *  smash the first 1 or 2 Y values.
304          */
305         if (tmp[3].fY < clip.fTop && tmp[4].fY < clip.fTop && tmp[5].fY < clip.fTop) {
306             SkPoint tmp2[4];
307             memcpy(tmp2, &tmp[3].fX, 4 * sizeof(SkPoint));
308             chop_mono_cubic_at_y(tmp2, clip.fTop, tmp);
309         }
310 
311         // tmp[3, 4].fY should all be to the below clip.fTop.
312         // Since we can't trust the numerics of the chopper, we force those conditions now
313         tmp[3].fY = clip.fTop;
314         clamp_ge(tmp[4].fY, clip.fTop);
315 
316         pts[0] = tmp[3];
317         pts[1] = tmp[4];
318         pts[2] = tmp[5];
319     }
320 
321     // are we partially below
322     if (pts[3].fY > clip.fBottom) {
323         SkPoint tmp[7];
324         chop_mono_cubic_at_y(pts, clip.fBottom, tmp);
325         tmp[3].fY = clip.fBottom;
326         clamp_le(tmp[2].fY, clip.fBottom);
327 
328         pts[1] = tmp[1];
329         pts[2] = tmp[2];
330         pts[3] = tmp[3];
331     }
332 }
333 
chop_mono_cubic_at_x(SkPoint src[4],SkScalar x,SkPoint dst[7])334 static void chop_mono_cubic_at_x(SkPoint src[4], SkScalar x, SkPoint dst[7]) {
335     if (SkChopMonoCubicAtX(src, x, dst)) {
336         return;
337     }
338     SkChopCubicAt(src, dst, mono_cubic_closestT(&src->fX, x));
339 }
340 
341 // srcPts[] must be monotonic in X and Y
clipMonoCubic(const SkPoint src[4],const SkRect & clip)342 void SkEdgeClipper::clipMonoCubic(const SkPoint src[4], const SkRect& clip) {
343     SkPoint pts[4];
344     bool reverse = sort_increasing_Y(pts, src, 4);
345 
346     // are we completely above or below
347     if (pts[3].fY <= clip.fTop || pts[0].fY >= clip.fBottom) {
348         return;
349     }
350 
351     // Now chop so that pts is contained within clip in Y
352     chop_cubic_in_Y(pts, clip);
353 
354     if (pts[0].fX > pts[3].fX) {
355         using std::swap;
356         swap(pts[0], pts[3]);
357         swap(pts[1], pts[2]);
358         reverse = !reverse;
359     }
360 
361     // Now chop in X has needed, and record the segments
362 
363     if (pts[3].fX <= clip.fLeft) {  // wholly to the left
364         this->appendVLine(clip.fLeft, pts[0].fY, pts[3].fY, reverse);
365         return;
366     }
367     if (pts[0].fX >= clip.fRight) {  // wholly to the right
368         if (!this->canCullToTheRight()) {
369             this->appendVLine(clip.fRight, pts[0].fY, pts[3].fY, reverse);
370         }
371         return;
372     }
373 
374     // are we partially to the left
375     if (pts[0].fX < clip.fLeft) {
376         SkPoint tmp[7];
377         chop_mono_cubic_at_x(pts, clip.fLeft, tmp);
378         this->appendVLine(clip.fLeft, tmp[0].fY, tmp[3].fY, reverse);
379 
380         // tmp[3, 4].fX should all be to the right of clip.fLeft.
381         // Since we can't trust the numerics of
382         // the chopper, we force those conditions now
383         tmp[3].fX = clip.fLeft;
384         clamp_ge(tmp[4].fX, clip.fLeft);
385 
386         pts[0] = tmp[3];
387         pts[1] = tmp[4];
388         pts[2] = tmp[5];
389     }
390 
391     // are we partially to the right
392     if (pts[3].fX > clip.fRight) {
393         SkPoint tmp[7];
394         chop_mono_cubic_at_x(pts, clip.fRight, tmp);
395         tmp[3].fX = clip.fRight;
396         clamp_le(tmp[2].fX, clip.fRight);
397 
398         this->appendCubic(tmp, reverse);
399         this->appendVLine(clip.fRight, tmp[3].fY, tmp[6].fY, reverse);
400     } else {    // wholly inside the clip
401         this->appendCubic(pts, reverse);
402     }
403 }
404 
compute_cubic_bounds(const SkPoint pts[4])405 static SkRect compute_cubic_bounds(const SkPoint pts[4]) {
406     SkRect r;
407     r.setBounds(pts, 4);
408     return r;
409 }
410 
too_big_for_reliable_float_math(const SkRect & r)411 static bool too_big_for_reliable_float_math(const SkRect& r) {
412     // limit set as the largest float value for which we can still reliably compute things like
413     // - chopping at XY extrema
414     // - chopping at Y or X values for clipping
415     //
416     // Current value chosen just by experiment. Larger (and still succeeds) is always better.
417     //
418     const SkScalar limit = 1 << 22;
419     return r.fLeft < -limit || r.fTop < -limit || r.fRight > limit || r.fBottom > limit;
420 }
421 
clipCubic(const SkPoint srcPts[4],const SkRect & clip)422 bool SkEdgeClipper::clipCubic(const SkPoint srcPts[4], const SkRect& clip) {
423     fCurrPoint = fPoints;
424     fCurrVerb = fVerbs;
425 
426     const SkRect bounds = compute_cubic_bounds(srcPts);
427     // check if we're clipped out vertically
428     if (bounds.fBottom > clip.fTop && bounds.fTop < clip.fBottom) {
429         if (too_big_for_reliable_float_math(bounds)) {
430             // can't safely clip the cubic, so we give up and draw a line (which we can safely clip)
431             //
432             // If we rewrote chopcubicat*extrema and chopmonocubic using doubles, we could very
433             // likely always handle the cubic safely, but (it seems) at a big loss in speed, so
434             // we'd only want to take that alternate impl if needed. Perhaps a TODO to try it.
435             //
436             return this->clipLine(srcPts[0], srcPts[3], clip);
437         } else {
438             SkPoint monoY[10];
439             int countY = SkChopCubicAtYExtrema(srcPts, monoY);
440             for (int y = 0; y <= countY; y++) {
441                 SkPoint monoX[10];
442                 int countX = SkChopCubicAtXExtrema(&monoY[y * 3], monoX);
443                 for (int x = 0; x <= countX; x++) {
444                     this->clipMonoCubic(&monoX[x * 3], clip);
445                     SkASSERT(fCurrVerb - fVerbs < kMaxVerbs);
446                     SkASSERT(fCurrPoint - fPoints <= kMaxPoints);
447                 }
448             }
449         }
450     }
451 
452     *fCurrVerb = SkPath::kDone_Verb;
453     fCurrPoint = fPoints;
454     fCurrVerb = fVerbs;
455     return SkPath::kDone_Verb != fVerbs[0];
456 }
457 
458 ///////////////////////////////////////////////////////////////////////////////
459 
appendLine(SkPoint p0,SkPoint p1)460 void SkEdgeClipper::appendLine(SkPoint p0, SkPoint p1) {
461     *fCurrVerb++ = SkPath::kLine_Verb;
462     fCurrPoint[0] = p0;
463     fCurrPoint[1] = p1;
464     fCurrPoint += 2;
465 }
466 
appendVLine(SkScalar x,SkScalar y0,SkScalar y1,bool reverse)467 void SkEdgeClipper::appendVLine(SkScalar x, SkScalar y0, SkScalar y1, bool reverse) {
468     *fCurrVerb++ = SkPath::kLine_Verb;
469 
470     if (reverse) {
471         using std::swap;
472         swap(y0, y1);
473     }
474     fCurrPoint[0].set(x, y0);
475     fCurrPoint[1].set(x, y1);
476     fCurrPoint += 2;
477 }
478 
appendQuad(const SkPoint pts[3],bool reverse)479 void SkEdgeClipper::appendQuad(const SkPoint pts[3], bool reverse) {
480     *fCurrVerb++ = SkPath::kQuad_Verb;
481 
482     if (reverse) {
483         fCurrPoint[0] = pts[2];
484         fCurrPoint[2] = pts[0];
485     } else {
486         fCurrPoint[0] = pts[0];
487         fCurrPoint[2] = pts[2];
488     }
489     fCurrPoint[1] = pts[1];
490     fCurrPoint += 3;
491 }
492 
appendCubic(const SkPoint pts[4],bool reverse)493 void SkEdgeClipper::appendCubic(const SkPoint pts[4], bool reverse) {
494     *fCurrVerb++ = SkPath::kCubic_Verb;
495 
496     if (reverse) {
497         for (int i = 0; i < 4; i++) {
498             fCurrPoint[i] = pts[3 - i];
499         }
500     } else {
501         memcpy(fCurrPoint, pts, 4 * sizeof(SkPoint));
502     }
503     fCurrPoint += 4;
504 }
505 
next(SkPoint pts[])506 SkPath::Verb SkEdgeClipper::next(SkPoint pts[]) {
507     SkPath::Verb verb = *fCurrVerb;
508 
509     switch (verb) {
510         case SkPath::kLine_Verb:
511             memcpy(pts, fCurrPoint, 2 * sizeof(SkPoint));
512             fCurrPoint += 2;
513             fCurrVerb += 1;
514             break;
515         case SkPath::kQuad_Verb:
516             memcpy(pts, fCurrPoint, 3 * sizeof(SkPoint));
517             fCurrPoint += 3;
518             fCurrVerb += 1;
519             break;
520         case SkPath::kCubic_Verb:
521             memcpy(pts, fCurrPoint, 4 * sizeof(SkPoint));
522             fCurrPoint += 4;
523             fCurrVerb += 1;
524             break;
525         case SkPath::kDone_Verb:
526             break;
527         default:
528             SkDEBUGFAIL("unexpected verb in quadclippper2 iter");
529             break;
530     }
531     return verb;
532 }
533 
534 ///////////////////////////////////////////////////////////////////////////////
535 
536 #ifdef SK_DEBUG
assert_monotonic(const SkScalar coord[],int count)537 static void assert_monotonic(const SkScalar coord[], int count) {
538     if (coord[0] > coord[(count - 1) * 2]) {
539         for (int i = 1; i < count; i++) {
540             SkASSERT(coord[2 * (i - 1)] >= coord[i * 2]);
541         }
542     } else if (coord[0] < coord[(count - 1) * 2]) {
543         for (int i = 1; i < count; i++) {
544             SkASSERT(coord[2 * (i - 1)] <= coord[i * 2]);
545         }
546     } else {
547         for (int i = 1; i < count; i++) {
548             SkASSERT(coord[2 * (i - 1)] == coord[i * 2]);
549         }
550     }
551 }
552 
sk_assert_monotonic_y(const SkPoint pts[],int count)553 void sk_assert_monotonic_y(const SkPoint pts[], int count) {
554     if (count > 1) {
555         assert_monotonic(&pts[0].fY, count);
556     }
557 }
558 
sk_assert_monotonic_x(const SkPoint pts[],int count)559 void sk_assert_monotonic_x(const SkPoint pts[], int count) {
560     if (count > 1) {
561         assert_monotonic(&pts[0].fX, count);
562     }
563 }
564 #endif
565 
ClipPath(const SkPath & path,const SkRect & clip,bool canCullToTheRight,void (* consume)(SkEdgeClipper *,bool newCtr,void * ctx),void * ctx)566 void SkEdgeClipper::ClipPath(const SkPath& path, const SkRect& clip, bool canCullToTheRight,
567                              void (*consume)(SkEdgeClipper*, bool newCtr, void* ctx), void* ctx) {
568     SkASSERT(path.isFinite());
569 
570     SkAutoConicToQuads quadder;
571     const SkScalar conicTol = SK_Scalar1 / 4;
572 
573     SkPathEdgeIter iter(path);
574     SkEdgeClipper clipper(canCullToTheRight);
575 
576     while (auto e = iter.next()) {
577         switch (e.fEdge) {
578             case SkPathEdgeIter::Edge::kLine:
579                 if (clipper.clipLine(e.fPts[0], e.fPts[1], clip)) {
580                     consume(&clipper, e.fIsNewContour, ctx);
581                 }
582                 break;
583             case SkPathEdgeIter::Edge::kQuad:
584                 if (clipper.clipQuad(e.fPts, clip)) {
585                     consume(&clipper, e.fIsNewContour, ctx);
586                 }
587                 break;
588             case SkPathEdgeIter::Edge::kConic: {
589                 const SkPoint* quadPts = quadder.computeQuads(e.fPts, iter.conicWeight(), conicTol);
590                 for (int i = 0; i < quadder.countQuads(); ++i) {
591                     if (clipper.clipQuad(quadPts, clip)) {
592                         consume(&clipper, e.fIsNewContour, ctx);
593                     }
594                     quadPts += 2;
595                 }
596             } break;
597             case SkPathEdgeIter::Edge::kCubic:
598                 if (clipper.clipCubic(e.fPts, clip)) {
599                     consume(&clipper, e.fIsNewContour, ctx);
600                 }
601                 break;
602         }
603     }
604 }
605