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:
Mike Reed 2020-06-23 21:06:28 -04:00 committed by Skia Commit-Bot
parent 5aaaeea4da
commit 22f246f5ad
9 changed files with 787 additions and 3 deletions

View File

@ -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); )

View File

@ -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",

View File

@ -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",

View File

@ -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;

View 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

View File

@ -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,

View File

@ -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
View 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
View 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);
}