blob: 5178bb2deba79c842e9a8bbbda33a3eeb6cde793 [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.comc17972e2012-02-20 21:33:22 +000017static bool fShowDebugf = true; // FIXME: remove once debugging is complete
18
19const int kOpenerVerbBitShift = 3; // leaves 3 bits for SkPath::Verb
20
caryclark@google.com6680fb12012-02-07 22:10:51 +000021static int LineIntersect(const SkPoint a[2], const SkPoint b[2],
caryclark@google.comc6825902012-02-03 22:07:47 +000022 double aRange[2], double bRange[2]) {
23 _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
24 _Line bLine = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}};
25 return intersect(aLine, bLine, aRange, bRange);
26}
27
caryclark@google.com6680fb12012-02-07 22:10:51 +000028static int LineIntersect(const SkPoint a[2], SkScalar y, double aRange[2]) {
caryclark@google.comc6825902012-02-03 22:07:47 +000029 _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
30 return horizontalIntersect(aLine, y, aRange);
31}
32
caryclark@google.com6680fb12012-02-07 22:10:51 +000033static SkScalar LineYAtT(const SkPoint a[2], double t) {
34 _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
35 double y;
36 xy_at_t(aLine, t, *(double*) 0, y);
37 return SkDoubleToScalar(y);
38}
39
40static void LineSubDivide(const SkPoint a[2], double startT, double endT,
41 SkPoint sub[2]) {
42 _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
43 _Line dst;
44 sub_divide(aLine, startT, endT, dst);
45 sub[0].fX = SkDoubleToScalar(dst[0].x);
46 sub[0].fY = SkDoubleToScalar(dst[0].y);
47 sub[1].fX = SkDoubleToScalar(dst[1].x);
48 sub[1].fY = SkDoubleToScalar(dst[1].y);
49}
50
51
caryclark@google.comc6825902012-02-03 22:07:47 +000052// functions
53void contourBounds(const SkPath& path, SkTDArray<SkRect>& boundsArray);
54void simplify(const SkPath& path, bool asFill, SkPath& simple);
55/*
56list of edges
57bounds for edge
58sort
59active T
60
61if a contour's bounds is outside of the active area, no need to create edges
62*/
63
64/* given one or more paths,
65 find the bounds of each contour, select the active contours
66 for each active contour, compute a set of edges
67 each edge corresponds to one or more lines and curves
68 leave edges unbroken as long as possible
69 when breaking edges, compute the t at the break but leave the control points alone
70
71 */
72
73void contourBounds(const SkPath& path, SkTDArray<SkRect>& boundsArray) {
74 SkPath::Iter iter(path, false);
75 SkPoint pts[4];
76 SkPath::Verb verb;
77 SkRect bounds;
78 bounds.setEmpty();
79 int count = 0;
80 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
81 switch (verb) {
82 case SkPath::kMove_Verb:
83 if (!bounds.isEmpty()) {
84 *boundsArray.append() = bounds;
85 }
86 bounds.set(pts[0].fX, pts[0].fY, pts[0].fX, pts[0].fY);
87 count = 0;
88 break;
89 case SkPath::kLine_Verb:
90 count = 1;
91 break;
92 case SkPath::kQuad_Verb:
93 count = 2;
94 break;
95 case SkPath::kCubic_Verb:
96 count = 3;
97 break;
98 case SkPath::kClose_Verb:
99 count = 0;
100 break;
101 default:
102 SkDEBUGFAIL("bad verb");
103 return;
104 }
105 for (int i = 1; i <= count; ++i) {
106 bounds.growToInclude(pts[i].fX, pts[i].fY);
107 }
108 }
109}
110
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000111static bool extendLine(const SkPoint line[2], const SkPoint& add) {
112 // FIXME: allow this to extend lines that have slopes that are nearly equal
113 SkScalar dx1 = line[1].fX - line[0].fX;
114 SkScalar dy1 = line[1].fY - line[0].fY;
115 SkScalar dx2 = add.fX - line[0].fX;
116 SkScalar dy2 = add.fY - line[0].fY;
117 return dx1 * dy2 == dx2 * dy1;
118}
caryclark@google.com6680fb12012-02-07 22:10:51 +0000119
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000120struct OutEdge {
121 bool operator<(const OutEdge& rh) const {
122 const SkPoint& first = fPts.begin()[0];
123 const SkPoint& rhFirst = rh.fPts.begin()[0];
124 return first.fY == rhFirst.fY
125 ? first.fX < rhFirst.fX
126 : first.fY < rhFirst.fY;
127 }
128
caryclark@google.com6680fb12012-02-07 22:10:51 +0000129 SkTDArray<SkPoint> fPts;
caryclark@google.comc17972e2012-02-20 21:33:22 +0000130 // contains the SkPath verb, plus 1<<kOpenerVerbBitShift if edge opens span
131 SkTDArray<uint8_t> fVerbs; // FIXME: not read from everywhere
132 bool fOpener;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000133};
134
caryclark@google.com6680fb12012-02-07 22:10:51 +0000135class OutEdgeBuilder {
136public:
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000137 OutEdgeBuilder(bool fill)
138 : fFill(fill) {
caryclark@google.com6680fb12012-02-07 22:10:51 +0000139 }
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000140
caryclark@google.comc17972e2012-02-20 21:33:22 +0000141 void addLine(const SkPoint line[2], bool opener) {
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000142 size_t count = fEdges.count();
143 for (size_t index = 0; index < count; ++index) {
caryclark@google.comc17972e2012-02-20 21:33:22 +0000144 OutEdge& edge = fEdges[index];
145 if (opener != edge.fOpener) {
146 continue;
147 }
148 SkTDArray<SkPoint>& pts = edge.fPts;
149 SkPoint& last = pts.top();
150 if (last == line[0]) {
151 SkTDArray<uint8_t>& verbs = edge.fVerbs;
152 uint8_t lastVerb = verbs.top();
153 if (lastVerb == SkPath::kLine_Verb
154 && extendLine(&last - 1, line[1])) {
155 last = line[1];
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000156 } else {
157 *pts.append() = line[1];
caryclark@google.comc17972e2012-02-20 21:33:22 +0000158 *verbs.append() = SkPath::kLine_Verb;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000159 }
160 return;
161 }
162 }
caryclark@google.comc17972e2012-02-20 21:33:22 +0000163 OutEdge& newEdge = fEdges.push_back();
164 *newEdge.fPts.append() = line[0];
165 *newEdge.fVerbs.append() = SkPath::kMove_Verb;
166 *newEdge.fPts.append() = line[1];
167 *newEdge.fVerbs.append() = SkPath::kLine_Verb;
168 newEdge.fOpener = opener;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000169 }
170
171 void assemble(SkPath& simple) {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000172 size_t listCount = fEdges.count();
173 if (listCount == 0) {
174 return;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000175 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000176 do {
177 size_t listIndex = 0;
178 int advance = 1;
179 while (listIndex < listCount && fTops[listIndex] == 0) {
180 ++listIndex;
181 }
182 if (listIndex >= listCount) {
183 break;
184 }
185 SkPoint firstPt;
186 bool doMove = true;
187 int edgeIndex;
188 do {
189 SkTDArray<SkPoint>& ptArray = fEdges[listIndex].fPts;
190 SkASSERT(ptArray.count() > 0);
191 SkPoint* pts, * end;
192 if (advance < 0) {
193 pts = ptArray.end() - 1;
194 end = ptArray.begin();
195 } else {
196 pts = ptArray.begin();
197 end = ptArray.end() - 1;
198 }
199 if (doMove) {
200 firstPt = pts[0];
201 simple.moveTo(pts[0].fX, pts[0].fY);
caryclark@google.comc17972e2012-02-20 21:33:22 +0000202 if (fShowDebugf) {
203 SkDebugf("%s moveTo (%g,%g)\n", __FUNCTION__, pts[0].fX, pts[0].fY);
204 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000205 doMove = false;
206 } else {
207 simple.lineTo(pts[0].fX, pts[0].fY);
caryclark@google.comc17972e2012-02-20 21:33:22 +0000208 if (fShowDebugf) {
209 SkDebugf("%s 1 lineTo (%g,%g)\n", __FUNCTION__, pts[0].fX, pts[0].fY);
210 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000211 if (firstPt == pts[0]) {
212 simple.close();
caryclark@google.comc17972e2012-02-20 21:33:22 +0000213 if (fShowDebugf) {
214 SkDebugf("%s close\n", __FUNCTION__);
215 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000216 break;
217 }
218 }
219 while (pts != end) {
220 pts += advance;
221 simple.lineTo(pts->fX, pts->fY);
caryclark@google.comc17972e2012-02-20 21:33:22 +0000222 if (fShowDebugf) {
223 SkDebugf("%s 2 lineTo (%g,%g)\n", __FUNCTION__, pts[0].fX, pts[0].fY);
224 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000225 }
226 if (advance < 0) {
227 edgeIndex = fTops[listIndex];
228 fTops[listIndex] = 0;
229 } else {
230 edgeIndex = fBottoms[listIndex];
231 fBottoms[listIndex] = 0;
232 }
233 listIndex = abs(edgeIndex) - 1;
234 if (edgeIndex < 0) {
235 fTops[listIndex] = 0;
236 } else {
237 fBottoms[listIndex] = 0;
238 }
239 // if this and next edge go different directions
240 if (advance > 0 ^ edgeIndex < 0) {
241 advance = -advance;
242 }
243 } while (edgeIndex);
244 } while (true);
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000245 }
246
caryclark@google.com6008c6562012-02-15 22:01:16 +0000247 static bool lessThan(SkTArray<OutEdge>& edges, const int* onePtr,
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000248 const int* twoPtr) {
249 int one = *onePtr;
250 const OutEdge& oneEdge = edges[(one < 0 ? -one : one) - 1];
251 const SkPoint* onePt = one < 0 ? oneEdge.fPts.begin()
252 : oneEdge.fPts.end() - 1;
253 int two = *twoPtr;
254 const OutEdge& twoEdge = edges[(two < 0 ? -two : two) - 1];
255 const SkPoint* twoPt = two < 0 ? twoEdge.fPts.begin()
256 : twoEdge.fPts.end() - 1;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000257 return onePt->fY == twoPt->fY ? onePt->fX < twoPt->fX : onePt->fY < twoPt->fY;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000258 }
259
caryclark@google.com6008c6562012-02-15 22:01:16 +0000260 // Sort the indices of paired points and then create more indices so
261 // assemble() can find the next edge and connect the top or bottom
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000262 void bridge() {
263 size_t index;
264 size_t count = fEdges.count();
265 if (!count) {
266 return;
267 }
268 SkASSERT(!fFill || (count & 1) == 0);
269 fTops.setCount(count);
270 sk_bzero(fTops.begin(), sizeof(fTops[0]) * count);
271 fBottoms.setCount(count);
272 sk_bzero(fBottoms.begin(), sizeof(fBottoms[0]) * count);
caryclark@google.com6008c6562012-02-15 22:01:16 +0000273 SkTDArray<int> order;
274 for (index = 1; index <= count; ++index) {
275 *order.append() = index;
276 *order.append() = -index;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000277 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000278 QSort<SkTArray<OutEdge>, int>(fEdges, order.begin(), count * 2, lessThan);
279 int* lastPtr = order.end() - 1;
280 int* leftPtr = order.begin();
281 while (leftPtr < lastPtr) {
282 int leftIndex = *leftPtr;
283 int leftOutIndex = abs(leftIndex) - 1;
284 const OutEdge& left = fEdges[leftOutIndex];
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000285 int* rightPtr = leftPtr + 1;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000286 int rightIndex = *rightPtr;
287 int rightOutIndex = abs(rightIndex) - 1;
288 const OutEdge& right = fEdges[rightOutIndex];
289 // OPTIMIZATION: if fFill is true, we don't need leftMatch, rightMatch
290 SkPoint& leftMatch = left.fPts[leftIndex < 0 ? 0
291 : left.fPts.count() - 1];
292 SkPoint& rightMatch = right.fPts[rightIndex < 0 ? 0
293 : right.fPts.count() - 1];
294 SkASSERT(!fFill || leftMatch.fY == rightMatch.fY);
295 if (fFill || leftMatch == rightMatch) {
296 if (leftIndex < 0) {
297 fTops[leftOutIndex] = rightIndex;
298 } else {
299 fBottoms[leftOutIndex] = rightIndex;
300 }
301 if (rightIndex < 0) {
302 fTops[rightOutIndex] = leftIndex;
303 } else {
304 fBottoms[rightOutIndex] = leftIndex;
305 }
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000306 ++rightPtr;
307 }
308 leftPtr = rightPtr;
309 }
310 }
311
caryclark@google.com6008c6562012-02-15 22:01:16 +0000312protected:
caryclark@google.com6680fb12012-02-07 22:10:51 +0000313 SkTArray<OutEdge> fEdges;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000314 SkTDArray<int> fTops;
315 SkTDArray<int> fBottoms;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000316 bool fFill;
caryclark@google.com6680fb12012-02-07 22:10:51 +0000317};
318
caryclark@google.comc6825902012-02-03 22:07:47 +0000319// Bounds, unlike Rect, does not consider a vertical line to be empty.
320struct Bounds : public SkRect {
321 static bool Intersects(const Bounds& a, const Bounds& b) {
322 return a.fLeft <= b.fRight && b.fLeft <= a.fRight &&
323 a.fTop <= b.fBottom && b.fTop <= a.fBottom;
324 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000325
326 bool isEmpty() {
327 return fLeft > fRight || fTop > fBottom
328 || fLeft == fRight && fTop == fBottom
329 || isnan(fLeft) || isnan(fRight)
330 || isnan(fTop) || isnan(fBottom);
331 }
caryclark@google.comc6825902012-02-03 22:07:47 +0000332};
333
caryclark@google.comc6825902012-02-03 22:07:47 +0000334struct Intercepts {
335 SkTDArray<double> fTs;
336};
337
caryclark@google.com6680fb12012-02-07 22:10:51 +0000338struct InEdge {
339 bool operator<(const InEdge& rh) const {
caryclark@google.comc6825902012-02-03 22:07:47 +0000340 return fBounds.fTop == rh.fBounds.fTop
341 ? fBounds.fLeft < rh.fBounds.fLeft
342 : fBounds.fTop < rh.fBounds.fTop;
343 }
344
caryclark@google.com6008c6562012-02-15 22:01:16 +0000345 bool add(double* ts, size_t count, ptrdiff_t verbIndex) {
caryclark@google.comc6825902012-02-03 22:07:47 +0000346 // FIXME: in the pathological case where there is a ton of intercepts, binary search?
caryclark@google.com6008c6562012-02-15 22:01:16 +0000347 bool foundIntercept = false;
348 Intercepts& intercepts = fIntercepts[verbIndex];
caryclark@google.comc6825902012-02-03 22:07:47 +0000349 for (size_t index = 0; index < count; ++index) {
350 double t = ts[index];
caryclark@google.com6008c6562012-02-15 22:01:16 +0000351 if (t <= 0 || t >= 1) {
352 continue;
353 }
354 foundIntercept = true;
caryclark@google.comc6825902012-02-03 22:07:47 +0000355 size_t tCount = intercepts.fTs.count();
356 for (size_t idx2 = 0; idx2 < tCount; ++idx2) {
357 if (t <= intercepts.fTs[idx2]) {
358 if (t < intercepts.fTs[idx2]) {
359 *intercepts.fTs.insert(idx2) = t;
360 break;
361 }
362 }
363 }
364 if (tCount == 0 || t > intercepts.fTs[tCount - 1]) {
365 *intercepts.fTs.append() = t;
366 }
367 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000368 return foundIntercept;
caryclark@google.comc6825902012-02-03 22:07:47 +0000369 }
370
caryclark@google.com6680fb12012-02-07 22:10:51 +0000371 bool cached(const InEdge* edge) {
caryclark@google.comc6825902012-02-03 22:07:47 +0000372 // FIXME: in the pathological case where there is a ton of edges, binary search?
373 size_t count = fCached.count();
374 for (size_t index = 0; index < count; ++index) {
375 if (edge == fCached[index]) {
376 return true;
377 }
378 if (edge < fCached[index]) {
379 *fCached.insert(index) = edge;
380 return false;
381 }
382 }
383 *fCached.append() = edge;
384 return false;
385 }
386
387 void complete(signed char winding) {
388 SkPoint* ptPtr = fPts.begin();
389 SkPoint* ptLast = fPts.end();
390 if (ptPtr == ptLast) {
391 SkDebugf("empty edge\n");
392 SkASSERT(0);
393 // FIXME: delete empty edge?
394 return;
395 }
396 fBounds.set(ptPtr->fX, ptPtr->fY, ptPtr->fX, ptPtr->fY);
397 ++ptPtr;
398 while (ptPtr != ptLast) {
399 fBounds.growToInclude(ptPtr->fX, ptPtr->fY);
400 ++ptPtr;
401 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000402 fIntercepts.push_back_n(fVerbs.count());
caryclark@google.com6680fb12012-02-07 22:10:51 +0000403 if ((fWinding = winding) < 0) { // reverse verbs, pts, if bottom to top
404 size_t index;
405 size_t last = fPts.count() - 1;
406 for (index = 0; index < last; ++index, --last) {
407 SkTSwap<SkPoint>(fPts[index], fPts[last]);
408 }
409 last = fVerbs.count() - 1;
410 for (index = 0; index < last; ++index, --last) {
411 SkTSwap<uint8_t>(fVerbs[index], fVerbs[last]);
412 }
413 }
414 fContainsIntercepts = false;
caryclark@google.comc6825902012-02-03 22:07:47 +0000415 }
416
417 // temporary data : move this to a separate struct?
caryclark@google.com6680fb12012-02-07 22:10:51 +0000418 SkTDArray<const InEdge*> fCached; // list of edges already intercepted
caryclark@google.comc6825902012-02-03 22:07:47 +0000419 SkTArray<Intercepts> fIntercepts; // one per verb
420
caryclark@google.comc6825902012-02-03 22:07:47 +0000421 // persistent data
422 SkTDArray<SkPoint> fPts;
423 SkTDArray<uint8_t> fVerbs;
424 Bounds fBounds;
425 signed char fWinding;
caryclark@google.com6680fb12012-02-07 22:10:51 +0000426 bool fContainsIntercepts;
caryclark@google.comc6825902012-02-03 22:07:47 +0000427};
428
caryclark@google.com6680fb12012-02-07 22:10:51 +0000429class InEdgeBuilder {
caryclark@google.comc6825902012-02-03 22:07:47 +0000430public:
431
caryclark@google.com6680fb12012-02-07 22:10:51 +0000432InEdgeBuilder(const SkPath& path, bool ignoreHorizontal, SkTArray<InEdge>& edges)
caryclark@google.comc6825902012-02-03 22:07:47 +0000433 : fPath(path)
434 , fCurrentEdge(NULL)
435 , fEdges(edges)
436 , fIgnoreHorizontal(ignoreHorizontal)
437{
438 walk();
439}
440
441protected:
442
443void addEdge() {
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000444 SkASSERT(fCurrentEdge);
caryclark@google.comc6825902012-02-03 22:07:47 +0000445 fCurrentEdge->fPts.append(fPtCount - fPtOffset, &fPts[fPtOffset]);
446 fPtOffset = 1;
447 *fCurrentEdge->fVerbs.append() = fVerb;
448}
449
caryclark@google.com6008c6562012-02-15 22:01:16 +0000450bool complete() {
451 if (fCurrentEdge && fCurrentEdge->fVerbs.count()) {
452 fCurrentEdge->complete(fWinding);
453 fCurrentEdge = NULL;
454 return true;
455 }
456 return false;
457}
458
caryclark@google.comc6825902012-02-03 22:07:47 +0000459int direction(int count) {
460 fPtCount = count;
461 fIgnorableHorizontal = fIgnoreHorizontal && isHorizontal();
462 if (fIgnorableHorizontal) {
463 return 0;
464 }
465 int last = count - 1;
466 return fPts[0].fY == fPts[last].fY
caryclark@google.com6680fb12012-02-07 22:10:51 +0000467 ? fPts[0].fX == fPts[last].fX ? 0 : fPts[0].fX < fPts[last].fX
468 ? 1 : -1 : fPts[0].fY < fPts[last].fY ? 1 : -1;
caryclark@google.comc6825902012-02-03 22:07:47 +0000469}
470
471bool isHorizontal() {
472 SkScalar y = fPts[0].fY;
473 for (int i = 1; i < fPtCount; ++i) {
474 if (fPts[i].fY != y) {
475 return false;
476 }
477 }
478 return true;
479}
480
481void startEdge() {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000482 if (!fCurrentEdge) {
483 fCurrentEdge = fEdges.push_back_n(1);
484 }
caryclark@google.comc6825902012-02-03 22:07:47 +0000485 fWinding = 0;
486 fPtOffset = 0;
487}
488
489void walk() {
490 SkPath::Iter iter(fPath, true);
caryclark@google.com6008c6562012-02-15 22:01:16 +0000491 int winding;
caryclark@google.comc6825902012-02-03 22:07:47 +0000492 while ((fVerb = iter.next(fPts)) != SkPath::kDone_Verb) {
493 switch (fVerb) {
494 case SkPath::kMove_Verb:
caryclark@google.comc6825902012-02-03 22:07:47 +0000495 startEdge();
496 continue;
497 case SkPath::kLine_Verb:
498 winding = direction(2);
499 break;
500 case SkPath::kQuad_Verb:
501 winding = direction(3);
502 break;
503 case SkPath::kCubic_Verb:
504 winding = direction(4);
505 break;
506 case SkPath::kClose_Verb:
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000507 SkASSERT(fCurrentEdge);
caryclark@google.com6008c6562012-02-15 22:01:16 +0000508 complete();
caryclark@google.comc6825902012-02-03 22:07:47 +0000509 continue;
510 default:
511 SkDEBUGFAIL("bad verb");
512 return;
513 }
514 if (fIgnorableHorizontal) {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000515 if (complete()) {
516 startEdge();
517 }
caryclark@google.comc6825902012-02-03 22:07:47 +0000518 continue;
519 }
520 if (fWinding + winding == 0) {
521 // FIXME: if prior verb or this verb is a horizontal line, reverse
522 // it instead of starting a new edge
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000523 SkASSERT(fCurrentEdge);
caryclark@google.comc6825902012-02-03 22:07:47 +0000524 fCurrentEdge->complete(fWinding);
525 startEdge();
526 }
527 fWinding = winding;
528 addEdge();
529 }
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000530 if (!complete()) {
531 if (fCurrentEdge) {
532 fEdges.pop_back();
533 }
534 }
caryclark@google.comc6825902012-02-03 22:07:47 +0000535}
536
537private:
538 const SkPath& fPath;
caryclark@google.com6680fb12012-02-07 22:10:51 +0000539 InEdge* fCurrentEdge;
540 SkTArray<InEdge>& fEdges;
caryclark@google.comc6825902012-02-03 22:07:47 +0000541 SkPoint fPts[4];
542 SkPath::Verb fVerb;
543 int fPtCount;
544 int fPtOffset;
545 int8_t fWinding;
546 bool fIgnorableHorizontal;
547 bool fIgnoreHorizontal;
548};
549
caryclark@google.com6680fb12012-02-07 22:10:51 +0000550struct WorkEdge {
551 SkScalar bottom() const {
552 return fPts[verb()].fY;
caryclark@google.comc6825902012-02-03 22:07:47 +0000553 }
554
caryclark@google.com6680fb12012-02-07 22:10:51 +0000555 void init(const InEdge* edge) {
556 fEdge = edge;
557 fPts = edge->fPts.begin();
558 fVerb = edge->fVerbs.begin();
caryclark@google.comc6825902012-02-03 22:07:47 +0000559 }
560
561 bool next() {
caryclark@google.com6680fb12012-02-07 22:10:51 +0000562 SkASSERT(fVerb < fEdge->fVerbs.end());
563 fPts += *fVerb++;
564 return fVerb != fEdge->fVerbs.end();
caryclark@google.comc6825902012-02-03 22:07:47 +0000565 }
566
567 SkPath::Verb verb() const {
568 return (SkPath::Verb) *fVerb;
569 }
570
caryclark@google.com6008c6562012-02-15 22:01:16 +0000571 ptrdiff_t verbIndex() const {
caryclark@google.com6680fb12012-02-07 22:10:51 +0000572 return fVerb - fEdge->fVerbs.begin();
573 }
574
575 int winding() const {
576 return fEdge->fWinding;
caryclark@google.comc6825902012-02-03 22:07:47 +0000577 }
578
caryclark@google.com6680fb12012-02-07 22:10:51 +0000579 const InEdge* fEdge;
caryclark@google.comc6825902012-02-03 22:07:47 +0000580 const SkPoint* fPts;
581 const uint8_t* fVerb;
caryclark@google.comc6825902012-02-03 22:07:47 +0000582};
583
caryclark@google.com6680fb12012-02-07 22:10:51 +0000584// always constructed with SkTDArray because new edges are inserted
585// this may be a inappropriate optimization, suggesting that a separate array of
586// ActiveEdge* may be faster to insert and search
caryclark@google.comc6825902012-02-03 22:07:47 +0000587struct ActiveEdge {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000588 bool operator<(const ActiveEdge& rh) const {
589 return fX < rh.fX;
590 }
591
592 void calcLeft() {
593 fX = fWorkEdge.fPts[fWorkEdge.verb()].fX;
594 }
595
caryclark@google.com6680fb12012-02-07 22:10:51 +0000596 void init(const InEdge* edge) {
597 fWorkEdge.init(edge);
598 initT();
599 }
600
601 void initT() {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000602 const Intercepts& intercepts = fWorkEdge.fEdge->fIntercepts.front();
603 SkASSERT(fWorkEdge.verbIndex() <= fWorkEdge.fEdge->fIntercepts.count());
604 const Intercepts* interceptPtr = &intercepts + fWorkEdge.verbIndex();
605 fTs = &interceptPtr->fTs;
606 // the above is conceptually the same as
607 // fTs = &fWorkEdge.fEdge->fIntercepts[fWorkEdge.verbIndex()].fTs;
608 // but templated arrays don't allow returning a pointer to the end() element
caryclark@google.com6680fb12012-02-07 22:10:51 +0000609 fTIndex = 0;
caryclark@google.comc6825902012-02-03 22:07:47 +0000610 }
611
caryclark@google.com6680fb12012-02-07 22:10:51 +0000612 bool nextT() {
613 SkASSERT(fTIndex <= fTs->count());
614 return ++fTIndex == fTs->count() + 1;
615 }
616
617 bool next() {
618 bool result = fWorkEdge.next();
619 initT();
620 return result;
621 }
622
623 double t() {
624 if (fTIndex == 0) {
625 return 0;
626 }
627 if (fTIndex > fTs->count()) {
628 return 1;
629 }
630 return (*fTs)[fTIndex - 1];
631 }
632
633 WorkEdge fWorkEdge;
634 const SkTDArray<double>* fTs;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000635 SkScalar fX;
caryclark@google.com6680fb12012-02-07 22:10:51 +0000636 int fTIndex;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000637 bool fSkip;
caryclark@google.comc6825902012-02-03 22:07:47 +0000638};
639
caryclark@google.com6680fb12012-02-07 22:10:51 +0000640static void addToActive(SkTDArray<ActiveEdge>& activeEdges, const InEdge* edge) {
caryclark@google.com6680fb12012-02-07 22:10:51 +0000641 size_t count = activeEdges.count();
642 for (size_t index = 0; index < count; ++index) {
caryclark@google.com6680fb12012-02-07 22:10:51 +0000643 if (edge == activeEdges[index].fWorkEdge.fEdge) {
644 return;
645 }
646 }
647 ActiveEdge* active = activeEdges.append();
648 active->init(edge);
649}
650
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000651 // find any intersections in the range of active edges
652static void addBottomT(InEdge** currentPtr, InEdge** lastPtr, SkScalar bottom) {
653 InEdge** testPtr = currentPtr;
654 InEdge* test = *testPtr;
655 while (testPtr != lastPtr) {
656 if (test->fBounds.fBottom > bottom) {
657 WorkEdge wt;
658 wt.init(test);
659 do {
660 // FIXME: add all curve types
661 // OPTIMIZATION: if bottom intersection does not change
662 // the winding on either side of the split, don't intersect
663 if (wt.verb() == SkPath::kLine_Verb) {
664 double wtTs[2];
665 int pts = LineIntersect(wt.fPts, bottom, wtTs);
666 if (pts) {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000667 test->fContainsIntercepts |= test->add(wtTs, pts,
668 wt.verbIndex());
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000669 }
670 }
671 } while (wt.next());
672 }
673 test = *++testPtr;
674 }
675}
676
677static void addIntersectingTs(InEdge** currentPtr, InEdge** lastPtr) {
678 InEdge** testPtr = currentPtr;
679 InEdge* test = *testPtr;
680 while (testPtr != lastPtr - 1) {
681 InEdge* next = *++testPtr;
682 if (!test->cached(next)
683 && Bounds::Intersects(test->fBounds, next->fBounds)) {
684 WorkEdge wt, wn;
685 wt.init(test);
686 wn.init(next);
687 do {
688 // FIXME: add all combinations of curve types
689 if (wt.verb() == SkPath::kLine_Verb
690 && wn.verb() == SkPath::kLine_Verb) {
691 double wtTs[2], wnTs[2];
692 int pts = LineIntersect(wt.fPts, wn.fPts, wtTs, wnTs);
693 if (pts) {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000694 test->fContainsIntercepts |= test->add(wtTs, pts,
695 wt.verbIndex());
696 next->fContainsIntercepts |= next->add(wnTs, pts,
697 wn.verbIndex());
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000698 }
699 }
700 } while (wt.bottom() <= wn.bottom() ? wt.next() : wn.next());
701 }
702 test = next;
703 }
704}
705
caryclark@google.com6008c6562012-02-15 22:01:16 +0000706static InEdge** advanceEdges(SkTDArray<ActiveEdge>& activeEdges,
707 InEdge** currentPtr, InEdge** lastPtr, SkScalar y) {
708 InEdge** testPtr = currentPtr - 1;
709 while (++testPtr != lastPtr) {
710 if ((*testPtr)->fBounds.fBottom > y) {
711 continue;
712 }
713 InEdge* test = *testPtr;
714 ActiveEdge* activePtr = activeEdges.begin() - 1;
715 ActiveEdge* lastActive = activeEdges.end();
716 while (++activePtr != lastActive) {
717 if (activePtr->fWorkEdge.fEdge == test) {
718 activeEdges.remove(activePtr - activeEdges.begin());
719 break;
720 }
721 }
722 if (testPtr == currentPtr) {
723 ++currentPtr;
724 }
725 }
726 return currentPtr;
727}
728
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000729// compute bottom taking into account any intersected edges
730static void computeInterceptBottom(SkTDArray<ActiveEdge>& activeEdges,
caryclark@google.com6008c6562012-02-15 22:01:16 +0000731 SkScalar y, SkScalar& bottom) {
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000732 ActiveEdge* activePtr = activeEdges.begin() - 1;
733 ActiveEdge* lastActive = activeEdges.end();
734 while (++activePtr != lastActive) {
735 const InEdge* test = activePtr->fWorkEdge.fEdge;
736 if (!test->fContainsIntercepts) {
737 continue;
738 }
739 WorkEdge wt;
740 wt.init(test);
741 do {
742 // FIXME: add all curve types
743 const Intercepts& intercepts = test->fIntercepts[wt.verbIndex()];
744 const SkTDArray<double>& fTs = intercepts.fTs;
745 size_t count = fTs.count();
746 for (size_t index = 0; index < count; ++index) {
747 if (wt.verb() == SkPath::kLine_Verb) {
748 SkScalar yIntercept = LineYAtT(wt.fPts, fTs[index]);
caryclark@google.com6008c6562012-02-15 22:01:16 +0000749 if (yIntercept > y && bottom > yIntercept) {
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000750 bottom = yIntercept;
751 }
752 }
753 }
754 } while (wt.next());
755 }
756}
757
758static SkScalar findBottom(InEdge** currentPtr,
759 InEdge** edgeListEnd, SkTDArray<ActiveEdge>& activeEdges, SkScalar y,
caryclark@google.com6008c6562012-02-15 22:01:16 +0000760 bool asFill, InEdge**& testPtr) {
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000761 InEdge* current = *currentPtr;
762 SkScalar bottom = current->fBounds.fBottom;
763
764 // find the list of edges that cross y
caryclark@google.com6008c6562012-02-15 22:01:16 +0000765 InEdge* test = *testPtr;
766 while (testPtr != edgeListEnd) {
767 SkScalar testTop = test->fBounds.fTop;
768 if (bottom <= testTop) {
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000769 break;
770 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000771 SkScalar testBottom = test->fBounds.fBottom;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000772 // OPTIMIZATION: Shortening the bottom is only interesting when filling
773 // and when the edge is to the left of a longer edge. If it's a framing
774 // edge, or part of the right, it won't effect the longer edges.
caryclark@google.com6008c6562012-02-15 22:01:16 +0000775 if (testTop > y) {
776 bottom = testTop;
777 break;
778 }
779 if (y < testBottom) {
780 if (bottom > testBottom) {
781 bottom = testBottom;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000782 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000783 addToActive(activeEdges, test);
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000784 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000785 test = *++testPtr;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000786 }
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000787 return bottom;
788}
789
790static void makeEdgeList(SkTArray<InEdge>& edges, InEdge& edgeSentinel,
791 SkTDArray<InEdge*>& edgeList) {
792 size_t edgeCount = edges.count();
793 if (edgeCount == 0) {
794 return;
795 }
796 for (size_t index = 0; index < edgeCount; ++index) {
797 *edgeList.append() = &edges[index];
798 }
799 edgeSentinel.fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
800 *edgeList.append() = &edgeSentinel;
801 ++edgeCount;
802 QSort<InEdge>(edgeList.begin(), edgeCount);
803}
804
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000805
806static void skipCoincidence(int lastWinding, int winding, int windingMask,
807 ActiveEdge* activePtr, ActiveEdge* firstCoincident) {
808 if (((lastWinding & windingMask) == 0) ^ (winding & windingMask) != 0) {
809 return;
810 }
811 if (lastWinding) {
812 activePtr->fSkip = false;
813 } else {
814 firstCoincident->fSkip = false;
815 }
816}
817
caryclark@google.com6008c6562012-02-15 22:01:16 +0000818static void sortHorizontal(SkTDArray<ActiveEdge>& activeEdges,
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000819 SkTDArray<ActiveEdge*>& edgeList, int windingMask) {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000820 size_t edgeCount = activeEdges.count();
821 if (edgeCount == 0) {
822 return;
823 }
824 size_t index;
825 for (index = 0; index < edgeCount; ++index) {
826 ActiveEdge& activeEdge = activeEdges[index];
827 activeEdge.calcLeft();
828 activeEdge.fSkip = false;
829 *edgeList.append() = &activeEdge;
830 }
831 QSort<ActiveEdge>(edgeList.begin(), edgeCount);
832 // remove coincident edges
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000833 // OPTIMIZE: remove edges? This is tricky because the current logic expects
834 // the winding count to be maintained while skipping coincident edges. In
835 // addition to removing the coincident edges, the remaining edges would need
836 // to have a different winding value, possibly different per intercept span.
837 int lastWinding = 0;
838 bool lastSkipped = false;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000839 ActiveEdge* activePtr = edgeList[0];
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000840 ActiveEdge* firstCoincident = NULL;
841 int winding = 0;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000842 for (index = 1; index < edgeCount; ++index) {
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000843 winding += activePtr->fWorkEdge.winding();
caryclark@google.com6008c6562012-02-15 22:01:16 +0000844 ActiveEdge* nextPtr = edgeList[index];
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000845 if (activePtr->fX == nextPtr->fX) {
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000846 if (!firstCoincident) {
847 firstCoincident = activePtr;
848 }
849 activePtr->fSkip = nextPtr->fSkip = lastSkipped = true;
850 } else if (lastSkipped) {
851 skipCoincidence(lastWinding, winding, windingMask, activePtr,
852 firstCoincident);
853 lastSkipped = false;
854 firstCoincident = NULL;
855 }
856 if (!lastSkipped) {
857 lastWinding = winding;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000858 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000859 activePtr = nextPtr;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000860 }
caryclark@google.com6b9cfb32012-02-16 21:24:41 +0000861 if (lastSkipped) {
862 winding += activePtr->fWorkEdge.winding();
863 skipCoincidence(lastWinding, winding, windingMask, activePtr,
864 firstCoincident);
865 }
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000866}
867
868// stitch edge and t range that satisfies operation
caryclark@google.com6008c6562012-02-15 22:01:16 +0000869static void stitchEdge(SkTDArray<ActiveEdge*>& edgeList, SkScalar y,
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000870 SkScalar bottom, int windingMask, OutEdgeBuilder& outBuilder) {
871 int winding = 0;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000872 ActiveEdge** activeHandle = edgeList.begin() - 1;
873 ActiveEdge** lastActive = edgeList.end();
caryclark@google.comc17972e2012-02-20 21:33:22 +0000874 if (fShowDebugf) {
875 SkDebugf("%s y=%g bottom=%g\n", __FUNCTION__, y, bottom);
876 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000877 while (++activeHandle != lastActive) {
878 ActiveEdge* activePtr = *activeHandle;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000879 const WorkEdge& wt = activePtr->fWorkEdge;
880 int lastWinding = winding;
881 winding += wt.winding();
caryclark@google.comc17972e2012-02-20 21:33:22 +0000882 int opener = (lastWinding & windingMask) == 0;
883 bool closer = (winding & windingMask) == 0;
884 SkASSERT(!opener | !closer);
885 bool inWinding = opener | closer;
886 opener <<= kOpenerVerbBitShift;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000887 do {
888 double currentT = activePtr->t();
caryclark@google.com6008c6562012-02-15 22:01:16 +0000889 SkASSERT(currentT < 1);
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000890 const SkPoint* points = wt.fPts;
891 bool last;
892 do {
893 last = activePtr->nextT();
894 double nextT = activePtr->t();
895 // FIXME: add all combinations of curve types
896 if (wt.verb() == SkPath::kLine_Verb) {
897 SkPoint clippedPts[2];
898 const SkPoint* clipped;
899 if (currentT * nextT != 0 || currentT + nextT != 1) {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000900 // OPTIMIZATION: if !inWinding, we only need
901 // clipped[1].fY
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000902 LineSubDivide(points, currentT, nextT, clippedPts);
903 clipped = clippedPts;
904 } else {
905 clipped = points;
906 }
caryclark@google.com6008c6562012-02-15 22:01:16 +0000907 if (inWinding && !activePtr->fSkip) {
caryclark@google.comc17972e2012-02-20 21:33:22 +0000908 if (fShowDebugf) {
909 SkDebugf("%s line %g,%g %g,%g\n", __FUNCTION__,
910 clipped[0].fX, clipped[0].fY,
911 clipped[1].fX, clipped[1].fY);
912 }
913 outBuilder.addLine(clipped, opener);
caryclark@google.com6008c6562012-02-15 22:01:16 +0000914 }
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000915 if (clipped[1].fY >= bottom) {
caryclark@google.com6008c6562012-02-15 22:01:16 +0000916 if (last) {
917 activePtr->next();
918 }
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000919 goto nextEdge;
920 }
921 }
922 currentT = nextT;
923 } while (!last);
924 } while (activePtr->next());
925nextEdge:
926 ;
927 }
928}
929
caryclark@google.comc6825902012-02-03 22:07:47 +0000930void simplify(const SkPath& path, bool asFill, SkPath& simple) {
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000931 // returns 1 for evenodd, -1 for winding, regardless of inverse-ness
932 int windingMask = (path.getFillType() & 1) ? 1 : -1;
933 simple.reset();
934 simple.setFillType(SkPath::kEvenOdd_FillType);
caryclark@google.comc6825902012-02-03 22:07:47 +0000935 // turn path into list of edges increasing in y
936 // if an edge is a quad or a cubic with a y extrema, note it, but leave it unbroken
937 // once we have a list, sort it, then walk the list (walk edges twice that have y extrema's on top)
938 // and detect crossings -- look for raw bounds that cross over, then tight bounds that cross
caryclark@google.com6680fb12012-02-07 22:10:51 +0000939 SkTArray<InEdge> edges;
940 InEdgeBuilder builder(path, asFill, edges);
caryclark@google.com6680fb12012-02-07 22:10:51 +0000941 SkTDArray<InEdge*> edgeList;
caryclark@google.com6680fb12012-02-07 22:10:51 +0000942 InEdge edgeSentinel;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000943 makeEdgeList(edges, edgeSentinel, edgeList);
caryclark@google.com6680fb12012-02-07 22:10:51 +0000944 InEdge** currentPtr = edgeList.begin();
caryclark@google.comc6825902012-02-03 22:07:47 +0000945 // walk the sorted edges from top to bottom, computing accumulated winding
caryclark@google.com6680fb12012-02-07 22:10:51 +0000946 SkTDArray<ActiveEdge> activeEdges;
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000947 OutEdgeBuilder outBuilder(asFill);
948 SkScalar y = (*currentPtr)->fBounds.fTop;
caryclark@google.comc6825902012-02-03 22:07:47 +0000949 do {
caryclark@google.com6680fb12012-02-07 22:10:51 +0000950 InEdge** lastPtr = currentPtr; // find the edge below the bottom of the first set
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000951 SkScalar bottom = findBottom(currentPtr, edgeList.end(),
952 activeEdges, y, asFill, lastPtr);
caryclark@google.comc17972e2012-02-20 21:33:22 +0000953 if (lastPtr > currentPtr) {
954 addBottomT(currentPtr, lastPtr, bottom);
955 addIntersectingTs(currentPtr, lastPtr);
956 computeInterceptBottom(activeEdges, y, bottom);
957 SkTDArray<ActiveEdge*> activeEdgeList;
958 sortHorizontal(activeEdges, activeEdgeList, windingMask);
959 stitchEdge(activeEdgeList, y, bottom, windingMask, outBuilder);
960 }
caryclark@google.comc6825902012-02-03 22:07:47 +0000961 y = bottom;
caryclark@google.com6008c6562012-02-15 22:01:16 +0000962 currentPtr = advanceEdges(activeEdges, currentPtr, lastPtr, y);
caryclark@google.comc6825902012-02-03 22:07:47 +0000963 } while (*currentPtr != &edgeSentinel);
caryclark@google.comc6825902012-02-03 22:07:47 +0000964 // assemble output path from string of pts, verbs
caryclark@google.comf8b000d2012-02-09 22:04:27 +0000965 outBuilder.bridge();
966 outBuilder.assemble(simple);
caryclark@google.comc6825902012-02-03 22:07:47 +0000967}
968
caryclark@google.com6008c6562012-02-15 22:01:16 +0000969static void testSimplifyCoincidentVertical() {
970 SkPath path, out;
971 path.setFillType(SkPath::kWinding_FillType);
972 path.addRect(10, 10, 30, 30);
973 path.addRect(10, 30, 30, 40);
974 simplify(path, true, out);
975 SkRect rect;
976 if (!out.isRect(&rect)) {
977 SkDebugf("%s expected rect\n", __FUNCTION__);
978 }
979 if (rect != SkRect::MakeLTRB(10, 10, 30, 40)) {
980 SkDebugf("%s expected union\n", __FUNCTION__);
981 }
982}
caryclark@google.comc6825902012-02-03 22:07:47 +0000983
caryclark@google.com6008c6562012-02-15 22:01:16 +0000984static void testSimplifyCoincidentHorizontal() {
985 SkPath path, out;
986 path.setFillType(SkPath::kWinding_FillType);
987 path.addRect(10, 10, 30, 30);
988 path.addRect(30, 10, 40, 30);
989 simplify(path, true, out);
990 SkRect rect;
991 if (!out.isRect(&rect)) {
992 SkDebugf("%s expected rect\n", __FUNCTION__);
993 }
994 if (rect != SkRect::MakeLTRB(10, 10, 40, 30)) {
995 SkDebugf("%s expected union\n", __FUNCTION__);
996 }
997}
998
999static void testSimplifyMulti() {
caryclark@google.comc6825902012-02-03 22:07:47 +00001000 SkPath path, out;
1001 path.setFillType(SkPath::kWinding_FillType);
1002 path.addRect(10, 10, 30, 30);
1003 path.addRect(20, 20, 40, 40);
1004 simplify(path, true, out);
caryclark@google.com6b9cfb32012-02-16 21:24:41 +00001005 SkPath expected;
1006 expected.setFillType(SkPath::kEvenOdd_FillType);
1007 expected.moveTo(10,10); // two cutout corners
1008 expected.lineTo(10,30);
1009 expected.lineTo(20,30);
1010 expected.lineTo(20,40);
1011 expected.lineTo(40,40);
1012 expected.lineTo(40,20);
1013 expected.lineTo(30,20);
1014 expected.lineTo(30,10);
1015 expected.lineTo(10,10);
1016 expected.close();
1017 if (out != expected) {
1018 SkDebugf("%s expected equal\n", __FUNCTION__);
1019 }
1020
caryclark@google.comc6825902012-02-03 22:07:47 +00001021 path = out;
1022 path.addRect(30, 10, 40, 20);
1023 path.addRect(10, 30, 20, 40);
1024 simplify(path, true, out);
caryclark@google.com6b9cfb32012-02-16 21:24:41 +00001025 SkRect rect;
1026 if (!out.isRect(&rect)) {
1027 SkDebugf("%s expected rect\n", __FUNCTION__);
1028 }
1029 if (rect != SkRect::MakeLTRB(10, 10, 40, 40)) {
1030 SkDebugf("%s expected union\n", __FUNCTION__);
1031 }
1032
caryclark@google.comc6825902012-02-03 22:07:47 +00001033 path = out;
1034 path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
1035 simplify(path, true, out);
1036 if (!out.isEmpty()) {
caryclark@google.com6008c6562012-02-15 22:01:16 +00001037 SkDebugf("%s expected empty\n", __FUNCTION__);
caryclark@google.comc6825902012-02-03 22:07:47 +00001038 }
1039}
caryclark@google.com6008c6562012-02-15 22:01:16 +00001040
1041static void testSimplifyAddL() {
1042 SkPath path, out;
1043 path.moveTo(10,10); // 'L' shape
1044 path.lineTo(10,40);
1045 path.lineTo(40,40);
1046 path.lineTo(40,20);
1047 path.lineTo(30,20);
1048 path.lineTo(30,10);
1049 path.lineTo(10,10);
1050 path.close();
1051 path.addRect(30, 10, 40, 20); // missing notch of 'L'
1052 simplify(path, true, out);
1053 SkRect rect;
1054 if (!out.isRect(&rect)) {
1055 SkDebugf("%s expected rect\n", __FUNCTION__);
1056 }
1057 if (rect != SkRect::MakeLTRB(10, 10, 40, 40)) {
1058 SkDebugf("%s expected union\n", __FUNCTION__);
1059 }
1060}
1061
caryclark@google.com6b9cfb32012-02-16 21:24:41 +00001062static void testSimplifyCoincidentCCW() {
1063 SkPath path, out;
1064 path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
1065 path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
1066 simplify(path, true, out);
1067 SkRect rect;
1068 if (!out.isRect(&rect)) {
1069 SkDebugf("%s expected rect\n", __FUNCTION__);
1070 }
1071 if (rect != SkRect::MakeLTRB(10, 10, 40, 40)) {
1072 SkDebugf("%s expected union\n", __FUNCTION__);
1073 }
1074}
1075
1076static void testSimplifyCoincidentCW() {
1077 SkPath path, out;
1078 path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
1079 path.addRect(10, 10, 40, 40, SkPath::kCW_Direction);
1080 simplify(path, true, out);
1081 if (!out.isEmpty()) {
1082 SkDebugf("%s expected empty\n", __FUNCTION__);
1083 }
1084}
1085
caryclark@google.comc17972e2012-02-20 21:33:22 +00001086static void testSimplifyCorner() {
1087 SkPath path, out;
1088 path.addRect(10, 10, 20, 20, SkPath::kCCW_Direction);
1089 path.addRect(20, 20, 40, 40, SkPath::kCW_Direction);
1090 simplify(path, true, out);
1091 SkTDArray<SkRect> boundsArray;
1092 contourBounds(out, boundsArray);
1093 if (boundsArray.count() != 2) {
1094 SkDebugf("%s expected 2 contours\n", __FUNCTION__);
1095 return;
1096 }
1097 SkRect one = SkRect::MakeLTRB(10, 10, 20, 20);
1098 SkRect two = SkRect::MakeLTRB(20, 20, 40, 40);
1099 if (boundsArray[0] != one && boundsArray[0] != two
1100 || boundsArray[1] != one && boundsArray[1] != two) {
1101 SkDebugf("%s expected match\n", __FUNCTION__);
1102 }
1103}
1104
1105// non-intersecting test points, two equal sized rectangles
1106static void lookForFailingTests(const SkPoint* pts, size_t ptsSize, int width,
1107 int height, const SkRect& center) {
1108 size_t index = 0;
1109 for ( ; index < ptsSize; ++index) {
1110 SkPath path, out;
1111 path.addRect(center);
1112 SkRect test = SkRect::MakeXYWH(pts[index].fX,
1113 pts[index].fY, width, height);
1114 path.addRect(test);
1115 simplify(path, true, out);
1116 SkPath::Iter iter(out, false);
1117 SkPoint pts[2];
1118 SkRect bounds[2];
1119 bounds[0].setEmpty();
1120 bounds[1].setEmpty();
1121 SkRect* boundsPtr = bounds;
1122 int count = 0;
1123 SkPath::Verb verb;
1124 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1125 switch (verb) {
1126 case SkPath::kMove_Verb:
1127 if (!boundsPtr->isEmpty()) {
1128 SkASSERT(boundsPtr == bounds);
1129 ++boundsPtr;
1130 }
1131 boundsPtr->set(pts[0].fX, pts[0].fY, pts[0].fX, pts[0].fY);
1132 count = 0;
1133 break;
1134 case SkPath::kLine_Verb:
1135 count = 1;
1136 break;
1137 case SkPath::kClose_Verb:
1138 count = 0;
1139 break;
1140 default:
1141 SkDEBUGFAIL("bad verb");
1142 return;
1143 }
1144 for (int i = 1; i <= count; ++i) {
1145 boundsPtr->growToInclude(pts[i].fX, pts[i].fY);
1146 }
1147 }
1148 SkASSERT(bounds[0] == center && bounds[1] == test
1149 || bounds[1] == center && bounds[0] == test);
1150 }
1151}
1152
1153static void twoEqualRects() {
1154 SkPoint pts[] = {
1155 { 0, 0}, {10, 0}, {20, 0}, {30, 0}, {40, 0}, {50, 0}, {60, 0}, // above
1156 { 0, 10}, { 0, 20}, { 0, 30}, { 0, 40}, { 0, 50}, { 0, 60}, // left
1157 {10, 60}, {20, 60}, {30, 60}, {40, 60}, {50, 60}, // below
1158 {60, 10}, {60, 20}, {60, 30}, {60, 40}, {60, 50}, {60, 60}, // right
1159 };
1160 size_t ptsCount = sizeof(pts) / sizeof(pts[0]);
1161 SkRect center = SkRect::MakeLTRB(30, 30, 50, 50);
1162 lookForFailingTests(pts, ptsCount, 20, 20, center);
1163}
1164
1165static void largerOuter() {
1166 SkRect center = SkRect::MakeLTRB(50, 50, 70, 70);
1167 const size_t count = 9;
1168 SkPoint pts[count];
1169 size_t index;
1170 for (index = 0; index < count; ++index) { // above
1171 pts[index].fX = index * 10;
1172 pts[index].fY = 0;
1173 }
1174 lookForFailingTests(pts, count, 40, 20, center);
1175 for (index = 0; index < count; ++index) { // left
1176 pts[index].fX = 0;
1177 pts[index].fY = index * 10;
1178 }
1179 lookForFailingTests(pts, count, 20, 40, center);
1180 for (index = 0; index < count; ++index) { // below
1181 pts[index].fX = index * 10;
1182 pts[index].fY = 80;
1183 }
1184 lookForFailingTests(pts, count, 40, 20, center);
1185 for (index = 0; index < count; ++index) { // right
1186 pts[index].fX = 80;
1187 pts[index].fY = index * 10;
1188 }
1189 lookForFailingTests(pts, count, 20, 40, center);
1190}
1191
1192static void smallerOuter() {
1193 SkPoint pts[] = {
1194 { 0, 0}, {10, 0}, {20, 0}, {30, 0}, {40, 0}, {50, 0}, {60, 0}, // above
1195 { 0, 10}, { 0, 20}, { 0, 30}, { 0, 40}, { 0, 50}, { 0, 60}, // left
1196 {10, 60}, {20, 60}, {30, 60}, {40, 60}, {50, 60}, // below
1197 {60, 10}, {60, 20}, {60, 30}, {60, 40}, {60, 50}, {60, 60}, // right
1198 };
1199 size_t ptsCount = sizeof(pts) / sizeof(pts[0]);
1200 SkRect center = SkRect::MakeLTRB(20, 20, 50, 50);
1201 lookForFailingTests(pts, ptsCount, 10, 10, center);
1202}
1203
caryclark@google.com6008c6562012-02-15 22:01:16 +00001204void testSimplify();
1205
1206void (*simplifyTests[])() = {
caryclark@google.comc17972e2012-02-20 21:33:22 +00001207 testSimplifyCorner,
caryclark@google.com6b9cfb32012-02-16 21:24:41 +00001208 testSimplifyCoincidentCW,
1209 testSimplifyCoincidentCCW,
1210 testSimplifyCoincidentVertical,
1211 testSimplifyCoincidentHorizontal,
1212 testSimplifyAddL,
1213 testSimplifyMulti,
caryclark@google.com6008c6562012-02-15 22:01:16 +00001214};
1215
1216size_t simplifyTestsCount = sizeof(simplifyTests) / sizeof(simplifyTests[0]);
1217
caryclark@google.comc17972e2012-02-20 21:33:22 +00001218static void (*firstTest)() = 0;
1219static bool lookForFailing = false;
caryclark@google.com6008c6562012-02-15 22:01:16 +00001220
1221void testSimplify() {
caryclark@google.comc17972e2012-02-20 21:33:22 +00001222/* look for failing test cases */
1223 if (lookForFailing) {
1224// rects do not touch
1225 twoEqualRects();
1226 largerOuter();
1227 smallerOuter();
1228 }
caryclark@google.com6b9cfb32012-02-16 21:24:41 +00001229 size_t index = 0;
1230 if (firstTest) {
1231 while (index < simplifyTestsCount && simplifyTests[index] != firstTest) {
1232 ++index;
1233 }
1234 }
1235 for ( ; index < simplifyTestsCount; ++index) {
caryclark@google.com6008c6562012-02-15 22:01:16 +00001236 (*simplifyTests[index])();
1237 }
1238}
1239
1240