blob: bf090e7bd0bd0f7e6475d52bb72e9d286cfdb3ab [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
9#include "LineIntersection.h"
10#include "SkPath.h"
11#include "SkRect.h"
12#include "SkTArray.h"
13#include "SkTDArray.h"
14#include "TSearch.h"
15
16static int lineIntersect(const SkPoint a[2], const SkPoint b[2],
17 double aRange[2], double bRange[2]) {
18 _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
19 _Line bLine = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}};
20 return intersect(aLine, bLine, aRange, bRange);
21}
22
23static int lineIntersect(const SkPoint a[2], SkScalar y, double aRange[2]) {
24 _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
25 return horizontalIntersect(aLine, y, aRange);
26}
27
28// functions
29void contourBounds(const SkPath& path, SkTDArray<SkRect>& boundsArray);
30void simplify(const SkPath& path, bool asFill, SkPath& simple);
31/*
32list of edges
33bounds for edge
34sort
35active T
36
37if a contour's bounds is outside of the active area, no need to create edges
38*/
39
40/* given one or more paths,
41 find the bounds of each contour, select the active contours
42 for each active contour, compute a set of edges
43 each edge corresponds to one or more lines and curves
44 leave edges unbroken as long as possible
45 when breaking edges, compute the t at the break but leave the control points alone
46
47 */
48
49void contourBounds(const SkPath& path, SkTDArray<SkRect>& boundsArray) {
50 SkPath::Iter iter(path, false);
51 SkPoint pts[4];
52 SkPath::Verb verb;
53 SkRect bounds;
54 bounds.setEmpty();
55 int count = 0;
56 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
57 switch (verb) {
58 case SkPath::kMove_Verb:
59 if (!bounds.isEmpty()) {
60 *boundsArray.append() = bounds;
61 }
62 bounds.set(pts[0].fX, pts[0].fY, pts[0].fX, pts[0].fY);
63 count = 0;
64 break;
65 case SkPath::kLine_Verb:
66 count = 1;
67 break;
68 case SkPath::kQuad_Verb:
69 count = 2;
70 break;
71 case SkPath::kCubic_Verb:
72 count = 3;
73 break;
74 case SkPath::kClose_Verb:
75 count = 0;
76 break;
77 default:
78 SkDEBUGFAIL("bad verb");
79 return;
80 }
81 for (int i = 1; i <= count; ++i) {
82 bounds.growToInclude(pts[i].fX, pts[i].fY);
83 }
84 }
85}
86
87// Bounds, unlike Rect, does not consider a vertical line to be empty.
88struct Bounds : public SkRect {
89 static bool Intersects(const Bounds& a, const Bounds& b) {
90 return a.fLeft <= b.fRight && b.fLeft <= a.fRight &&
91 a.fTop <= b.fBottom && b.fTop <= a.fBottom;
92 }
93};
94
95struct Edge;
96
97struct Intercepts {
98 SkTDArray<double> fTs;
99};
100
101struct Edge {
102 bool operator<(const Edge& rh) const {
103 return fBounds.fTop == rh.fBounds.fTop
104 ? fBounds.fLeft < rh.fBounds.fLeft
105 : fBounds.fTop < rh.fBounds.fTop;
106 }
107
108 void add(double* ts, size_t count, int verbIndex) {
109 Intercepts& intercepts = fIntercepts[verbIndex];
110 // FIXME: in the pathological case where there is a ton of intercepts, binary search?
111 for (size_t index = 0; index < count; ++index) {
112 double t = ts[index];
113 size_t tCount = intercepts.fTs.count();
114 for (size_t idx2 = 0; idx2 < tCount; ++idx2) {
115 if (t <= intercepts.fTs[idx2]) {
116 if (t < intercepts.fTs[idx2]) {
117 *intercepts.fTs.insert(idx2) = t;
118 break;
119 }
120 }
121 }
122 if (tCount == 0 || t > intercepts.fTs[tCount - 1]) {
123 *intercepts.fTs.append() = t;
124 }
125 }
126 }
127
128 bool cached(const Edge* edge) {
129 // FIXME: in the pathological case where there is a ton of edges, binary search?
130 size_t count = fCached.count();
131 for (size_t index = 0; index < count; ++index) {
132 if (edge == fCached[index]) {
133 return true;
134 }
135 if (edge < fCached[index]) {
136 *fCached.insert(index) = edge;
137 return false;
138 }
139 }
140 *fCached.append() = edge;
141 return false;
142 }
143
144 void complete(signed char winding) {
145 SkPoint* ptPtr = fPts.begin();
146 SkPoint* ptLast = fPts.end();
147 if (ptPtr == ptLast) {
148 SkDebugf("empty edge\n");
149 SkASSERT(0);
150 // FIXME: delete empty edge?
151 return;
152 }
153 fBounds.set(ptPtr->fX, ptPtr->fY, ptPtr->fX, ptPtr->fY);
154 ++ptPtr;
155 while (ptPtr != ptLast) {
156 fBounds.growToInclude(ptPtr->fX, ptPtr->fY);
157 ++ptPtr;
158 }
159 fIntercepts.push_back_n(1);
160 fWinding = winding;
161 }
162
163 // temporary data : move this to a separate struct?
164 SkTDArray<const Edge*> fCached; // list of edges already intercepted
165 SkTArray<Intercepts> fIntercepts; // one per verb
166
167
168 // persistent data
169 SkTDArray<SkPoint> fPts;
170 SkTDArray<uint8_t> fVerbs;
171 Bounds fBounds;
172 signed char fWinding;
173};
174
175class EdgeBuilder {
176public:
177
178EdgeBuilder(const SkPath& path, bool ignoreHorizontal, SkTArray<Edge>& edges)
179 : fPath(path)
180 , fCurrentEdge(NULL)
181 , fEdges(edges)
182 , fIgnoreHorizontal(ignoreHorizontal)
183{
184 walk();
185}
186
187protected:
188
189void addEdge() {
190 fCurrentEdge->fPts.append(fPtCount - fPtOffset, &fPts[fPtOffset]);
191 fPtOffset = 1;
192 *fCurrentEdge->fVerbs.append() = fVerb;
193}
194
195int direction(int count) {
196 fPtCount = count;
197 fIgnorableHorizontal = fIgnoreHorizontal && isHorizontal();
198 if (fIgnorableHorizontal) {
199 return 0;
200 }
201 int last = count - 1;
202 return fPts[0].fY == fPts[last].fY
203 ? fPts[0].fX == fPts[last].fX ? 0 : fPts[0].fX > fPts[last].fX
204 ? 1 : -1 : fPts[0].fY > fPts[last].fY ? 1 : -1;
205}
206
207bool isHorizontal() {
208 SkScalar y = fPts[0].fY;
209 for (int i = 1; i < fPtCount; ++i) {
210 if (fPts[i].fY != y) {
211 return false;
212 }
213 }
214 return true;
215}
216
217void startEdge() {
218 fCurrentEdge = fEdges.push_back_n(1);
219 fWinding = 0;
220 fPtOffset = 0;
221}
222
223void walk() {
224 SkPath::Iter iter(fPath, true);
225 int winding = 0;
226 while ((fVerb = iter.next(fPts)) != SkPath::kDone_Verb) {
227 switch (fVerb) {
228 case SkPath::kMove_Verb:
229 winding = 0;
230 startEdge();
231 continue;
232 case SkPath::kLine_Verb:
233 winding = direction(2);
234 break;
235 case SkPath::kQuad_Verb:
236 winding = direction(3);
237 break;
238 case SkPath::kCubic_Verb:
239 winding = direction(4);
240 break;
241 case SkPath::kClose_Verb:
242 if (fCurrentEdge->fVerbs.count()) {
243 fCurrentEdge->complete(fWinding);
244 }
245 continue;
246 default:
247 SkDEBUGFAIL("bad verb");
248 return;
249 }
250 if (fIgnorableHorizontal) {
251 continue;
252 }
253 if (fWinding + winding == 0) {
254 // FIXME: if prior verb or this verb is a horizontal line, reverse
255 // it instead of starting a new edge
256 fCurrentEdge->complete(fWinding);
257 startEdge();
258 }
259 fWinding = winding;
260 addEdge();
261 }
262 fCurrentEdge->complete(fWinding);
263}
264
265private:
266 const SkPath& fPath;
267 Edge* fCurrentEdge;
268 SkTArray<Edge>& fEdges;
269 SkPoint fPts[4];
270 SkPath::Verb fVerb;
271 int fPtCount;
272 int fPtOffset;
273 int8_t fWinding;
274 bool fIgnorableHorizontal;
275 bool fIgnoreHorizontal;
276};
277
278class WorkEdge {
279public:
280 WorkEdge(const Edge* edge) {
281 fVerbStart = edge->fVerbs.begin();
282 if ((fWinding = edge->fWinding) > 0) {
283 fPts = edge->fPts.begin();
284 fVerb = fVerbStart;
285 fVerbEnd = edge->fVerbs.end();
286 } else {
287 fPts = edge->fPts.end();
288 fVerb = edge->fVerbs.end();
289 fVerbEnd = fVerbStart;
290 next();
291 }
292 }
293
294 SkScalar bottom() const {
295 return fPts[fWinding > 0 ? verb() : 0].fY;
296 }
297
298 bool next() {
299 if (fWinding > 0) {
300 fPts += *fVerb;
301 return ++fVerb != fVerbEnd;
302 } else {
303 if (fVerb == fVerbEnd) {
304 return false;
305 }
306 fPts -= *--fVerb;
307 return true;
308 }
309 }
310
311 const SkPoint* points() const {
312 return fPts;
313 }
314
315 SkPath::Verb verb() const {
316 return (SkPath::Verb) *fVerb;
317 }
318
319 int verbIndex() const {
320 return fVerb - fVerbStart;
321 }
322
323protected:
324 const SkPoint* fPts;
325 const uint8_t* fVerb;
326 const uint8_t* fVerbEnd;
327 const uint8_t* fVerbStart;
328 int8_t fWinding;
329};
330
331struct ActiveEdge {
332 void init(const Edge* test) {
333 fEdge = test;
334 if (!fEdge->fIntercepts.count()) {
335 fBounds = test->fBounds;
336 fPtStart = 0;
337 fPtEnd = test->fPts.count();
338 fVerbStart = 0;
339 fVerbEnd = test->fVerbs.count();
340 fTStart = 0;
341 fTEnd = SK_Scalar1;
342 } else {
343 // FIXME: initialize from intercepts
344
345 }
346 }
347
348 const Edge* fEdge;
349 SkRect fBounds;
350 int fPtStart;
351 int fPtEnd;
352 int fVerbStart;
353 int fVerbEnd;
354 SkScalar fTStart;
355 SkScalar fTEnd;
356};
357
358void simplify(const SkPath& path, bool asFill, SkPath& simple) {
359 // turn path into list of edges increasing in y
360 // if an edge is a quad or a cubic with a y extrema, note it, but leave it unbroken
361 // once we have a list, sort it, then walk the list (walk edges twice that have y extrema's on top)
362 // and detect crossings -- look for raw bounds that cross over, then tight bounds that cross
363 SkTArray<Edge> edges;
364 EdgeBuilder builder(path, asFill, edges);
365 size_t edgeCount = edges.count();
366 simple.reset();
367 if (edgeCount == 0) {
368 return;
369 }
370 // returns 1 for evenodd, -1 for winding, regardless of inverse-ness
371 int windingMask = (path.getFillType() & 1) ? 1 : -1;
372 SkTDArray<Edge*> edgeList;
373 for (size_t index = 0; index < edgeCount; ++index) {
374 *edgeList.append() = &edges[index];
375 }
376 Edge edgeSentinel;
377 edgeSentinel.fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
378 *edgeList.append() = &edgeSentinel;
379 ++edgeCount;
380 QSort<Edge>(edgeList.begin(), edgeCount);
381 Edge** currentPtr = edgeList.begin();
382 Edge* current = *currentPtr;
383 SkScalar y = current->fBounds.fTop;
384 SkScalar bottom = current->fBounds.fBottom;
385 // walk the sorted edges from top to bottom, computing accumulated winding
386 do {
387 // find the list of edges that cross y
388 Edge** lastPtr = currentPtr; // find the edge below the bottom of the first set
389 Edge* last = *lastPtr;
390 while (lastPtr != edgeList.end()) {
391 if (bottom <= last->fBounds.fTop) {
392 break;
393 }
394 SkScalar lastTop = last->fBounds.fTop;
395 // OPTIMIZATION: Shortening the bottom is only interesting when filling
396 // and when the edge is to the left of a longer edge. If it's a framing
397 // edge, or part of the right, it won't effect the longer edges.
398 if (lastTop > y) {
399 if (bottom > lastTop) {
400 bottom = lastTop;
401 break;
402 }
403 } else if (bottom > last->fBounds.fBottom) {
404 bottom = last->fBounds.fBottom;
405 }
406 last = *++lastPtr;
407 }
408 if (asFill && lastPtr - currentPtr <= 1) {
409 SkDebugf("expect 2 or more edges\n");
410 SkASSERT(0);
411 return;
412 }
413 // find any intersections in the range of active edges
414 Edge** testPtr = currentPtr;
415 Edge* test = *testPtr;
416 while (testPtr != lastPtr) {
417 if (test->fBounds.fBottom > bottom) {
418 WorkEdge wt(test);
419 do {
420 // FIXME: add all combinations of curve types
421 if (wt.verb() == SkPath::kLine_Verb) {
422 double wtTs[2];
423 int pts = lineIntersect(wt.points(), bottom, wtTs);
424 if (pts) {
425 test->add(wtTs, pts, wt.verbIndex());
426 }
427 }
428 } while (wt.next());
429 }
430 test = *++testPtr;
431 }
432 testPtr = currentPtr;
433 test = *testPtr;
434 while (testPtr != lastPtr - 1) {
435 Edge* next = *++testPtr;
436 // OPTIMIZATION: if test and next is inside the winding of outer
437 // edges such that intersecting them is irrelevent, skip them.
438 if (!test->cached(next)
439 && Bounds::Intersects(test->fBounds, next->fBounds)) {
440 WorkEdge wt(test);
441 WorkEdge wn(next);
442 do {
443 // FIXME: add all combinations of curve types
444 if (wt.verb() == SkPath::kLine_Verb && wn.verb() == SkPath::kLine_Verb) {
445 double wtTs[2], wnTs[2];
446 int pts = lineIntersect(wt.points(), wn.points(), wtTs, wnTs);
447 if (pts) {
448 test->add(wtTs, pts, wt.verbIndex());
449 next->add(wnTs, pts, wn.verbIndex());
450 }
451 }
452 } while (wt.bottom() <= wn.bottom() ? wt.next() : wn.next());
453 }
454 test = next;
455 }
456 // stitch edge and t range that satisfies operation
457 int winding = 0;
458 testPtr = currentPtr;
459 test = *testPtr;
460 while (testPtr != lastPtr - 1) {
461 int lastWinding = winding;
462 winding += test->fWinding;
463 if ((lastWinding & windingMask) == 0 || (winding & windingMask) == 0) {
464 // append pts, verbs, in front of or behind output
465 // a verb may have one or more inter-T value, but only break
466 // curve if curve at t changes winding inclusion
467 ;
468 }
469 test = *++testPtr;
470 }
471 y = bottom;
472 while ((*currentPtr)->fBounds.fBottom >= y) {
473 ++currentPtr;
474 }
475 } while (*currentPtr != &edgeSentinel);
476 // assemble output path from string of pts, verbs
477 ;
478}
479
480void testSimplify();
481
482void testSimplify() {
483 SkPath path, out;
484 path.setFillType(SkPath::kWinding_FillType);
485 path.addRect(10, 10, 30, 30);
486 path.addRect(20, 20, 40, 40);
487 simplify(path, true, out);
488 path = out;
489 path.addRect(30, 10, 40, 20);
490 path.addRect(10, 30, 20, 40);
491 simplify(path, true, out);
492 path = out;
493 path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
494 simplify(path, true, out);
495 if (!out.isEmpty()) {
496 SkDebugf("expected empty\n");
497 }
498}