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
13static bool quick_reject(const SkRect& bounds, const SkRect& clip) {
14    return bounds.fTop >= clip.fBottom || bounds.fBottom <= clip.fTop;
15}
16
17static inline void clamp_le(SkScalar& value, SkScalar max) {
18    if (value > max) {
19        value = max;
20    }
21}
22
23static 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 */
33static 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
46bool 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
65static 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
84static 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
88static 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
93static 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
142void 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
216bool 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
245static 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
270static 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
278static 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
324static 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
332void 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
394static SkRect compute_cubic_bounds(const SkPoint pts[4]) {
395    SkRect r;
396    r.set(pts, 4);
397    return r;
398}
399
400static 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
411bool 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
449void SkEdgeClipper::appendLine(SkPoint p0, SkPoint p1) {
450    *fCurrVerb++ = SkPath::kLine_Verb;
451    fCurrPoint[0] = p0;
452    fCurrPoint[1] = p1;
453    fCurrPoint += 2;
454}
455
456void 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
468void 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
482void 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
495SkPath::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
526static 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
542void sk_assert_monotonic_y(const SkPoint pts[], int count) {
543    if (count > 1) {
544        assert_monotonic(&pts[0].fY, count);
545    }
546}
547
548void 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