/* * Copyright 2012 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkAddIntersections.h" #include "SkOpCoincidence.h" #include "SkOpEdgeBuilder.h" #include "SkPathOpsCommon.h" #include "SkPathWriter.h" static bool bridgeWinding(SkOpContourHead* contourList, SkPathWriter* writer) { bool unsortable = false; do { SkOpSpan* span = FindSortableTop(contourList); if (!span) { break; } SkOpSegment* current = span->segment(); SkOpSpanBase* start = span->next(); SkOpSpanBase* end = span; SkTDArray chase; do { if (current->activeWinding(start, end)) { do { if (!unsortable && current->done()) { break; } SkASSERT(unsortable || !current->done()); SkOpSpanBase* nextStart = start; SkOpSpanBase* nextEnd = end; SkOpSegment* next = current->findNextWinding(&chase, &nextStart, &nextEnd, &unsortable); if (!next) { break; } #if DEBUG_FLOW SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, current->debugID(), start->pt().fX, start->pt().fY, end->pt().fX, end->pt().fY); #endif if (!current->addCurveTo(start, end, writer)) { return false; } current = next; start = nextStart; end = nextEnd; } while (!writer->isClosed() && (!unsortable || !start->starter(end)->done())); if (current->activeWinding(start, end) && !writer->isClosed()) { SkOpSpan* spanStart = start->starter(end); if (!spanStart->done()) { if (!current->addCurveTo(start, end, writer)) { return false; } current->markDone(spanStart); } } writer->finishContour(); } else { SkOpSpanBase* last; if (!current->markAndChaseDone(start, end, &last)) { return false; } if (last && !last->chased()) { last->setChased(true); SkASSERT(!SkPathOpsDebug::ChaseContains(chase, last)); *chase.append() = last; #if DEBUG_WINDING SkDebugf("%s chase.append id=%d", __FUNCTION__, last->segment()->debugID()); if (!last->final()) { SkDebugf(" windSum=%d", last->upCast()->windSum()); } SkDebugf("\n"); #endif } } current = FindChase(&chase, &start, &end); SkPathOpsDebug::ShowActiveSpans(contourList); if (!current) { break; } } while (true); } while (true); return true; } // returns true if all edges were processed static bool bridgeXor(SkOpContourHead* contourList, SkPathWriter* writer) { bool unsortable = false; int safetyNet = 1000000; do { SkOpSpan* span = FindUndone(contourList); if (!span) { break; } SkOpSegment* current = span->segment(); SkOpSpanBase* start = span->next(); SkOpSpanBase* end = span; do { if (--safetyNet < 0) { return false; } if (!unsortable && current->done()) { break; } SkASSERT(unsortable || !current->done()); SkOpSpanBase* nextStart = start; SkOpSpanBase* nextEnd = end; SkOpSegment* next = current->findNextXor(&nextStart, &nextEnd, &unsortable); if (!next) { break; } #if DEBUG_FLOW SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, current->debugID(), start->pt().fX, start->pt().fY, end->pt().fX, end->pt().fY); #endif if (!current->addCurveTo(start, end, writer)) { return false; } current = next; start = nextStart; end = nextEnd; } while (!writer->isClosed() && (!unsortable || !start->starter(end)->done())); if (!writer->isClosed()) { SkOpSpan* spanStart = start->starter(end); if (!spanStart->done()) { if (!current->addCurveTo(start, end, writer)) { return false; } current->markDone(spanStart); } } writer->finishContour(); SkPathOpsDebug::ShowActiveSpans(contourList); } while (true); return true; } // FIXME : add this as a member of SkPath bool SimplifyDebug(const SkPath& path, SkPath* result SkDEBUGPARAMS(bool skipAssert) SkDEBUGPARAMS(const char* testName)) { // returns 1 for evenodd, -1 for winding, regardless of inverse-ness SkPath::FillType fillType = path.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType : SkPath::kEvenOdd_FillType; if (path.isConvex()) { if (result != &path) { *result = path; } result->setFillType(fillType); return true; } // turn path into list of segments SkSTArenaAlloc<4096> allocator; // FIXME: constant-ize, tune SkOpContour contour; SkOpContourHead* contourList = static_cast(&contour); SkOpGlobalState globalState(contourList, &allocator SkDEBUGPARAMS(skipAssert) SkDEBUGPARAMS(testName)); SkOpCoincidence coincidence(&globalState); #if DEBUG_DUMP_VERIFY #ifndef SK_DEBUG const char* testName = "release"; #endif if (SkPathOpsDebug::gDumpOp) { SkPathOpsDebug::DumpSimplify(path, testName); } #endif SkScalar scaleFactor = ScaleFactor(path); SkPath scaledPath; const SkPath* workingPath; if (scaleFactor > SK_Scalar1) { ScalePath(path, 1.f / scaleFactor, &scaledPath); workingPath = &scaledPath; } else { workingPath = &path; } #if DEBUG_SORT SkPathOpsDebug::gSortCount = SkPathOpsDebug::gSortCountDefault; #endif SkOpEdgeBuilder builder(*workingPath, contourList, &globalState); if (!builder.finish()) { return false; } #if DEBUG_DUMP_SEGMENTS contour.dumpSegments(); #endif if (!SortContourList(&contourList, false, false)) { result->reset(); result->setFillType(fillType); return true; } // find all intersections between segments SkOpContour* current = contourList; do { SkOpContour* next = current; while (AddIntersectTs(current, next, &coincidence) && (next = next->next())); } while ((current = current->next())); #if DEBUG_VALIDATE globalState.setPhase(SkOpPhase::kWalking); #endif bool success = HandleCoincidence(contourList, &coincidence); #if DEBUG_COIN globalState.debugAddToGlobalCoinDicts(); #endif if (!success) { return false; } #if DEBUG_DUMP_ALIGNMENT contour.dumpSegments("aligned"); #endif // construct closed contours result->reset(); result->setFillType(fillType); SkPathWriter wrapper(*result); if (builder.xorMask() == kWinding_PathOpsMask ? !bridgeWinding(contourList, &wrapper) : !bridgeXor(contourList, &wrapper)) { return false; } wrapper.assemble(); // if some edges could not be resolved, assemble remaining if (scaleFactor > 1) { ScalePath(*result, scaleFactor, result); } return true; } bool Simplify(const SkPath& path, SkPath* result) { #if DEBUG_DUMP_VERIFY if (SkPathOpsDebug::gVerifyOp) { if (!SimplifyDebug(path, result SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr))) { SkPathOpsDebug::ReportSimplifyFail(path); return false; } SkPathOpsDebug::VerifySimplify(path, *result); return true; } #endif return SimplifyDebug(path, result SkDEBUGPARAMS(true) SkDEBUGPARAMS(nullptr)); }