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