blob: 5af4753b50b1360a87f030386f3e27f1a9e1f934 [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)) {
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +000024 if (last->unorderable()) {
25 continue;
26 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000027 *tIndex = last->start();
28 *endIndex = last->end();
caryclark@google.com07393ca2013-04-08 11:47:37 +000029 #if TRY_ROTATE
30 *chase.insert(0) = span;
31 #else
32 *chase.append() = span;
33 #endif
34 return last->segment();
35 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000036 if (done) {
caryclark@google.com07393ca2013-04-08 11:47:37 +000037 continue;
38 }
caryclark@google.com07393ca2013-04-08 11:47:37 +000039 if (!sortable) {
40 continue;
41 }
42 // find first angle, initialize winding to computed fWindSum
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000043 const SkOpAngle* angle = segment->spanToAngle(*tIndex, *endIndex);
44 const SkOpAngle* firstAngle = angle;
45 SkDEBUGCODE(bool loop = false);
46 int winding;
caryclark@google.com07393ca2013-04-08 11:47:37 +000047 do {
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000048 angle = angle->next();
49 SkASSERT(angle != firstAngle || !loop);
50 SkDEBUGCODE(loop |= angle == firstAngle);
caryclark@google.com07393ca2013-04-08 11:47:37 +000051 segment = angle->segment();
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000052 winding = segment->windSum(angle);
53 } while (winding == SK_MinS32);
caryclark@google.com07393ca2013-04-08 11:47:37 +000054 int sumMiWinding = segment->updateWindingReverse(angle);
55 int sumSuWinding = segment->updateOppWindingReverse(angle);
56 if (segment->operand()) {
57 SkTSwap<int>(sumMiWinding, sumSuWinding);
58 }
caryclark@google.com07393ca2013-04-08 11:47:37 +000059 SkOpSegment* first = NULL;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000060 while ((angle = angle->next()) != firstAngle) {
caryclark@google.com07393ca2013-04-08 11:47:37 +000061 segment = angle->segment();
62 int start = angle->start();
63 int end = angle->end();
64 int maxWinding, sumWinding, oppMaxWinding, oppSumWinding;
65 segment->setUpWindings(start, end, &sumMiWinding, &sumSuWinding,
66 &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding);
67 if (!segment->done(angle)) {
68 if (!first) {
69 first = segment;
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000070 *tIndex = start;
71 *endIndex = end;
caryclark@google.com07393ca2013-04-08 11:47:37 +000072 }
73 (void) segment->markAngle(maxWinding, sumWinding, oppMaxWinding,
commit-bot@chromium.org866f4e32013-11-21 17:04:29 +000074 oppSumWinding, angle);
caryclark@google.com07393ca2013-04-08 11:47:37 +000075 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +000076 }
caryclark@google.com07393ca2013-04-08 11:47:37 +000077 if (first) {
78 #if TRY_ROTATE
79 *chase.insert(0) = span;
80 #else
81 *chase.append() = span;
82 #endif
83 return first;
84 }
85 }
86 return NULL;
87}
88
89/*
90static bool windingIsActive(int winding, int oppWinding, int spanWinding, int oppSpanWinding,
91 bool windingIsOp, PathOp op) {
92 bool active = windingIsActive(winding, spanWinding);
93 if (!active) {
94 return false;
95 }
96 if (oppSpanWinding && windingIsActive(oppWinding, oppSpanWinding)) {
97 switch (op) {
98 case kIntersect_Op:
99 case kUnion_Op:
100 return true;
101 case kDifference_Op: {
102 int absSpan = abs(spanWinding);
103 int absOpp = abs(oppSpanWinding);
104 return windingIsOp ? absSpan < absOpp : absSpan > absOpp;
105 }
106 case kXor_Op:
107 return spanWinding != oppSpanWinding;
108 default:
109 SkASSERT(0);
110 }
111 }
112 bool opActive = oppWinding != 0;
113 return gOpLookup[op][opActive][windingIsOp];
114}
115*/
116
caryclark@google.comd892bd82013-06-17 14:10:36 +0000117static bool bridgeOp(SkTArray<SkOpContour*, true>& contourList, const SkPathOp op,
caryclark@google.com07393ca2013-04-08 11:47:37 +0000118 const int xorMask, const int xorOpMask, SkPathWriter* simple) {
119 bool firstContour = true;
120 bool unsortable = false;
121 bool topUnsortable = false;
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +0000122 bool firstPass = true;
123 SkPoint lastTopLeft;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000124 SkPoint topLeft = {SK_ScalarMin, SK_ScalarMin};
125 do {
126 int index, endIndex;
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +0000127 bool topDone;
128 lastTopLeft = topLeft;
caryclark@google.com570863f2013-09-16 15:55:01 +0000129 SkOpSegment* current = FindSortableTop(contourList, SkOpAngle::kBinarySingle, &firstContour,
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +0000130 &index, &endIndex, &topLeft, &topUnsortable, &topDone, firstPass);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000131 if (!current) {
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +0000132 if ((!topUnsortable || firstPass) && !topDone) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000133 SkASSERT(topLeft.fX != SK_ScalarMin && topLeft.fY != SK_ScalarMin);
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +0000134 if (lastTopLeft.fX == SK_ScalarMin && lastTopLeft.fY == SK_ScalarMin) {
135 if (firstPass) {
136 firstPass = false;
137 } else {
138 break;
139 }
140 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000141 topLeft.fX = topLeft.fY = SK_ScalarMin;
142 continue;
143 }
144 break;
145 }
commit-bot@chromium.org8cb1daa2014-04-25 12:59:11 +0000146 firstPass = !topUnsortable || lastTopLeft != topLeft;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000147 SkTDArray<SkOpSpan*> chaseArray;
148 do {
149 if (current->activeOp(index, endIndex, xorMask, xorOpMask, op)) {
150 do {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000151 if (!unsortable && current->done()) {
caryclark@google.coma5e55922013-05-07 18:51:31 +0000152 if (simple->isEmpty()) {
153 simple->init();
caryclark@google.coma5e55922013-05-07 18:51:31 +0000154 }
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000155 break;
caryclark@google.coma5e55922013-05-07 18:51:31 +0000156 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000157 SkASSERT(unsortable || !current->done());
158 int nextStart = index;
159 int nextEnd = endIndex;
160 SkOpSegment* next = current->findNextOp(&chaseArray, &nextStart, &nextEnd,
161 &unsortable, op, xorMask, xorOpMask);
162 if (!next) {
163 if (!unsortable && simple->hasMove()
164 && current->verb() != SkPath::kLine_Verb
165 && !simple->isClosed()) {
166 current->addCurveTo(index, endIndex, simple, true);
167 SkASSERT(simple->isClosed());
168 }
169 break;
170 }
171 #if DEBUG_FLOW
172 SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__,
173 current->debugID(), current->xyAtT(index).fX, current->xyAtT(index).fY,
174 current->xyAtT(endIndex).fX, current->xyAtT(endIndex).fY);
175 #endif
176 current->addCurveTo(index, endIndex, simple, true);
177 current = next;
178 index = nextStart;
179 endIndex = nextEnd;
caryclark@google.coma5e55922013-05-07 18:51:31 +0000180 } while (!simple->isClosed() && (!unsortable
caryclark@google.com07393ca2013-04-08 11:47:37 +0000181 || !current->done(SkMin32(index, endIndex))));
182 if (current->activeWinding(index, endIndex) && !simple->isClosed()) {
caryclark@google.com570863f2013-09-16 15:55:01 +0000183 // FIXME : add to simplify, xor cpaths
caryclark@google.com07393ca2013-04-08 11:47:37 +0000184 int min = SkMin32(index, endIndex);
caryclark@google.com570863f2013-09-16 15:55:01 +0000185 if (!unsortable && !simple->isEmpty()) {
186 unsortable = current->checkSmall(min);
187 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000188 if (!current->done(min)) {
189 current->addCurveTo(index, endIndex, simple, true);
190 current->markDoneBinary(min);
191 }
192 }
193 simple->close();
194 } else {
195 SkOpSpan* last = current->markAndChaseDoneBinary(index, endIndex);
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000196 if (last && !last->fChased && !last->fLoop) {
197 last->fChased = true;
198 SkASSERT(!SkPathOpsDebug::ChaseContains(chaseArray, last));
caryclark@google.com07393ca2013-04-08 11:47:37 +0000199 *chaseArray.append() = last;
200 }
201 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000202 current = findChaseOp(chaseArray, &index, &endIndex);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000203 #if DEBUG_ACTIVE_SPANS
204 DebugShowActiveSpans(contourList);
205 #endif
206 if (!current) {
207 break;
208 }
209 } while (true);
210 } while (true);
211 return simple->someAssemblyRequired();
212}
213
caryclark@google.com7dfbb072013-04-22 14:37:05 +0000214// pretty picture:
215// https://docs.google.com/a/google.com/drawings/d/1sPV8rPfpEFXymBp3iSbDRWAycp1b-7vD9JP2V-kn9Ss/edit?usp=sharing
216static const SkPathOp gOpInverse[kReverseDifference_PathOp + 1][2][2] = {
217// inside minuend outside minuend
218// inside subtrahend outside subtrahend inside subtrahend outside subtrahend
219 {{ kDifference_PathOp, kIntersect_PathOp }, { kUnion_PathOp, kReverseDifference_PathOp }},
220 {{ kIntersect_PathOp, kDifference_PathOp }, { kReverseDifference_PathOp, kUnion_PathOp }},
221 {{ kUnion_PathOp, kReverseDifference_PathOp }, { kDifference_PathOp, kIntersect_PathOp }},
222 {{ kXOR_PathOp, kXOR_PathOp }, { kXOR_PathOp, kXOR_PathOp }},
223 {{ kReverseDifference_PathOp, kUnion_PathOp }, { kIntersect_PathOp, kDifference_PathOp }},
224};
225
226static const bool gOutInverse[kReverseDifference_PathOp + 1][2][2] = {
227 {{ false, false }, { true, false }}, // diff
228 {{ false, false }, { false, true }}, // sect
229 {{ false, true }, { true, true }}, // union
230 {{ false, true }, { true, false }}, // xor
231 {{ false, true }, { false, false }}, // rev diff
232};
233
caryclark@google.com66560ca2013-04-26 19:51:16 +0000234bool Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) {
caryclark@google.com07e97fc2013-07-08 17:17:02 +0000235#if DEBUG_SHOW_TEST_NAME
236 char* debugName = DEBUG_FILENAME_STRING;
237 if (debugName && debugName[0]) {
caryclark@google.com570863f2013-09-16 15:55:01 +0000238 SkPathOpsDebug::BumpTestName(debugName);
239 SkPathOpsDebug::ShowPath(one, two, op, debugName);
caryclark@google.com07e97fc2013-07-08 17:17:02 +0000240 }
caryclark@google.coma5e55922013-05-07 18:51:31 +0000241#endif
caryclark@google.com7dfbb072013-04-22 14:37:05 +0000242 op = gOpInverse[op][one.isInverseFillType()][two.isInverseFillType()];
caryclark@google.com7dfbb072013-04-22 14:37:05 +0000243 SkPath::FillType fillType = gOutInverse[op][one.isInverseFillType()][two.isInverseFillType()]
244 ? SkPath::kInverseEvenOdd_FillType : SkPath::kEvenOdd_FillType;
caryclark@google.com7dfbb072013-04-22 14:37:05 +0000245 const SkPath* minuend = &one;
246 const SkPath* subtrahend = &two;
247 if (op == kReverseDifference_PathOp) {
248 minuend = &two;
249 subtrahend = &one;
250 op = kDifference_PathOp;
251 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000252#if DEBUG_SORT || DEBUG_SWAP_TOP
caryclark@google.com570863f2013-09-16 15:55:01 +0000253 SkPathOpsDebug::gSortCount = SkPathOpsDebug::gSortCountDefault;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000254#endif
caryclark@google.com07393ca2013-04-08 11:47:37 +0000255 // turn path into list of segments
256 SkTArray<SkOpContour> contours;
257 // FIXME: add self-intersecting cubics' T values to segment
caryclark@google.com7dfbb072013-04-22 14:37:05 +0000258 SkOpEdgeBuilder builder(*minuend, contours);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000259 const int xorMask = builder.xorMask();
caryclark@google.com7dfbb072013-04-22 14:37:05 +0000260 builder.addOperand(*subtrahend);
caryclark@google.com66560ca2013-04-26 19:51:16 +0000261 if (!builder.finish()) {
262 return false;
263 }
caryclark@google.com6dc7df62013-04-25 11:51:54 +0000264 result->reset();
265 result->setFillType(fillType);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000266 const int xorOpMask = builder.xorMask();
caryclark@google.comd892bd82013-06-17 14:10:36 +0000267 SkTArray<SkOpContour*, true> contourList;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000268 MakeContourList(contours, contourList, xorMask == kEvenOdd_PathOpsMask,
269 xorOpMask == kEvenOdd_PathOpsMask);
270 SkOpContour** currentPtr = contourList.begin();
271 if (!currentPtr) {
caryclark@google.com66560ca2013-04-26 19:51:16 +0000272 return true;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000273 }
274 SkOpContour** listEnd = contourList.end();
275 // find all intersections between segments
276 do {
277 SkOpContour** nextPtr = currentPtr;
278 SkOpContour* current = *currentPtr++;
279 if (current->containsCubics()) {
280 AddSelfIntersectTs(current);
281 }
282 SkOpContour* next;
283 do {
284 next = *nextPtr++;
285 } while (AddIntersectTs(current, next) && nextPtr != listEnd);
286 } while (currentPtr != listEnd);
287 // eat through coincident edges
288
289 int total = 0;
290 int index;
291 for (index = 0; index < contourList.count(); ++index) {
292 total += contourList[index]->segments().count();
293 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000294 if (!HandleCoincidence(&contourList, total)) {
295 return false;
296 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000297 // construct closed contours
298 SkPathWriter wrapper(*result);
299 bridgeOp(contourList, op, xorMask, xorOpMask, &wrapper);
300 { // if some edges could not be resolved, assemble remaining fragments
301 SkPath temp;
caryclark@google.com7dfbb072013-04-22 14:37:05 +0000302 temp.setFillType(fillType);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000303 SkPathWriter assembled(temp);
304 Assemble(wrapper, &assembled);
305 *result = *assembled.nativePath();
caryclark@google.com66560ca2013-04-26 19:51:16 +0000306 result->setFillType(fillType);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000307 }
caryclark@google.com66560ca2013-04-26 19:51:16 +0000308 return true;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000309}