| /* | |
| * Copyright 2006 The Android Open Source Project | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/core/SkPath.h" | |
| #include "include/core/SkPathBuilder.h" | |
| #include "include/core/SkPathTypes.h" | |
| #include "include/core/SkRRect.h" | |
| #include "include/core/SkSpan.h" | |
| #include "include/private/SkPathRef.h" | |
| #include "include/private/base/SkMalloc.h" | |
| #include "include/private/base/SkTArray.h" | |
| #include "include/private/base/SkTo.h" | |
| #include "src/core/SkPathEnums.h" | |
| #include "src/core/SkPathPriv.h" | |
| #include <algorithm> | |
| #include <cmath> | |
| #include <cstring> | |
| #include <limits.h> | |
| #include <utility> | |
| /* Contains path methods that require the legacy fields: | |
| * - fPathRef | |
| * - fConvexity | |
| * - fLastMoveToIndex | |
| * | |
| * ... these are encompaed by SkPathData | |
| * | |
| * The remaining fields: | |
| * - fFillType | |
| * - fIsVolatile | |
| * | |
| * ... are shared in both implemtations. | |
| */ | |
| // flag to require a moveTo if we begin with something else, like lineTo etc. | |
| // This will also be the value of lastMoveToIndex for a single contour | |
| // ending with close, so countVerbs needs to be checked against 0. | |
| #define INITIAL_LASTMOVETOINDEX_VALUE ~0 | |
| SkPath::SkPath(sk_sp<SkPathRef> pr, SkPathFillType ft, bool isVolatile, SkPathConvexity ct) | |
| : fPathRef(std::move(pr)) | |
| , fLastMoveToIndex(INITIAL_LASTMOVETOINDEX_VALUE) | |
| , fConvexity((uint8_t)ct) | |
| , fFillType(ft) | |
| , fIsVolatile(isVolatile) | |
| {} | |
| SkPath::SkPath(SkPathFillType ft) | |
| : fPathRef(SkPathRef::CreateEmpty()) | |
| , fLastMoveToIndex(INITIAL_LASTMOVETOINDEX_VALUE) | |
| , fConvexity((uint8_t)SkPathConvexity::kUnknown) | |
| , fFillType(ft) | |
| , fIsVolatile(false) | |
| {} | |
| void SkPath::resetFields() { | |
| //fPathRef is assumed to have been emptied by the caller. | |
| fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE; | |
| fFillType = SkPathFillType::kDefault; | |
| this->setConvexity(SkPathConvexity::kUnknown); | |
| } | |
| SkPath::SkPath(const SkPath& that) | |
| : fPathRef(SkRef(that.fPathRef.get())) { | |
| this->copyFields(that); | |
| SkDEBUGCODE(that.validate();) | |
| } | |
| // This is the public-facing non-const setConvexity(). | |
| void SkPath::setConvexity(SkPathConvexity c) { | |
| fConvexity.store((uint8_t)c, std::memory_order_relaxed); | |
| } | |
| // Const hooks for working with fConvexity and fFirstDirection from const methods. | |
| void SkPath::setConvexity(SkPathConvexity c) const { | |
| fConvexity.store((uint8_t)c, std::memory_order_relaxed); | |
| } | |
| SkPathConvexity SkPath::getConvexityOrUnknown() const { | |
| return (SkPathConvexity)fConvexity.load(std::memory_order_relaxed); | |
| } | |
| void SkPath::copyFields(const SkPath& that) { | |
| //fPathRef is assumed to have been set by the caller. | |
| fLastMoveToIndex = that.fLastMoveToIndex; | |
| fFillType = that.fFillType; | |
| fIsVolatile = that.fIsVolatile; | |
| // Non-atomic assignment of atomic values. | |
| this->setConvexity(that.getConvexityOrUnknown()); | |
| } | |
| SkPath& SkPath::operator=(const SkPath& that) { | |
| SkDEBUGCODE(that.validate();) | |
| if (this != &that) { | |
| fPathRef.reset(SkRef(that.fPathRef.get())); | |
| this->copyFields(that); | |
| } | |
| SkDEBUGCODE(this->validate();) | |
| return *this; | |
| } | |
| bool operator==(const SkPath& a, const SkPath& b) { | |
| // note: don't need to look at isConvex or bounds, since just comparing the | |
| // raw data is sufficient. | |
| return &a == &b || | |
| (a.fFillType == b.fFillType && *a.fPathRef == *b.fPathRef); | |
| } | |
| void SkPath::swap(SkPath& that) { | |
| if (this != &that) { | |
| fPathRef.swap(that.fPathRef); | |
| std::swap(fLastMoveToIndex, that.fLastMoveToIndex); | |
| std::swap(fFillType, that.fFillType); | |
| std::swap(fIsVolatile, that.fIsVolatile); | |
| // Non-atomic swaps of atomic values. | |
| SkPathConvexity c = this->getConvexityOrUnknown(); | |
| this->setConvexity(that.getConvexityOrUnknown()); | |
| that.setConvexity(c); | |
| } | |
| } | |
| uint32_t SkPath::getGenerationID() const { | |
| return fPathRef->genID(fFillType); | |
| } | |
| SkPath& SkPath::reset() { | |
| SkDEBUGCODE(this->validate();) | |
| if (fPathRef->unique()) { | |
| fPathRef->reset(); | |
| } else { | |
| fPathRef.reset(SkPathRef::CreateEmpty()); | |
| } | |
| this->resetFields(); | |
| return *this; | |
| } | |
| bool SkPath::isFinite() const { | |
| SkDEBUGCODE(this->validate();) | |
| return fPathRef->isFinite(); | |
| } | |
| const SkRect& SkPath::getBounds() const { | |
| return fPathRef->getBounds(); | |
| } | |
| uint32_t SkPath::getSegmentMasks() const { | |
| return fPathRef->getSegmentMasks(); | |
| } | |
| bool SkPath::isValid() const { | |
| return this->isValidImpl() && fPathRef->isValid(); | |
| } | |
| bool SkPath::hasComputedBounds() const { | |
| SkDEBUGCODE(this->validate();) | |
| return fPathRef->hasComputedBounds(); | |
| } | |
| SkPath SkPath::makeFillType(SkPathFillType ft) const { | |
| return SkPath(fPathRef, | |
| ft, | |
| fIsVolatile, | |
| this->getConvexityOrUnknown()); | |
| } | |
| SkPath SkPath::makeToggleInverseFillType() const { | |
| return SkPath(fPathRef, | |
| SkPathFillType_ToggleInverse(fFillType), | |
| fIsVolatile, | |
| this->getConvexityOrUnknown()); | |
| } | |
| SkPath SkPath::makeIsVolatile(bool v) const { | |
| return SkPath(fPathRef, | |
| fFillType, | |
| v, | |
| this->getConvexityOrUnknown()); | |
| } | |
| #ifdef SK_DEBUG | |
| void SkPath::validate() const { | |
| SkASSERT(this->isValidImpl()); | |
| } | |
| void SkPath::validateRef() const { | |
| // This will SkASSERT if not valid. | |
| fPathRef->validate(); | |
| } | |
| #endif | |
| bool SkPath::isOval(SkRect* bounds) const { | |
| if (auto info = fPathRef->isOval()) { | |
| if (bounds) { | |
| *bounds = info->fBounds; | |
| } | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool SkPath::isRRect(SkRRect* rrect) const { | |
| if (auto info = fPathRef->isRRect()) { | |
| if (rrect) { | |
| *rrect = info->fRRect; | |
| } | |
| return true; | |
| } | |
| return false; | |
| } | |
| SkSpan<const SkPoint> SkPath::points() const { | |
| return fPathRef->pointSpan(); | |
| } | |
| SkSpan<const SkPathVerb> SkPath::verbs() const { | |
| return fPathRef->verbs(); | |
| } | |
| SkSpan<const float> SkPath::conicWeights() const { | |
| return fPathRef->conicSpan(); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| bool SkPath::isValidImpl() const { | |
| if ((static_cast<int>(fFillType) & ~3) != 0) { | |
| return false; | |
| } | |
| #ifdef SK_DEBUG_PATH | |
| if (!fBoundsIsDirty) { | |
| SkRect bounds; | |
| bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get()); | |
| if (SkToBool(fIsFinite) != isFinite) { | |
| return false; | |
| } | |
| if (this->countPoints() <= 1) { | |
| // if we're empty, fBounds may be empty but translated, so we can't | |
| // necessarily compare to bounds directly | |
| // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will | |
| // be [2, 2, 2, 2] | |
| if (!bounds.isEmpty() || !fBounds.isEmpty()) { | |
| return false; | |
| } | |
| } else { | |
| if (bounds.isEmpty()) { | |
| if (!fBounds.isEmpty()) { | |
| return false; | |
| } | |
| } else { | |
| if (!fBounds.isEmpty()) { | |
| if (!fBounds.contains(bounds)) { | |
| return false; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| #endif // SK_DEBUG_PATH | |
| return true; | |
| } | |
| ////////////////////////////////////////////////////////////////////////////////////////////////// | |
| SkPath SkPath::Raw(SkSpan<const SkPoint> pts, SkSpan<const SkPathVerb> vbs, | |
| SkSpan<const float> ws, SkPathFillType ft, bool isVolatile) { | |
| if (vbs.empty()) { | |
| return SkPath(); | |
| } | |
| const auto info = SkPathPriv::AnalyzeVerbs(vbs); | |
| if (!info.valid || info.points > pts.size() || info.weights > ws.size()) { | |
| SkDEBUGFAIL("invalid verbs and number of points/weights"); | |
| return SkPath(); | |
| } | |
| return MakeInternal(info, pts.data(), vbs, ws.data(), ft, isVolatile); | |
| } | |
| SkPath SkPath::Rect(const SkRect& r, SkPathFillType ft, SkPathDirection dir, unsigned startIndex) { | |
| return SkPathBuilder(ft).addRect(r, dir, startIndex).detach(); | |
| } | |
| SkPath SkPath::Oval(const SkRect& r, SkPathDirection dir, unsigned startIndex) { | |
| return SkPathBuilder().addOval(r, dir, startIndex).detach(); | |
| } | |
| SkPath SkPath::RRect(const SkRRect& rr, SkPathDirection dir, unsigned startIndex) { | |
| return SkPathBuilder().addRRect(rr, dir, startIndex).detach(); | |
| } | |
| SkPath SkPath::Polygon(SkSpan<const SkPoint> pts, bool isClosed, | |
| SkPathFillType ft, bool isVolatile) { | |
| return SkPathBuilder().addPolygon(pts, isClosed) | |
| .setFillType(ft) | |
| .setIsVolatile(isVolatile) | |
| .detach(); | |
| } | |
| SkPath SkPath::MakeInternal(const SkPathVerbAnalysis& analysis, | |
| const SkPoint points[], | |
| SkSpan<const SkPathVerb> verbs, | |
| const SkScalar conics[], | |
| SkPathFillType fillType, | |
| bool isVolatile) { | |
| return SkPath(sk_sp<SkPathRef>(new SkPathRef( | |
| SkSpan(points, analysis.points), | |
| verbs, | |
| SkSpan(conics, analysis.weights), | |
| analysis.segmentMask, | |
| nullptr)), | |
| fillType, isVolatile, SkPathConvexity::kUnknown); | |
| } | |
| SkPath SkPath::makeTransform(const SkMatrix& matrix) const { | |
| SkPath dst; | |
| this->transform(matrix, &dst); | |
| return dst; | |
| } | |
| int SkPathPriv::GenIDChangeListenersCount(const SkPath& path) { | |
| return path.fPathRef->genIDChangeListenerCount(); | |
| } | |