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