/* * 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.
*/
/** * Path.bounds is defined to be the bounds of all the control points. * If we called bounds.join(r) we would skip r if r was empty, which breaks * our promise. Hence we have a custom joiner that doesn't look at emptiness
*/ staticvoid joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
dst->fLeft = std::min(dst->fLeft, src.fLeft);
dst->fTop = std::min(dst->fTop, src.fTop);
dst->fRight = std::max(dst->fRight, src.fRight);
dst->fBottom = std::max(dst->fBottom, src.fBottom);
}
/* This class's constructor/destructor bracket a path editing operation. It is used when we know the bounds of the amount we are going to add to the path (usually a new contour, but not required).
It captures some state about the path up front (i.e. if it already has a cached bounds), and then if it can, it updates the cache bounds explicitly, avoiding the need to revisit all of the points in getBounds().
It also notes if the path was originally degenerate, and if so, sets isConvex to true. Thus it can only be used if the contour being added is convex.
*/ class SkAutoPathBoundsUpdate { public:
SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fPath(path), fRect(r) { // Cannot use fRect for our bounds unless we know it is sorted
fRect.sort(); // Mark the path's bounds as dirty if (1) they are, or (2) the path // is non-finite, and therefore its bounds are not meaningful
fHasValidBounds = path->hasComputedBounds() && path->isFinite();
fEmpty = path->isEmpty(); if (fHasValidBounds && !fEmpty) {
joinNoEmptyChecks(&fRect, fPath->getBounds());
}
fDegenerate = is_degenerate(*path);
}
/* Stores the verbs and points as they are given to us, with exceptions: - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic - we insert a Move(0,0) if Line | Quad | Cubic is our first command
The iterator does more cleanup, especially if forceClose == true 1. If we encounter degenerate segments, remove them 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt) 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
*/
// 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
void SkPath::resetFields() { //fPathRef is assumed to have been emptied by the caller.
fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
fFillType = SkToU8(SkPathFillType::kWinding);
this->setConvexity(SkPathConvexity::kUnknown);
this->setFirstDirection(SkPathFirstDirection::kUnknown);
}
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());
this->setFirstDirection(that.getFirstDirection());
}
booloperator==(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);
}
for (auto [verb, pts, weight] : SkPathPriv::Iterate(*this)) { if (verb == SkPathVerb::kClose || (segmentCount > 0 && verb == SkPathVerb::kMove)) { // Closing the current contour; but since convexity is a precondition, it's the only // contour that matters.
SkASSERT(moveCnt);
segmentCount++; break;
} elseif (verb == SkPathVerb::kMove) { // A move at the start of the contour (or multiple leading moves, in which case we // keep the last one before a non-move verb).
SkASSERT(!segmentCount);
SkDEBUGCODE(++moveCnt);
firstPt = prevPt = pts[0];
} else { int pointCount = SkPathPriv::PtsInVerb((unsigned) verb);
SkASSERT(pointCount > 0);
if (!SkPathPriv::AllPointsEq(pts, pointCount + 1)) {
SkASSERT(moveCnt); int nextPt = pointCount;
segmentCount++;
if (prevPt == pts[nextPt]) { // A pre-condition to getting here is that the path is convex, so if a // verb's start and end points are the same, it means it's the only // verb in the contour (and the only contour). While it's possible for // such a single verb to be a convex curve, we do not have any non-zero // length edges to conservatively test against without splitting or // evaluating the curve. For simplicity, just reject the rectangle. returnfalse;
} elseif (SkPathVerb::kConic == verb) {
SkConic orig;
orig.set(pts, *weight);
SkPoint quadPts[5]; int count = orig.chopIntoQuadsPOW2(quadPts, 1);
SkASSERT_RELEASE(2 == count);
void SkPath::validateRef() const { // This will SkASSERT if not valid.
fPathRef->validate();
} #endif /* Determines if path is a rect by keeping track of changes in direction and looking for a loop either clockwise or counterclockwise.
The direction is computed such that: 0: vertical up 1: horizontal left 2: vertical down 3: horizontal right
A rectangle cycles up/right/down/left or up/left/down/right.
The test fails if: The path is closed, and followed by a line. A second move creates a new endpoint. A diagonal line is parsed. There's more than four changes of direction. There's a discontinuity on the line (e.g., a move in the middle) The line reverses direction. The path contains a quadratic or cubic. The path contains fewer than four points. *The rectangle doesn't complete a cycle. *The final point isn't equal to the first point.
*These last two conditions we relax if we have a 3-edge path that would form a rectangle if it were closed (as we do when we fill a path)
It's OK if the path has: Several colinear line segments composing a rectangle side. Single points on the rectangle side.
The direction takes advantage of the corners found since opposite sides must travel in opposite directions.
FIXME: Allow colinear quads and cubics to be treated like lines. FIXME: If the API passes fill-only, return true if the filled stroke is a rectangle, though the caller failed to close the path.
directions values: 0x1 is set if the segment is horizontal 0x2 is set if the segment is moving to the right or down thus: two directions are opposites iff (dirA ^ dirB) == 0x2 two directions are perpendicular iff (dirA ^ dirB) == 0x1
int count = fPathRef->countPoints(); if (count == 0) {
this->moveTo(x, y);
} else {
SkPathRef::Editor ed(&fPathRef);
ed.atPoint(count-1)->set(x, y);
}
}
// This is the public-facing non-const setConvexity(). void SkPath::setConvexity(SkPathConvexity c) {
fConvexity.store((uint8_t)c, std::memory_order_relaxed);
}
// Const hooks for working with fConvexity and fFirstDirection from const methods. void SkPath::setConvexity(SkPathConvexity c) const {
fConvexity.store((uint8_t)c, std::memory_order_relaxed);
} void SkPath::setFirstDirection(SkPathFirstDirection d) const {
fFirstDirection.store((uint8_t)d, std::memory_order_relaxed);
}
SkPathFirstDirection SkPath::getFirstDirection() const { return (SkPathFirstDirection)fFirstDirection.load(std::memory_order_relaxed);
}
bool SkPath::isConvexityAccurate() const {
SkPathConvexity convexity = this->getConvexityOrUnknown(); if (convexity != SkPathConvexity::kUnknown) { auto conv = this->computeConvexity(); if (conv != convexity) {
SkASSERT(false); returnfalse;
}
} returntrue;
}
SkPathConvexity SkPath::getConvexity() const { // Enable once we fix all the bugs // SkDEBUGCODE(this->isConvexityAccurate());
SkPathConvexity convexity = this->getConvexityOrUnknown(); if (convexity == SkPathConvexity::kUnknown) {
convexity = this->computeConvexity();
}
SkASSERT(convexity != SkPathConvexity::kUnknown); return convexity;
}
////////////////////////////////////////////////////////////////////////////// // Construction methods
#ifdef SK_DEBUG // enable this as needed for testing, but it slows down some chrome tests so much // that they don't complete, so we don't enable it by default // e.g. TEST(IdentifiabilityPaintOpDigestTest, MassiveOpSkipped) if (this->countVerbs() < 16) {
SkASSERT(fPathRef->dataMatchesVerbs());
} #endif
return *this;
}
void SkPath::incReserve(int extraPtCount, int extraVerbCount, int extraConicCount) {
SkDEBUGCODE(this->validate();) if (extraPtCount > 0) { // For compat with when this function only took a single argument, use // extraPtCount if extraVerbCount is 0 (default value).
SkPathRef::Editor(&fPathRef, extraVerbCount == 0 ? extraPtCount : extraVerbCount, extraPtCount, extraConicCount);
}
SkDEBUGCODE(this->validate();)
}
int count = fPathRef->countVerbs(); if (count > 0) { switch (fPathRef->atVerb(count - 1)) { case kLine_Verb: case kQuad_Verb: case kConic_Verb: case kCubic_Verb: case kMove_Verb: {
SkPathRef::Editor ed(&fPathRef);
ed.growForVerb(kClose_Verb); break;
} case kClose_Verb: // don't add a close if it's the first verb or a repeat break; default:
SkDEBUGFAIL("unexpected verb"); break;
}
}
// signal that we need a moveTo to follow us (unless we're done) #if 0 if (fLastMoveToIndex >= 0) {
fLastMoveToIndex = ~fLastMoveToIndex;
} #else
fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1); #endif return *this;
}
staticbool 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()); returntrue;
} elseif (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); returntrue;
} returnfalse;
}
// Return the unit vectors pointing at the start/stop points for the given start/sweep angles // staticvoid angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
SkScalar startRad = SkDegreesToRadians(startAngle),
stopRad = SkDegreesToRadians(startAngle + sweepAngle);
/* 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.
*/ staticint build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
SkPoint* singlePt) {
SkMatrix matrix;
// we start with a conic on odd indices when moving CW vs. even indices when moving CCW constbool startsWithConic = ((startIndex & 1) == (dir == SkPathDirection::kCW)); const SkScalar weight = SK_ScalarRoot2Over2;
/* If addOval() is called after previous moveTo(), this path is still marked as an oval. This is used to fit into WebKit's calling sequences. We can't simply check isEmpty() in this case, as additional moveTo() would mark the path non empty.
*/ bool isOval = hasOnlyMoveTos(); if (isOval) {
this->setFirstDirection((SkPathFirstDirection)dir);
} else {
this->setFirstDirection(SkPathFirstDirection::kUnknown);
}
// 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, &isArc, this](const SkPoint& pt) {
SkPoint lastPt; if (forceMoveTo) {
this->moveTo(pt);
} elseif (!this->getLastPt(&lastPt) ||
!SkScalarNearlyEqual(lastPt.fX, pt.fX) ||
!SkScalarNearlyEqual(lastPt.fY, pt.fY)) {
this->lineTo(pt);
isArc = false;
}
};
// 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) { // Conics take two points. Add one to the verb in case there is a moveto.
this->incReserve(count * 2 + 1, count + 1, count); 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);
} if (isArc) {
SkPathRef::Editor ed(&fPathRef);
ed.setIsArc(SkArc::Make(oval, startAngle, sweepAngle, SkArc::Type::kArc));
}
} else {
addPt(singlePt);
} return *this;
}
// 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.
SkPath& SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
SkPathDirection arcSweep, SkScalar x, SkScalar y) {
this->injectMoveToIfNeeded();
SkPoint srcPts[2];
this->getLastPt(&srcPts[0]); // 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 (!rx || !ry) { return this->lineTo(x, y);
} // 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.
srcPts[1].set(x, y); if (srcPts[0] == srcPts[1]) { return this->lineTo(x, y);
}
rx = SkScalarAbs(rx);
ry = SkScalarAbs(ry);
SkVector midPointDistance = srcPts[0] - srcPts[1];
midPointDistance *= 0.5f;
// 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;
}
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;
} elseif (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/9272) // 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(x, y);
}
// 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(x) && scalar_is_integer(y);
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, (int) std::size(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.
this->setLastPt(x, y); return *this;
}
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);
}
/* Need to handle the case when the angle is sharp, and our computed end-points for the arc go behind pt1 and/or p2...
*/
SkPath& SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
this->injectMoveToIfNeeded();
if (radius == 0) { return this->lineTo(x1, y1);
}
// need to know our prev pt so we can construct tangent vectors
SkPoint start;
this->getLastPt(&start);
// need double precision for these calcs.
skvx::double2 befored = normalize(skvx::double2{x1 - start.fX, y1 - start.fY});
skvx::double2 afterd = normalize(skvx::double2{x2 - x1, y2 - y1}); 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(x1, y1);
}
// safe to convert back to floats now
SkScalar dist = SkScalarAbs(SkDoubleToScalar(radius * (1 - cosh) / sinh));
SkScalar xx = x1 - dist * befored[0];
SkScalar yy = y1 - dist * befored[1];
while (verbs > verbsBegin) {
uint8_t v = *--verbs;
pts -= SkPathPriv::PtsInVerb(v); switch (v) { case kMove_Verb: // if the path has multiple contours, stop after reversing the last return *this; case kLine_Verb:
this->lineTo(pts[0]); break; case kQuad_Verb:
this->quadTo(pts[1], pts[0]); break; case kConic_Verb:
this->conicTo(pts[1], pts[0], *--conicWeights); break; case kCubic_Verb:
this->cubicTo(pts[2], pts[1], pts[0]); break; case kClose_Verb: break; default:
SkDEBUGFAIL("bad verb"); break;
}
} return *this;
}
SkPath& SkPath::reverseAddPath(const SkPath& srcPath) { // Detect if we're trying to add ourself const SkPath* src = &srcPath;
SkTLazy<SkPath> tmp; if (this == src) {
src = tmp.set(srcPath);
}
// Due to finite/fragile float numerics, we can't assume that a convex path remains // convex after a transformation, so mark it as unknown here. // However, some transformations are thought to be safe: // axis-aligned values under scale/translate. // if (convexity == SkPathConvexity::kConvex &&
(!matrix.isScaleTranslate() || !SkPathPriv::IsAxisAligned(*this))) { // Not safe to still assume we're convex...
convexity = SkPathConvexity::kUnknown;
}
dst->setConvexity(convexity);
if (kMove_Verb == *verbs) {
verbs += 1; // skip the initial moveto
}
while (verbs < stop) { // verbs points one beyond the current verb, decrement first. unsigned v = *verbs++; if (kMove_Verb == v) { break;
} if (kClose_Verb == v) { returntrue;
}
} returnfalse;
}
SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
SkASSERT(pts); if (fLastPt != fMoveTo) { // A special case: if both points are NaN, SkPoint::operation== returns // false, but the iterator expects that they are treated as the same. // (consider SkPoint is a 2-dimension float point). if (SkIsNaN(fLastPt.fX) || SkIsNaN(fLastPt.fY) ||
SkIsNaN(fMoveTo.fX) || SkIsNaN(fMoveTo.fY)) { return kClose_Verb;
}
if (fVerbs == fVerbStop) { // Close the curve if requested and if there is some curve to close if (fNeedClose) { if (kLine_Verb == this->autoClose(ptsParam)) { return kLine_Verb;
}
fNeedClose = false; return kClose_Verb;
} return kDone_Verb;
}
if (fPathRef->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()) { returnfalse;
}
} else { if (bounds.isEmpty()) { if (!fBounds.isEmpty()) { returnfalse;
}
} else { if (!fBounds.isEmpty()) { if (!fBounds.contains(bounds)) { returnfalse;
}
}
}
}
} #endif// SK_DEBUG_PATH returntrue;
}
enum DirChange {
kUnknown_DirChange,
kLeft_DirChange,
kRight_DirChange,
kStraight_DirChange,
kBackwards_DirChange, // if double back, allow simple lines to be convex
kInvalid_DirChange
};
// only valid for a single contour struct Convexicator {
/** The direction returned is only valid if the path is determined convex */
SkPathFirstDirection getFirstDirection() const { return fFirstDirection; }
bool addPt(const SkPoint& pt) { if (fLastPt == pt) { returntrue;
} // should only be true for first non-zero vector after setMovePt was called. It is possible // we doubled backed at the start so need to check if fLastVec is zero or not. if (fFirstPt == fLastPt && fExpectedDir == kInvalid_DirChange && fLastVec.equals(0,0)) {
fLastVec = pt - fLastPt;
fFirstVec = fLastVec;
} elseif (!this->addVec(pt - fLastPt)) { returnfalse;
}
fLastPt = pt; returntrue;
}
static SkPathConvexity BySign(const SkPoint points[], int count) { if (count <= 3) { // point, line, or triangle are always convex return SkPathConvexity::kConvex;
}
const SkPoint* last = points + count;
SkPoint currPt = *points++;
SkPoint firstPt = currPt; int dxes = 0; int dyes = 0; int lastSx = kValueNeverReturnedBySign; int lastSy = kValueNeverReturnedBySign; for (int outerLoop = 0; outerLoop < 2; ++outerLoop ) { while (points != last) {
SkVector vec = *points - currPt; if (!vec.isZero()) { // give up if vector construction failed if (!vec.isFinite()) { return SkPathConvexity::kUnknown;
} int sx = sign(vec.fX); int sy = sign(vec.fY);
dxes += (sx != lastSx);
dyes += (sy != lastSy); if (dxes > 3 || dyes > 3) { return SkPathConvexity::kConcave;
}
lastSx = sx;
lastSy = sy;
}
currPt = *points++; if (outerLoop) { break;
}
}
points = &firstPt;
} return SkPathConvexity::kConvex; // that is, it may be convex, don't know yet
}
bool close() { // If this was an explicit close, there was already a lineTo to fFirstPoint, so this // addPt() is a no-op. Otherwise, the addPt implicitly closes the contour. In either case, // we have to check the direction change along the first vector in case it is concave. return this->addPt(fFirstPt) && this->addVec(fFirstVec);
}
bool addVec(const SkVector& curVec) {
DirChange dir = this->directionChange(curVec); switch (dir) { case kLeft_DirChange: // fall through case kRight_DirChange: if (kInvalid_DirChange == fExpectedDir) {
fExpectedDir = dir;
fFirstDirection = (kRight_DirChange == dir) ? SkPathFirstDirection::kCW
: SkPathFirstDirection::kCCW;
} elseif (dir != fExpectedDir) {
fFirstDirection = SkPathFirstDirection::kUnknown; returnfalse;
}
fLastVec = curVec; break; case kStraight_DirChange: break; case kBackwards_DirChange: // allow path to reverse direction twice // Given path.moveTo(0, 0); path.lineTo(1, 1); // - 1st reversal: direction change formed by line (0,0 1,1), line (1,1 0,0) // - 2nd reversal: direction change formed by line (1,1 0,0), line (0,0 1,1)
fLastVec = curVec; return ++fReversals < 3; case kUnknown_DirChange: return (fIsFinite = false); case kInvalid_DirChange:
SK_ABORT("Use of invalid direction change flag"); break;
} returntrue;
}
SkPoint fFirstPt {0, 0}; // The first point of the contour, e.g. moveTo(x,y)
SkVector fFirstVec {0, 0}; // The direction leaving fFirstPt to the next vertex
SkPoint fLastPt {0, 0}; // The last point passed to addPt()
SkVector fLastVec {0, 0}; // The direction that brought the path to fLastPt
auto setFail = [&]() { return setComputedConvexity(SkPathConvexity::kConcave); };
if (!this->isFinite()) { return setFail();
}
// pointCount potentially includes a block of leading moveTos and trailing moveTos. Convexity // only cares about the last of the initial moveTos and the verbs before the final moveTos. int pointCount = this->countPoints(); int skipCount = SkPathPriv::LeadingMoveToCount(*this) - 1;
if (fLastMoveToIndex >= 0) { if (fLastMoveToIndex == pointCount - 1) { // Find the last real verb that affects convexity auto verbs = fPathRef->verbsEnd() - 1; while(verbs > fPathRef->verbsBegin() && *verbs == Verb::kMove_Verb) {
verbs--;
pointCount--;
}
} elseif (fLastMoveToIndex != skipCount) { // There's an additional moveTo between two blocks of other verbs, so the path must have // more than one contour and cannot be convex. return setComputedConvexity(SkPathConvexity::kConcave);
} // else no trailing or intermediate moveTos to worry about
} const SkPoint* points = fPathRef->points(); if (skipCount > 0) {
points += skipCount;
pointCount -= skipCount;
}
// Check to see if path changes direction more than three times as quick concave test
SkPathConvexity convexity = Convexicator::BySign(points, pointCount); if (SkPathConvexity::kConvex != convexity) { return setComputedConvexity(SkPathConvexity::kConcave);
}
int contourCount = 0; bool needsClose = false;
Convexicator state;
for (auto [verb, pts, wt] : SkPathPriv::Iterate(*this)) { // Looking for the last moveTo before non-move verbs start if (contourCount == 0) { if (verb == SkPathVerb::kMove) {
state.setMovePt(pts[0]);
} else { // Starting the actual contour, fall through to c=1 to add the points
contourCount++;
needsClose = true;
}
} // Accumulating points into the Convexicator until we hit a close or another move if (contourCount == 1) { if (verb == SkPathVerb::kClose || verb == SkPathVerb::kMove) { if (!state.close()) { return setFail();
}
needsClose = false;
contourCount++;
} else { // lines add 1 point, cubics add 3, conics and quads add 2 int count = SkPathPriv::PtsInVerb((unsigned) verb);
SkASSERT(count > 0); for (int i = 1; i <= count; ++i) { if (!state.addPt(pts[i])) { return setFail();
}
}
}
} else { // The first contour has closed and anything other than spurious trailing moves means // there's multiple contours and the path can't be convex if (verb != SkPathVerb::kMove) { return setFail();
}
}
}
// If the path isn't explicitly closed do so implicitly if (needsClose && !state.close()) { return setFail();
}
class ContourIter { public:
ContourIter(const SkPathRef& pathRef);
bool done() const { return fDone; } // if !done() then these may be called int count() const { return fCurrPtCount; } const SkPoint* pts() const { return fCurrPt; } void next();
for (verbs++; verbs < fStopVerbs; verbs++) { switch (*verbs) { case SkPath::kMove_Verb: goto CONTOUR_END; case SkPath::kLine_Verb:
ptCount += 1; break; case SkPath::kConic_Verb:
fCurrConicWeight += 1;
[[fallthrough]]; case SkPath::kQuad_Verb:
ptCount += 2; break; case SkPath::kCubic_Verb:
ptCount += 3; break; case SkPath::kClose_Verb: break; default:
SkDEBUGFAIL("unexpected verb"); break;
}
}
CONTOUR_END:
fCurrPtCount = ptCount;
fCurrVerb = verbs;
SkDEBUGCODE(++fContourCounter;)
}
// returns cross product of (p1 - p0) and (p2 - p0) static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0); // We may get 0 when the above subtracts underflow. We expect this to be // very rare and lazily promote to double. if (0 == cross) { double p0x = SkScalarToDouble(p0.fX); double p0y = SkScalarToDouble(p0.fY);
// Returns the first pt with the maximum Y coordinate staticint find_max_y(const SkPoint pts[], int count) {
SkASSERT(count > 0);
SkScalar max = pts[0].fY; int firstIndex = 0; for (int i = 1; i < count; ++i) {
SkScalar y = pts[i].fY; if (y > max) {
max = y;
firstIndex = i;
}
} return firstIndex;
}
staticint find_diff_pt(const SkPoint pts[], int index, int n, int inc) { int i = index; for (;;) {
i = (i + inc) % n; if (i == index) { // we wrapped around, so abort break;
} if (pts[index] != pts[i]) { // found a different point, success! break;
}
} return i;
}
/** * Starting at index, and moving forward (incrementing), find the xmin and * xmax of the contiguous points that have the same Y.
*/ staticint find_min_max_x_at_y(const SkPoint pts[], int index, int n, int* maxIndexPtr) { const SkScalar y = pts[index].fY;
SkScalar min = pts[index].fX;
SkScalar max = min; int minIndex = index; int maxIndex = index; for (int i = index + 1; i < n; ++i) { if (pts[i].fY != y) { break;
}
SkScalar x = pts[i].fX; if (x < min) {
min = x;
minIndex = i;
} elseif (x > max) {
max = x;
maxIndex = i;
}
}
*maxIndexPtr = maxIndex; return minIndex;
}
/* * We loop through all contours, and keep the computed cross-product of the * contour that contained the global y-max. If we just look at the first * contour, we may find one that is wound the opposite way (correctly) since * it is the interior of a hole (e.g. 'o'). Thus we must find the contour * that is outer most (or at least has the global y-max) before we can consider * its cross product.
*/
SkPathFirstDirection SkPathPriv::ComputeFirstDirection(const SkPath& path) { auto d = path.getFirstDirection(); if (d != SkPathFirstDirection::kUnknown) { return d;
}
// We don't want to pay the cost for computing convexity if it is unknown, // so we call getConvexityOrUnknown() instead of isConvex(). if (path.getConvexityOrUnknown() == SkPathConvexity::kConvex) {
SkASSERT(d == SkPathFirstDirection::kUnknown); return d;
}
for (; !iter.done(); iter.next()) { int n = iter.count(); if (n < 3) { continue;
}
const SkPoint* pts = iter.pts();
SkScalar cross = 0; int index = find_max_y(pts, n); if (pts[index].fY < ymax) { continue;
}
// If there is more than 1 distinct point at the y-max, we take the // x-min and x-max of them and just subtract to compute the dir. if (pts[(index + 1) % n].fY == pts[index].fY) { int maxIndex; int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex); if (minIndex == maxIndex) { goto TRY_CROSSPROD;
}
SkASSERT(pts[minIndex].fY == pts[index].fY);
SkASSERT(pts[maxIndex].fY == pts[index].fY);
SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX); // we just subtract the indices, and let that auto-convert to // SkScalar, since we just want - or + to signal the direction.
cross = minIndex - maxIndex;
} else {
TRY_CROSSPROD: // Find a next and prev index to use for the cross-product test, // but we try to find pts that form non-zero vectors from pts[index] // // Its possible that we can't find two non-degenerate vectors, so // we have to guard our search (e.g. all the pts could be in the // same place).
// we pass n - 1 instead of -1 so we don't foul up % operator by // passing it a negative LH argument. int prev = find_diff_pt(pts, index, n, n - 1); if (prev == index) { // completely degenerate, skip to next contour continue;
} int next = find_diff_pt(pts, index, n, 1);
SkASSERT(next != index);
cross = cross_prod(pts[prev], pts[index], pts[next]); // if we get a zero and the points are horizontal, then we look at the spread in // x-direction. We really should continue to walk away from the degeneracy until // there is a divergence. if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) { // construct the subtract so we get the correct Direction below
cross = pts[index].fX - pts[next].fX;
}
}
if (cross) { // record our best guess so far
ymax = pts[index].fY;
ymaxCross = cross;
}
} if (ymaxCross) {
d = crossToDir(ymaxCross);
path.setFirstDirection(d);
} return d; // may still be kUnknown
}
staticbool between(SkScalar a, SkScalar b, SkScalar c) {
SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
|| (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c))); return (a - b) * (c - b) <= 0;
}
int dir = 1; if (y0 > y3) { using std::swap;
swap(y0, y3);
dir = -1;
} if (y < y0 || y > y3) { return 0;
} if (checkOnCurve(x, y, pts[0], pts[3])) {
*onCurveCount += 1; return 0;
} if (y == y3) { return 0;
}
// quickreject or quickaccept
SkScalar min, max;
find_minmax<4>(pts, &min, &max); if (x < min) { return 0;
} if (x > max) { return dir;
}
// compute the actual x(t) value
SkScalar t; if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) { return 0;
}
SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t); if (SkScalarNearlyEqual(xt, x)) { if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
*onCurveCount += 1; return 0;
}
} return xt < x ? dir : 0;
}
staticint winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
SkPoint dst[10]; int n = SkChopCubicAtYExtrema(pts, dst); int w = 0; for (int i = 0; i <= n; ++i) {
w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
} return w;
}
int dir = 1; if (y0 > y2) { using std::swap;
swap(y0, y2);
dir = -1;
} if (y < y0 || y > y2) { return 0;
} if (checkOnCurve(x, y, pts[0], pts[2])) {
*onCurveCount += 1; return 0;
} if (y == y2) { return 0;
}
SkScalar roots[2];
SkScalar A = pts[2].fY;
SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
SkScalar C = pts[0].fY;
A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
B -= C; // B = b*w - w * yCept + yCept - a
C -= y; int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
SkASSERT(n <= 1);
SkScalar xt; if (0 == n) { // zero roots are returned only when y0 == y // Need [0] if dir == 1 // and [2] if dir == -1
xt = pts[1 - dir].fX;
} else {
SkScalar t = roots[0];
xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
} if (SkScalarNearlyEqual(xt, x)) { if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
*onCurveCount += 1; return 0;
}
} return xt < x ? dir : 0;
}
staticint winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight, int* onCurveCount) {
SkConic conic(pts, weight);
SkConic chopped[2]; // If the data points are very large, the conic may not be monotonic but may also // fail to chop. Then, the chopper does not split the original conic in two. bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped); int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount); if (!isMono) {
w += winding_mono_conic(chopped[1], x, y, onCurveCount);
} return w;
}
int dir = 1; if (y0 > y2) { using std::swap;
swap(y0, y2);
dir = -1;
} if (y < y0 || y > y2) { return 0;
} if (checkOnCurve(x, y, pts[0], pts[2])) {
*onCurveCount += 1; return 0;
} if (y == y2) { return 0;
} // bounds check on X (not required. is it faster?) #if 0 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) { return 0;
} #endif
SkScalar roots[2]; int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2 * (pts[1].fY - pts[0].fY),
pts[0].fY - y,
roots);
SkASSERT(n <= 1);
SkScalar xt; if (0 == n) { // zero roots are returned only when y0 == y // Need [0] if dir == 1 // and [2] if dir == -1
xt = pts[1 - dir].fX;
} else {
SkScalar t = roots[0];
SkScalar C = pts[0].fX;
SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
SkScalar B = 2 * (pts[1].fX - C);
xt = poly_eval(A, B, C, t);
} if (SkScalarNearlyEqual(xt, x)) { if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
*onCurveCount += 1; return 0;
}
} return xt < x ? dir : 0;
}
staticint winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
SkPoint dst[5]; int n = 0;
if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
n = SkChopQuadAtYExtrema(pts, dst);
pts = dst;
} int w = winding_mono_quad(pts, x, y, onCurveCount); if (n > 0) {
w += winding_mono_quad(&pts[2], x, y, onCurveCount);
} return w;
}
int dir = 1; if (y0 > y1) { using std::swap;
swap(y0, y1);
dir = -1;
} if (y < y0 || y > y1) { return 0;
} if (checkOnCurve(x, y, pts[0], pts[1])) {
*onCurveCount += 1; return 0;
} if (y == y1) { return 0;
}
SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
if (!cross) { // zero cross means the point is on the line, and since the case where // y of the query point is at the end point is handled above, we can be // sure that we're on the line (excluding the end point) here if (x != x1 || y != pts[1].fY) {
*onCurveCount += 1;
}
dir = 0;
} elseif (SkScalarSignAsInt(cross) == dir) {
dir = 0;
} return dir;
}
staticvoid tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
SkTDArray<SkVector>* tangents) { if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
&& !between(pts[2].fY, y, pts[3].fY)) { return;
} if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
&& !between(pts[2].fX, x, pts[3].fX)) { return;
}
SkPoint dst[10]; int n = SkChopCubicAtYExtrema(pts, dst); for (int i = 0; i <= n; ++i) {
SkPoint* c = &dst[i * 3];
SkScalar t; if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) { continue;
}
SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t); if (!SkScalarNearlyEqual(x, xt)) { continue;
}
SkVector tangent;
SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
tangents->push_back(tangent);
}
}
staticvoid tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
SkTDArray<SkVector>* tangents) { if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) { return;
} if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) { return;
}
SkScalar roots[2];
SkScalar A = pts[2].fY;
SkScalar B = pts[1].fY * w - y * w + y;
SkScalar C = pts[0].fY;
A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
B -= C; // B = b*w - w * yCept + yCept - a
C -= y; int n = SkFindUnitQuadRoots(A, 2 * B, C, roots); for (int index = 0; index < n; ++index) {
SkScalar t = roots[index];
SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t); if (!SkScalarNearlyEqual(x, xt)) { continue;
}
SkConic conic(pts, w);
tangents->push_back(conic.evalTangentAt(t));
}
}
staticvoid tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
SkTDArray<SkVector>* tangents) { if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) { return;
} if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) { return;
}
SkScalar roots[2]; int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2 * (pts[1].fY - pts[0].fY),
pts[0].fY - y,
roots); for (int index = 0; index < n; ++index) {
SkScalar t = roots[index];
SkScalar C = pts[0].fX;
SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
SkScalar B = 2 * (pts[1].fX - C);
SkScalar xt = poly_eval(A, B, C, t); if (!SkScalarNearlyEqual(x, xt)) { continue;
}
tangents->push_back(SkEvalQuadTangentAt(pts, t));
}
}
if (!contains_inclusive(this->getBounds(), x, y)) { return isInverse;
}
SkPath::Iter iter(*this, true); bool done = false; int w = 0; int onCurveCount = 0; do {
SkPoint pts[4]; switch (iter.next(pts)) { case SkPath::kMove_Verb: case SkPath::kClose_Verb: break; case SkPath::kLine_Verb:
w += winding_line(pts, x, y, &onCurveCount); break; case SkPath::kQuad_Verb:
w += winding_quad(pts, x, y, &onCurveCount); break; case SkPath::kConic_Verb:
w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount); break; case SkPath::kCubic_Verb:
w += winding_cubic(pts, x, y, &onCurveCount); break; case SkPath::kDone_Verb:
done = true; break;
}
} while (!done); bool evenOddFill = SkPathFillType::kEvenOdd == this->getFillType()
|| SkPathFillType::kInverseEvenOdd == this->getFillType(); if (evenOddFill) {
w &= 1;
} if (w) { return !isInverse;
} if (onCurveCount <= 1) { return SkToBool(onCurveCount) ^ isInverse;
} if ((onCurveCount & 1) || evenOddFill) { return SkToBool(onCurveCount & 1) ^ isInverse;
} // If the point touches an even number of curves, and the fill is winding, check for // coincidence. Count coincidence as places where the on curve points have identical tangents.
iter.setPath(*this, true);
done = false;
SkTDArray<SkVector> tangents; do {
SkPoint pts[4]; int oldCount = tangents.size(); switch (iter.next(pts)) { case SkPath::kMove_Verb: case SkPath::kClose_Verb: break; case SkPath::kLine_Verb:
tangent_line(pts, x, y, &tangents); break; case SkPath::kQuad_Verb:
tangent_quad(pts, x, y, &tangents); break; case SkPath::kConic_Verb:
tangent_conic(pts, x, y, iter.conicWeight(), &tangents); break; case SkPath::kCubic_Verb:
tangent_cubic(pts, x, y, &tangents); break; case SkPath::kDone_Verb:
done = true; break;
} if (tangents.size() > oldCount) { int last = tangents.size() - 1; const SkVector& tangent = tangents[last]; if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
tangents.remove(last);
} else { for (int index = 0; index < last; ++index) { const SkVector& test = tangents[index]; if (SkScalarNearlyZero(test.cross(tangent))
&& SkScalarSignAsInt(tangent.fX * test.fX) <= 0
&& SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
tangents.remove(last);
tangents.removeShuffle(index); break;
}
}
}
}
} while (!done); return SkToBool(tangents.size()) ^ isInverse;
}
// Sort of like makeSpace(0) but the the additional requirement that we actively shrink the // allocations to just fit the current needs. makeSpace() will only grow, but never shrinks. // void SkPath::shrinkToFit() { // Since this can relocate the allocated arrays, we have to defensively copy ourselves if // we're not the only owner of the pathref... since relocating the arrays will invalidate // any existing iterators. if (!fPathRef->unique()) {
SkPathRef* pr = new SkPathRef;
pr->copy(*fPathRef, 0, 0, 0);
fPathRef.reset(pr);
}
fPathRef->fPoints.shrink_to_fit();
fPathRef->fVerbs.shrink_to_fit();
fPathRef->fConicWeights.shrink_to_fit();
SkDEBUGCODE(fPathRef->validate();)
}
bool SkPathPriv::IsSimpleRect(const SkPath& path, bool isSimpleFill, SkRect* rect,
SkPathDirection* direction, unsigned* start) { if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) { returnfalse;
}
SkPoint rectPts[5]; int rectPtCnt = 0; bool needsClose = !isSimpleFill; for (auto [v, verbPts, w] : SkPathPriv::Iterate(path)) { switch (v) { case SkPathVerb::kMove: if (0 != rectPtCnt) { returnfalse;
}
rectPts[0] = verbPts[0];
++rectPtCnt; break; case SkPathVerb::kLine: if (5 == rectPtCnt) { returnfalse;
}
rectPts[rectPtCnt] = verbPts[1];
++rectPtCnt; break; case SkPathVerb::kClose: if (4 == rectPtCnt) {
rectPts[4] = rectPts[0];
rectPtCnt = 5;
}
needsClose = false; break; case SkPathVerb::kQuad: case SkPathVerb::kConic: case SkPathVerb::kCubic: returnfalse;
}
} if (needsClose) { returnfalse;
} if (rectPtCnt < 5) { returnfalse;
} if (rectPts[0] != rectPts[4]) { returnfalse;
} // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge ( // and pts 1 and 2 the opposite vertical or horizontal edge). bool vec03IsVertical; if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) { // Make sure it has non-zero width and height if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) { returnfalse;
}
vec03IsVertical = true;
} elseif (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) { // Make sure it has non-zero width and height if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) { returnfalse;
}
vec03IsVertical = false;
} else { returnfalse;
} // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit // set if it is on the bottom edge. unsigned sortFlags =
((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10); switch (sortFlags) { case 0b00:
rect->setLTRB(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
*direction = vec03IsVertical ? SkPathDirection::kCW : SkPathDirection::kCCW;
*start = 0; break; case 0b01:
rect->setLTRB(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
*direction = vec03IsVertical ? SkPathDirection::kCCW : SkPathDirection::kCW;
*start = 1; break; case 0b10:
rect->setLTRB(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
*direction = vec03IsVertical ? SkPathDirection::kCCW : SkPathDirection::kCW;
*start = 3; break; case 0b11:
rect->setLTRB(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
*direction = vec03IsVertical ? SkPathDirection::kCW : SkPathDirection::kCCW;
*start = 2; break;
} returntrue;
}
bool SkPathPriv::DrawArcIsConvex(SkScalar sweepAngle,
SkArc::Type arcType, bool isFillNoPathEffect) { if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) { // This gets converted to an oval. returntrue;
} if (arcType == SkArc::Type::kWedge) { // This is a pie wedge. It's convex if the angle is <= 180. return SkScalarAbs(sweepAngle) <= 180.f;
} // When the angle exceeds 360 this wraps back on top of itself. Otherwise it is a circle clipped // to a secant, i.e. convex. return SkScalarAbs(sweepAngle) <= 360.f;
}
void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkArc& arc, bool isFillNoPathEffect) {
SkRect oval = arc.fOval;
SkScalar startAngle = arc.fStartAngle, sweepAngle = arc.fSweepAngle;
SkASSERT(!oval.isEmpty());
SkASSERT(sweepAngle); // We cap the number of total rotations. This keeps the resulting paths simpler. More important, // it prevents values so large that the loops below never terminate (once ULP > 360). if (SkScalarAbs(sweepAngle) > 3600.0f) {
sweepAngle = std::copysign(3600.0f, sweepAngle) + std::fmod(sweepAngle, 360.0f);
}
path->reset();
path->setIsVolatile(true);
path->setFillType(SkPathFillType::kWinding); if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
path->addOval(oval);
SkASSERT(path->isConvex() &&
DrawArcIsConvex(sweepAngle, SkArc::Type::kArc, isFillNoPathEffect)); return;
} if (arc.isWedge()) {
path->moveTo(oval.centerX(), oval.centerY());
} auto firstDir =
sweepAngle > 0 ? SkPathFirstDirection::kCW : SkPathFirstDirection::kCCW; bool convex = DrawArcIsConvex(sweepAngle, arc.fType, isFillNoPathEffect); // Arc to mods at 360 and drawArc is not supposed to. bool forceMoveTo = !arc.isWedge(); while (sweepAngle <= -360.f) {
path->arcTo(oval, startAngle, -180.f, forceMoveTo);
startAngle -= 180.f;
path->arcTo(oval, startAngle, -180.f, false);
startAngle -= 180.f;
forceMoveTo = false;
sweepAngle += 360.f;
} while (sweepAngle >= 360.f) {
path->arcTo(oval, startAngle, 180.f, forceMoveTo);
startAngle += 180.f;
path->arcTo(oval, startAngle, 180.f, false);
startAngle += 180.f;
forceMoveTo = false;
sweepAngle -= 360.f;
}
path->arcTo(oval, startAngle, sweepAngle, forceMoveTo); if (arc.isWedge()) {
path->close();
}
path->setConvexity(convex ? SkPathConvexity::kConvex : SkPathConvexity::kConcave);
path->setFirstDirection(firstDir);
}
if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) { return this->getBounds();
}
SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
// initial with the first MoveTo, so we don't have to check inside the switch
skvx::float2 min, max;
min = max = from_point(this->getPoint(0)); for (auto [verb, pts, w] : SkPathPriv::Iterate(*this)) { int count = 0; switch (verb) { case SkPathVerb::kMove:
extremas[0] = pts[0];
count = 1; break; case SkPathVerb::kLine:
extremas[0] = pts[1];
count = 1; break; case SkPathVerb::kQuad:
count = compute_quad_extremas(pts, extremas); break; case SkPathVerb::kConic:
count = compute_conic_extremas(pts, *w, extremas); break; case SkPathVerb::kCubic:
count = compute_cubic_extremas(pts, extremas); break; case SkPathVerb::kClose: break;
} for (int i = 0; i < count; ++i) {
skvx::float2 tmp = from_point(extremas[i]);
min = skvx::min(min, tmp);
max = skvx::max(max, tmp);
}
}
SkRect bounds;
min.store((SkPoint*)&bounds.fLeft);
max.store((SkPoint*)&bounds.fRight); return bounds;
}
if (verbCount >= (INT_MAX / 3)) SK_UNLIKELY { // A path with an extremely high number of quad, conic or cubic verbs could cause // `info.points` to overflow. To prevent against this, we reject extremely large paths. This // check is conservative and assumes the worst case (in particular, it assumes that every // verb consumes 3 points, which would only happen for a path composed entirely of cubics). // This limits us to 700 million verbs, which is large enough for any reasonable use case.
invalid = true;
} else { 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 SkPath::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();
}
constauto info = sk_path_analyze_verbs(vbs, verbCount); if (!info.valid || info.points > pointCount || info.weights > wCount) {
SkDEBUGFAIL("invalid verbs and number of points/weights"); return SkPath();
}
SkScalar eval(SkScalar x, SkScalar y) const { return fA * x + fB * y + fC;
}
SkScalar operator()(SkScalar x, SkScalar y) const { return this->eval(x, y); }
bool normalize() { double a = fA; double b = fB; double c = fC; double dmag = sqrt(a * a + b * b); // length of initial plane normal is zero if (dmag == 0) {
fA = fB = 0;
fC = SK_Scalar1; returntrue;
} double dscale = sk_ieee_double_divide(1.0, dmag);
a *= dscale;
b *= dscale;
c *= dscale; // check if we're not finite, or normal is zero-length if (!SkIsFinite(a, b, c) ||
(a == 0 && b == 0)) {
fA = fB = 0;
fC = SK_Scalar1; returnfalse;
}
fA = a;
fB = b;
fC = c; returntrue;
}
enum Result {
kAllNegative,
kAllPositive,
kMixed
};
Result test(const SkRect& bounds) const { // check whether the diagonal aligned with the normal crosses the plane
SkPoint diagMin, diagMax; if (fA >= 0) {
diagMin.fX = bounds.fLeft;
diagMax.fX = bounds.fRight;
} else {
diagMin.fX = bounds.fRight;
diagMax.fX = bounds.fLeft;
} if (fB >= 0) {
diagMin.fY = bounds.fTop;
diagMax.fY = bounds.fBottom;
} else {
diagMin.fY = bounds.fBottom;
diagMax.fY = bounds.fTop;
}
SkScalar test = this->eval(diagMin.fX, diagMin.fY);
SkScalar sign = test*this->eval(diagMax.fX, diagMax.fY); if (sign > 0) { // the path is either all on one side of the half-plane or the other if (test < 0) { return kAllNegative;
} else { return kAllPositive;
}
} return kMixed;
}
};
// assumes plane is pre-normalized // If we fail in our calculations, we return the empty path static SkPath clip(const SkPath& path, const SkHalfPlane& plane) {
SkMatrix mx, inv;
SkPoint p0 = { -plane.fA*plane.fC, -plane.fB*plane.fC };
mx.setAll( plane.fB, plane.fA, p0.fX,
-plane.fA, plane.fB, p0.fY,
0, 0, 1); if (!mx.invert(&inv)) { return SkPath();
}
SkPath rotated;
path.transform(inv, &rotated); if (!rotated.isFinite()) { return SkPath();
}
SkScalar big = SK_ScalarMax;
SkRect clip = {-big, 0, big, big };
rec.fResult.setFillType(path.getFillType());
SkPath result = rec.fResult.detach().makeTransform(mx); if (!result.isFinite()) {
result = SkPath();
} return result;
}
// true means we have written to clippedPath bool SkPathPriv::PerspectiveClip(const SkPath& path, const SkMatrix& matrix, SkPath* clippedPath) { if (!matrix.hasPerspective()) { returnfalse;
}
SkHalfPlane plane {
matrix[SkMatrix::kMPersp0],
matrix[SkMatrix::kMPersp1],
matrix[SkMatrix::kMPersp2] - kW0PlaneDistance
}; if (plane.normalize()) { switch (plane.test(path.getBounds())) { case SkHalfPlane::kAllPositive: returnfalse; case SkHalfPlane::kMixed: {
*clippedPath = clip(path, plane); returntrue;
} default: break; // handled outside of the switch
}
} // clipped out (or failed)
*clippedPath = SkPath(); returntrue;
}
int SkPathPriv::GenIDChangeListenersCount(const SkPath& path) { return path.fPathRef->genIDChangeListenerCount();
}
bool SkPathPriv::IsAxisAligned(const SkPath& path) { // Conservative (quick) test to see if all segments are axis-aligned. // Multiple contours might give a false-negative, but for speed, we ignore that // and just look at the raw points.