blob: 7aafd31baa924f990b0d3a94b5bca2d4db445b07 [file] [log] [blame]
/*
* Copyright 2025 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/SkPath.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkPathTypes.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "src/core/SkPathPriv.h"
#include "src/core/SkPathRaw.h"
#include "tests/Test.h"
class SkSPathRawBuilder {
public:
SkSPathRawBuilder(SkSpan<SkPoint> ptStore, SkSpan<uint8_t> vbStore, SkSpan<SkScalar> cnStore)
: fPtStorage(ptStore)
, fVbStorage(vbStore)
, fCnStorage(cnStore)
, fPts(0), fCns(0), fVbs(0)
{}
void moveTo(SkPoint);
void lineTo(SkPoint);
void quadTo(SkPoint, SkPoint);
void conicTo(SkPoint, SkPoint, SkScalar w);
void cubicTo(SkPoint, SkPoint, SkPoint);
void close();
SkPathRaw raw(SkPathFillType, bool isConvex = false) const;
private:
SkSpan<SkPoint> fPtStorage;
SkSpan<uint8_t> fVbStorage;
SkSpan<SkScalar> fCnStorage;
size_t fPts, fCns, fVbs;
void check_extend_pts(size_t n) const {
SkASSERT(fPts + n <= fPtStorage.size());
}
void check_extend_vbs(size_t n) const {
SkASSERT(fVbs + n <= fVbStorage.size());
}
void check_extend_cns(size_t n) const {
SkASSERT(fCns + n <= fCnStorage.size());
}
};
void SkSPathRawBuilder::moveTo(SkPoint p) {
check_extend_pts(1);
check_extend_vbs(1);
fPtStorage[fPts++] = p;
fVbStorage[fVbs++] = (uint8_t)SkPathVerb::kMove;
}
void SkSPathRawBuilder::lineTo(SkPoint p) {
check_extend_pts(1);
check_extend_vbs(1);
fPtStorage[fPts++] = p;
fVbStorage[fVbs++] = (uint8_t)SkPathVerb::kLine;
}
void SkSPathRawBuilder::quadTo(SkPoint p1, SkPoint p2) {
check_extend_pts(2);
check_extend_vbs(1);
fPtStorage[fPts++] = p1;
fPtStorage[fPts++] = p2;
fVbStorage[fVbs++] = (uint8_t)SkPathVerb::kQuad;
}
void SkSPathRawBuilder::conicTo(SkPoint p1, SkPoint p2, SkScalar w) {
check_extend_pts(2);
check_extend_cns(1);
check_extend_vbs(1);
fPtStorage[fPts++] = p1;
fPtStorage[fPts++] = p2;
fCnStorage[fCns++] = w;
fVbStorage[fVbs++] = (uint8_t)SkPathVerb::kConic;
}
void SkSPathRawBuilder::cubicTo(SkPoint p1, SkPoint p2, SkPoint p3) {
check_extend_pts(3);
check_extend_vbs(1);
fPtStorage[fPts++] = p1;
fPtStorage[fPts++] = p2;
fPtStorage[fPts++] = p3;
fVbStorage[fVbs++] = (uint8_t)SkPathVerb::kCubic;
}
void SkSPathRawBuilder::close() {
check_extend_vbs(1);
fVbStorage[fVbs++] = (uint8_t)SkPathVerb::kClose;
}
SkPathRaw SkSPathRawBuilder::raw(SkPathFillType ft, bool isConvex) const {
const auto ptSpan = fPtStorage.first(fPts);
return {
ptSpan,
fVbStorage.first(fVbs),
fCnStorage.first(fCns),
SkRect::BoundsOrEmpty(ptSpan),
ft,
isConvex,
};
}
static void check_iter(skiatest::Reporter* reporter, SkPathRaw::Iter iter,
SkSpan<SkPoint> pts, SkSpan<const uint8_t> vbs, SkSpan<const SkScalar> cns) {
size_t pIndex = 0, vIndex = 0, cIndex = 0;
size_t moveToIndex = 0; // track the start of each contour
while (auto r = iter.next()) {
REPORTER_ASSERT(reporter, vIndex < vbs.size());
REPORTER_ASSERT(reporter, vbs[vIndex++] == (uint8_t)r->vrb);
switch (r->vrb) {
case SkPathVerb::kMove:
moveToIndex = pIndex;
REPORTER_ASSERT(reporter, pIndex < pts.size());
REPORTER_ASSERT(reporter, pts[pIndex++] == r->pts[0]);
break;
case SkPathVerb::kLine:
REPORTER_ASSERT(reporter, pIndex < pts.size());
REPORTER_ASSERT(reporter, pts[pIndex-1] == r->pts[0]);
REPORTER_ASSERT(reporter, pts[pIndex++] == r->pts[1]);
break;
case SkPathVerb::kQuad:
REPORTER_ASSERT(reporter, pIndex+1 < pts.size());
REPORTER_ASSERT(reporter, pts[pIndex-1] == r->pts[0]);
REPORTER_ASSERT(reporter, pts[pIndex++] == r->pts[1]);
REPORTER_ASSERT(reporter, pts[pIndex++] == r->pts[2]);
break;
case SkPathVerb::kConic:
REPORTER_ASSERT(reporter, pIndex+1 < pts.size());
REPORTER_ASSERT(reporter, pts[pIndex-1] == r->pts[0]);
REPORTER_ASSERT(reporter, pts[pIndex++] == r->pts[1]);
REPORTER_ASSERT(reporter, pts[pIndex++] == r->pts[2]);
REPORTER_ASSERT(reporter, cIndex < cns.size());
REPORTER_ASSERT(reporter, cns[cIndex++] == r->w);
break;
case SkPathVerb::kCubic:
REPORTER_ASSERT(reporter, pIndex+2 < pts.size());
REPORTER_ASSERT(reporter, pts[pIndex-1] == r->pts[0]);
REPORTER_ASSERT(reporter, pts[pIndex++] == r->pts[1]);
REPORTER_ASSERT(reporter, pts[pIndex++] == r->pts[2]);
REPORTER_ASSERT(reporter, pts[pIndex++] == r->pts[3]);
break;
case SkPathVerb::kClose:
REPORTER_ASSERT(reporter, pts[pIndex-1] == r->pts[0]); // last pt in contour
REPORTER_ASSERT(reporter, pts[moveToIndex] == r->pts[1]); // first pt in contour
break;
}
}
// make sure the iter is really done
REPORTER_ASSERT(reporter, !iter.next());
}
DEF_TEST(pathraw_iter, reporter) {
SkPoint pts[11];
uint8_t vbs[8];
SkScalar cns[1];
constexpr size_t N = 11;
SkPoint p[N];
for (size_t i = 0; i < N; ++i) {
p[i] = {SkScalar(i), SkScalar(i)};
}
SkSPathRawBuilder bu(pts, vbs, cns);
const uint8_t verbs[] = {
(uint8_t)SkPathVerb::kMove,
(uint8_t)SkPathVerb::kLine,
(uint8_t)SkPathVerb::kQuad,
(uint8_t)SkPathVerb::kCubic,
(uint8_t)SkPathVerb::kClose,
(uint8_t)SkPathVerb::kMove,
(uint8_t)SkPathVerb::kLine,
(uint8_t)SkPathVerb::kConic,
};
bu.moveTo(p[0]);
bu.lineTo(p[1]);
bu.quadTo(p[2], p[3]);
bu.cubicTo(p[4], p[5], p[6]);
bu.close();
bu.moveTo(p[7]);
bu.lineTo(p[8]);
bu.conicTo(p[9], p[10], 2);
auto raw = bu.raw(SkPathFillType::kWinding);
REPORTER_ASSERT(reporter, raw.fPoints.size() == N);
REPORTER_ASSERT(reporter, raw.fVerbs.size() == 8);
REPORTER_ASSERT(reporter, raw.fConics.size() == 1);
check_iter(reporter, raw, p, verbs, cns);
// now make sure pathbuilder generates the same results
SkPathBuilder pb;
pb.moveTo(p[0]);
pb.lineTo(p[1]);
pb.quadTo(p[2], p[3]);
pb.cubicTo(p[4], p[5], p[6]);
pb.close();
pb.moveTo(p[7]);
pb.lineTo(p[8]);
pb.conicTo(p[9], p[10], 2);
auto path = pb.detach();
raw = SkPathPriv::Raw(path);
check_iter(reporter, raw, p, verbs, cns);
}