diff options
author | caryclark <caryclark@google.com> | 2015-05-18 05:12:56 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-05-18 05:12:56 -0700 |
commit | 5b5ddd73b4baf22752924bf20d097e96236c36f8 (patch) | |
tree | e4ae4c9db97d4a19fe504b9aabf5f6d373aeeffa | |
parent | 2e0303f91bdada6dddb73105a82f17601265379d (diff) |
The path ops builder code needs to determine the winding of each contour added, and reverse windings if the contours are nested in other contours.
Cheap (one contour) paths can be evaluated and reversed as needed with a minimum of checking, but multi-contour paths invoke the regular path ops machinery to determine who is contained by whom.
More tests need to be added to verify that all corner cases are considered, but this fixes the cases in the bug thus far.
R=fmalita@chromium.org
TBR=reed@google.com
BUG=skia:3838
Review URL: https://codereview.chromium.org/1129193006
-rw-r--r-- | src/pathops/SkOpBuilder.cpp | 75 | ||||
-rw-r--r-- | src/pathops/SkOpContour.cpp | 10 | ||||
-rw-r--r-- | src/pathops/SkOpContour.h | 34 | ||||
-rw-r--r-- | src/pathops/SkOpSegment.cpp | 7 | ||||
-rw-r--r-- | src/pathops/SkOpSegment.h | 1 | ||||
-rw-r--r-- | src/pathops/SkPathOpsCommon.h | 1 | ||||
-rw-r--r-- | src/pathops/SkPathOpsSimplify.cpp | 1 | ||||
-rw-r--r-- | src/pathops/SkPathOpsTypes.h | 13 | ||||
-rw-r--r-- | src/pathops/SkPathOpsWinding.cpp | 8 | ||||
-rw-r--r-- | tests/PathOpsBuilderTest.cpp | 31 |
10 files changed, 158 insertions, 23 deletions
diff --git a/src/pathops/SkOpBuilder.cpp b/src/pathops/SkOpBuilder.cpp index 1a446f15b6..39da2efbce 100644 --- a/src/pathops/SkOpBuilder.cpp +++ b/src/pathops/SkOpBuilder.cpp @@ -6,8 +6,80 @@ */ #include "SkMatrix.h" +#include "SkOpEdgeBuilder.h" #include "SkPath.h" #include "SkPathOps.h" +#include "SkPathOpsCommon.h" + +static bool one_contour(const SkPath& path) { + SkChunkAlloc allocator(256); + int verbCount = path.countVerbs(); + uint8_t* verbs = (uint8_t*) allocator.alloc(sizeof(uint8_t) * verbCount, + SkChunkAlloc::kThrow_AllocFailType); + (void) path.getVerbs(verbs, verbCount); + for (int index = 1; index < verbCount; ++index) { + if (verbs[index] == SkPath::kMove_Verb) { + return false; + } + } + return true; +} + +void FixWinding(SkPath* path) { + SkPath::FillType fillType = path->getFillType(); + if (fillType == SkPath::kInverseEvenOdd_FillType) { + fillType = SkPath::kInverseWinding_FillType; + } else if (fillType == SkPath::kEvenOdd_FillType) { + fillType = SkPath::kWinding_FillType; + } + SkPath::Direction dir; + if (one_contour(*path) && path->cheapComputeDirection(&dir)) { + if (dir != SkPath::kCCW_Direction) { + SkPath temp; + temp.reverseAddPath(*path); + *path = temp; + } + path->setFillType(fillType); + return; + } + SkChunkAlloc allocator(4096); + SkOpContourHead contourHead; + SkOpGlobalState globalState(NULL, &contourHead); + SkOpEdgeBuilder builder(*path, &contourHead, &allocator, &globalState); + builder.finish(&allocator); + SkASSERT(contourHead.next()); + contourHead.resetReverse(); + bool writePath = false; + SkOpSpan* topSpan; + globalState.setPhase(SkOpGlobalState::kFixWinding); + while ((topSpan = FindSortableTop(&contourHead))) { + SkOpSegment* topSegment = topSpan->segment(); + SkOpContour* topContour = topSegment->contour(); + bool active = topSegment->activeWinding(topSpan, topSpan->next()); + SkASSERT(topContour->isCcw() >= 0); + if (active != SkToBool(topContour->isCcw())) { + topContour->setReverse(); + writePath = true; + } + topContour->markDone(); + } + if (!writePath) { + path->setFillType(fillType); + return; + } + SkPath empty; + SkPathWriter woundPath(empty); + SkOpContour* test = &contourHead; + do { + if (test->reversed()) { + test->toReversePath(&woundPath); + } else { + test->toPath(&woundPath); + } + } while ((test = test->next())); + *path = *woundPath.nativePath(); + path->setFillType(fillType); +} void SkOpBuilder::add(const SkPath& path, SkPathOp op) { if (0 == fOps.count() && op != kUnion_SkPathOp) { @@ -82,10 +154,11 @@ bool SkOpBuilder::resolve(SkPath* result) { *result = original; return false; } + // convert the even odd result back to winding form before accumulating it + FixWinding(&fPathRefs[index]); sum.addPath(fPathRefs[index]); } reset(); - sum.setFillType(SkPath::kEvenOdd_FillType); bool success = Simplify(sum, result); if (!success) { *result = original; diff --git a/src/pathops/SkOpContour.cpp b/src/pathops/SkOpContour.cpp index 178ba3e89c..18b6328a7e 100644 --- a/src/pathops/SkOpContour.cpp +++ b/src/pathops/SkOpContour.cpp @@ -47,6 +47,16 @@ void SkOpContour::toPath(SkPathWriter* path) const { path->close(); } +void SkOpContour::toReversePath(SkPathWriter* path) const { + const SkPoint& pt = fTail->pts()[0]; + path->deferredMove(pt); + const SkOpSegment* segment = fTail; + do { + segment->addCurveTo(segment->tail(), segment->head(), path, true); + } while ((segment = segment->prev())); + path->close(); +} + SkOpSegment* SkOpContour::undoneSegment(SkOpSpanBase** startPtr, SkOpSpanBase** endPtr) { SkOpSegment* segment = &fHead; do { diff --git a/src/pathops/SkOpContour.h b/src/pathops/SkOpContour.h index dd5dbb40ca..f8143cf555 100644 --- a/src/pathops/SkOpContour.h +++ b/src/pathops/SkOpContour.h @@ -207,10 +207,21 @@ public: SkDEBUGCODE(fID = globalState->nextContourID()); } + int isCcw() const { + return fCcw; + } + bool isXor() const { return fXor; } + void markDone() { + SkOpSegment* segment = &fHead; + do { + segment->markAllDone(); + } while ((segment = segment->next())); + } + void missingCoincidence(SkOpCoincidence* coincidences, SkChunkAlloc* allocator) { SkASSERT(fCount > 0); SkOpSegment* segment = &fHead; @@ -289,6 +300,18 @@ public: SkDEBUGCODE(fDebugIndent = 0); } + void resetReverse() { + SkOpContour* next = this; + do { + next->fCcw = -1; + next->fReverse = false; + } while ((next = next->next())); + } + + bool reversed() const { + return fReverse; + } + void setBounds() { SkASSERT(fCount > 0); const SkOpSegment* segment = &fHead; @@ -298,6 +321,10 @@ public: } } + void setCcw(int ccw) { + fCcw = ccw; + } + void setGlobalState(SkOpGlobalState* state) { fState = state; } @@ -315,6 +342,10 @@ public: fOppXor = isOppXor; } + void setReverse() { + fReverse = true; + } + void setXor(bool isXor) { fXor = isXor; } @@ -347,6 +378,7 @@ public: } while ((segment = segment->next())); } + void toReversePath(SkPathWriter* path) const; void toPath(SkPathWriter* path) const; SkOpSegment* undoneSegment(SkOpSpanBase** startPtr, SkOpSpanBase** endPtr); @@ -356,11 +388,13 @@ private: SkOpSegment* fTail; SkOpContour* fNext; SkPathOpsBounds fBounds; + int fCcw; int fCount; int fFirstSorted; bool fDone; // set by find top segment bool fTopsFound; bool fOperand; // true for the second argument to a binary operator + bool fReverse; // true if contour should be reverse written to path (used only by fix winding) bool fXor; // set if original path had even-odd fill bool fOppXor; // set if opposite path had even-odd fill SkDEBUGCODE(int fID); diff --git a/src/pathops/SkOpSegment.cpp b/src/pathops/SkOpSegment.cpp index 8bd0f03518..462cff60cf 100644 --- a/src/pathops/SkOpSegment.cpp +++ b/src/pathops/SkOpSegment.cpp @@ -852,6 +852,13 @@ bool SkOpSegment::isXor() const { return fContour->isXor(); } +void SkOpSegment::markAllDone() { + SkOpSpan* span = this->head(); + do { + this->markDone(span); + } while ((span = span->next()->upCastable())); +} + SkOpSpanBase* SkOpSegment::markAndChaseDone(SkOpSpanBase* start, SkOpSpanBase* end) { int step = start->step(end); SkOpSpan* minSpan = start->starter(end); diff --git a/src/pathops/SkOpSegment.h b/src/pathops/SkOpSegment.h index 1162c2cfaf..38de40617a 100644 --- a/src/pathops/SkOpSegment.h +++ b/src/pathops/SkOpSegment.h @@ -231,6 +231,7 @@ public: return fPts[SkPathOpsVerbToPoints(fVerb)]; } + void markAllDone(); SkOpSpanBase* markAndChaseDone(SkOpSpanBase* start, SkOpSpanBase* end); bool markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, int winding, SkOpSpanBase** lastPtr); diff --git a/src/pathops/SkPathOpsCommon.h b/src/pathops/SkPathOpsCommon.h index b7fbf38b3d..f76d57c83b 100644 --- a/src/pathops/SkPathOpsCommon.h +++ b/src/pathops/SkPathOpsCommon.h @@ -22,6 +22,7 @@ SkOpSegment* FindChase(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBase** startPtr, SkOpSpan* FindSortableTop(SkOpContourHead* ); SkOpSegment* FindUndone(SkOpContourHead* , SkOpSpanBase** startPtr, SkOpSpanBase** endPtr); +void FixWinding(SkPath* path); bool SortContourList(SkOpContourHead** , bool evenOdd, bool oppEvenOdd); bool HandleCoincidence(SkOpContourHead* , SkOpCoincidence* , SkChunkAlloc* ); bool OpDebug(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result, bool expectSuccess); diff --git a/src/pathops/SkPathOpsSimplify.cpp b/src/pathops/SkPathOpsSimplify.cpp index d37998b448..11b6833a73 100644 --- a/src/pathops/SkPathOpsSimplify.cpp +++ b/src/pathops/SkPathOpsSimplify.cpp @@ -212,3 +212,4 @@ bool Simplify(const SkPath& path, SkPath* result) { } return true; } + diff --git a/src/pathops/SkPathOpsTypes.h b/src/pathops/SkPathOpsTypes.h index 845288f609..a780a88c6d 100644 --- a/src/pathops/SkPathOpsTypes.h +++ b/src/pathops/SkPathOpsTypes.h @@ -33,9 +33,7 @@ public: , fContourHead(head) , fWindingFailed(false) , fAngleCoincidence(false) -#if DEBUG_VALIDATE , fPhase(kIntersecting) -#endif SkDEBUGPARAMS(fAngleID(0)) SkDEBUGPARAMS(fContourID(0)) SkDEBUGPARAMS(fPtTID(0)) @@ -43,12 +41,11 @@ public: SkDEBUGPARAMS(fSpanID(0)) { } -#if DEBUG_VALIDATE enum Phase { kIntersecting, - kWalking + kWalking, + kFixWinding, }; -#endif enum { kMaxWindingTries = 10 @@ -93,11 +90,9 @@ public: } #endif -#if DEBUG_VALIDATE Phase phase() const { return fPhase; } -#endif void setAngleCoincidence() { fAngleCoincidence = true; @@ -107,12 +102,10 @@ public: fContourHead = contourHead; } -#if DEBUG_VALIDATE void setPhase(Phase phase) { SkASSERT(fPhase != phase); fPhase = phase; } -#endif // called in very rare cases where angles are sorted incorrectly -- signfies op will fail void setWindingFailed() { @@ -128,9 +121,7 @@ private: SkOpContourHead* fContourHead; bool fWindingFailed; bool fAngleCoincidence; -#if DEBUG_VALIDATE Phase fPhase; -#endif #ifdef SK_DEBUG int fAngleID; int fContourID; diff --git a/src/pathops/SkPathOpsWinding.cpp b/src/pathops/SkPathOpsWinding.cpp index 53083e484e..4a7b1bc3eb 100644 --- a/src/pathops/SkPathOpsWinding.cpp +++ b/src/pathops/SkPathOpsWinding.cpp @@ -342,8 +342,12 @@ bool SkOpSpan::sortableTop(SkOpContour* contourHead) { #endif } if (sumSet) { - (void) hitSegment->markAndChaseWinding(span, span->next(), windSum, oppSum, NULL); - (void) hitSegment->markAndChaseWinding(span->next(), span, windSum, oppSum, NULL); + if (this->globalState()->phase() == SkOpGlobalState::kFixWinding) { + hitSegment->contour()->setCcw(ccw); + } else { + (void) hitSegment->markAndChaseWinding(span, span->next(), windSum, oppSum, NULL); + (void) hitSegment->markAndChaseWinding(span->next(), span, windSum, oppSum, NULL); + } } if (operand) { SkTSwap(wind, oppWind); diff --git a/tests/PathOpsBuilderTest.cpp b/tests/PathOpsBuilderTest.cpp index 8ab92c3124..ea4c567e62 100644 --- a/tests/PathOpsBuilderTest.cpp +++ b/tests/PathOpsBuilderTest.cpp @@ -33,8 +33,10 @@ DEF_TEST(PathOpsBuilder, reporter) { SkPath::Direction dir; REPORTER_ASSERT(reporter, result.isRect(NULL, &closed, &dir)); REPORTER_ASSERT(reporter, closed); - REPORTER_ASSERT(reporter, dir == SkPath::kCW_Direction); - REPORTER_ASSERT(reporter, rectPath == result); + REPORTER_ASSERT(reporter, dir == SkPath::kCCW_Direction); + SkBitmap bitmap; + int pixelDiff = comparePaths(reporter, __FUNCTION__, rectPath, result, bitmap); + REPORTER_ASSERT(reporter, pixelDiff == 0); rectPath.reset(); rectPath.setFillType(SkPath::kEvenOdd_FillType); @@ -44,7 +46,7 @@ DEF_TEST(PathOpsBuilder, reporter) { REPORTER_ASSERT(reporter, result.isRect(NULL, &closed, &dir)); REPORTER_ASSERT(reporter, closed); REPORTER_ASSERT(reporter, dir == SkPath::kCCW_Direction); - REPORTER_ASSERT(reporter, rectPath == result); + REPORTER_ASSERT(reporter, rectPath == result); builder.add(rectPath, kDifference_SkPathOp); REPORTER_ASSERT(reporter, builder.resolve(&result)); @@ -74,12 +76,11 @@ DEF_TEST(PathOpsBuilder, reporter) { builder.add(circle2, kUnion_SkPathOp); builder.add(circle3, kDifference_SkPathOp); REPORTER_ASSERT(reporter, builder.resolve(&result)); - SkBitmap bitmap; - int pixelDiff = comparePaths(reporter, __FUNCTION__, opCompare, result, bitmap); + pixelDiff = comparePaths(reporter, __FUNCTION__, opCompare, result, bitmap); REPORTER_ASSERT(reporter, pixelDiff == 0); } -DEF_TEST(Issue3838, reporter) { +DEF_TEST(BuilderIssue3838, reporter) { SkPath path; path.moveTo(200, 170); path.lineTo(220, 170); @@ -93,9 +94,6 @@ DEF_TEST(Issue3838, reporter) { path.lineTo(200, 250); path.lineTo(200, 170); path.close(); - testSimplify(reporter, path, __FUNCTION__); - SkPath path3; - Simplify(path, &path3); SkPath path2; SkOpBuilder builder; builder.add(path, kUnion_SkPathOp); @@ -104,3 +102,18 @@ DEF_TEST(Issue3838, reporter) { int pixelDiff = comparePaths(reporter, __FUNCTION__, path, path2, bitmap); REPORTER_ASSERT(reporter, pixelDiff == 0); } + +DEF_TEST(BuilderIssue3838_2, reporter) { + SkPath path; + path.addCircle(100, 100, 50); + + SkOpBuilder builder; + builder.add(path, kUnion_SkPathOp); + builder.add(path, kUnion_SkPathOp); + + SkPath result; + SkBitmap bitmap; + builder.resolve(&result); + int pixelDiff = comparePaths(reporter, __FUNCTION__, path, result, bitmap); + REPORTER_ASSERT(reporter, pixelDiff == 0); +} |