blob: 4f624bd75ff9deb698d4d16b91f17fe2e227acd8 [file] [log] [blame]
/*
* 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/SkMatrix.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathTypes.h"
#include "include/core/SkRRect.h"
#include "include/core/SkTypes.h"
#include "include/private/SkPathRef.h"
#include "include/private/base/SkAssert.h" // IWYU pragma: keep
#include "include/private/base/SkFloatingPoint.h"
#include "include/private/base/SkSafe32.h"
#include "include/private/base/SkTArray.h"
#include "include/private/base/SkTo.h"
#include "src/base/SkVx.h"
#include "src/core/SkGeometry.h"
#include "src/core/SkMatrixPriv.h"
#include "src/core/SkPathEnums.h"
#include "src/core/SkPathPriv.h"
#include "src/core/SkPathRawShapes.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <utility>
namespace {
void subdivide_cubic_to(SkPathBuilder* path, const SkPoint pts[4], int level = 2) {
if (--level >= 0) {
SkPoint tmp[7];
SkChopCubicAtHalf(pts, tmp);
subdivide_cubic_to(path, &tmp[0], level);
subdivide_cubic_to(path, &tmp[3], level);
} else {
path->cubicTo(pts[1], pts[2], pts[3]);
}
}
} // namespace
SkPathBuilder::SkPathBuilder() {
this->reset();
}
SkPathBuilder::SkPathBuilder(SkPathFillType ft) {
this->reset();
fFillType = ft;
}
SkPathBuilder::SkPathBuilder(const SkPath& src) {
*this = src;
}
SkPathBuilder::~SkPathBuilder() {
}
SkPathBuilder& SkPathBuilder::reset() {
fPts.clear();
fVerbs.clear();
fConicWeights.clear();
fFillType = SkPathFillType::kDefault;
fIsVolatile = false;
// these are internal state
fSegmentMask = 0;
fLastMovePoint = {0, 0};
fLastMoveIndex = -1; // illegal
fNeedsMoveVerb = true;
fIsA = kIsA_JustMoves;
fConvexity = SkPathConvexity::kUnknown;
return *this;
}
SkPathBuilder& SkPathBuilder::operator=(const SkPath& src) {
this->reset().setFillType(src.getFillType());
this->setIsVolatile(src.isVolatile());
auto is_a = [](const sk_sp<SkPathRef>& p) -> IsA {
if (p->fVerbs.empty()) {
return IsA::kIsA_JustMoves;
}
switch (p->fType) {
case SkPathRef::PathType::kGeneral: return IsA::kIsA_MoreThanMoves;
case SkPathRef::PathType::kOval: return IsA::kIsA_Oval;
case SkPathRef::PathType::kRRect: return IsA::kIsA_RRect;
case SkPathRef::PathType::kArc: return IsA::kIsA_MoreThanMoves; // TODO: isArc
}
SkUNREACHABLE;
};
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;
fLastMovePoint = fPts.empty() ? SkPoint{0, 0} : fPts[fLastMoveIndex];
fNeedsMoveVerb = src.fLastMoveToIndex < 0;
fIsA = is_a(ref);
fIsAStart = ref->fRRectOrOvalStartIdx;
fIsACCW = ref->fRRectOrOvalIsCCW;
fConvexity = src.getConvexityOrUnknown();
return *this;
}
void SkPathBuilder::incReserve(int extraPtCount, int extraVbCount) {
fPts.reserve_exact(Sk32_sat_add(fPts.size(), extraPtCount));
fVerbs.reserve_exact(Sk32_sat_add(fVerbs.size(), extraVbCount));
}
std::tuple<SkPoint*, SkScalar*> SkPathBuilder::growForVerbsInPath(const SkPathRef& path) {
fSegmentMask |= path.fSegmentMask;
if (int numVerbs = path.countVerbs()) {
// TODO(borenet): If the current builder is empty or JustMoves, we can use the type of the
// path. If the path is empty, we can keep the current type.
fIsA = SkPathBuilder::IsA::kIsA_MoreThanMoves;
memcpy(fVerbs.push_back_n(numVerbs), path.fVerbs.begin(), numVerbs * sizeof(fVerbs[0]));
}
SkPoint* pts = nullptr;
if (int numPts = path.countPoints()) {
pts = fPts.push_back_n(numPts);
}
SkScalar* weights = nullptr;
if (int numConics = path.countWeights()) {
weights = fConicWeights.push_back_n(numConics);
}
return {pts, weights};
}
SkRect SkPathBuilder::computeBounds() const {
return SkRect::BoundsOrEmpty(fPts);
}
/*
* Some old behavior in SkPath -- should we keep it?
*
* After each edit (i.e. adding a verb)
this->setConvexityType(SkPathConvexity::kUnknown);
this->setFirstDirection(SkPathPriv::kUnknown_FirstDirection);
*/
SkPathBuilder& SkPathBuilder::moveTo(SkPoint pt) {
// only needed while SkPath is mutable
fLastMoveIndex = SkToInt(fPts.size());
fPts.push_back(pt);
fVerbs.push_back(SkPathVerb::kMove);
fLastMovePoint = pt;
fNeedsMoveVerb = false;
if (fIsA == kIsA_Oval || fIsA == kIsA_RRect) {
fIsA = kIsA_MoreThanMoves;
}
fConvexity = SkPathConvexity::kUnknown;
return *this;
}
SkPathBuilder& SkPathBuilder::lineTo(SkPoint pt) {
this->ensureMove();
fPts.push_back(pt);
fVerbs.push_back(SkPathVerb::kLine);
fSegmentMask |= kLine_SkPathSegmentMask;
return *this;
}
SkPathBuilder& SkPathBuilder::quadTo(SkPoint pt1, SkPoint pt2) {
this->ensureMove();
SkPoint* p = fPts.push_back_n(2);
p[0] = pt1;
p[1] = pt2;
fVerbs.push_back(SkPathVerb::kQuad);
fSegmentMask |= kQuad_SkPathSegmentMask;
return *this;
}
SkPathBuilder& SkPathBuilder::conicTo(SkPoint pt1, SkPoint pt2, SkScalar w) {
this->ensureMove();
SkPoint* p = fPts.push_back_n(2);
p[0] = pt1;
p[1] = pt2;
fVerbs.push_back(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.push_back_n(3);
p[0] = pt1;
p[1] = pt2;
p[2] = pt3;
fVerbs.push_back(SkPathVerb::kCubic);
fSegmentMask |= kCubic_SkPathSegmentMask;
return *this;
}
SkPathBuilder& SkPathBuilder::close() {
// If this is a 2nd 'close', we just ignore it
if (!fVerbs.empty() && fVerbs.back() != SkPathVerb::kClose) {
this->ensureMove();
fVerbs.push_back(SkPathVerb::kClose);
// fLastMovePoint stays where it is -- the previous moveTo
fNeedsMoveVerb = true;
}
return *this;
}
///////////////////////////////////////////////////////////////////////////////////////////
SkPathBuilder& SkPathBuilder::rMoveTo(SkPoint pt) {
SkPoint lastPt = {0,0};
int count = fPts.size();
if (count > 0) {
if (!fNeedsMoveVerb) {
lastPt = fPts[count - 1];
} else {
lastPt = fPts[fLastMoveIndex];
}
}
return this->moveTo(lastPt.fX + pt.fX, lastPt.fY + pt.fY);
}
SkPathBuilder& SkPathBuilder::rLineTo(SkPoint p1) {
this->ensureMove();
return this->lineTo(fPts.back() + p1);
}
SkPathBuilder& SkPathBuilder::rQuadTo(SkPoint p1, SkPoint p2) {
this->ensureMove();
SkPoint base = fPts.back();
return this->quadTo(base + p1, base + p2);
}
SkPathBuilder& SkPathBuilder::rConicTo(SkPoint p1, SkPoint p2, SkScalar w) {
this->ensureMove();
SkPoint base = fPts.back();
return this->conicTo(base + p1, base + p2, w);
}
SkPathBuilder& SkPathBuilder::rCubicTo(SkPoint p1, SkPoint p2, SkPoint p3) {
this->ensureMove();
SkPoint base = fPts.back();
return this->cubicTo(base + p1, base + p2, base + p3);
}
///////////////////////////////////////////////////////////////////////////////////////////
SkPath SkPathBuilder::make(sk_sp<SkPathRef> pr) const {
SkPathFirstDirection dir = SkPathFirstDirection::kUnknown;
switch (fIsA) {
case kIsA_Oval:
pr->setIsOval(fIsACCW, fIsAStart);
dir = fIsACCW ? SkPathFirstDirection::kCCW : SkPathFirstDirection::kCW;
SkASSERT(fConvexity == SkPathConvexity::kConvex);
break;
case kIsA_RRect:
pr->setIsRRect(fIsACCW, fIsAStart);
dir = fIsACCW ? SkPathFirstDirection::kCCW : SkPathFirstDirection::kCW;
SkASSERT(fConvexity == SkPathConvexity::kConvex);
break;
default: 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, dir);
// 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 {
return this->make(sk_sp<SkPathRef>(new SkPathRef(fPts,
fVerbs,
fConicWeights,
fSegmentMask)));
}
SkPath SkPathBuilder::detach() {
auto path = this->make(sk_sp<SkPathRef>(new SkPathRef(std::move(fPts),
std::move(fVerbs),
std::move(fConicWeights),
fSegmentMask)));
this->reset();
return path;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
SkPoint* pt) {
if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
// Chrome uses this path to move into and out of ovals. If not
// treated as a special case the moves can distort the oval's
// bounding box (and break the circle special case).
pt->set(oval.fRight, oval.centerY());
return true;
} else if (0 == oval.width() && 0 == oval.height()) {
// Chrome will sometimes create 0 radius round rects. Having degenerate
// quad segments in the path prevents the path from being recognized as
// a rect.
// TODO: optimizing the case where only one of width or height is zero
// should also be considered. This case, however, doesn't seem to be
// as common as the single point case.
pt->set(oval.fRight, oval.fTop);
return true;
}
return false;
}
// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
//
static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
SkScalar startRad = SkDegreesToRadians(startAngle),
stopRad = SkDegreesToRadians(startAngle + sweepAngle);
startV->fY = SkScalarSinSnapToZero(startRad);
startV->fX = SkScalarCosSnapToZero(startRad);
stopV->fY = SkScalarSinSnapToZero(stopRad);
stopV->fX = SkScalarCosSnapToZero(stopRad);
/* If the sweep angle is nearly (but less than) 360, then due to precision
loss in radians-conversion and/or sin/cos, we may end up with coincident
vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
of drawing a nearly complete circle (good).
e.g. canvas.drawArc(0, 359.99, ...)
-vs- canvas.drawArc(0, 359.9, ...)
We try to detect this edge case, and tweak the stop vector
*/
if (*startV == *stopV) {
SkScalar sw = SkScalarAbs(sweepAngle);
if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
// make a guess at a tiny angle (in radians) to tweak by
SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
// not sure how much will be enough, so we use a loop
do {
stopRad -= deltaRad;
stopV->fY = SkScalarSinSnapToZero(stopRad);
stopV->fX = SkScalarCosSnapToZero(stopRad);
} while (*startV == *stopV);
}
}
*dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
}
/**
* If this returns 0, then the caller should just line-to the singlePt, else it should
* ignore singlePt and append the specified number of conics.
*/
static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
SkPoint* singlePt) {
SkMatrix matrix;
matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
matrix.postTranslate(oval.centerX(), oval.centerY());
int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
if (0 == count) {
*singlePt = matrix.mapPoint(stop);
}
return count;
}
static bool nearly_equal(const SkPoint& a, const SkPoint& b) {
return SkScalarNearlyEqual(a.fX, b.fX)
&& SkScalarNearlyEqual(a.fY, b.fY);
}
SkPathBuilder& SkPathBuilder::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
bool forceMoveTo) {
if (oval.width() < 0 || oval.height() < 0) {
return *this;
}
if (fVerbs.empty()) {
forceMoveTo = true;
}
SkPoint lonePt;
if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
return forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
}
SkVector startV, stopV;
SkRotationDirection dir;
angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
SkPoint singlePt;
// Adds a move-to to 'pt' if forceMoveTo is true. Otherwise a lineTo unless we're sufficiently
// close to 'pt' currently. This prevents spurious lineTos when adding a series of contiguous
// arcs from the same oval.
auto addPt = [forceMoveTo, this](const SkPoint& pt) {
if (forceMoveTo) {
this->moveTo(pt);
} else if (!nearly_equal(fPts.back(), pt)) {
this->lineTo(pt);
}
};
// At this point, we know that the arc is not a lone point, but startV == stopV
// indicates that the sweepAngle is too small such that angles_to_unit_vectors
// cannot handle it.
if (startV == stopV) {
SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
SkScalar radiusX = oval.width() / 2;
SkScalar radiusY = oval.height() / 2;
// We do not use SkScalar[Sin|Cos]SnapToZero here. When sin(startAngle) is 0 and sweepAngle
// is very small and radius is huge, the expected behavior here is to draw a line. But
// calling SkScalarSinSnapToZero will make sin(endAngle) be 0 which will then draw a dot.
singlePt.set(oval.centerX() + radiusX * SkScalarCos(endAngle),
oval.centerY() + radiusY * SkScalarSin(endAngle));
addPt(singlePt);
return *this;
}
SkConic conics[SkConic::kMaxConicsForArc];
int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
if (count) {
this->incReserve(count * 2 + 1);
const SkPoint& pt = conics[0].fPts[0];
addPt(pt);
for (int i = 0; i < count; ++i) {
this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
}
} else {
addPt(singlePt);
}
return *this;
}
SkPathBuilder& SkPathBuilder::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate,
SkPathBuilder::ArcSize largeArc,
SkPathDirection sweep, SkScalar dx, SkScalar dy) {
const SkPoint currentPoint = this->getLastPt().value_or(SkPoint{0, 0});
return this->arcTo({rx, ry}, xAxisRotate, largeArc, sweep,
{currentPoint.fX + dx, currentPoint.fY + dy});
}
SkPathBuilder& SkPathBuilder::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
if (oval.isEmpty() || 0 == sweepAngle) {
return *this;
}
const SkScalar kFullCircleAngle = SkIntToScalar(360);
if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
// We can treat the arc as an oval if it begins at one of our legal starting positions.
// See SkPath::addOval() docs.
SkScalar startOver90 = startAngle / 90.f;
SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
SkScalar error = startOver90 - startOver90I;
if (SkScalarNearlyEqual(error, 0)) {
// Index 1 is at startAngle == 0.
SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
return this->addOval(oval, sweepAngle > 0 ? SkPathDirection::kCW : SkPathDirection::kCCW,
(unsigned) startIndex);
}
}
return this->arcTo(oval, startAngle, sweepAngle, true);
}
SkPathBuilder& SkPathBuilder::arcTo(SkPoint p1, SkPoint p2, SkScalar radius) {
this->ensureMove();
if (radius == 0) {
return this->lineTo(p1);
}
// need to know our prev pt so we can construct tangent vectors
SkPoint start = fPts.back();
// need double precision for these calcs.
skvx::double2 befored = normalize(skvx::double2{p1.fX - start.fX, p1.fY - start.fY});
skvx::double2 afterd = normalize(skvx::double2{p2.fX - p1.fX, p2.fY - p1.fY});
double cosh = dot(befored, afterd);
double sinh = cross(befored, afterd);
// If the previous point equals the first point, befored will be denormalized.
// If the two points equal, afterd will be denormalized.
// If the second point equals the first point, sinh will be zero.
// In all these cases, we cannot construct an arc, so we construct a line to the first point.
if (!isfinite(befored) || !isfinite(afterd) || SkScalarNearlyZero(SkDoubleToScalar(sinh))) {
return this->lineTo(p1);
}
// safe to convert back to floats now
SkScalar dist = SkScalarAbs(SkDoubleToScalar(radius * (1 - cosh) / sinh));
SkScalar xx = p1.fX - dist * befored[0];
SkScalar yy = p1.fY - dist * befored[1];
SkVector after = SkVector::Make(afterd[0], afterd[1]);
after.setLength(dist);
this->lineTo(xx, yy);
SkScalar weight = SkScalarSqrt(SkDoubleToScalar(SK_ScalarHalf + cosh * 0.5));
return this->conicTo(p1, p1 + after, weight);
}
// This converts the SVG arc to conics.
// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
// See also SVG implementation notes:
// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
// Note that arcSweep bool value is flipped from the original implementation.
SkPathBuilder& SkPathBuilder::arcTo(SkPoint rad, SkScalar angle, SkPathBuilder::ArcSize arcLarge,
SkPathDirection arcSweep, SkPoint endPt) {
this->ensureMove();
const SkPoint srcPts[2] = { fPts.back(), endPt };
// If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
// joining the endpoints.
// http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
if (!rad.fX || !rad.fY) {
return this->lineTo(endPt);
}
// If the current point and target point for the arc are identical, it should be treated as a
// zero length path. This ensures continuity in animations.
if (srcPts[0] == srcPts[1]) {
return this->lineTo(endPt);
}
SkScalar rx = SkScalarAbs(rad.fX);
SkScalar ry = SkScalarAbs(rad.fY);
SkVector midPointDistance = srcPts[0] - srcPts[1];
midPointDistance *= 0.5f;
SkMatrix pointTransform;
pointTransform.setRotate(-angle);
SkPoint transformedMidPoint = pointTransform.mapPoint(midPointDistance);
SkScalar squareRx = rx * rx;
SkScalar squareRy = ry * ry;
SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
// Check if the radii are big enough to draw the arc, scale radii if not.
// http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
if (radiiScale > 1) {
radiiScale = SkScalarSqrt(radiiScale);
rx *= radiiScale;
ry *= radiiScale;
}
pointTransform.setScale(1 / rx, 1 / ry);
pointTransform.preRotate(-angle);
SkPoint unitPts[2];
pointTransform.mapPoints(unitPts, srcPts);
SkVector delta = unitPts[1] - unitPts[0];
SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
SkScalar scaleFactorSquared = std::max(1 / d - 0.25f, 0.f);
SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
if ((arcSweep == SkPathDirection::kCCW) != SkToBool(arcLarge)) { // flipped from the original implementation
scaleFactor = -scaleFactor;
}
delta.scale(scaleFactor);
SkPoint centerPoint = unitPts[0] + unitPts[1];
centerPoint *= 0.5f;
centerPoint.offset(-delta.fY, delta.fX);
unitPts[0] -= centerPoint;
unitPts[1] -= centerPoint;
SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
SkScalar thetaArc = theta2 - theta1;
if (thetaArc < 0 && (arcSweep == SkPathDirection::kCW)) { // arcSweep flipped from the original implementation
thetaArc += SK_ScalarPI * 2;
} else if (thetaArc > 0 && (arcSweep != SkPathDirection::kCW)) { // arcSweep flipped from the original implementation
thetaArc -= SK_ScalarPI * 2;
}
// Very tiny angles cause our subsequent math to go wonky (skbug.com/40040578)
// so we do a quick check here. The precise tolerance amount is just made up.
// PI/million happens to fix the bug in 9272, but a larger value is probably
// ok too.
if (SkScalarAbs(thetaArc) < (SK_ScalarPI / (1000 * 1000))) {
return this->lineTo(endPt);
}
pointTransform.setRotate(angle);
pointTransform.preScale(rx, ry);
// the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
SkScalar thetaWidth = thetaArc / segments;
SkScalar t = SkScalarTan(0.5f * thetaWidth);
if (!SkIsFinite(t)) {
return *this;
}
SkScalar startTheta = theta1;
SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
auto scalar_is_integer = [](SkScalar scalar) -> bool {
return scalar == SkScalarFloorToScalar(scalar);
};
bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
scalar_is_integer(rx) && scalar_is_integer(ry) &&
scalar_is_integer(endPt.fX) && scalar_is_integer(endPt.fY);
for (int i = 0; i < segments; ++i) {
SkScalar endTheta = startTheta + thetaWidth,
sinEndTheta = SkScalarSinSnapToZero(endTheta),
cosEndTheta = SkScalarCosSnapToZero(endTheta);
unitPts[1].set(cosEndTheta, sinEndTheta);
unitPts[1] += centerPoint;
unitPts[0] = unitPts[1];
unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
SkPoint mapped[2];
pointTransform.mapPoints(mapped, unitPts);
/*
Computing the arc width introduces rounding errors that cause arcs to start
outside their marks. A round rect may lose convexity as a result. If the input
values are on integers, place the conic on integers as well.
*/
if (expectIntegers) {
for (SkPoint& point : mapped) {
point.fX = SkScalarRoundToScalar(point.fX);
point.fY = SkScalarRoundToScalar(point.fY);
}
}
this->conicTo(mapped[0], mapped[1], w);
startTheta = endTheta;
}
// The final point should match the input point (by definition); replace it to
// ensure that rounding errors in the above math don't cause any problems.
fPts.back() = endPt;
return *this;
}
///////////////////////////////////////////////////////////////////////////////////////////
SkPathBuilder& SkPathBuilder::addRaw(const SkPathRaw& raw) {
this->incReserve(raw.points().size(), raw.verbs().size());
for (auto iter = raw.iter(); auto rec = iter.next();) {
const auto pts = rec->pts;
switch (rec->vrb) {
case SkPathVerb::kMove: this->moveTo( pts[0]); break;
case SkPathVerb::kLine: this->lineTo( pts[1]); break;
case SkPathVerb::kQuad: this->quadTo( pts[1], pts[2]); break;
case SkPathVerb::kConic: this->conicTo(pts[1], pts[2], rec->w); break;
case SkPathVerb::kCubic: this->cubicTo(pts[1], pts[2], pts[3]); break;
case SkPathVerb::kClose: this->close(); break;
}
}
return *this;
}
SkPathBuilder& SkPathBuilder::addRect(const SkRect& rect, SkPathDirection dir, unsigned index) {
const IsA prevIsA = fIsA;
this->addRaw(SkPathRawShapes::Rect(rect, dir, index));
if (prevIsA == kIsA_JustMoves) {
fConvexity = SkPathConvexity::kConvex;
}
return *this;
}
SkPathBuilder& SkPathBuilder::addOval(const SkRect& oval, SkPathDirection dir, unsigned index) {
const IsA prevIsA = fIsA;
this->addRaw(SkPathRawShapes::Oval(oval, dir, index));
if (prevIsA == kIsA_JustMoves) {
fIsA = kIsA_Oval;
fIsACCW = (dir == SkPathDirection::kCCW);
fIsAStart = index % 4;
fConvexity = SkPathConvexity::kConvex;
}
return *this;
}
SkPathBuilder& SkPathBuilder::addRRect(const SkRRect& rrect, SkPathDirection dir, unsigned index) {
const IsA prevIsA = fIsA;
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 {
this->addRaw(SkPathRawShapes::RRect(rrect, dir, index));
}
if (prevIsA == kIsA_JustMoves) {
fIsA = kIsA_RRect;
fIsACCW = (dir == SkPathDirection::kCCW);
fIsAStart = index % 8;
fConvexity = SkPathConvexity::kConvex;
}
return *this;
}
SkPathBuilder& SkPathBuilder::addCircle(SkScalar x, SkScalar y, SkScalar r, SkPathDirection dir) {
if (r >= 0) {
this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
}
return *this;
}
SkPathBuilder& SkPathBuilder::addPolygon(SkSpan<const SkPoint> pts, bool isClosed) {
if (pts.empty()) {
return *this;
}
this->moveTo(pts[0]);
this->polylineTo(pts.last(pts.size() - 1));
if (isClosed) {
this->close();
}
return *this;
}
SkPathBuilder& SkPathBuilder::polylineTo(SkSpan<const SkPoint> pts) {
if (!pts.empty()) {
this->ensureMove();
const auto count = pts.size();
this->incReserve(count, count);
memcpy(fPts.push_back_n(count), pts.data(), count * sizeof(SkPoint));
memset(fVerbs.push_back_n(count), (uint8_t)SkPathVerb::kLine, count);
fSegmentMask |= kLine_SkPathSegmentMask;
}
return *this;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
SkPathBuilder& SkPathBuilder::offset(SkScalar dx, SkScalar dy) {
for (auto& p : fPts) {
p += {dx, dy};
}
return *this;
}
SkPathBuilder& SkPathBuilder::addPath(const SkPath& path, SkScalar dx, SkScalar dy,
SkPath::AddPathMode mode) {
SkMatrix matrix = SkMatrix::Translate(dx, dy);
return this->addPath(path, matrix, mode);
}
SkPathBuilder& SkPathBuilder::addPath(const SkPath& src, const SkMatrix& matrix,
SkPath::AddPathMode mode) {
if (src.isEmpty()) {
return *this;
}
if (this->isEmpty() && matrix.isIdentity()) {
const SkPathFillType fillType = fFillType;
*this = src;
fFillType = fillType;
return *this;
}
if (SkPath::AddPathMode::kAppend_AddPathMode == mode && !matrix.hasPerspective()) {
if (src.fLastMoveToIndex >= 0) {
fLastMoveIndex = src.fLastMoveToIndex + this->countPoints();
fNeedsMoveVerb = false;
} else {
fLastMoveIndex = ~src.fLastMoveToIndex + this->countPoints();
fNeedsMoveVerb = true;
}
auto [newPts, newWeights] = this->growForVerbsInPath(*src.fPathRef);
const auto count = src.countPoints();
matrix.mapPoints({newPts, count}, {src.fPathRef->points(), count});
if (int numWeights = src.fPathRef->countWeights()) {
memcpy(newWeights, src.fPathRef->conicWeights(), numWeights * sizeof(newWeights[0]));
}
fLastMovePoint = fPts.at(fLastMoveIndex);
return *this; // TODO(borenet): dirtyAfterEdit sets convexity and firstDirection.
}
SkMatrixPriv::MapPtsProc mapPtsProc = SkMatrixPriv::GetMapPtsProc(matrix);
bool firstVerb = true;
for (auto [verb, pts, w] : SkPathPriv::Iterate(src)) {
SkPoint mappedPts[3];
switch (verb) {
case SkPathVerb::kMove:
mapPtsProc(matrix, mappedPts, &pts[0], 1);
if (firstVerb && mode == SkPath::kExtend_AddPathMode && !isEmpty()) {
this->ensureMove(); // In case last contour is closed
std::optional<SkPoint> lastPt = this->getLastPt();
// don't add lineTo if it is degenerate
if (!lastPt.has_value() || lastPt.value() != mappedPts[0]) {
this->lineTo(mappedPts[0]);
}
} else {
this->moveTo(mappedPts[0]);
}
break;
case SkPathVerb::kLine:
mapPtsProc(matrix, mappedPts, &pts[1], 1);
this->lineTo(mappedPts[0]);
break;
case SkPathVerb::kQuad:
mapPtsProc(matrix, mappedPts, &pts[1], 2);
this->quadTo(mappedPts[0], mappedPts[1]);
break;
case SkPathVerb::kConic:
mapPtsProc(matrix, mappedPts, &pts[1], 2);
this->conicTo(mappedPts[0], mappedPts[1], *w);
break;
case SkPathVerb::kCubic:
mapPtsProc(matrix, mappedPts, &pts[1], 3);
this->cubicTo(mappedPts[0], mappedPts[1], mappedPts[2]);
break;
case SkPathVerb::kClose:
this->close();
break;
}
firstVerb = false;
}
return *this;
}
// ignore the last point of the 1st contour
SkPathBuilder& SkPathBuilder::privateReversePathTo(const SkPath& path) {
if (path.fPathRef->fVerbs.empty()) {
return *this;
}
const SkPathVerb* verbs = path.fPathRef->verbsEnd();
const SkPathVerb* verbsBegin = path.fPathRef->verbsBegin();
const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
while (verbs > verbsBegin) {
SkPathVerb v = *--verbs;
pts -= SkPathPriv::PtsInVerb(v);
switch (v) {
case SkPathVerb::kMove:
// if the path has multiple contours, stop after reversing the last
return *this;
case SkPathVerb::kLine:
this->lineTo(pts[0]);
break;
case SkPathVerb::kQuad:
this->quadTo(pts[1], pts[0]);
break;
case SkPathVerb::kConic:
this->conicTo(pts[1], pts[0], *--conicWeights);
break;
case SkPathVerb::kCubic:
this->cubicTo(pts[2], pts[1], pts[0]);
break;
case SkPathVerb::kClose:
break;
}
}
return *this;
}
SkPathBuilder& SkPathBuilder::privateReverseAddPath(const SkPath& src) {
const SkPathVerb* verbsBegin = src.fPathRef->verbsBegin();
const SkPathVerb* verbs = src.fPathRef->verbsEnd();
const SkPoint* pts = src.fPathRef->pointsEnd();
const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
bool needMove = true;
bool needClose = false;
while (verbs > verbsBegin) {
SkPathVerb v = *--verbs;
int n = SkPathPriv::PtsInVerb(v);
if (needMove) {
--pts;
this->moveTo(pts->fX, pts->fY);
needMove = false;
}
pts -= n;
switch ((SkPathVerb)v) {
case SkPathVerb::kMove:
if (needClose) {
this->close();
needClose = false;
}
needMove = true;
pts += 1; // so we see the point in "if (needMove)" above
break;
case SkPathVerb::kLine:
this->lineTo(pts[0]);
break;
case SkPathVerb::kQuad:
this->quadTo(pts[1], pts[0]);
break;
case SkPathVerb::kConic:
this->conicTo(pts[1], pts[0], *--conicWeights);
break;
case SkPathVerb::kCubic:
this->cubicTo(pts[2], pts[1], pts[0]);
break;
case SkPathVerb::kClose:
needClose = true;
break;
}
}
return *this;
}
std::optional<SkPoint> SkPathBuilder::getLastPt() const {
int count = this->fPts.size();
if (count > 0) {
return this->fPts.at(count - 1);
}
return std::nullopt;
};
void SkPathBuilder::setLastPt(SkScalar x, SkScalar y) {
int count = fPts.size();
if (count == 0) {
this->moveTo(x, y);
} else {
fPts.at(count-1).set(x, y);
}
}
SkPathBuilder& SkPathBuilder::transform(const SkMatrix& matrix, SkApplyPerspectiveClip pc) {
if (matrix.isIdentity()) {
return *this;
}
if (matrix.hasPerspective()) {
SkPath src = this->detach();
// Apply perspective clip if needed.
if (pc == SkApplyPerspectiveClip::kYes) {
SkPath clipped;
if (SkPathPriv::PerspectiveClip(src, matrix, &clipped)) {
src = std::move(clipped);
}
}
// Convert to a format more amenable to perspective.
*this = SkPathBuilder(fFillType);
for (auto [verb, pts, wt] : SkPathPriv::Iterate(src)) {
switch (verb) {
case SkPathVerb::kMove:
this->moveTo(pts[0]);
break;
case SkPathVerb::kLine:
this->lineTo(pts[1]);
break;
case SkPathVerb::kQuad:
// promote the quad to a conic
this->conicTo(pts[1], pts[2],
SkConic::TransformW(pts, SK_Scalar1, matrix));
break;
case SkPathVerb::kConic:
this->conicTo(pts[1], pts[2],
SkConic::TransformW(pts, wt[0], matrix));
break;
case SkPathVerb::kCubic:
subdivide_cubic_to(this, pts);
break;
case SkPathVerb::kClose:
this->close();
break;
}
}
}
matrix.mapPoints(fPts);
const bool isScaleTrans = matrix.isScaleTranslate();
// this logic is lifted from SkPath::transform()
if (fConvexity == SkPathConvexity::kConvex &&
(!isScaleTrans || !SkPathPriv::IsAxisAligned(this->points()))) {
// Not safe to still assume we're convex...
fConvexity = SkPathConvexity::kUnknown;
}
if (!isScaleTrans && (fIsA == kIsA_Oval || fIsA == kIsA_RRect)) {
fIsA = kIsA_MoreThanMoves;
}
// TODO: handle bounds, and direction when added.
return *this;
}
bool SkPathBuilder::isFinite() const {
for (auto p : fPts) {
if (!p.isFinite()) {
return false;
}
}
return true;
}
bool SkPathBuilder::isZeroLengthSincePoint(int startPtIndex) const {
int count = fPts.size() - startPtIndex;
if (count < 2) {
return true;
}
const SkPoint* pts = fPts.begin() + startPtIndex;
const SkPoint& first = *pts;
for (int index = 1; index < count; ++index) {
if (first != pts[index]) {
return false;
}
}
return true;
}