add simple pathbuilder
88.26 ? makepath_arrays_reserve nonrendering 235.97 makepath_detach_reserve nonrendering 274.56 makepath_snapshot_reserve nonrendering 537.61 makepath_path_reserve nonrendering 472.98 makepath_detach_noreserve nonrendering 482.56 makepath_snapshot_noreserve nonrendering 750.83 ? makepath_path_noreserve nonrendering Bug: skia:9000 Change-Id: I346537e899b08946c5778042a021f464006b029c Reviewed-on: https://skia-review.googlesource.com/c/skia/+/209403 Commit-Queue: Mike Reed <reed@google.com> Reviewed-by: Chris Dalton <csmartdalton@google.com>
This commit is contained in:
parent
5aaaeea4da
commit
22f246f5ad
@ -78,8 +78,6 @@ protected:
|
||||
private:
|
||||
typedef Benchmark INHERITED;
|
||||
};
|
||||
|
||||
|
||||
DEF_BENCH( return new PathOpsBench("sect", kIntersect_SkPathOp); )
|
||||
DEF_BENCH( return new PathOpsBench("join", kUnion_SkPathOp); )
|
||||
|
||||
@ -94,5 +92,131 @@ static SkPath makerects() {
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
DEF_BENCH( return new PathOpsSimplifyBench("rects", makerects()); )
|
||||
|
||||
#include "include/core/SkPathBuilder.h"
|
||||
|
||||
template <size_t N> struct ArrayPath {
|
||||
SkPoint fPts[N];
|
||||
uint8_t fVbs[N];
|
||||
int fPIndex = 0, fVIndex = 0;
|
||||
|
||||
void moveTo(float x, float y) {
|
||||
fVbs[fVIndex++] = (uint8_t)SkPathVerb::kMove;
|
||||
fPts[fPIndex++] = {x, y};
|
||||
}
|
||||
void lineTo(float x, float y) {
|
||||
fVbs[fVIndex++] = (uint8_t)SkPathVerb::kLine;
|
||||
fPts[fPIndex++] = {x, y};
|
||||
}
|
||||
void quadTo(float x, float y, float x1, float y1) {
|
||||
fVbs[fVIndex++] = (uint8_t)SkPathVerb::kQuad;
|
||||
fPts[fPIndex++] = {x, y};
|
||||
fPts[fPIndex++] = {x1, y1};
|
||||
}
|
||||
void cubicTo(float x, float y, float x1, float y1, float x2, float y2) {
|
||||
fVbs[fVIndex++] = (uint8_t)SkPathVerb::kCubic;
|
||||
fPts[fPIndex++] = {x, y};
|
||||
fPts[fPIndex++] = {x1, y1};
|
||||
fPts[fPIndex++] = {x2, y2};
|
||||
}
|
||||
void incReserve(int) {}
|
||||
};
|
||||
|
||||
template <typename T> void run_builder(T& b, bool useReserve, int N) {
|
||||
if (useReserve) {
|
||||
b.incReserve(N * 12);
|
||||
}
|
||||
|
||||
float x = 0, y = 0;
|
||||
b.moveTo(x, y);
|
||||
for (int i = 1; i < N; ++i) {
|
||||
b.lineTo(x, y);
|
||||
b.quadTo(x, y, x, y);
|
||||
b.cubicTo(x, y, x, y, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
enum class MakeType {
|
||||
kPath,
|
||||
kSnapshot,
|
||||
kDetach,
|
||||
kArray,
|
||||
};
|
||||
|
||||
class PathBuilderBench : public Benchmark {
|
||||
SkString fName;
|
||||
MakeType fMakeType;
|
||||
bool fUseReserve;
|
||||
|
||||
enum { N = 100 };
|
||||
ArrayPath<N*12> fArrays;
|
||||
|
||||
public:
|
||||
PathBuilderBench(MakeType mt, bool reserve) : fMakeType(mt), fUseReserve(reserve) {
|
||||
const char* typenames[] = { "path", "snapshot", "detach", "arrays" };
|
||||
|
||||
fName.printf("makepath_%s_%s", typenames[(int)mt], reserve ? "reserve" : "noreserve");
|
||||
}
|
||||
|
||||
bool isSuitableFor(Backend backend) override {
|
||||
return backend == kNonRendering_Backend;
|
||||
}
|
||||
|
||||
protected:
|
||||
const char* onGetName() override {
|
||||
return fName.c_str();
|
||||
}
|
||||
|
||||
void onDelayedSetup() override {
|
||||
run_builder(fArrays, false, N);
|
||||
}
|
||||
|
||||
SkPath build() {
|
||||
switch (fMakeType) {
|
||||
case MakeType::kSnapshot:
|
||||
case MakeType::kDetach: {
|
||||
SkPathBuilder b;
|
||||
run_builder(b, fUseReserve, N);
|
||||
return MakeType::kSnapshot == fMakeType ? b.snapshot() : b.detach();
|
||||
}
|
||||
case MakeType::kPath: {
|
||||
SkPath p;
|
||||
run_builder(p, fUseReserve, N);
|
||||
return p;
|
||||
}
|
||||
case MakeType::kArray: {
|
||||
// ArrayPath<N*12> arrays;
|
||||
// run_builder(arrays, false, N);
|
||||
return SkPathBuilder::Make(fArrays.fPts, fArrays.fPIndex,
|
||||
fArrays.fVbs, fArrays.fVIndex,
|
||||
nullptr, 0, SkPathFillType::kWinding);
|
||||
}
|
||||
}
|
||||
return SkPath();
|
||||
}
|
||||
|
||||
void onDraw(int loops, SkCanvas* canvas) override {
|
||||
for (int i = 0; i < loops; i++) {
|
||||
for (int j = 0; j < 100; ++j) {
|
||||
SkPath result = this->build();
|
||||
// force bounds calc as part of the test
|
||||
if (!result.getBounds().isFinite()) {
|
||||
SkDebugf("should never get here!\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
typedef Benchmark INHERITED;
|
||||
};
|
||||
DEF_BENCH( return new PathBuilderBench(MakeType::kPath, false); )
|
||||
DEF_BENCH( return new PathBuilderBench(MakeType::kSnapshot, false); )
|
||||
DEF_BENCH( return new PathBuilderBench(MakeType::kDetach, false); )
|
||||
DEF_BENCH( return new PathBuilderBench(MakeType::kPath, true); )
|
||||
DEF_BENCH( return new PathBuilderBench(MakeType::kSnapshot, true); )
|
||||
DEF_BENCH( return new PathBuilderBench(MakeType::kDetach, true); )
|
||||
|
||||
DEF_BENCH( return new PathBuilderBench(MakeType::kArray, true); )
|
||||
|
@ -58,6 +58,7 @@ skia_core_public = [
|
||||
"$_include/core/SkOverdrawCanvas.h",
|
||||
"$_include/core/SkPaint.h",
|
||||
"$_include/core/SkPath.h",
|
||||
"$_include/core/SkPathBuilder.h",
|
||||
"$_include/core/SkPathEffect.h",
|
||||
"$_include/core/SkPathMeasure.h",
|
||||
"$_include/core/SkPixelRef.h",
|
||||
@ -279,6 +280,7 @@ skia_core_sources = [
|
||||
"$_src/core/SkPaintPriv.cpp",
|
||||
"$_src/core/SkPaintPriv.h",
|
||||
"$_src/core/SkPath.cpp",
|
||||
"$_src/core/SkPathBuilder.cpp",
|
||||
"$_src/core/SkPathEffect.cpp",
|
||||
"$_src/core/SkPathMeasure.cpp",
|
||||
"$_src/core/SkPathPriv.h",
|
||||
|
@ -187,6 +187,7 @@ tests_sources = [
|
||||
"$_tests/ParametricStageTest.cpp",
|
||||
"$_tests/ParseColorTest.cpp",
|
||||
"$_tests/ParsePathTest.cpp",
|
||||
"$_tests/PathBuilderTest.cpp",
|
||||
"$_tests/PathCoverageTest.cpp",
|
||||
"$_tests/PathMeasureTest.cpp",
|
||||
"$_tests/PathRendererCacheTests.cpp",
|
||||
|
@ -1759,6 +1759,8 @@ public:
|
||||
bool isValid() const { return this->isValidImpl() && fPathRef->isValid(); }
|
||||
|
||||
private:
|
||||
SkPath(sk_sp<SkPathRef>, SkPathFillType, bool isVolatile);
|
||||
|
||||
sk_sp<SkPathRef> fPathRef;
|
||||
int fLastMoveToIndex;
|
||||
mutable std::atomic<uint8_t> fConvexity; // SkPathConvexityType
|
||||
@ -1843,6 +1845,7 @@ private:
|
||||
friend class SkAutoPathBoundsUpdate;
|
||||
friend class SkAutoDisableOvalCheck;
|
||||
friend class SkAutoDisableDirectionCheck;
|
||||
friend class SkPathBuilder;
|
||||
friend class SkPathEdgeIter;
|
||||
friend class SkPathWriter;
|
||||
friend class SkOpBuilder;
|
||||
|
101
include/core/SkPathBuilder.h
Normal file
101
include/core/SkPathBuilder.h
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef SkPathBuilder_DEFINED
|
||||
#define SkPathBuilder_DEFINED
|
||||
|
||||
#include "include/core/SkMatrix.h"
|
||||
#include "include/core/SkPath.h"
|
||||
#include "include/core/SkPathTypes.h"
|
||||
#include "include/private/SkTDArray.h"
|
||||
|
||||
class SkPathBuilder {
|
||||
public:
|
||||
SkPathBuilder();
|
||||
~SkPathBuilder();
|
||||
|
||||
SkPath snapshot(); // the builder is unchanged after returning this path
|
||||
SkPath detach(); // the builder is reset to empty after returning this path
|
||||
|
||||
void setFillType(SkPathFillType ft) { fFillType = ft; }
|
||||
void setIsVolatile(bool isVolatile) { fIsVolatile = isVolatile; }
|
||||
|
||||
SkPathBuilder& reset();
|
||||
|
||||
SkPathBuilder& moveTo(SkPoint pt);
|
||||
SkPathBuilder& lineTo(SkPoint pt);
|
||||
SkPathBuilder& quadTo(SkPoint pt1, SkPoint pt2);
|
||||
SkPathBuilder& conicTo(SkPoint pt1, SkPoint pt2, SkScalar w);
|
||||
SkPathBuilder& cubicTo(SkPoint pt1, SkPoint pt2, SkPoint pt3);
|
||||
SkPathBuilder& close();
|
||||
|
||||
SkPathBuilder& moveTo(SkScalar x, SkScalar y) { return this->moveTo(SkPoint::Make(x, y)); }
|
||||
SkPathBuilder& lineTo(SkScalar x, SkScalar y) { return this->lineTo(SkPoint::Make(x, y)); }
|
||||
SkPathBuilder& quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
|
||||
return this->quadTo(SkPoint::Make(x1, y1), SkPoint::Make(x2, y2));
|
||||
}
|
||||
SkPathBuilder& quadTo(const SkPoint pts[2]) { return this->quadTo(pts[0], pts[1]); }
|
||||
SkPathBuilder& conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar w) {
|
||||
return this->conicTo(SkPoint::Make(x1, y1), SkPoint::Make(x2, y2), w);
|
||||
}
|
||||
SkPathBuilder& conicTo(const SkPoint pts[2], SkScalar w) {
|
||||
return this->conicTo(pts[0], pts[1], w);
|
||||
}
|
||||
SkPathBuilder& cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar x3, SkScalar y3) {
|
||||
return this->cubicTo(SkPoint::Make(x1, y1), SkPoint::Make(x2, y2), SkPoint::Make(x3, y3));
|
||||
}
|
||||
SkPathBuilder& cubicTo(const SkPoint pts[3]) {
|
||||
return this->cubicTo(pts[0], pts[1], pts[2]);
|
||||
}
|
||||
|
||||
SkPathBuilder& addRect(const SkRect&, SkPathDirection, unsigned startIndex);
|
||||
SkPathBuilder& addOval(const SkRect&, SkPathDirection, unsigned startIndex);
|
||||
SkPathBuilder& addRRect(const SkRRect&, SkPathDirection, unsigned startIndex);
|
||||
|
||||
SkPathBuilder& addRect(const SkRect& rect, SkPathDirection dir = SkPathDirection::kCW) {
|
||||
return this->addRect(rect, dir, 0);
|
||||
}
|
||||
SkPathBuilder& addOval(const SkRect& rect, SkPathDirection dir = SkPathDirection::kCW) {
|
||||
return this->addOval(rect, dir, 0);
|
||||
}
|
||||
SkPathBuilder& addRRect(const SkRRect& rect, SkPathDirection dir = SkPathDirection::kCW) {
|
||||
return this->addRRect(rect, dir, 0);
|
||||
}
|
||||
|
||||
void incReserve(int extraPtCount, int extraVerbCount);
|
||||
void incReserve(int extraPtCount) {
|
||||
this->incReserve(extraPtCount, extraPtCount);
|
||||
}
|
||||
|
||||
static SkPath Make(const SkPoint[], int pointCount,
|
||||
const uint8_t[], int verbCount,
|
||||
const SkScalar[], int conicWeightCount,
|
||||
SkPathFillType, bool isVolatile = false);
|
||||
|
||||
private:
|
||||
SkTDArray<SkPoint> fPts;
|
||||
SkTDArray<uint8_t> fVerbs;
|
||||
SkTDArray<SkScalar> fConicWeights;
|
||||
|
||||
SkPathFillType fFillType;
|
||||
bool fIsVolatile;
|
||||
|
||||
unsigned fSegmentMask;
|
||||
SkPoint fLastMovePoint;
|
||||
bool fNeedsMoveVerb;
|
||||
|
||||
int countVerbs() const { return fVerbs.count(); }
|
||||
|
||||
void ensureMove() {
|
||||
if (fNeedsMoveVerb) {
|
||||
this->moveTo(fLastMovePoint);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -42,6 +42,24 @@ class SkWBuffer;
|
||||
|
||||
class SK_API SkPathRef final : public SkNVRefCnt<SkPathRef> {
|
||||
public:
|
||||
SkPathRef(SkTDArray<SkPoint> points, SkTDArray<uint8_t> verbs, SkTDArray<SkScalar> weights,
|
||||
unsigned segmentMask)
|
||||
: fPoints(std::move(points))
|
||||
, fVerbs(std::move(verbs))
|
||||
, fConicWeights(std::move(weights))
|
||||
{
|
||||
fBoundsIsDirty = true; // this also invalidates fIsFinite
|
||||
fGenerationID = kEmptyGenID;
|
||||
fSegmentMask = segmentMask;
|
||||
fIsOval = false;
|
||||
fIsRRect = false;
|
||||
// The next two values don't matter unless fIsOval or fIsRRect are true.
|
||||
fRRectOrOvalIsCCW = false;
|
||||
fRRectOrOvalStartIdx = 0xAC;
|
||||
SkDEBUGCODE(fEditorsAttached.store(0);)
|
||||
SkDEBUGCODE(this->validate();)
|
||||
}
|
||||
|
||||
class Editor {
|
||||
public:
|
||||
Editor(sk_sp<SkPathRef>* pathRef,
|
||||
|
@ -148,6 +148,15 @@ SkPath::SkPath()
|
||||
fIsVolatile = false;
|
||||
}
|
||||
|
||||
SkPath::SkPath(sk_sp<SkPathRef> pr, SkPathFillType ft, bool isVolatile)
|
||||
: fPathRef(std::move(pr))
|
||||
, fLastMoveToIndex(INITIAL_LASTMOVETOINDEX_VALUE)
|
||||
, fConvexity((uint8_t)SkPathConvexityType::kUnknown)
|
||||
, fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
|
||||
, fFillType((unsigned)ft)
|
||||
, fIsVolatile(isVolatile)
|
||||
{}
|
||||
|
||||
void SkPath::resetFields() {
|
||||
//fPathRef is assumed to have been emptied by the caller.
|
||||
fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
|
||||
|
358
src/core/SkPathBuilder.cpp
Normal file
358
src/core/SkPathBuilder.cpp
Normal file
@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "include/core/SkPathBuilder.h"
|
||||
#include "include/core/SkRRect.h"
|
||||
#include "include/private/SkPathRef.h"
|
||||
#include "include/private/SkSafe32.h"
|
||||
|
||||
SkPathBuilder::SkPathBuilder() {
|
||||
this->reset();
|
||||
}
|
||||
|
||||
SkPathBuilder::~SkPathBuilder() {
|
||||
}
|
||||
|
||||
SkPathBuilder& SkPathBuilder::reset() {
|
||||
fPts.reset();
|
||||
fVerbs.reset();
|
||||
fConicWeights.reset();
|
||||
fFillType = SkPathFillType::kWinding;
|
||||
fIsVolatile = false;
|
||||
|
||||
// these are internal state
|
||||
|
||||
fSegmentMask = 0;
|
||||
fLastMovePoint = {0, 0};
|
||||
fNeedsMoveVerb = true;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void SkPathBuilder::incReserve(int extraPtCount, int extraVbCount) {
|
||||
fPts.setReserve( Sk32_sat_add(fPts.count(), extraPtCount));
|
||||
fVerbs.setReserve(Sk32_sat_add(fVerbs.count(), extraVbCount));
|
||||
}
|
||||
|
||||
/*
|
||||
* Some old behavior in SkPath -- should we keep it?
|
||||
*
|
||||
* After each edit (i.e. adding a verb)
|
||||
this->setConvexityType(SkPathConvexityType::kUnknown);
|
||||
this->setFirstDirection(SkPathPriv::kUnknown_FirstDirection);
|
||||
*/
|
||||
|
||||
SkPathBuilder& SkPathBuilder::moveTo(SkPoint pt) {
|
||||
fPts.push_back(pt);
|
||||
fVerbs.push_back((uint8_t)SkPathVerb::kMove);
|
||||
|
||||
fLastMovePoint = pt;
|
||||
fNeedsMoveVerb = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SkPathBuilder& SkPathBuilder::lineTo(SkPoint pt) {
|
||||
this->ensureMove();
|
||||
|
||||
fPts.push_back(pt);
|
||||
fVerbs.push_back((uint8_t)SkPathVerb::kLine);
|
||||
|
||||
fSegmentMask |= kLine_SkPathSegmentMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SkPathBuilder& SkPathBuilder::quadTo(SkPoint pt1, SkPoint pt2) {
|
||||
this->ensureMove();
|
||||
|
||||
SkPoint* p = fPts.append(2);
|
||||
p[0] = pt1;
|
||||
p[1] = pt2;
|
||||
fVerbs.push_back((uint8_t)SkPathVerb::kQuad);
|
||||
|
||||
fSegmentMask |= kQuad_SkPathSegmentMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SkPathBuilder& SkPathBuilder::conicTo(SkPoint pt1, SkPoint pt2, SkScalar w) {
|
||||
this->ensureMove();
|
||||
|
||||
SkPoint* p = fPts.append(2);
|
||||
p[0] = pt1;
|
||||
p[1] = pt2;
|
||||
fVerbs.push_back((uint8_t)SkPathVerb::kConic);
|
||||
fConicWeights.push_back(w);
|
||||
|
||||
fSegmentMask |= kConic_SkPathSegmentMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SkPathBuilder& SkPathBuilder::cubicTo(SkPoint pt1, SkPoint pt2, SkPoint pt3) {
|
||||
this->ensureMove();
|
||||
|
||||
SkPoint* p = fPts.append(3);
|
||||
p[0] = pt1;
|
||||
p[1] = pt2;
|
||||
p[2] = pt3;
|
||||
fVerbs.push_back((uint8_t)SkPathVerb::kCubic);
|
||||
|
||||
fSegmentMask |= kCubic_SkPathSegmentMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SkPathBuilder& SkPathBuilder::close() {
|
||||
this->ensureMove();
|
||||
|
||||
fVerbs.push_back((uint8_t)SkPathVerb::kClose);
|
||||
|
||||
// fLastMovePoint stays where it is -- the previous moveTo
|
||||
fNeedsMoveVerb = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SkPath SkPathBuilder::snapshot() {
|
||||
return SkPath(sk_sp<SkPathRef>(new SkPathRef(fPts, fVerbs, fConicWeights, fSegmentMask)),
|
||||
fFillType, fIsVolatile);
|
||||
}
|
||||
|
||||
SkPath SkPathBuilder::detach() {
|
||||
auto path = SkPath(sk_sp<SkPathRef>(new SkPathRef(std::move(fPts),
|
||||
std::move(fVerbs),
|
||||
std::move(fConicWeights),
|
||||
fSegmentMask)),
|
||||
fFillType, fIsVolatile);
|
||||
this->reset();
|
||||
return path;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
template <unsigned N> class PointIterator {
|
||||
public:
|
||||
PointIterator(SkPathDirection dir, unsigned startIndex)
|
||||
: fCurrent(startIndex % N)
|
||||
, fAdvance(dir == SkPathDirection::kCW ? 1 : N - 1)
|
||||
{}
|
||||
|
||||
const SkPoint& current() const {
|
||||
SkASSERT(fCurrent < N);
|
||||
return fPts[fCurrent];
|
||||
}
|
||||
|
||||
const SkPoint& next() {
|
||||
fCurrent = (fCurrent + fAdvance) % N;
|
||||
return this->current();
|
||||
}
|
||||
|
||||
protected:
|
||||
SkPoint fPts[N];
|
||||
|
||||
private:
|
||||
unsigned fCurrent;
|
||||
unsigned fAdvance;
|
||||
};
|
||||
|
||||
class RectPointIterator : public PointIterator<4> {
|
||||
public:
|
||||
RectPointIterator(const SkRect& rect, SkPathDirection dir, unsigned startIndex)
|
||||
: PointIterator(dir, startIndex) {
|
||||
|
||||
fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
|
||||
fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
|
||||
fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
|
||||
fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
|
||||
}
|
||||
};
|
||||
|
||||
class OvalPointIterator : public PointIterator<4> {
|
||||
public:
|
||||
OvalPointIterator(const SkRect& oval, SkPathDirection dir, unsigned startIndex)
|
||||
: PointIterator(dir, startIndex) {
|
||||
|
||||
const SkScalar cx = oval.centerX();
|
||||
const SkScalar cy = oval.centerY();
|
||||
|
||||
fPts[0] = SkPoint::Make(cx, oval.fTop);
|
||||
fPts[1] = SkPoint::Make(oval.fRight, cy);
|
||||
fPts[2] = SkPoint::Make(cx, oval.fBottom);
|
||||
fPts[3] = SkPoint::Make(oval.fLeft, cy);
|
||||
}
|
||||
};
|
||||
|
||||
class RRectPointIterator : public PointIterator<8> {
|
||||
public:
|
||||
RRectPointIterator(const SkRRect& rrect, SkPathDirection dir, unsigned startIndex)
|
||||
: PointIterator(dir, startIndex)
|
||||
{
|
||||
const SkRect& bounds = rrect.getBounds();
|
||||
const SkScalar L = bounds.fLeft;
|
||||
const SkScalar T = bounds.fTop;
|
||||
const SkScalar R = bounds.fRight;
|
||||
const SkScalar B = bounds.fBottom;
|
||||
|
||||
fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
|
||||
fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
|
||||
fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
|
||||
fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
|
||||
fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
|
||||
fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
|
||||
fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
|
||||
fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
|
||||
}
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
|
||||
SkPathBuilder& SkPathBuilder::addRect(const SkRect& rect, SkPathDirection dir, unsigned index) {
|
||||
const int kPts = 4; // moveTo + 3 lines
|
||||
const int kVerbs = 5; // moveTo + 3 lines + close
|
||||
this->incReserve(kPts, kVerbs);
|
||||
|
||||
RectPointIterator iter(rect, dir, index);
|
||||
|
||||
this->moveTo(iter.current());
|
||||
this->lineTo(iter.next());
|
||||
this->lineTo(iter.next());
|
||||
this->lineTo(iter.next());
|
||||
return this->close();
|
||||
}
|
||||
|
||||
SkPathBuilder& SkPathBuilder::addOval(const SkRect& oval, SkPathDirection dir, unsigned index) {
|
||||
const int kPts = 9; // moveTo + 4 conics(2 pts each)
|
||||
const int kVerbs = 6; // moveTo + 4 conics + close
|
||||
this->incReserve(kPts, kVerbs);
|
||||
|
||||
OvalPointIterator ovalIter(oval, dir, index);
|
||||
RectPointIterator rectIter(oval, dir, index + (dir == SkPathDirection::kCW ? 0 : 1));
|
||||
|
||||
// The corner iterator pts are tracking "behind" the oval/radii pts.
|
||||
|
||||
this->moveTo(ovalIter.current());
|
||||
for (unsigned i = 0; i < 4; ++i) {
|
||||
this->conicTo(rectIter.next(), ovalIter.next(), SK_ScalarRoot2Over2);
|
||||
}
|
||||
return this->close();
|
||||
}
|
||||
|
||||
SkPathBuilder& SkPathBuilder::addRRect(const SkRRect& rrect, SkPathDirection dir, unsigned index) {
|
||||
const SkRect& bounds = rrect.getBounds();
|
||||
|
||||
if (rrect.isRect() || rrect.isEmpty()) {
|
||||
// degenerate(rect) => radii points are collapsing
|
||||
this->addRect(bounds, dir, (index + 1) / 2);
|
||||
} else if (rrect.isOval()) {
|
||||
// degenerate(oval) => line points are collapsing
|
||||
this->addOval(bounds, dir, index / 2);
|
||||
} else {
|
||||
// we start with a conic on odd indices when moving CW vs. even indices when moving CCW
|
||||
const bool startsWithConic = ((index & 1) == (dir == SkPathDirection::kCW));
|
||||
const SkScalar weight = SK_ScalarRoot2Over2;
|
||||
|
||||
const int kVerbs = startsWithConic
|
||||
? 9 // moveTo + 4x conicTo + 3x lineTo + close
|
||||
: 10; // moveTo + 4x lineTo + 4x conicTo + close
|
||||
this->incReserve(kVerbs);
|
||||
|
||||
RRectPointIterator rrectIter(rrect, dir, index);
|
||||
// Corner iterator indices follow the collapsed radii model,
|
||||
// adjusted such that the start pt is "behind" the radii start pt.
|
||||
const unsigned rectStartIndex = index / 2 + (dir == SkPathDirection::kCW ? 0 : 1);
|
||||
RectPointIterator rectIter(bounds, dir, rectStartIndex);
|
||||
|
||||
this->moveTo(rrectIter.current());
|
||||
if (startsWithConic) {
|
||||
for (unsigned i = 0; i < 3; ++i) {
|
||||
this->conicTo(rectIter.next(), rrectIter.next(), weight);
|
||||
this->lineTo(rrectIter.next());
|
||||
}
|
||||
this->conicTo(rectIter.next(), rrectIter.next(), weight);
|
||||
// final lineTo handled by close().
|
||||
} else {
|
||||
for (unsigned i = 0; i < 4; ++i) {
|
||||
this->lineTo(rrectIter.next());
|
||||
this->conicTo(rectIter.next(), rrectIter.next(), weight);
|
||||
}
|
||||
}
|
||||
this->close();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct PathInfo {
|
||||
bool valid;
|
||||
int points, weights;
|
||||
unsigned segmentMask;
|
||||
};
|
||||
|
||||
static PathInfo validate_verbs(const uint8_t vbs[], int verbCount) {
|
||||
PathInfo info = {false, 0, 0, 0};
|
||||
|
||||
bool needMove = true;
|
||||
bool invalid = false;
|
||||
for (int i = 0; i < verbCount; ++i) {
|
||||
switch ((SkPathVerb)vbs[i]) {
|
||||
case SkPathVerb::kMove:
|
||||
needMove = false;
|
||||
info.points += 1;
|
||||
break;
|
||||
case SkPathVerb::kLine:
|
||||
invalid |= needMove;
|
||||
info.segmentMask |= kLine_SkPathSegmentMask;
|
||||
info.points += 1;
|
||||
break;
|
||||
case SkPathVerb::kQuad:
|
||||
invalid |= needMove;
|
||||
info.segmentMask |= kQuad_SkPathSegmentMask;
|
||||
info.points += 2;
|
||||
break;
|
||||
case SkPathVerb::kConic:
|
||||
invalid |= needMove;
|
||||
info.segmentMask |= kConic_SkPathSegmentMask;
|
||||
info.points += 2;
|
||||
info.weights += 1;
|
||||
break;
|
||||
case SkPathVerb::kCubic:
|
||||
invalid |= needMove;
|
||||
info.segmentMask |= kCubic_SkPathSegmentMask;
|
||||
info.points += 3;
|
||||
break;
|
||||
case SkPathVerb::kClose:
|
||||
invalid |= needMove;
|
||||
needMove = true;
|
||||
break;
|
||||
default:
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
info.valid = !invalid;
|
||||
return info;
|
||||
}
|
||||
|
||||
SkPath SkPathBuilder::Make(const SkPoint pts[], int pointCount,
|
||||
const uint8_t vbs[], int verbCount,
|
||||
const SkScalar ws[], int wCount,
|
||||
SkPathFillType ft, bool isVolatile) {
|
||||
if (verbCount <= 0) {
|
||||
return SkPath();
|
||||
}
|
||||
|
||||
const auto info = validate_verbs(vbs, verbCount);
|
||||
if (!info.valid || info.points > pointCount || info.weights > wCount) {
|
||||
SkDEBUGFAIL("invalid verbs and number of points/weights");
|
||||
return SkPath();
|
||||
}
|
||||
|
||||
return SkPath(sk_sp<SkPathRef>(new SkPathRef(SkTDArray<SkPoint>(pts, info.points),
|
||||
SkTDArray<uint8_t>(vbs, verbCount),
|
||||
SkTDArray<SkScalar>(ws, info.weights),
|
||||
info.segmentMask)),
|
||||
ft, isVolatile);
|
||||
}
|
||||
|
168
tests/PathBuilderTest.cpp
Normal file
168
tests/PathBuilderTest.cpp
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2020 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "include/core/SkPathBuilder.h"
|
||||
#include "src/core/SkPathPriv.h"
|
||||
#include "tests/Test.h"
|
||||
|
||||
static void is_empty(skiatest::Reporter* reporter, const SkPath& p) {
|
||||
REPORTER_ASSERT(reporter, p.getBounds().isEmpty());
|
||||
REPORTER_ASSERT(reporter, p.countPoints() == 0);
|
||||
}
|
||||
|
||||
DEF_TEST(pathbuilder, reporter) {
|
||||
SkPathBuilder b;
|
||||
|
||||
is_empty(reporter, b.snapshot());
|
||||
is_empty(reporter, b.detach());
|
||||
|
||||
b.moveTo(10, 10).lineTo(20, 20).quadTo(30, 10, 10, 20);
|
||||
|
||||
SkPath p0 = b.snapshot();
|
||||
SkPath p1 = b.snapshot();
|
||||
SkPath p2 = b.detach();
|
||||
|
||||
REPORTER_ASSERT(reporter, p0.getBounds() == SkRect::MakeLTRB(10, 10, 30, 20));
|
||||
REPORTER_ASSERT(reporter, p0.countPoints() == 4);
|
||||
|
||||
REPORTER_ASSERT(reporter, p0 == p1);
|
||||
REPORTER_ASSERT(reporter, p0 == p2);
|
||||
|
||||
is_empty(reporter, b.snapshot());
|
||||
is_empty(reporter, b.detach());
|
||||
}
|
||||
|
||||
static bool check_points(const SkPath& path, const SkPoint expected[], size_t count) {
|
||||
std::vector<SkPoint> iter_pts;
|
||||
|
||||
for (auto [v, p, w] : SkPathPriv::Iterate(path)) {
|
||||
switch (v) {
|
||||
case SkPathVerb::kMove:
|
||||
iter_pts.push_back(p[0]);
|
||||
break;
|
||||
case SkPathVerb::kLine:
|
||||
iter_pts.push_back(p[1]);
|
||||
break;
|
||||
case SkPathVerb::kQuad:
|
||||
case SkPathVerb::kConic:
|
||||
iter_pts.push_back(p[1]);
|
||||
iter_pts.push_back(p[2]);
|
||||
break;
|
||||
case SkPathVerb::kCubic:
|
||||
iter_pts.push_back(p[1]);
|
||||
iter_pts.push_back(p[2]);
|
||||
iter_pts.push_back(p[3]);
|
||||
break;
|
||||
case SkPathVerb::kClose:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (iter_pts.size() != count) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
if (iter_pts[i] != expected[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
DEF_TEST(pathbuilder_missing_move, reporter) {
|
||||
SkPathBuilder b;
|
||||
|
||||
b.lineTo(10, 10).lineTo(20, 30);
|
||||
const SkPoint pts0[] = {
|
||||
{0, 0}, {10, 10}, {20, 30},
|
||||
};
|
||||
REPORTER_ASSERT(reporter, check_points(b.snapshot(), pts0, SK_ARRAY_COUNT(pts0)));
|
||||
|
||||
b.reset().moveTo(20, 20).lineTo(10, 10).lineTo(20, 30).close().lineTo(60, 60);
|
||||
const SkPoint pts1[] = {
|
||||
{20, 20}, {10, 10}, {20, 30},
|
||||
{20, 20}, {60, 60},
|
||||
};
|
||||
REPORTER_ASSERT(reporter, check_points(b.snapshot(), pts1, SK_ARRAY_COUNT(pts1)));
|
||||
}
|
||||
|
||||
DEF_TEST(pathbuilder_addRect, reporter) {
|
||||
const SkRect r = { 10, 20, 30, 40 };
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
|
||||
SkPathBuilder b;
|
||||
b.addRect(r, dir, i);
|
||||
auto bp = b.detach();
|
||||
|
||||
SkRect r2;
|
||||
bool closed = false;
|
||||
SkPathDirection dir2;
|
||||
REPORTER_ASSERT(reporter, bp.isRect(&r2, &closed, &dir2));
|
||||
REPORTER_ASSERT(reporter, r2 == r);
|
||||
REPORTER_ASSERT(reporter, closed);
|
||||
REPORTER_ASSERT(reporter, dir == dir2);
|
||||
|
||||
SkPath p;
|
||||
p.addRect(r, dir, i);
|
||||
REPORTER_ASSERT(reporter, p == bp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DEF_TEST(pathbuilder_addOval, reporter) {
|
||||
const SkRect r = { 10, 20, 30, 40 };
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
|
||||
SkPathBuilder b;
|
||||
b.addOval(r, dir, i);
|
||||
auto bp = b.detach();
|
||||
|
||||
SkPath p;
|
||||
p.addOval(r, dir, i);
|
||||
REPORTER_ASSERT(reporter, p == bp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DEF_TEST(pathbuilder_addRRect, reporter) {
|
||||
const SkRRect rr = SkRRect::MakeRectXY({ 10, 20, 30, 40 }, 5, 6);
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
|
||||
SkPathBuilder b;
|
||||
b.addRRect(rr, dir, i);
|
||||
auto bp = b.detach();
|
||||
|
||||
SkPath p;
|
||||
p.addRRect(rr, dir, i);
|
||||
REPORTER_ASSERT(reporter, p == bp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#include "include/utils/SkRandom.h"
|
||||
|
||||
DEF_TEST(pathbuilder_make, reporter) {
|
||||
constexpr int N = 100;
|
||||
uint8_t vbs[N];
|
||||
SkPoint pts[N];
|
||||
|
||||
SkRandom rand;
|
||||
SkPathBuilder b;
|
||||
b.moveTo(0, 0);
|
||||
pts[0] = {0, 0}; vbs[0] = (uint8_t)SkPathVerb::kMove;
|
||||
for (int i = 1; i < N; ++i) {
|
||||
float x = rand.nextF();
|
||||
float y = rand.nextF();
|
||||
b.lineTo(x, y);
|
||||
pts[i] = {x, y}; vbs[i] = (uint8_t)SkPathVerb::kLine;
|
||||
}
|
||||
auto p0 = b.detach();
|
||||
auto p1 = SkPathBuilder::Make(pts, N, vbs, N, nullptr, 0, p0.getFillType());
|
||||
REPORTER_ASSERT(reporter, p0 == p1);
|
||||
}
|
Loading…
Reference in New Issue
Block a user