blob: 130d4983f9cc2d728ebe80ade042a0b0ccee1dbf [file] [log] [blame]
caryclark@google.com07393ca2013-04-08 11:47:37 +00001/*
2 * Copyright 2012 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7#include "SkAddIntersections.h"
8#include "SkOpEdgeBuilder.h"
9#include "SkPathOpsCommon.h"
10#include "SkPathWriter.h"
11
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000012static SkOpSegment* findChaseOp(SkTDArray<SkOpSpan*>& chase, int* tIndex, int* endIndex) {
caryclark@google.com07393ca2013-04-08 11:47:37 +000013 while (chase.count()) {
14 SkOpSpan* span;
15 chase.pop(&span);
16 const SkOpSpan& backPtr = span->fOther->span(span->fOtherIndex);
17 SkOpSegment* segment = backPtr.fOther;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000018 *tIndex = backPtr.fOtherIndex;
19 bool sortable = true;
20 bool done = true;
21 *endIndex = -1;
22 if (const SkOpAngle* last = segment->activeAngle(*tIndex, tIndex, endIndex, &done,
23 &sortable)) {
24 *tIndex = last->start();
25 *endIndex = last->end();
caryclark@google.com07393ca2013-04-08 11:47:37 +000026 #if TRY_ROTATE
27 *chase.insert(0) = span;
28 #else
29 *chase.append() = span;
30 #endif
31 return last->segment();
32 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000033 if (done) {
caryclark@google.com07393ca2013-04-08 11:47:37 +000034 continue;
35 }
caryclark@google.com07393ca2013-04-08 11:47:37 +000036 if (!sortable) {
37 continue;
38 }
39 // find first angle, initialize winding to computed fWindSum
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000040 const SkOpAngle* angle = segment->spanToAngle(*tIndex, *endIndex);
41 const SkOpAngle* firstAngle = angle;
42 SkDEBUGCODE(bool loop = false);
43 int winding;
caryclark@google.com07393ca2013-04-08 11:47:37 +000044 do {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000045 angle = angle->next();
46 SkASSERT(angle != firstAngle || !loop);
47 SkDEBUGCODE(loop |= angle == firstAngle);
caryclark@google.com07393ca2013-04-08 11:47:37 +000048 segment = angle->segment();
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000049 winding = segment->windSum(angle);
50 } while (winding == SK_MinS32);
caryclark@google.com07393ca2013-04-08 11:47:37 +000051 int sumMiWinding = segment->updateWindingReverse(angle);
52 int sumSuWinding = segment->updateOppWindingReverse(angle);
53 if (segment->operand()) {
54 SkTSwap<int>(sumMiWinding, sumSuWinding);
55 }
caryclark@google.com07393ca2013-04-08 11:47:37 +000056 SkOpSegment* first = NULL;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000057 while ((angle = angle->next()) != firstAngle) {
caryclark@google.com07393ca2013-04-08 11:47:37 +000058 segment = angle->segment();
59 int start = angle->start();
60 int end = angle->end();
61 int maxWinding, sumWinding, oppMaxWinding, oppSumWinding;
62 segment->setUpWindings(start, end, &sumMiWinding, &sumSuWinding,
63 &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding);
64 if (!segment->done(angle)) {
65 if (!first) {
66 first = segment;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000067 *tIndex = start;
68 *endIndex = end;
caryclark@google.com07393ca2013-04-08 11:47:37 +000069 }
70 (void) segment->markAngle(maxWinding, sumWinding, oppMaxWinding,
commit-bot@chromium.org866f4e32013-11-21 17:04:29 +000071 oppSumWinding, angle);
caryclark@google.com07393ca2013-04-08 11:47:37 +000072 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000073 }
caryclark@google.com07393ca2013-04-08 11:47:37 +000074 if (first) {
75 #if TRY_ROTATE
76 *chase.insert(0) = span;
77 #else
78 *chase.append() = span;
79 #endif
80 return first;
81 }
82 }
83 return NULL;
84}
85
86/*
87static bool windingIsActive(int winding, int oppWinding, int spanWinding, int oppSpanWinding,
88 bool windingIsOp, PathOp op) {
89 bool active = windingIsActive(winding, spanWinding);
90 if (!active) {
91 return false;
92 }
93 if (oppSpanWinding && windingIsActive(oppWinding, oppSpanWinding)) {
94 switch (op) {
95 case kIntersect_Op:
96 case kUnion_Op:
97 return true;
98 case kDifference_Op: {
99 int absSpan = abs(spanWinding);
100 int absOpp = abs(oppSpanWinding);
101 return windingIsOp ? absSpan < absOpp : absSpan > absOpp;
102 }
103 case kXor_Op:
104 return spanWinding != oppSpanWinding;
105 default:
106 SkASSERT(0);
107 }
108 }
109 bool opActive = oppWinding != 0;
110 return gOpLookup[op][opActive][windingIsOp];
111}
112*/
113
caryclark@google.comd892bd82013-06-17 14:10:36 +0000114static bool bridgeOp(SkTArray<SkOpContour*, true>& contourList, const SkPathOp op,
caryclark@google.com07393ca2013-04-08 11:47:37 +0000115 const int xorMask, const int xorOpMask, SkPathWriter* simple) {
116 bool firstContour = true;
117 bool unsortable = false;
118 bool topUnsortable = false;
119 SkPoint topLeft = {SK_ScalarMin, SK_ScalarMin};
120 do {
121 int index, endIndex;
122 bool done;
caryclark@google.com570863f2013-09-16 15:55:01 +0000123 SkOpSegment* current = FindSortableTop(contourList, SkOpAngle::kBinarySingle, &firstContour,
124 &index, &endIndex, &topLeft, &topUnsortable, &done);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000125 if (!current) {
126 if (topUnsortable || !done) {
127 topUnsortable = false;
128 SkASSERT(topLeft.fX != SK_ScalarMin && topLeft.fY != SK_ScalarMin);
129 topLeft.fX = topLeft.fY = SK_ScalarMin;
130 continue;
131 }
132 break;
133 }
134 SkTDArray<SkOpSpan*> chaseArray;
135 do {
136 if (current->activeOp(index, endIndex, xorMask, xorOpMask, op)) {
137 do {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000138 if (!unsortable && current->done()) {
caryclark@google.coma5e55922013-05-07 18:51:31 +0000139 if (simple->isEmpty()) {
140 simple->init();
caryclark@google.coma5e55922013-05-07 18:51:31 +0000141 }
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000142 break;
caryclark@google.coma5e55922013-05-07 18:51:31 +0000143 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000144 SkASSERT(unsortable || !current->done());
145 int nextStart = index;
146 int nextEnd = endIndex;
147 SkOpSegment* next = current->findNextOp(&chaseArray, &nextStart, &nextEnd,
148 &unsortable, op, xorMask, xorOpMask);
149 if (!next) {
150 if (!unsortable && simple->hasMove()
151 && current->verb() != SkPath::kLine_Verb
152 && !simple->isClosed()) {
153 current->addCurveTo(index, endIndex, simple, true);
154 SkASSERT(simple->isClosed());
155 }
156 break;
157 }
158 #if DEBUG_FLOW
159 SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__,
160 current->debugID(), current->xyAtT(index).fX, current->xyAtT(index).fY,
161 current->xyAtT(endIndex).fX, current->xyAtT(endIndex).fY);
162 #endif
163 current->addCurveTo(index, endIndex, simple, true);
164 current = next;
165 index = nextStart;
166 endIndex = nextEnd;
caryclark@google.coma5e55922013-05-07 18:51:31 +0000167 } while (!simple->isClosed() && (!unsortable
caryclark@google.com07393ca2013-04-08 11:47:37 +0000168 || !current->done(SkMin32(index, endIndex))));
169 if (current->activeWinding(index, endIndex) && !simple->isClosed()) {
caryclark@google.com570863f2013-09-16 15:55:01 +0000170 // FIXME : add to simplify, xor cpaths
caryclark@google.com07393ca2013-04-08 11:47:37 +0000171 int min = SkMin32(index, endIndex);
caryclark@google.com570863f2013-09-16 15:55:01 +0000172 if (!unsortable && !simple->isEmpty()) {
173 unsortable = current->checkSmall(min);
174 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000175 if (!current->done(min)) {
176 current->addCurveTo(index, endIndex, simple, true);
177 current->markDoneBinary(min);
178 }
179 }
180 simple->close();
181 } else {
182 SkOpSpan* last = current->markAndChaseDoneBinary(index, endIndex);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000183 if (last && !last->fChased && !last->fLoop) {
184 last->fChased = true;
185 SkASSERT(!SkPathOpsDebug::ChaseContains(chaseArray, last));
caryclark@google.com07393ca2013-04-08 11:47:37 +0000186 *chaseArray.append() = last;
187 }
188 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000189 current = findChaseOp(chaseArray, &index, &endIndex);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000190 #if DEBUG_ACTIVE_SPANS
191 DebugShowActiveSpans(contourList);
192 #endif
193 if (!current) {
194 break;
195 }
196 } while (true);
197 } while (true);
198 return simple->someAssemblyRequired();
199}
200
caryclark@google.com7dfbb072013-04-22 14:37:05 +0000201// pretty picture:
202// https://docs.google.com/a/google.com/drawings/d/1sPV8rPfpEFXymBp3iSbDRWAycp1b-7vD9JP2V-kn9Ss/edit?usp=sharing
203static const SkPathOp gOpInverse[kReverseDifference_PathOp + 1][2][2] = {
204// inside minuend outside minuend
205// inside subtrahend outside subtrahend inside subtrahend outside subtrahend
206 {{ kDifference_PathOp, kIntersect_PathOp }, { kUnion_PathOp, kReverseDifference_PathOp }},
207 {{ kIntersect_PathOp, kDifference_PathOp }, { kReverseDifference_PathOp, kUnion_PathOp }},
208 {{ kUnion_PathOp, kReverseDifference_PathOp }, { kDifference_PathOp, kIntersect_PathOp }},
209 {{ kXOR_PathOp, kXOR_PathOp }, { kXOR_PathOp, kXOR_PathOp }},
210 {{ kReverseDifference_PathOp, kUnion_PathOp }, { kIntersect_PathOp, kDifference_PathOp }},
211};
212
213static const bool gOutInverse[kReverseDifference_PathOp + 1][2][2] = {
214 {{ false, false }, { true, false }}, // diff
215 {{ false, false }, { false, true }}, // sect
216 {{ false, true }, { true, true }}, // union
217 {{ false, true }, { true, false }}, // xor
218 {{ false, true }, { false, false }}, // rev diff
219};
220
caryclark@google.com66560ca2013-04-26 19:51:16 +0000221bool Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) {
caryclark@google.com07e97fc2013-07-08 17:17:02 +0000222#if DEBUG_SHOW_TEST_NAME
223 char* debugName = DEBUG_FILENAME_STRING;
224 if (debugName && debugName[0]) {
caryclark@google.com570863f2013-09-16 15:55:01 +0000225 SkPathOpsDebug::BumpTestName(debugName);
226 SkPathOpsDebug::ShowPath(one, two, op, debugName);
caryclark@google.com07e97fc2013-07-08 17:17:02 +0000227 }
caryclark@google.coma5e55922013-05-07 18:51:31 +0000228#endif
caryclark@google.com7dfbb072013-04-22 14:37:05 +0000229 op = gOpInverse[op][one.isInverseFillType()][two.isInverseFillType()];
caryclark@google.com7dfbb072013-04-22 14:37:05 +0000230 SkPath::FillType fillType = gOutInverse[op][one.isInverseFillType()][two.isInverseFillType()]
231 ? SkPath::kInverseEvenOdd_FillType : SkPath::kEvenOdd_FillType;
caryclark@google.com7dfbb072013-04-22 14:37:05 +0000232 const SkPath* minuend = &one;
233 const SkPath* subtrahend = &two;
234 if (op == kReverseDifference_PathOp) {
235 minuend = &two;
236 subtrahend = &one;
237 op = kDifference_PathOp;
238 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000239#if DEBUG_SORT || DEBUG_SWAP_TOP
caryclark@google.com570863f2013-09-16 15:55:01 +0000240 SkPathOpsDebug::gSortCount = SkPathOpsDebug::gSortCountDefault;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000241#endif
caryclark@google.com07393ca2013-04-08 11:47:37 +0000242 // turn path into list of segments
243 SkTArray<SkOpContour> contours;
244 // FIXME: add self-intersecting cubics' T values to segment
caryclark@google.com7dfbb072013-04-22 14:37:05 +0000245 SkOpEdgeBuilder builder(*minuend, contours);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000246 const int xorMask = builder.xorMask();
caryclark@google.com7dfbb072013-04-22 14:37:05 +0000247 builder.addOperand(*subtrahend);
caryclark@google.com66560ca2013-04-26 19:51:16 +0000248 if (!builder.finish()) {
249 return false;
250 }
caryclark@google.com6dc7df62013-04-25 11:51:54 +0000251 result->reset();
252 result->setFillType(fillType);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000253 const int xorOpMask = builder.xorMask();
caryclark@google.comd892bd82013-06-17 14:10:36 +0000254 SkTArray<SkOpContour*, true> contourList;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000255 MakeContourList(contours, contourList, xorMask == kEvenOdd_PathOpsMask,
256 xorOpMask == kEvenOdd_PathOpsMask);
257 SkOpContour** currentPtr = contourList.begin();
258 if (!currentPtr) {
caryclark@google.com66560ca2013-04-26 19:51:16 +0000259 return true;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000260 }
261 SkOpContour** listEnd = contourList.end();
262 // find all intersections between segments
263 do {
264 SkOpContour** nextPtr = currentPtr;
265 SkOpContour* current = *currentPtr++;
266 if (current->containsCubics()) {
267 AddSelfIntersectTs(current);
268 }
269 SkOpContour* next;
270 do {
271 next = *nextPtr++;
272 } while (AddIntersectTs(current, next) && nextPtr != listEnd);
273 } while (currentPtr != listEnd);
274 // eat through coincident edges
275
276 int total = 0;
277 int index;
278 for (index = 0; index < contourList.count(); ++index) {
279 total += contourList[index]->segments().count();
280 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000281 if (!HandleCoincidence(&contourList, total)) {
282 return false;
283 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000284 // construct closed contours
285 SkPathWriter wrapper(*result);
286 bridgeOp(contourList, op, xorMask, xorOpMask, &wrapper);
287 { // if some edges could not be resolved, assemble remaining fragments
288 SkPath temp;
caryclark@google.com7dfbb072013-04-22 14:37:05 +0000289 temp.setFillType(fillType);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000290 SkPathWriter assembled(temp);
291 Assemble(wrapper, &assembled);
292 *result = *assembled.nativePath();
caryclark@google.com66560ca2013-04-26 19:51:16 +0000293 result->setFillType(fillType);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000294 }
caryclark@google.com66560ca2013-04-26 19:51:16 +0000295 return true;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000296}