• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 /*
3  * Copyright 2009 The Android Open Source Project
4  *
5  * Use of this source code is governed by a BSD-style license that can be
6  * found in the LICENSE file.
7  */
8 
9 
10 #include "SkEdgeClipper.h"
11 #include "SkGeometry.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 
46 ///////////////////////////////////////////////////////////////////////////////
47 
chopMonoQuadAt(SkScalar c0,SkScalar c1,SkScalar c2,SkScalar target,SkScalar * t)48 static bool chopMonoQuadAt(SkScalar c0, SkScalar c1, SkScalar c2,
49                            SkScalar target, SkScalar* t) {
50     /* Solve F(t) = y where F(t) := [0](1-t)^2 + 2[1]t(1-t) + [2]t^2
51      *  We solve for t, using quadratic equation, hence we have to rearrange
52      * our cooefficents to look like At^2 + Bt + C
53      */
54     SkScalar A = c0 - c1 - c1 + c2;
55     SkScalar B = 2*(c1 - c0);
56     SkScalar C = c0 - target;
57 
58     SkScalar roots[2];  // we only expect one, but make room for 2 for safety
59     int count = SkFindUnitQuadRoots(A, B, C, roots);
60     if (count) {
61         *t = roots[0];
62         return true;
63     }
64     return false;
65 }
66 
chopMonoQuadAtY(SkPoint pts[3],SkScalar y,SkScalar * t)67 static bool chopMonoQuadAtY(SkPoint pts[3], SkScalar y, SkScalar* t) {
68     return chopMonoQuadAt(pts[0].fY, pts[1].fY, pts[2].fY, y, t);
69 }
70 
chopMonoQuadAtX(SkPoint pts[3],SkScalar x,SkScalar * t)71 static bool chopMonoQuadAtX(SkPoint pts[3], SkScalar x, SkScalar* t) {
72     return chopMonoQuadAt(pts[0].fX, pts[1].fX, pts[2].fX, x, t);
73 }
74 
75 // 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)76 static void chop_quad_in_Y(SkPoint pts[3], const SkRect& clip) {
77     SkScalar t;
78     SkPoint tmp[5]; // for SkChopQuadAt
79 
80     // are we partially above
81     if (pts[0].fY < clip.fTop) {
82         if (chopMonoQuadAtY(pts, clip.fTop, &t)) {
83             // take the 2nd chopped quad
84             SkChopQuadAt(pts, tmp, t);
85             clamp_ge(tmp[2].fY, clip.fTop);
86             clamp_ge(tmp[3].fY, clip.fTop);
87             pts[0] = tmp[2];
88             pts[1] = tmp[3];
89         } else {
90             // if chopMonoQuadAtY failed, then we may have hit inexact numerics
91             // so we just clamp against the top
92             for (int i = 0; i < 3; i++) {
93                 if (pts[i].fY < clip.fTop) {
94                     pts[i].fY = clip.fTop;
95                 }
96             }
97         }
98     }
99 
100     // are we partially below
101     if (pts[2].fY > clip.fBottom) {
102         if (chopMonoQuadAtY(pts, clip.fBottom, &t)) {
103             SkChopQuadAt(pts, tmp, t);
104             clamp_le(tmp[1].fY, clip.fBottom);
105             clamp_le(tmp[2].fY, clip.fBottom);
106             pts[1] = tmp[1];
107             pts[2] = tmp[2];
108         } else {
109             // if chopMonoQuadAtY failed, then we may have hit inexact numerics
110             // so we just clamp against the bottom
111             for (int i = 0; i < 3; i++) {
112                 if (pts[i].fY > clip.fBottom) {
113                     pts[i].fY = clip.fBottom;
114                 }
115             }
116         }
117     }
118 }
119 
120 // srcPts[] must be monotonic in X and Y
clipMonoQuad(const SkPoint srcPts[3],const SkRect & clip)121 void SkEdgeClipper::clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip) {
122     SkPoint pts[3];
123     bool reverse = sort_increasing_Y(pts, srcPts, 3);
124 
125     // are we completely above or below
126     if (pts[2].fY <= clip.fTop || pts[0].fY >= clip.fBottom) {
127         return;
128     }
129 
130     // Now chop so that pts is contained within clip in Y
131     chop_quad_in_Y(pts, clip);
132 
133     if (pts[0].fX > pts[2].fX) {
134         SkTSwap<SkPoint>(pts[0], pts[2]);
135         reverse = !reverse;
136     }
137     SkASSERT(pts[0].fX <= pts[1].fX);
138     SkASSERT(pts[1].fX <= pts[2].fX);
139 
140     // Now chop in X has needed, and record the segments
141 
142     if (pts[2].fX <= clip.fLeft) {  // wholly to the left
143         this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse);
144         return;
145     }
146     if (pts[0].fX >= clip.fRight) {  // wholly to the right
147         this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse);
148         return;
149     }
150 
151     SkScalar t;
152     SkPoint tmp[5]; // for SkChopQuadAt
153 
154     // are we partially to the left
155     if (pts[0].fX < clip.fLeft) {
156         if (chopMonoQuadAtX(pts, clip.fLeft, &t)) {
157             SkChopQuadAt(pts, tmp, t);
158             this->appendVLine(clip.fLeft, tmp[0].fY, tmp[2].fY, reverse);
159             clamp_ge(tmp[2].fX, clip.fLeft);
160             clamp_ge(tmp[3].fX, clip.fLeft);
161             pts[0] = tmp[2];
162             pts[1] = tmp[3];
163         } else {
164             // if chopMonoQuadAtY failed, then we may have hit inexact numerics
165             // so we just clamp against the left
166             this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse);
167             return;
168         }
169     }
170 
171     // are we partially to the right
172     if (pts[2].fX > clip.fRight) {
173         if (chopMonoQuadAtX(pts, clip.fRight, &t)) {
174             SkChopQuadAt(pts, tmp, t);
175             clamp_le(tmp[1].fX, clip.fRight);
176             clamp_le(tmp[2].fX, clip.fRight);
177             this->appendQuad(tmp, reverse);
178             this->appendVLine(clip.fRight, tmp[2].fY, tmp[4].fY, reverse);
179         } else {
180             // if chopMonoQuadAtY failed, then we may have hit inexact numerics
181             // so we just clamp against the right
182             this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse);
183         }
184     } else {    // wholly inside the clip
185         this->appendQuad(pts, reverse);
186     }
187 }
188 
clipQuad(const SkPoint srcPts[3],const SkRect & clip)189 bool SkEdgeClipper::clipQuad(const SkPoint srcPts[3], const SkRect& clip) {
190     fCurrPoint = fPoints;
191     fCurrVerb = fVerbs;
192 
193     SkRect  bounds;
194     bounds.set(srcPts, 3);
195 
196     if (!quick_reject(bounds, clip)) {
197         SkPoint monoY[5];
198         int countY = SkChopQuadAtYExtrema(srcPts, monoY);
199         for (int y = 0; y <= countY; y++) {
200             SkPoint monoX[5];
201             int countX = SkChopQuadAtXExtrema(&monoY[y * 2], monoX);
202             for (int x = 0; x <= countX; x++) {
203                 this->clipMonoQuad(&monoX[x * 2], clip);
204                 SkASSERT(fCurrVerb - fVerbs < kMaxVerbs);
205                 SkASSERT(fCurrPoint - fPoints <= kMaxPoints);
206             }
207         }
208     }
209 
210     *fCurrVerb = SkPath::kDone_Verb;
211     fCurrPoint = fPoints;
212     fCurrVerb = fVerbs;
213     return SkPath::kDone_Verb != fVerbs[0];
214 }
215 
216 ///////////////////////////////////////////////////////////////////////////////
217 
eval_cubic_coeff(SkScalar A,SkScalar B,SkScalar C,SkScalar D,SkScalar t)218 static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
219                                  SkScalar D, SkScalar t) {
220     return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
221 }
222 
223 /*  Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
224     t value such that cubic(t) = target
225  */
chopMonoCubicAt(SkScalar c0,SkScalar c1,SkScalar c2,SkScalar c3,SkScalar target,SkScalar * t)226 static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
227                            SkScalar target, SkScalar* t) {
228  //   SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
229     SkASSERT(c0 < target && target < c3);
230 
231     SkScalar D = c0 - target;
232     SkScalar A = c3 + 3*(c1 - c2) - c0;
233     SkScalar B = 3*(c2 - c1 - c1 + c0);
234     SkScalar C = 3*(c1 - c0);
235 
236     const SkScalar TOLERANCE = SK_Scalar1 / 4096;
237     SkScalar minT = 0;
238     SkScalar maxT = SK_Scalar1;
239     SkScalar mid;
240     int i;
241     for (i = 0; i < 16; i++) {
242         mid = SkScalarAve(minT, maxT);
243         SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
244         if (delta < 0) {
245             minT = mid;
246             delta = -delta;
247         } else {
248             maxT = mid;
249         }
250         if (delta < TOLERANCE) {
251             break;
252         }
253     }
254     *t = mid;
255 //    SkDebugf("-- evalCubicAt %d delta %g\n", i, eval_cubic_coeff(A, B, C, D, *t));
256     return true;
257 }
258 
chopMonoCubicAtY(SkPoint pts[4],SkScalar y,SkScalar * t)259 static bool chopMonoCubicAtY(SkPoint pts[4], SkScalar y, SkScalar* t) {
260     return chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, t);
261 }
262 
chopMonoCubicAtX(SkPoint pts[4],SkScalar x,SkScalar * t)263 static bool chopMonoCubicAtX(SkPoint pts[4], SkScalar x, SkScalar* t) {
264     return chopMonoCubicAt(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, x, t);
265 }
266 
267 // 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)268 static void chop_cubic_in_Y(SkPoint pts[4], const SkRect& clip) {
269 
270     // are we partially above
271     if (pts[0].fY < clip.fTop) {
272         SkScalar t;
273         if (chopMonoCubicAtY(pts, clip.fTop, &t)) {
274             SkPoint tmp[7];
275             SkChopCubicAt(pts, tmp, t);
276 
277             // tmp[3, 4, 5].fY should all be to the below clip.fTop, and
278             // still be monotonic in Y. Since we can't trust the numerics of
279             // the chopper, we force those conditions now
280             tmp[3].fY = clip.fTop;
281             tmp[4].fY = SkMaxScalar(tmp[4].fY, clip.fTop);
282             tmp[5].fY = SkMaxScalar(tmp[5].fY, tmp[4].fY);
283 
284             pts[0] = tmp[3];
285             pts[1] = tmp[4];
286             pts[2] = tmp[5];
287         } else {
288             // if chopMonoCubicAtY failed, then we may have hit inexact numerics
289             // so we just clamp against the top
290             for (int i = 0; i < 4; i++) {
291                 clamp_ge(pts[i].fY, clip.fTop);
292             }
293         }
294     }
295 
296     // are we partially below
297     if (pts[3].fY > clip.fBottom) {
298         SkScalar t;
299         if (chopMonoCubicAtY(pts, clip.fBottom, &t)) {
300             SkPoint tmp[7];
301             SkChopCubicAt(pts, tmp, t);
302             clamp_le(tmp[1].fY, clip.fBottom);
303             clamp_le(tmp[2].fY, clip.fBottom);
304             clamp_le(tmp[3].fY, clip.fBottom);
305             pts[1] = tmp[1];
306             pts[2] = tmp[2];
307             pts[3] = tmp[3];
308         } else {
309             // if chopMonoCubicAtY failed, then we may have hit inexact numerics
310             // so we just clamp against the bottom
311             for (int i = 0; i < 4; i++) {
312                 clamp_le(pts[i].fY, clip.fBottom);
313             }
314         }
315     }
316 }
317 
318 // srcPts[] must be monotonic in X and Y
clipMonoCubic(const SkPoint src[4],const SkRect & clip)319 void SkEdgeClipper::clipMonoCubic(const SkPoint src[4], const SkRect& clip) {
320     SkPoint pts[4];
321     bool reverse = sort_increasing_Y(pts, src, 4);
322 
323     // are we completely above or below
324     if (pts[3].fY <= clip.fTop || pts[0].fY >= clip.fBottom) {
325         return;
326     }
327 
328     // Now chop so that pts is contained within clip in Y
329     chop_cubic_in_Y(pts, clip);
330 
331     if (pts[0].fX > pts[3].fX) {
332         SkTSwap<SkPoint>(pts[0], pts[3]);
333         SkTSwap<SkPoint>(pts[1], pts[2]);
334         reverse = !reverse;
335     }
336 
337     // Now chop in X has needed, and record the segments
338 
339     if (pts[3].fX <= clip.fLeft) {  // wholly to the left
340         this->appendVLine(clip.fLeft, pts[0].fY, pts[3].fY, reverse);
341         return;
342     }
343     if (pts[0].fX >= clip.fRight) {  // wholly to the right
344         this->appendVLine(clip.fRight, pts[0].fY, pts[3].fY, reverse);
345         return;
346     }
347 
348     // are we partially to the left
349     if (pts[0].fX < clip.fLeft) {
350         SkScalar t;
351         if (chopMonoCubicAtX(pts, clip.fLeft, &t)) {
352             SkPoint tmp[7];
353             SkChopCubicAt(pts, tmp, t);
354             this->appendVLine(clip.fLeft, tmp[0].fY, tmp[3].fY, reverse);
355 
356             // tmp[3, 4, 5].fX should all be to the right of clip.fLeft, and
357             // still be monotonic in X. Since we can't trust the numerics of
358             // the chopper, we force those conditions now
359             tmp[3].fX = clip.fLeft;
360             tmp[4].fX = SkMaxScalar(tmp[4].fX, clip.fLeft);
361             tmp[5].fX = SkMaxScalar(tmp[5].fX, tmp[4].fX);
362 
363             pts[0] = tmp[3];
364             pts[1] = tmp[4];
365             pts[2] = tmp[5];
366         } else {
367             // if chopMonocubicAtY failed, then we may have hit inexact numerics
368             // so we just clamp against the left
369             this->appendVLine(clip.fLeft, pts[0].fY, pts[3].fY, reverse);
370             return;
371         }
372     }
373 
374     // are we partially to the right
375     if (pts[3].fX > clip.fRight) {
376         SkScalar t;
377         if (chopMonoCubicAtX(pts, clip.fRight, &t)) {
378             SkPoint tmp[7];
379             SkChopCubicAt(pts, tmp, t);
380             clamp_le(tmp[1].fX, clip.fRight);
381             clamp_le(tmp[2].fX, clip.fRight);
382             clamp_le(tmp[3].fX, clip.fRight);
383             this->appendCubic(tmp, reverse);
384             this->appendVLine(clip.fRight, tmp[3].fY, tmp[6].fY, reverse);
385         } else {
386             // if chopMonoCubicAtX failed, then we may have hit inexact numerics
387             // so we just clamp against the right
388             this->appendVLine(clip.fRight, pts[0].fY, pts[3].fY, reverse);
389         }
390     } else {    // wholly inside the clip
391         this->appendCubic(pts, reverse);
392     }
393 }
394 
clipCubic(const SkPoint srcPts[4],const SkRect & clip)395 bool SkEdgeClipper::clipCubic(const SkPoint srcPts[4], const SkRect& clip) {
396     fCurrPoint = fPoints;
397     fCurrVerb = fVerbs;
398 
399     SkRect  bounds;
400     bounds.set(srcPts, 4);
401 
402     if (!quick_reject(bounds, clip)) {
403         SkPoint monoY[10];
404         int countY = SkChopCubicAtYExtrema(srcPts, monoY);
405         for (int y = 0; y <= countY; y++) {
406         //    sk_assert_monotonic_y(&monoY[y * 3], 4);
407             SkPoint monoX[10];
408             int countX = SkChopCubicAtXExtrema(&monoY[y * 3], monoX);
409             for (int x = 0; x <= countX; x++) {
410             //    sk_assert_monotonic_y(&monoX[x * 3], 4);
411             //    sk_assert_monotonic_x(&monoX[x * 3], 4);
412                 this->clipMonoCubic(&monoX[x * 3], clip);
413                 SkASSERT(fCurrVerb - fVerbs < kMaxVerbs);
414                 SkASSERT(fCurrPoint - fPoints <= kMaxPoints);
415             }
416         }
417     }
418 
419     *fCurrVerb = SkPath::kDone_Verb;
420     fCurrPoint = fPoints;
421     fCurrVerb = fVerbs;
422     return SkPath::kDone_Verb != fVerbs[0];
423 }
424 
425 ///////////////////////////////////////////////////////////////////////////////
426 
appendVLine(SkScalar x,SkScalar y0,SkScalar y1,bool reverse)427 void SkEdgeClipper::appendVLine(SkScalar x, SkScalar y0, SkScalar y1,
428                                 bool reverse) {
429     *fCurrVerb++ = SkPath::kLine_Verb;
430 
431     if (reverse) {
432         SkTSwap<SkScalar>(y0, y1);
433     }
434     fCurrPoint[0].set(x, y0);
435     fCurrPoint[1].set(x, y1);
436     fCurrPoint += 2;
437 }
438 
appendQuad(const SkPoint pts[3],bool reverse)439 void SkEdgeClipper::appendQuad(const SkPoint pts[3], bool reverse) {
440     *fCurrVerb++ = SkPath::kQuad_Verb;
441 
442     if (reverse) {
443         fCurrPoint[0] = pts[2];
444         fCurrPoint[2] = pts[0];
445     } else {
446         fCurrPoint[0] = pts[0];
447         fCurrPoint[2] = pts[2];
448     }
449     fCurrPoint[1] = pts[1];
450     fCurrPoint += 3;
451 }
452 
appendCubic(const SkPoint pts[4],bool reverse)453 void SkEdgeClipper::appendCubic(const SkPoint pts[4], bool reverse) {
454     *fCurrVerb++ = SkPath::kCubic_Verb;
455 
456     if (reverse) {
457         for (int i = 0; i < 4; i++) {
458             fCurrPoint[i] = pts[3 - i];
459         }
460     } else {
461         memcpy(fCurrPoint, pts, 4 * sizeof(SkPoint));
462     }
463     fCurrPoint += 4;
464 }
465 
next(SkPoint pts[])466 SkPath::Verb SkEdgeClipper::next(SkPoint pts[]) {
467     SkPath::Verb verb = *fCurrVerb;
468 
469     switch (verb) {
470         case SkPath::kLine_Verb:
471             memcpy(pts, fCurrPoint, 2 * sizeof(SkPoint));
472             fCurrPoint += 2;
473             fCurrVerb += 1;
474             break;
475         case SkPath::kQuad_Verb:
476             memcpy(pts, fCurrPoint, 3 * sizeof(SkPoint));
477             fCurrPoint += 3;
478             fCurrVerb += 1;
479             break;
480         case SkPath::kCubic_Verb:
481             memcpy(pts, fCurrPoint, 4 * sizeof(SkPoint));
482             fCurrPoint += 4;
483             fCurrVerb += 1;
484             break;
485         case SkPath::kDone_Verb:
486             break;
487         default:
488             SkDEBUGFAIL("unexpected verb in quadclippper2 iter");
489             break;
490     }
491     return verb;
492 }
493 
494 ///////////////////////////////////////////////////////////////////////////////
495 
496 #ifdef SK_DEBUG
assert_monotonic(const SkScalar coord[],int count)497 static void assert_monotonic(const SkScalar coord[], int count) {
498     if (coord[0] > coord[(count - 1) * 2]) {
499         for (int i = 1; i < count; i++) {
500             SkASSERT(coord[2 * (i - 1)] >= coord[i * 2]);
501         }
502     } else if (coord[0] < coord[(count - 1) * 2]) {
503         for (int i = 1; i < count; i++) {
504             SkASSERT(coord[2 * (i - 1)] <= coord[i * 2]);
505         }
506     } else {
507         for (int i = 1; i < count; i++) {
508             SkASSERT(coord[2 * (i - 1)] == coord[i * 2]);
509         }
510     }
511 }
512 
sk_assert_monotonic_y(const SkPoint pts[],int count)513 void sk_assert_monotonic_y(const SkPoint pts[], int count) {
514     if (count > 1) {
515         assert_monotonic(&pts[0].fY, count);
516     }
517 }
518 
sk_assert_monotonic_x(const SkPoint pts[],int count)519 void sk_assert_monotonic_x(const SkPoint pts[], int count) {
520     if (count > 1) {
521         assert_monotonic(&pts[0].fX, count);
522     }
523 }
524 #endif
525