blob: b810a91ba055afd0d9c5aab047b23c5804baa005 [file] [log] [blame]
caryclark@google.comc6825902012-02-03 22:07:47 +00001
2/*
3 * Copyright 2012 Google Inc.
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
caryclark@google.com6680fb12012-02-07 22:10:51 +00009#include "CurveIntersection.h"
caryclark@google.comc6825902012-02-03 22:07:47 +000010#include "LineIntersection.h"
11#include "SkPath.h"
12#include "SkRect.h"
13#include "SkTArray.h"
14#include "SkTDArray.h"
15#include "TSearch.h"
16
caryclark@google.com6680fb12012-02-07 22:10:51 +000017static int LineIntersect(const SkPoint a[2], const SkPoint b[2],
caryclark@google.comc6825902012-02-03 22:07:47 +000018 double aRange[2], double bRange[2]) {
19 _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
20 _Line bLine = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}};
21 return intersect(aLine, bLine, aRange, bRange);
22}
23
caryclark@google.com6680fb12012-02-07 22:10:51 +000024static int LineIntersect(const SkPoint a[2], SkScalar y, double aRange[2]) {
caryclark@google.comc6825902012-02-03 22:07:47 +000025 _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
26 return horizontalIntersect(aLine, y, aRange);
27}
28
caryclark@google.com6680fb12012-02-07 22:10:51 +000029static SkScalar LineYAtT(const SkPoint a[2], double t) {
30 _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
31 double y;
32 xy_at_t(aLine, t, *(double*) 0, y);
33 return SkDoubleToScalar(y);
34}
35
36static void LineSubDivide(const SkPoint a[2], double startT, double endT,
37 SkPoint sub[2]) {
38 _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
39 _Line dst;
40 sub_divide(aLine, startT, endT, dst);
41 sub[0].fX = SkDoubleToScalar(dst[0].x);
42 sub[0].fY = SkDoubleToScalar(dst[0].y);
43 sub[1].fX = SkDoubleToScalar(dst[1].x);
44 sub[1].fY = SkDoubleToScalar(dst[1].y);
45}
46
47
caryclark@google.comc6825902012-02-03 22:07:47 +000048// functions
49void contourBounds(const SkPath& path, SkTDArray<SkRect>& boundsArray);
50void simplify(const SkPath& path, bool asFill, SkPath& simple);
51/*
52list of edges
53bounds for edge
54sort
55active T
56
57if a contour's bounds is outside of the active area, no need to create edges
58*/
59
60/* given one or more paths,
61 find the bounds of each contour, select the active contours
62 for each active contour, compute a set of edges
63 each edge corresponds to one or more lines and curves
64 leave edges unbroken as long as possible
65 when breaking edges, compute the t at the break but leave the control points alone
66
67 */
68
69void contourBounds(const SkPath& path, SkTDArray<SkRect>& boundsArray) {
70 SkPath::Iter iter(path, false);
71 SkPoint pts[4];
72 SkPath::Verb verb;
73 SkRect bounds;
74 bounds.setEmpty();
75 int count = 0;
76 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
77 switch (verb) {
78 case SkPath::kMove_Verb:
79 if (!bounds.isEmpty()) {
80 *boundsArray.append() = bounds;
81 }
82 bounds.set(pts[0].fX, pts[0].fY, pts[0].fX, pts[0].fY);
83 count = 0;
84 break;
85 case SkPath::kLine_Verb:
86 count = 1;
87 break;
88 case SkPath::kQuad_Verb:
89 count = 2;
90 break;
91 case SkPath::kCubic_Verb:
92 count = 3;
93 break;
94 case SkPath::kClose_Verb:
95 count = 0;
96 break;
97 default:
98 SkDEBUGFAIL("bad verb");
99 return;
100 }
101 for (int i = 1; i <= count; ++i) {
102 bounds.growToInclude(pts[i].fX, pts[i].fY);
103 }
104 }
105}
106
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000107static bool extendLine(const SkPoint line[2], const SkPoint& add) {
108 // FIXME: allow this to extend lines that have slopes that are nearly equal
109 SkScalar dx1 = line[1].fX - line[0].fX;
110 SkScalar dy1 = line[1].fY - line[0].fY;
111 SkScalar dx2 = add.fX - line[0].fX;
112 SkScalar dy2 = add.fY - line[0].fY;
113 return dx1 * dy2 == dx2 * dy1;
114}
caryclark@google.com6680fb12012-02-07 22:10:51 +0000115
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000116struct OutEdge {
117 bool operator<(const OutEdge& rh) const {
118 const SkPoint& first = fPts.begin()[0];
119 const SkPoint& rhFirst = rh.fPts.begin()[0];
120 return first.fY == rhFirst.fY
121 ? first.fX < rhFirst.fX
122 : first.fY < rhFirst.fY;
123 }
124
caryclark@google.com6680fb12012-02-07 22:10:51 +0000125 SkTDArray<SkPoint> fPts;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000126 SkTDArray<uint8_t> fVerbs; // FIXME: unused for now
caryclark@google.com6680fb12012-02-07 22:10:51 +0000127};
128
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000129// for sorting only
130class OutBottomEdge : public OutEdge {
131public:
132 bool operator<(const OutBottomEdge& rh) const {
133 const SkPoint& last = fPts.end()[-1];
134 const SkPoint& rhLast = rh.fPts.end()[-1];
135 return last.fY == rhLast.fY
136 ? last.fX < rhLast.fX
137 : last.fY < rhLast.fY;
138 }
139
140};
141
caryclark@google.com6680fb12012-02-07 22:10:51 +0000142class OutEdgeBuilder {
143public:
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000144 OutEdgeBuilder(bool fill)
145 : fFill(fill) {
caryclark@google.com6680fb12012-02-07 22:10:51 +0000146 }
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000147
148 void addLine(const SkPoint line[2]) {
149 size_t count = fEdges.count();
150 for (size_t index = 0; index < count; ++index) {
151 SkTDArray<SkPoint>& pts = fEdges[index].fPts;
152 SkPoint* last = pts.end() - 1;
153 if (last[0] == line[0]) {
154 if (extendLine(&last[-1], line[1])) {
155 last[0] = line[1];
156 } else {
157 *pts.append() = line[1];
158 }
159 return;
160 }
161 }
162 OutEdge& edge = fEdges.push_back();
163 *edge.fPts.append() = line[0];
164 *edge.fPts.append() = line[1];
165 }
166
167 void assemble(SkPath& simple) {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000168 size_t listCount = fEdges.count();
169 if (listCount == 0) {
170 return;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000171 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000172 do {
173 size_t listIndex = 0;
174 int advance = 1;
175 while (listIndex < listCount && fTops[listIndex] == 0) {
176 ++listIndex;
177 }
178 if (listIndex >= listCount) {
179 break;
180 }
181 SkPoint firstPt;
182 bool doMove = true;
183 int edgeIndex;
184 do {
185 SkTDArray<SkPoint>& ptArray = fEdges[listIndex].fPts;
186 SkASSERT(ptArray.count() > 0);
187 SkPoint* pts, * end;
188 if (advance < 0) {
189 pts = ptArray.end() - 1;
190 end = ptArray.begin();
191 } else {
192 pts = ptArray.begin();
193 end = ptArray.end() - 1;
194 }
195 if (doMove) {
196 firstPt = pts[0];
197 simple.moveTo(pts[0].fX, pts[0].fY);
198 SkDebugf("%s moveTo (%g,%g)\n", __FUNCTION__, pts[0].fX, pts[0].fY);
199 doMove = false;
200 } else {
201 simple.lineTo(pts[0].fX, pts[0].fY);
202 SkDebugf("%s 1 lineTo (%g,%g)\n", __FUNCTION__, pts[0].fX, pts[0].fY);
203 if (firstPt == pts[0]) {
204 simple.close();
205 SkDebugf("%s close\n", __FUNCTION__);
206 break;
207 }
208 }
209 while (pts != end) {
210 pts += advance;
211 simple.lineTo(pts->fX, pts->fY);
212 SkDebugf("%s 2 lineTo (%g,%g)\n", __FUNCTION__, pts[0].fX, pts[0].fY);
213 }
214 if (advance < 0) {
215 edgeIndex = fTops[listIndex];
216 fTops[listIndex] = 0;
217 } else {
218 edgeIndex = fBottoms[listIndex];
219 fBottoms[listIndex] = 0;
220 }
221 listIndex = abs(edgeIndex) - 1;
222 if (edgeIndex < 0) {
223 fTops[listIndex] = 0;
224 } else {
225 fBottoms[listIndex] = 0;
226 }
227 // if this and next edge go different directions
228 if (advance > 0 ^ edgeIndex < 0) {
229 advance = -advance;
230 }
231 } while (edgeIndex);
232 } while (true);
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000233 }
234
caryclark@google.com6008c6562012-02-15 22:01:16 +0000235 static bool lessThan(SkTArray<OutEdge>& edges, const int* onePtr,
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000236 const int* twoPtr) {
237 int one = *onePtr;
238 const OutEdge& oneEdge = edges[(one < 0 ? -one : one) - 1];
239 const SkPoint* onePt = one < 0 ? oneEdge.fPts.begin()
240 : oneEdge.fPts.end() - 1;
241 int two = *twoPtr;
242 const OutEdge& twoEdge = edges[(two < 0 ? -two : two) - 1];
243 const SkPoint* twoPt = two < 0 ? twoEdge.fPts.begin()
244 : twoEdge.fPts.end() - 1;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000245 return onePt->fY == twoPt->fY ? onePt->fX < twoPt->fX : onePt->fY < twoPt->fY;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000246 }
247
caryclark@google.com6008c6562012-02-15 22:01:16 +0000248 // Sort the indices of paired points and then create more indices so
249 // assemble() can find the next edge and connect the top or bottom
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000250 void bridge() {
251 size_t index;
252 size_t count = fEdges.count();
253 if (!count) {
254 return;
255 }
256 SkASSERT(!fFill || (count & 1) == 0);
257 fTops.setCount(count);
258 sk_bzero(fTops.begin(), sizeof(fTops[0]) * count);
259 fBottoms.setCount(count);
260 sk_bzero(fBottoms.begin(), sizeof(fBottoms[0]) * count);
caryclark@google.com6008c6562012-02-15 22:01:16 +0000261 SkTDArray<int> order;
262 for (index = 1; index <= count; ++index) {
263 *order.append() = index;
264 *order.append() = -index;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000265 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000266 QSort<SkTArray<OutEdge>, int>(fEdges, order.begin(), count * 2, lessThan);
267 int* lastPtr = order.end() - 1;
268 int* leftPtr = order.begin();
269 while (leftPtr < lastPtr) {
270 int leftIndex = *leftPtr;
271 int leftOutIndex = abs(leftIndex) - 1;
272 const OutEdge& left = fEdges[leftOutIndex];
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000273 int* rightPtr = leftPtr + 1;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000274 int rightIndex = *rightPtr;
275 int rightOutIndex = abs(rightIndex) - 1;
276 const OutEdge& right = fEdges[rightOutIndex];
277 // OPTIMIZATION: if fFill is true, we don't need leftMatch, rightMatch
278 SkPoint& leftMatch = left.fPts[leftIndex < 0 ? 0
279 : left.fPts.count() - 1];
280 SkPoint& rightMatch = right.fPts[rightIndex < 0 ? 0
281 : right.fPts.count() - 1];
282 SkASSERT(!fFill || leftMatch.fY == rightMatch.fY);
283 if (fFill || leftMatch == rightMatch) {
284 if (leftIndex < 0) {
285 fTops[leftOutIndex] = rightIndex;
286 } else {
287 fBottoms[leftOutIndex] = rightIndex;
288 }
289 if (rightIndex < 0) {
290 fTops[rightOutIndex] = leftIndex;
291 } else {
292 fBottoms[rightOutIndex] = leftIndex;
293 }
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000294 ++rightPtr;
295 }
296 leftPtr = rightPtr;
297 }
298 }
299
caryclark@google.com6008c6562012-02-15 22:01:16 +0000300protected:
caryclark@google.com6680fb12012-02-07 22:10:51 +0000301 SkTArray<OutEdge> fEdges;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000302 SkTDArray<int> fTops;
303 SkTDArray<int> fBottoms;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000304 bool fFill;
caryclark@google.com6680fb12012-02-07 22:10:51 +0000305};
306
caryclark@google.comc6825902012-02-03 22:07:47 +0000307// Bounds, unlike Rect, does not consider a vertical line to be empty.
308struct Bounds : public SkRect {
309 static bool Intersects(const Bounds& a, const Bounds& b) {
310 return a.fLeft <= b.fRight && b.fLeft <= a.fRight &&
311 a.fTop <= b.fBottom && b.fTop <= a.fBottom;
312 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000313
314 bool isEmpty() {
315 return fLeft > fRight || fTop > fBottom
316 || fLeft == fRight && fTop == fBottom
317 || isnan(fLeft) || isnan(fRight)
318 || isnan(fTop) || isnan(fBottom);
319 }
caryclark@google.comc6825902012-02-03 22:07:47 +0000320};
321
caryclark@google.comc6825902012-02-03 22:07:47 +0000322struct Intercepts {
323 SkTDArray<double> fTs;
324};
325
caryclark@google.com6680fb12012-02-07 22:10:51 +0000326struct InEdge {
327 bool operator<(const InEdge& rh) const {
caryclark@google.comc6825902012-02-03 22:07:47 +0000328 return fBounds.fTop == rh.fBounds.fTop
329 ? fBounds.fLeft < rh.fBounds.fLeft
330 : fBounds.fTop < rh.fBounds.fTop;
331 }
332
caryclark@google.com6008c6562012-02-15 22:01:16 +0000333 bool add(double* ts, size_t count, ptrdiff_t verbIndex) {
caryclark@google.comc6825902012-02-03 22:07:47 +0000334 // FIXME: in the pathological case where there is a ton of intercepts, binary search?
caryclark@google.com6008c6562012-02-15 22:01:16 +0000335 bool foundIntercept = false;
336 Intercepts& intercepts = fIntercepts[verbIndex];
caryclark@google.comc6825902012-02-03 22:07:47 +0000337 for (size_t index = 0; index < count; ++index) {
338 double t = ts[index];
caryclark@google.com6008c6562012-02-15 22:01:16 +0000339 if (t <= 0 || t >= 1) {
340 continue;
341 }
342 foundIntercept = true;
caryclark@google.comc6825902012-02-03 22:07:47 +0000343 size_t tCount = intercepts.fTs.count();
344 for (size_t idx2 = 0; idx2 < tCount; ++idx2) {
345 if (t <= intercepts.fTs[idx2]) {
346 if (t < intercepts.fTs[idx2]) {
347 *intercepts.fTs.insert(idx2) = t;
348 break;
349 }
350 }
351 }
352 if (tCount == 0 || t > intercepts.fTs[tCount - 1]) {
353 *intercepts.fTs.append() = t;
354 }
355 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000356 return foundIntercept;
caryclark@google.comc6825902012-02-03 22:07:47 +0000357 }
358
caryclark@google.com6680fb12012-02-07 22:10:51 +0000359 bool cached(const InEdge* edge) {
caryclark@google.comc6825902012-02-03 22:07:47 +0000360 // FIXME: in the pathological case where there is a ton of edges, binary search?
361 size_t count = fCached.count();
362 for (size_t index = 0; index < count; ++index) {
363 if (edge == fCached[index]) {
364 return true;
365 }
366 if (edge < fCached[index]) {
367 *fCached.insert(index) = edge;
368 return false;
369 }
370 }
371 *fCached.append() = edge;
372 return false;
373 }
374
375 void complete(signed char winding) {
376 SkPoint* ptPtr = fPts.begin();
377 SkPoint* ptLast = fPts.end();
378 if (ptPtr == ptLast) {
379 SkDebugf("empty edge\n");
380 SkASSERT(0);
381 // FIXME: delete empty edge?
382 return;
383 }
384 fBounds.set(ptPtr->fX, ptPtr->fY, ptPtr->fX, ptPtr->fY);
385 ++ptPtr;
386 while (ptPtr != ptLast) {
387 fBounds.growToInclude(ptPtr->fX, ptPtr->fY);
388 ++ptPtr;
389 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000390 fIntercepts.push_back_n(fVerbs.count());
caryclark@google.com6680fb12012-02-07 22:10:51 +0000391 if ((fWinding = winding) < 0) { // reverse verbs, pts, if bottom to top
392 size_t index;
393 size_t last = fPts.count() - 1;
394 for (index = 0; index < last; ++index, --last) {
395 SkTSwap<SkPoint>(fPts[index], fPts[last]);
396 }
397 last = fVerbs.count() - 1;
398 for (index = 0; index < last; ++index, --last) {
399 SkTSwap<uint8_t>(fVerbs[index], fVerbs[last]);
400 }
401 }
402 fContainsIntercepts = false;
caryclark@google.comc6825902012-02-03 22:07:47 +0000403 }
404
405 // temporary data : move this to a separate struct?
caryclark@google.com6680fb12012-02-07 22:10:51 +0000406 SkTDArray<const InEdge*> fCached; // list of edges already intercepted
caryclark@google.comc6825902012-02-03 22:07:47 +0000407 SkTArray<Intercepts> fIntercepts; // one per verb
408
caryclark@google.comc6825902012-02-03 22:07:47 +0000409 // persistent data
410 SkTDArray<SkPoint> fPts;
411 SkTDArray<uint8_t> fVerbs;
412 Bounds fBounds;
413 signed char fWinding;
caryclark@google.com6680fb12012-02-07 22:10:51 +0000414 bool fContainsIntercepts;
caryclark@google.comc6825902012-02-03 22:07:47 +0000415};
416
caryclark@google.com6680fb12012-02-07 22:10:51 +0000417class InEdgeBuilder {
caryclark@google.comc6825902012-02-03 22:07:47 +0000418public:
419
caryclark@google.com6680fb12012-02-07 22:10:51 +0000420InEdgeBuilder(const SkPath& path, bool ignoreHorizontal, SkTArray<InEdge>& edges)
caryclark@google.comc6825902012-02-03 22:07:47 +0000421 : fPath(path)
422 , fCurrentEdge(NULL)
423 , fEdges(edges)
424 , fIgnoreHorizontal(ignoreHorizontal)
425{
426 walk();
427}
428
429protected:
430
431void addEdge() {
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000432 SkASSERT(fCurrentEdge);
caryclark@google.comc6825902012-02-03 22:07:47 +0000433 fCurrentEdge->fPts.append(fPtCount - fPtOffset, &fPts[fPtOffset]);
434 fPtOffset = 1;
435 *fCurrentEdge->fVerbs.append() = fVerb;
436}
437
caryclark@google.com6008c6562012-02-15 22:01:16 +0000438bool complete() {
439 if (fCurrentEdge && fCurrentEdge->fVerbs.count()) {
440 fCurrentEdge->complete(fWinding);
441 fCurrentEdge = NULL;
442 return true;
443 }
444 return false;
445}
446
caryclark@google.comc6825902012-02-03 22:07:47 +0000447int direction(int count) {
448 fPtCount = count;
449 fIgnorableHorizontal = fIgnoreHorizontal && isHorizontal();
450 if (fIgnorableHorizontal) {
451 return 0;
452 }
453 int last = count - 1;
454 return fPts[0].fY == fPts[last].fY
caryclark@google.com6680fb12012-02-07 22:10:51 +0000455 ? fPts[0].fX == fPts[last].fX ? 0 : fPts[0].fX < fPts[last].fX
456 ? 1 : -1 : fPts[0].fY < fPts[last].fY ? 1 : -1;
caryclark@google.comc6825902012-02-03 22:07:47 +0000457}
458
459bool isHorizontal() {
460 SkScalar y = fPts[0].fY;
461 for (int i = 1; i < fPtCount; ++i) {
462 if (fPts[i].fY != y) {
463 return false;
464 }
465 }
466 return true;
467}
468
469void startEdge() {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000470 if (!fCurrentEdge) {
471 fCurrentEdge = fEdges.push_back_n(1);
472 }
caryclark@google.comc6825902012-02-03 22:07:47 +0000473 fWinding = 0;
474 fPtOffset = 0;
475}
476
477void walk() {
478 SkPath::Iter iter(fPath, true);
caryclark@google.com6008c6562012-02-15 22:01:16 +0000479 int winding;
caryclark@google.comc6825902012-02-03 22:07:47 +0000480 while ((fVerb = iter.next(fPts)) != SkPath::kDone_Verb) {
481 switch (fVerb) {
482 case SkPath::kMove_Verb:
caryclark@google.comc6825902012-02-03 22:07:47 +0000483 startEdge();
484 continue;
485 case SkPath::kLine_Verb:
486 winding = direction(2);
487 break;
488 case SkPath::kQuad_Verb:
489 winding = direction(3);
490 break;
491 case SkPath::kCubic_Verb:
492 winding = direction(4);
493 break;
494 case SkPath::kClose_Verb:
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000495 SkASSERT(fCurrentEdge);
caryclark@google.com6008c6562012-02-15 22:01:16 +0000496 complete();
caryclark@google.comc6825902012-02-03 22:07:47 +0000497 continue;
498 default:
499 SkDEBUGFAIL("bad verb");
500 return;
501 }
502 if (fIgnorableHorizontal) {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000503 if (complete()) {
504 startEdge();
505 }
caryclark@google.comc6825902012-02-03 22:07:47 +0000506 continue;
507 }
508 if (fWinding + winding == 0) {
509 // FIXME: if prior verb or this verb is a horizontal line, reverse
510 // it instead of starting a new edge
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000511 SkASSERT(fCurrentEdge);
caryclark@google.comc6825902012-02-03 22:07:47 +0000512 fCurrentEdge->complete(fWinding);
513 startEdge();
514 }
515 fWinding = winding;
516 addEdge();
517 }
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000518 if (!complete()) {
519 if (fCurrentEdge) {
520 fEdges.pop_back();
521 }
522 }
caryclark@google.comc6825902012-02-03 22:07:47 +0000523}
524
525private:
526 const SkPath& fPath;
caryclark@google.com6680fb12012-02-07 22:10:51 +0000527 InEdge* fCurrentEdge;
528 SkTArray<InEdge>& fEdges;
caryclark@google.comc6825902012-02-03 22:07:47 +0000529 SkPoint fPts[4];
530 SkPath::Verb fVerb;
531 int fPtCount;
532 int fPtOffset;
533 int8_t fWinding;
534 bool fIgnorableHorizontal;
535 bool fIgnoreHorizontal;
536};
537
caryclark@google.com6680fb12012-02-07 22:10:51 +0000538struct WorkEdge {
539 SkScalar bottom() const {
540 return fPts[verb()].fY;
caryclark@google.comc6825902012-02-03 22:07:47 +0000541 }
542
caryclark@google.com6680fb12012-02-07 22:10:51 +0000543 void init(const InEdge* edge) {
544 fEdge = edge;
545 fPts = edge->fPts.begin();
546 fVerb = edge->fVerbs.begin();
caryclark@google.comc6825902012-02-03 22:07:47 +0000547 }
548
549 bool next() {
caryclark@google.com6680fb12012-02-07 22:10:51 +0000550 SkASSERT(fVerb < fEdge->fVerbs.end());
551 fPts += *fVerb++;
552 return fVerb != fEdge->fVerbs.end();
caryclark@google.comc6825902012-02-03 22:07:47 +0000553 }
554
555 SkPath::Verb verb() const {
556 return (SkPath::Verb) *fVerb;
557 }
558
caryclark@google.com6008c6562012-02-15 22:01:16 +0000559 ptrdiff_t verbIndex() const {
caryclark@google.com6680fb12012-02-07 22:10:51 +0000560 return fVerb - fEdge->fVerbs.begin();
561 }
562
563 int winding() const {
564 return fEdge->fWinding;
caryclark@google.comc6825902012-02-03 22:07:47 +0000565 }
566
caryclark@google.com6680fb12012-02-07 22:10:51 +0000567 const InEdge* fEdge;
caryclark@google.comc6825902012-02-03 22:07:47 +0000568 const SkPoint* fPts;
569 const uint8_t* fVerb;
caryclark@google.comc6825902012-02-03 22:07:47 +0000570};
571
caryclark@google.com6680fb12012-02-07 22:10:51 +0000572// always constructed with SkTDArray because new edges are inserted
573// this may be a inappropriate optimization, suggesting that a separate array of
574// ActiveEdge* may be faster to insert and search
caryclark@google.comc6825902012-02-03 22:07:47 +0000575struct ActiveEdge {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000576 bool operator<(const ActiveEdge& rh) const {
577 return fX < rh.fX;
578 }
579
580 void calcLeft() {
581 fX = fWorkEdge.fPts[fWorkEdge.verb()].fX;
582 }
583
caryclark@google.com6680fb12012-02-07 22:10:51 +0000584 void init(const InEdge* edge) {
585 fWorkEdge.init(edge);
586 initT();
587 }
588
589 void initT() {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000590 const Intercepts& intercepts = fWorkEdge.fEdge->fIntercepts.front();
591 SkASSERT(fWorkEdge.verbIndex() <= fWorkEdge.fEdge->fIntercepts.count());
592 const Intercepts* interceptPtr = &intercepts + fWorkEdge.verbIndex();
593 fTs = &interceptPtr->fTs;
594 // the above is conceptually the same as
595 // fTs = &fWorkEdge.fEdge->fIntercepts[fWorkEdge.verbIndex()].fTs;
596 // but templated arrays don't allow returning a pointer to the end() element
caryclark@google.com6680fb12012-02-07 22:10:51 +0000597 fTIndex = 0;
caryclark@google.comc6825902012-02-03 22:07:47 +0000598 }
599
caryclark@google.com6680fb12012-02-07 22:10:51 +0000600 bool nextT() {
601 SkASSERT(fTIndex <= fTs->count());
602 return ++fTIndex == fTs->count() + 1;
603 }
604
605 bool next() {
606 bool result = fWorkEdge.next();
607 initT();
608 return result;
609 }
610
611 double t() {
612 if (fTIndex == 0) {
613 return 0;
614 }
615 if (fTIndex > fTs->count()) {
616 return 1;
617 }
618 return (*fTs)[fTIndex - 1];
619 }
620
621 WorkEdge fWorkEdge;
622 const SkTDArray<double>* fTs;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000623 SkScalar fX;
caryclark@google.com6680fb12012-02-07 22:10:51 +0000624 int fTIndex;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000625 bool fSkip;
caryclark@google.comc6825902012-02-03 22:07:47 +0000626};
627
caryclark@google.com6680fb12012-02-07 22:10:51 +0000628static void addToActive(SkTDArray<ActiveEdge>& activeEdges, const InEdge* edge) {
caryclark@google.com6680fb12012-02-07 22:10:51 +0000629 size_t count = activeEdges.count();
630 for (size_t index = 0; index < count; ++index) {
caryclark@google.com6680fb12012-02-07 22:10:51 +0000631 if (edge == activeEdges[index].fWorkEdge.fEdge) {
632 return;
633 }
634 }
635 ActiveEdge* active = activeEdges.append();
636 active->init(edge);
637}
638
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000639 // find any intersections in the range of active edges
640static void addBottomT(InEdge** currentPtr, InEdge** lastPtr, SkScalar bottom) {
641 InEdge** testPtr = currentPtr;
642 InEdge* test = *testPtr;
643 while (testPtr != lastPtr) {
644 if (test->fBounds.fBottom > bottom) {
645 WorkEdge wt;
646 wt.init(test);
647 do {
648 // FIXME: add all curve types
649 // OPTIMIZATION: if bottom intersection does not change
650 // the winding on either side of the split, don't intersect
651 if (wt.verb() == SkPath::kLine_Verb) {
652 double wtTs[2];
653 int pts = LineIntersect(wt.fPts, bottom, wtTs);
654 if (pts) {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000655 test->fContainsIntercepts |= test->add(wtTs, pts,
656 wt.verbIndex());
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000657 }
658 }
659 } while (wt.next());
660 }
661 test = *++testPtr;
662 }
663}
664
665static void addIntersectingTs(InEdge** currentPtr, InEdge** lastPtr) {
666 InEdge** testPtr = currentPtr;
667 InEdge* test = *testPtr;
668 while (testPtr != lastPtr - 1) {
669 InEdge* next = *++testPtr;
670 if (!test->cached(next)
671 && Bounds::Intersects(test->fBounds, next->fBounds)) {
672 WorkEdge wt, wn;
673 wt.init(test);
674 wn.init(next);
675 do {
676 // FIXME: add all combinations of curve types
677 if (wt.verb() == SkPath::kLine_Verb
678 && wn.verb() == SkPath::kLine_Verb) {
679 double wtTs[2], wnTs[2];
680 int pts = LineIntersect(wt.fPts, wn.fPts, wtTs, wnTs);
681 if (pts) {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000682 test->fContainsIntercepts |= test->add(wtTs, pts,
683 wt.verbIndex());
684 next->fContainsIntercepts |= next->add(wnTs, pts,
685 wn.verbIndex());
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000686 }
687 }
688 } while (wt.bottom() <= wn.bottom() ? wt.next() : wn.next());
689 }
690 test = next;
691 }
692}
693
caryclark@google.com6008c6562012-02-15 22:01:16 +0000694static InEdge** advanceEdges(SkTDArray<ActiveEdge>& activeEdges,
695 InEdge** currentPtr, InEdge** lastPtr, SkScalar y) {
696 InEdge** testPtr = currentPtr - 1;
697 while (++testPtr != lastPtr) {
698 if ((*testPtr)->fBounds.fBottom > y) {
699 continue;
700 }
701 InEdge* test = *testPtr;
702 ActiveEdge* activePtr = activeEdges.begin() - 1;
703 ActiveEdge* lastActive = activeEdges.end();
704 while (++activePtr != lastActive) {
705 if (activePtr->fWorkEdge.fEdge == test) {
706 activeEdges.remove(activePtr - activeEdges.begin());
707 break;
708 }
709 }
710 if (testPtr == currentPtr) {
711 ++currentPtr;
712 }
713 }
714 return currentPtr;
715}
716
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000717// compute bottom taking into account any intersected edges
718static void computeInterceptBottom(SkTDArray<ActiveEdge>& activeEdges,
caryclark@google.com6008c6562012-02-15 22:01:16 +0000719 SkScalar y, SkScalar& bottom) {
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000720 ActiveEdge* activePtr = activeEdges.begin() - 1;
721 ActiveEdge* lastActive = activeEdges.end();
722 while (++activePtr != lastActive) {
723 const InEdge* test = activePtr->fWorkEdge.fEdge;
724 if (!test->fContainsIntercepts) {
725 continue;
726 }
727 WorkEdge wt;
728 wt.init(test);
729 do {
730 // FIXME: add all curve types
731 const Intercepts& intercepts = test->fIntercepts[wt.verbIndex()];
732 const SkTDArray<double>& fTs = intercepts.fTs;
733 size_t count = fTs.count();
734 for (size_t index = 0; index < count; ++index) {
735 if (wt.verb() == SkPath::kLine_Verb) {
736 SkScalar yIntercept = LineYAtT(wt.fPts, fTs[index]);
caryclark@google.com6008c6562012-02-15 22:01:16 +0000737 if (yIntercept > y && bottom > yIntercept) {
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000738 bottom = yIntercept;
739 }
740 }
741 }
742 } while (wt.next());
743 }
744}
745
746static SkScalar findBottom(InEdge** currentPtr,
747 InEdge** edgeListEnd, SkTDArray<ActiveEdge>& activeEdges, SkScalar y,
caryclark@google.com6008c6562012-02-15 22:01:16 +0000748 bool asFill, InEdge**& testPtr) {
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000749 InEdge* current = *currentPtr;
750 SkScalar bottom = current->fBounds.fBottom;
751
752 // find the list of edges that cross y
caryclark@google.com6008c6562012-02-15 22:01:16 +0000753 InEdge* test = *testPtr;
754 while (testPtr != edgeListEnd) {
755 SkScalar testTop = test->fBounds.fTop;
756 if (bottom <= testTop) {
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000757 break;
758 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000759 SkScalar testBottom = test->fBounds.fBottom;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000760 // OPTIMIZATION: Shortening the bottom is only interesting when filling
761 // and when the edge is to the left of a longer edge. If it's a framing
762 // edge, or part of the right, it won't effect the longer edges.
caryclark@google.com6008c6562012-02-15 22:01:16 +0000763 if (testTop > y) {
764 bottom = testTop;
765 break;
766 }
767 if (y < testBottom) {
768 if (bottom > testBottom) {
769 bottom = testBottom;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000770 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000771 addToActive(activeEdges, test);
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000772 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000773 test = *++testPtr;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000774 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000775 if (asFill && testPtr - currentPtr <= 1) {
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000776 SkDebugf("expect 2 or more edges\n");
777 SkASSERT(0);
778 }
779 return bottom;
780}
781
782static void makeEdgeList(SkTArray<InEdge>& edges, InEdge& edgeSentinel,
783 SkTDArray<InEdge*>& edgeList) {
784 size_t edgeCount = edges.count();
785 if (edgeCount == 0) {
786 return;
787 }
788 for (size_t index = 0; index < edgeCount; ++index) {
789 *edgeList.append() = &edges[index];
790 }
791 edgeSentinel.fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
792 *edgeList.append() = &edgeSentinel;
793 ++edgeCount;
794 QSort<InEdge>(edgeList.begin(), edgeCount);
795}
796
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000797
798static void skipCoincidence(int lastWinding, int winding, int windingMask,
799 ActiveEdge* activePtr, ActiveEdge* firstCoincident) {
800 if (((lastWinding & windingMask) == 0) ^ (winding & windingMask) != 0) {
801 return;
802 }
803 if (lastWinding) {
804 activePtr->fSkip = false;
805 } else {
806 firstCoincident->fSkip = false;
807 }
808}
809
caryclark@google.com6008c6562012-02-15 22:01:16 +0000810static void sortHorizontal(SkTDArray<ActiveEdge>& activeEdges,
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000811 SkTDArray<ActiveEdge*>& edgeList, int windingMask) {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000812 size_t edgeCount = activeEdges.count();
813 if (edgeCount == 0) {
814 return;
815 }
816 size_t index;
817 for (index = 0; index < edgeCount; ++index) {
818 ActiveEdge& activeEdge = activeEdges[index];
819 activeEdge.calcLeft();
820 activeEdge.fSkip = false;
821 *edgeList.append() = &activeEdge;
822 }
823 QSort<ActiveEdge>(edgeList.begin(), edgeCount);
824 // remove coincident edges
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000825 // OPTIMIZE: remove edges? This is tricky because the current logic expects
826 // the winding count to be maintained while skipping coincident edges. In
827 // addition to removing the coincident edges, the remaining edges would need
828 // to have a different winding value, possibly different per intercept span.
829 int lastWinding = 0;
830 bool lastSkipped = false;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000831 ActiveEdge* activePtr = edgeList[0];
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000832 ActiveEdge* firstCoincident = NULL;
833 int winding = 0;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000834 for (index = 1; index < edgeCount; ++index) {
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000835 winding += activePtr->fWorkEdge.winding();
caryclark@google.com6008c6562012-02-15 22:01:16 +0000836 ActiveEdge* nextPtr = edgeList[index];
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000837 if (activePtr->fX == nextPtr->fX) {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000838 SkDebugf("%s coincident\n", __FUNCTION__);
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000839 if (!firstCoincident) {
840 firstCoincident = activePtr;
841 }
842 activePtr->fSkip = nextPtr->fSkip = lastSkipped = true;
843 } else if (lastSkipped) {
844 skipCoincidence(lastWinding, winding, windingMask, activePtr,
845 firstCoincident);
846 lastSkipped = false;
847 firstCoincident = NULL;
848 }
849 if (!lastSkipped) {
850 lastWinding = winding;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000851 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000852 activePtr = nextPtr;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000853 }
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000854 if (lastSkipped) {
855 winding += activePtr->fWorkEdge.winding();
856 skipCoincidence(lastWinding, winding, windingMask, activePtr,
857 firstCoincident);
858 }
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000859}
860
861// stitch edge and t range that satisfies operation
caryclark@google.com6008c6562012-02-15 22:01:16 +0000862static void stitchEdge(SkTDArray<ActiveEdge*>& edgeList, SkScalar y,
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000863 SkScalar bottom, int windingMask, OutEdgeBuilder& outBuilder) {
864 int winding = 0;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000865 ActiveEdge** activeHandle = edgeList.begin() - 1;
866 ActiveEdge** lastActive = edgeList.end();
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000867 SkDebugf("%s y=%g bottom=%g\n", __FUNCTION__, y, bottom);
caryclark@google.com6008c6562012-02-15 22:01:16 +0000868 while (++activeHandle != lastActive) {
869 ActiveEdge* activePtr = *activeHandle;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000870 const WorkEdge& wt = activePtr->fWorkEdge;
871 int lastWinding = winding;
872 winding += wt.winding();
caryclark@google.com6008c6562012-02-15 22:01:16 +0000873 bool inWinding = (lastWinding & windingMask) == 0
874 || (winding & windingMask) == 0;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000875 do {
876 double currentT = activePtr->t();
caryclark@google.com6008c6562012-02-15 22:01:16 +0000877 SkASSERT(currentT < 1);
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000878 const SkPoint* points = wt.fPts;
879 bool last;
880 do {
881 last = activePtr->nextT();
882 double nextT = activePtr->t();
883 // FIXME: add all combinations of curve types
884 if (wt.verb() == SkPath::kLine_Verb) {
885 SkPoint clippedPts[2];
886 const SkPoint* clipped;
887 if (currentT * nextT != 0 || currentT + nextT != 1) {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000888 // OPTIMIZATION: if !inWinding, we only need
889 // clipped[1].fY
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000890 LineSubDivide(points, currentT, nextT, clippedPts);
891 clipped = clippedPts;
892 } else {
893 clipped = points;
894 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000895 if (inWinding && !activePtr->fSkip) {
896 SkDebugf("%s line %g,%g %g,%g\n", __FUNCTION__,
897 clipped[0].fX, clipped[0].fY,
898 clipped[1].fX, clipped[1].fY);
899 outBuilder.addLine(clipped);
900 }
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000901 if (clipped[1].fY >= bottom) {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000902 if (last) {
903 activePtr->next();
904 }
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000905 goto nextEdge;
906 }
907 }
908 currentT = nextT;
909 } while (!last);
910 } while (activePtr->next());
911nextEdge:
912 ;
913 }
914}
915
caryclark@google.comc6825902012-02-03 22:07:47 +0000916void simplify(const SkPath& path, bool asFill, SkPath& simple) {
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000917 // returns 1 for evenodd, -1 for winding, regardless of inverse-ness
918 int windingMask = (path.getFillType() & 1) ? 1 : -1;
919 simple.reset();
920 simple.setFillType(SkPath::kEvenOdd_FillType);
caryclark@google.comc6825902012-02-03 22:07:47 +0000921 // turn path into list of edges increasing in y
922 // if an edge is a quad or a cubic with a y extrema, note it, but leave it unbroken
923 // once we have a list, sort it, then walk the list (walk edges twice that have y extrema's on top)
924 // and detect crossings -- look for raw bounds that cross over, then tight bounds that cross
caryclark@google.com6680fb12012-02-07 22:10:51 +0000925 SkTArray<InEdge> edges;
926 InEdgeBuilder builder(path, asFill, edges);
caryclark@google.com6680fb12012-02-07 22:10:51 +0000927 SkTDArray<InEdge*> edgeList;
caryclark@google.com6680fb12012-02-07 22:10:51 +0000928 InEdge edgeSentinel;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000929 makeEdgeList(edges, edgeSentinel, edgeList);
caryclark@google.com6680fb12012-02-07 22:10:51 +0000930 InEdge** currentPtr = edgeList.begin();
caryclark@google.comc6825902012-02-03 22:07:47 +0000931 // walk the sorted edges from top to bottom, computing accumulated winding
caryclark@google.com6680fb12012-02-07 22:10:51 +0000932 SkTDArray<ActiveEdge> activeEdges;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000933 OutEdgeBuilder outBuilder(asFill);
934 SkScalar y = (*currentPtr)->fBounds.fTop;
caryclark@google.comc6825902012-02-03 22:07:47 +0000935 do {
caryclark@google.com6680fb12012-02-07 22:10:51 +0000936 InEdge** lastPtr = currentPtr; // find the edge below the bottom of the first set
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000937 SkScalar bottom = findBottom(currentPtr, edgeList.end(),
938 activeEdges, y, asFill, lastPtr);
939 addBottomT(currentPtr, lastPtr, bottom);
940 addIntersectingTs(currentPtr, lastPtr);
caryclark@google.com6008c6562012-02-15 22:01:16 +0000941 computeInterceptBottom(activeEdges, y, bottom);
942 SkTDArray<ActiveEdge*> activeEdgeList;
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000943 sortHorizontal(activeEdges, activeEdgeList, windingMask);
caryclark@google.com6008c6562012-02-15 22:01:16 +0000944 stitchEdge(activeEdgeList, y, bottom, windingMask, outBuilder);
caryclark@google.comc6825902012-02-03 22:07:47 +0000945 y = bottom;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000946 currentPtr = advanceEdges(activeEdges, currentPtr, lastPtr, y);
caryclark@google.comc6825902012-02-03 22:07:47 +0000947 } while (*currentPtr != &edgeSentinel);
caryclark@google.comc6825902012-02-03 22:07:47 +0000948 // assemble output path from string of pts, verbs
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000949 outBuilder.bridge();
950 outBuilder.assemble(simple);
caryclark@google.comc6825902012-02-03 22:07:47 +0000951}
952
caryclark@google.com6008c6562012-02-15 22:01:16 +0000953static void testSimplifyCoincidentVertical() {
954 SkPath path, out;
955 path.setFillType(SkPath::kWinding_FillType);
956 path.addRect(10, 10, 30, 30);
957 path.addRect(10, 30, 30, 40);
958 simplify(path, true, out);
959 SkRect rect;
960 if (!out.isRect(&rect)) {
961 SkDebugf("%s expected rect\n", __FUNCTION__);
962 }
963 if (rect != SkRect::MakeLTRB(10, 10, 30, 40)) {
964 SkDebugf("%s expected union\n", __FUNCTION__);
965 }
966}
caryclark@google.comc6825902012-02-03 22:07:47 +0000967
caryclark@google.com6008c6562012-02-15 22:01:16 +0000968static void testSimplifyCoincidentHorizontal() {
969 SkPath path, out;
970 path.setFillType(SkPath::kWinding_FillType);
971 path.addRect(10, 10, 30, 30);
972 path.addRect(30, 10, 40, 30);
973 simplify(path, true, out);
974 SkRect rect;
975 if (!out.isRect(&rect)) {
976 SkDebugf("%s expected rect\n", __FUNCTION__);
977 }
978 if (rect != SkRect::MakeLTRB(10, 10, 40, 30)) {
979 SkDebugf("%s expected union\n", __FUNCTION__);
980 }
981}
982
983static void testSimplifyMulti() {
caryclark@google.comc6825902012-02-03 22:07:47 +0000984 SkPath path, out;
985 path.setFillType(SkPath::kWinding_FillType);
986 path.addRect(10, 10, 30, 30);
987 path.addRect(20, 20, 40, 40);
988 simplify(path, true, out);
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000989 SkPath expected;
990 expected.setFillType(SkPath::kEvenOdd_FillType);
991 expected.moveTo(10,10); // two cutout corners
992 expected.lineTo(10,30);
993 expected.lineTo(20,30);
994 expected.lineTo(20,40);
995 expected.lineTo(40,40);
996 expected.lineTo(40,20);
997 expected.lineTo(30,20);
998 expected.lineTo(30,10);
999 expected.lineTo(10,10);
1000 expected.close();
1001 if (out != expected) {
1002 SkDebugf("%s expected equal\n", __FUNCTION__);
1003 }
1004
caryclark@google.comc6825902012-02-03 22:07:47 +00001005 path = out;
1006 path.addRect(30, 10, 40, 20);
1007 path.addRect(10, 30, 20, 40);
1008 simplify(path, true, out);
caryclark@google.com6b9cfb32012-02-16 21:24:41 +00001009 SkRect rect;
1010 if (!out.isRect(&rect)) {
1011 SkDebugf("%s expected rect\n", __FUNCTION__);
1012 }
1013 if (rect != SkRect::MakeLTRB(10, 10, 40, 40)) {
1014 SkDebugf("%s expected union\n", __FUNCTION__);
1015 }
1016
caryclark@google.comc6825902012-02-03 22:07:47 +00001017 path = out;
1018 path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
1019 simplify(path, true, out);
1020 if (!out.isEmpty()) {
caryclark@google.com6008c6562012-02-15 22:01:16 +00001021 SkDebugf("%s expected empty\n", __FUNCTION__);
caryclark@google.comc6825902012-02-03 22:07:47 +00001022 }
1023}
caryclark@google.com6008c6562012-02-15 22:01:16 +00001024
1025static void testSimplifyAddL() {
1026 SkPath path, out;
1027 path.moveTo(10,10); // 'L' shape
1028 path.lineTo(10,40);
1029 path.lineTo(40,40);
1030 path.lineTo(40,20);
1031 path.lineTo(30,20);
1032 path.lineTo(30,10);
1033 path.lineTo(10,10);
1034 path.close();
1035 path.addRect(30, 10, 40, 20); // missing notch of 'L'
1036 simplify(path, true, out);
1037 SkRect rect;
1038 if (!out.isRect(&rect)) {
1039 SkDebugf("%s expected rect\n", __FUNCTION__);
1040 }
1041 if (rect != SkRect::MakeLTRB(10, 10, 40, 40)) {
1042 SkDebugf("%s expected union\n", __FUNCTION__);
1043 }
1044}
1045
caryclark@google.com6b9cfb32012-02-16 21:24:41 +00001046static void testSimplifyCoincidentCCW() {
1047 SkPath path, out;
1048 path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
1049 path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
1050 simplify(path, true, out);
1051 SkRect rect;
1052 if (!out.isRect(&rect)) {
1053 SkDebugf("%s expected rect\n", __FUNCTION__);
1054 }
1055 if (rect != SkRect::MakeLTRB(10, 10, 40, 40)) {
1056 SkDebugf("%s expected union\n", __FUNCTION__);
1057 }
1058}
1059
1060static void testSimplifyCoincidentCW() {
1061 SkPath path, out;
1062 path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
1063 path.addRect(10, 10, 40, 40, SkPath::kCW_Direction);
1064 simplify(path, true, out);
1065 if (!out.isEmpty()) {
1066 SkDebugf("%s expected empty\n", __FUNCTION__);
1067 }
1068}
1069
caryclark@google.com6008c6562012-02-15 22:01:16 +00001070void testSimplify();
1071
1072void (*simplifyTests[])() = {
caryclark@google.com6b9cfb32012-02-16 21:24:41 +00001073 testSimplifyCoincidentCW,
1074 testSimplifyCoincidentCCW,
1075 testSimplifyCoincidentVertical,
1076 testSimplifyCoincidentHorizontal,
1077 testSimplifyAddL,
1078 testSimplifyMulti,
caryclark@google.com6008c6562012-02-15 22:01:16 +00001079};
1080
1081size_t simplifyTestsCount = sizeof(simplifyTests) / sizeof(simplifyTests[0]);
1082
caryclark@google.com6b9cfb32012-02-16 21:24:41 +00001083static void (*firstTest)() = 0;
caryclark@google.com6008c6562012-02-15 22:01:16 +00001084
1085void testSimplify() {
caryclark@google.com6b9cfb32012-02-16 21:24:41 +00001086 size_t index = 0;
1087 if (firstTest) {
1088 while (index < simplifyTestsCount && simplifyTests[index] != firstTest) {
1089 ++index;
1090 }
1091 }
1092 for ( ; index < simplifyTestsCount; ++index) {
caryclark@google.com6008c6562012-02-15 22:01:16 +00001093 (*simplifyTests[index])();
1094 }
1095}
1096
1097