blob: d4b54053737bd9f2f310000cc6405ac5445cac4b [file] [log] [blame]
/*
* 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();
}