blob: 5569b705f729934d7d5fbe1adaf4ecf2a385df3d [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/SkGeometry.h"
#include "src/core/SkPathEnums.h"
#include "src/core/SkPathPriv.h"
#include <algorithm>
#include <cmath>
#include <cstring>
#include <limits.h>
#include <utility>
#ifndef SK_PATH_USES_PATHDATA
/* 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();)
}
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();
}
#ifdef SK_DEBUG
void SkPath::validate() const {
SkASSERT(this->isValidImpl());
}
void SkPath::validateRef() const {
// This will SkASSERT if not valid.
fPathRef->validate();
}
#endif
std::optional<SkPathOvalInfo> SkPath::getOvalInfo() const { return fPathRef->isOval(); }
std::optional<SkPathRRectInfo> SkPath::getRRectInfo() const { return fPathRef->isRRect(); }
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;
}
void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
SkMatrix matrix;
matrix.setTranslate(dx, dy);
this->transform(matrix, dst);
}
static void subdivide_cubic_to(SkPathBuilder* builder, const SkPoint pts[4],
int level = 2) {
if (--level >= 0) {
SkPoint tmp[7];
SkChopCubicAtHalf(pts, tmp);
subdivide_cubic_to(builder, &tmp[0], level);
subdivide_cubic_to(builder, &tmp[3], level);
} else {
builder->cubicTo(pts[1], pts[2], pts[3]);
}
}
void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
if (matrix.isIdentity()) {
if (dst != nullptr && dst != this) {
*dst = *this;
}
return;
}
SkDEBUGCODE(this->validate();)
if (dst == nullptr) {
dst = const_cast<SkPath*>(this);
}
if (matrix.hasPerspective()) {
SkPath clipped;
const SkPath* src = this;
if (SkPathPriv::PerspectiveClip(*this, matrix, &clipped)) {
src = &clipped;
}
SkPathBuilder tmp(this->getFillType());
SkPath::Iter iter(*src, false);
while (auto rec = iter.next()) {
const SkSpan<const SkPoint> pts = rec->fPoints;
switch (rec->fVerb) {
case SkPathVerb::kMove:
tmp.moveTo(pts[0]);
break;
case SkPathVerb::kLine:
tmp.lineTo(pts[1]);
break;
case SkPathVerb::kQuad:
// promote the quad to a conic
tmp.conicTo(pts[1], pts[2],
SkConic::TransformW(pts.data(), SK_Scalar1, matrix));
break;
case SkPathVerb::kConic:
tmp.conicTo(pts[1], pts[2],
SkConic::TransformW(pts.data(), rec->conicWeight(), matrix));
break;
case SkPathVerb::kCubic:
subdivide_cubic_to(&tmp, pts.data());
break;
case SkPathVerb::kClose:
tmp.close();
break;
}
}
*dst = tmp.detach(&matrix);
} else {
SkPathConvexity convexity = this->getConvexityOrUnknown();
SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef, matrix);
if (this != dst) {
dst->fLastMoveToIndex = fLastMoveToIndex;
dst->fFillType = fFillType;
dst->fIsVolatile = fIsVolatile;
}
dst->setConvexity(SkPathPriv::TransformConvexity(matrix, fPathRef->pointSpan(), convexity));
SkDEBUGCODE(dst->validate();)
}
}
// TODO: evolve this one to the source of truth (when we have SkPathData),
// and have makeTransform() call it and mark the non-finite flag if it fails.
std::optional<SkPath> SkPath::tryMakeTransform(const SkMatrix& matrix) const {
auto path = this->makeTransform(matrix);
if (path.isFinite()) {
return path;
}
return {};
}
std::optional<SkPathRaw> SkPath::raw(SkResolveConvexity rc) const {
const SkPathRef* ref = fPathRef.get();
SkASSERT(ref);
if (!ref->isFinite()) {
return {};
}
return SkPathRaw{
ref->pointSpan(),
ref->verbs(),
ref->conicSpan(),
ref->getBounds(),
this->getFillType(),
rc == SkResolveConvexity::kYes ? this->getConvexity() : this->getConvexityOrUnknown(),
SkTo<uint8_t>(ref->getSegmentMasks()),
};
}
int SkPathPriv::GenIDChangeListenersCount(const SkPath& path) {
return path.fPathRef->genIDChangeListenerCount();
}
bool SkPathPriv::TestingOnly_unique(const SkPath& path) {
return path.fPathRef->unique();
}
void SkPathPriv::AddGenIDChangeListener(const SkPath& path, sk_sp<SkIDChangeListener> listener) {
path.fPathRef->addGenIDChangeListener(std::move(listener));
}
/////////////////////////////////////////////////////////////////////////////////////
SkPathBuilder& SkPathBuilder::operator=(const SkPath& src) {
this->reset().setFillType(src.getFillType());
this->setIsVolatile(src.isVolatile());
const sk_sp<SkPathRef>& ref = src.fPathRef;
fVerbs = ref->fVerbs;
fPts = ref->fPoints;
fConicWeights = ref->fConicWeights;
fSegmentMask = ref->fSegmentMask;
fLastMoveIndex = src.fLastMoveToIndex < 0 ? ~src.fLastMoveToIndex : src.fLastMoveToIndex;
fType = ref->fType;
fIsA = ref->fIsA;
fConvexity = src.getConvexityOrUnknown();
return *this;
}
SkPath SkPathBuilder::make(sk_sp<SkPathRef> pr) const {
switch (fType) {
case SkPathIsAType::kGeneral:
break;
case SkPathIsAType::kOval:
pr->setIsOval(fIsA.fDirection, fIsA.fStartIndex);
SkASSERT(SkPathConvexity_IsConvex(fConvexity));
break;
case SkPathIsAType::kRRect:
pr->setIsRRect(fIsA.fDirection, fIsA.fStartIndex);
SkASSERT(SkPathConvexity_IsConvex(fConvexity));
break;
}
// Wonder if we can combine convexity and dir internally...
// unknown, convex_cw, convex_ccw, concave
// Do we ever have direction w/o convexity, or viceversa (inside path)?
//
auto path = SkPath(std::move(pr), fFillType, fIsVolatile, fConvexity);
// This hopefully can go away in the future when Paths are immutable,
// but if while they are still editable, we need to correctly set this.
SkSpan<const SkPathVerb> verbs = path.fPathRef->verbs();
if (!verbs.empty()) {
SkASSERT(fLastMoveIndex >= 0);
// peek at the last verb, to know if our last contour is closed
const bool isClosed = (verbs.back() == SkPathVerb::kClose);
path.fLastMoveToIndex = isClosed ? ~fLastMoveIndex : fLastMoveIndex;
}
return path;
}
SkPath SkPathBuilder::snapshot(const SkMatrix* mx) const {
return this->make(sk_sp<SkPathRef>(new SkPathRef(fPts,
fVerbs,
fConicWeights,
fSegmentMask,
mx)));
}
#endif