/* * Copyright 2014 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkArenaAlloc.h" #include "SkMatrix.h" #include "SkOpEdgeBuilder.h" #include "SkPathPriv.h" #include "SkPathOps.h" #include "SkPathOpsCommon.h" static bool one_contour(const SkPath& path) { SkSTArenaAlloc<256> allocator; int verbCount = path.countVerbs(); uint8_t* verbs = (uint8_t*) allocator.makeArrayDefault<uint8_t>(verbCount); (void) path.getVerbs(verbs, verbCount); for (int index = 1; index < verbCount; ++index) { if (verbs[index] == SkPath::kMove_Verb) { return false; } } return true; } void SkOpBuilder::ReversePath(SkPath* path) { SkPath temp; SkPoint lastPt; SkAssertResult(path->getLastPt(&lastPt)); temp.moveTo(lastPt); temp.reversePathTo(*path); temp.close(); *path = temp; } bool SkOpBuilder::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; } SkPathPriv::FirstDirection dir; if (one_contour(*path) && SkPathPriv::CheapComputeFirstDirection(*path, &dir)) { if (dir != SkPathPriv::kCCW_FirstDirection) { ReversePath(path); } path->setFillType(fillType); return true; } SkSTArenaAlloc<4096> allocator; SkOpContourHead contourHead; SkOpGlobalState globalState(&contourHead, &allocator SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr)); SkOpEdgeBuilder builder(*path, &contourHead, &globalState); if (builder.unparseable() || !builder.finish()) { return false; } if (!contourHead.count()) { return true; } if (!contourHead.next()) { return false; } contourHead.joinAllSegments(); contourHead.resetReverse(); bool writePath = false; SkOpSpan* topSpan; globalState.setPhase(SkOpPhase::kFixWinding); while ((topSpan = FindSortableTop(&contourHead))) { SkOpSegment* topSegment = topSpan->segment(); SkOpContour* topContour = topSegment->contour(); SkASSERT(topContour->isCcw() >= 0); #if DEBUG_WINDING SkDebugf("%s id=%d nested=%d ccw=%d\n", __FUNCTION__, topSegment->debugID(), globalState.nested(), topContour->isCcw()); #endif if ((globalState.nested() & 1) != SkToBool(topContour->isCcw())) { topContour->setReverse(); writePath = true; } topContour->markAllDone(); globalState.clearNested(); } if (!writePath) { path->setFillType(fillType); return true; } SkPath empty; SkPathWriter woundPath(empty); SkOpContour* test = &contourHead; do { if (!test->count()) { continue; } if (test->reversed()) { test->toReversePath(&woundPath); } else { test->toPath(&woundPath); } } while ((test = test->next())); *path = *woundPath.nativePath(); path->setFillType(fillType); return true; } void SkOpBuilder::add(const SkPath& path, SkPathOp op) { if (0 == fOps.count() && op != kUnion_SkPathOp) { fPathRefs.push_back() = SkPath(); *fOps.append() = kUnion_SkPathOp; } fPathRefs.push_back() = path; *fOps.append() = op; } void SkOpBuilder::reset() { fPathRefs.reset(); fOps.reset(); } /* OPTIMIZATION: Union doesn't need to be all-or-nothing. A run of three or more convex paths with union ops could be locally resolved and still improve over doing the ops one at a time. */ bool SkOpBuilder::resolve(SkPath* result) { SkPath original = *result; int count = fOps.count(); bool allUnion = true; SkPathPriv::FirstDirection firstDir = SkPathPriv::kUnknown_FirstDirection; for (int index = 0; index < count; ++index) { SkPath* test = &fPathRefs[index]; if (kUnion_SkPathOp != fOps[index] || test->isInverseFillType()) { allUnion = false; break; } // If all paths are convex, track direction, reversing as needed. if (test->isConvex()) { SkPathPriv::FirstDirection dir; if (!SkPathPriv::CheapComputeFirstDirection(*test, &dir)) { allUnion = false; break; } if (firstDir == SkPathPriv::kUnknown_FirstDirection) { firstDir = dir; } else if (firstDir != dir) { ReversePath(test); } continue; } // If the path is not convex but its bounds do not intersect the others, simplify is enough. const SkRect& testBounds = test->getBounds(); for (int inner = 0; inner < index; ++inner) { // OPTIMIZE: check to see if the contour bounds do not intersect other contour bounds? if (SkRect::Intersects(fPathRefs[inner].getBounds(), testBounds)) { allUnion = false; break; } } } if (!allUnion) { *result = fPathRefs[0]; for (int index = 1; index < count; ++index) { if (!Op(*result, fPathRefs[index], fOps[index], result)) { reset(); *result = original; return false; } } reset(); return true; } SkPath sum; for (int index = 0; index < count; ++index) { if (!Simplify(fPathRefs[index], &fPathRefs[index])) { reset(); *result = original; return false; } if (!fPathRefs[index].isEmpty()) { // convert the even odd result back to winding form before accumulating it if (!FixWinding(&fPathRefs[index])) { *result = original; return false; } sum.addPath(fPathRefs[index]); } } reset(); bool success = Simplify(sum, result); if (!success) { *result = original; } return success; }