// These are not part of SkPathRaw, so we set them separately
fLastMoveIndex = SkPathPriv::FindLastMoveToIndex(fVerbs, fPts.size());
SkASSERT(fLastMoveIndex < fPts.size());
fType = src.fPathData->fType;
fIsA = src.fPathData->fIsA;
const SkSpan<const SkPathVerb> verbs = path.verbs(); if (!verbs.empty()) { // 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.
fType = SkPathIsAType::kGeneral;
fVerbs.push_back_n(verbs.size(), verbs.data());
}
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);
} 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()); return true;
} 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); return true;
} 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, SkPathDirection* dir) {
SkScalar startRad = SkDegreesToRadians(startAngle),
stopRad = SkDegreesToRadians(startAngle + sweepAngle);
/* If the sweep angle is nearly (but less than) 360, then due to precision lossinradians-conversionand/orsin/cos,wemayendupwithcoincident vectors,whichwillfoolSkBuildQuadArcintodoingnothing(bad)instead ofdrawinganearlycompletecircle(good). e.g.canvas.drawArc(0,359.99,...) -vs-canvas.drawArc(0,359.9,...) Wetrytodetectthisedgecase,andtweakthestopvector
*/ 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 ? SkPathDirection::kCW : SkPathDirection::kCCW;
}
// 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);
} elseif (!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;
}
// 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];
// 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;
// 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/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);
}
// 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;
}
// if the iterator 'trimmed' off a trialing move, we restore it here if (has_trailing_move(raw.verbs()) && !has_trailing_move(this->verbs())) {
this->moveTo(raw.points().back());
}
auto [asType, newIndex] = SkPathPriv::SimplifyRRect(rrect, index); switch (asType) { case SkPathPriv::RRectAsEnum::kRect: return this->addRect(bounds, dir, newIndex); case SkPathPriv::RRectAsEnum::kOval: return this->addOval(bounds, dir, newIndex); case SkPathPriv::RRectAsEnum::kRRect: // fall through ... break;
}
// We're about to append - clear convexity.
fConvexity = SkPathConvexity::kUnknown;
if (SkPath::AddPathMode::kAppend_AddPathMode == mode && !matrix.hasPerspective()) { // If the current builder ends with a moveTo and src starts with one (which is always // true if non-empty), we must discard the builder moveTo in order to maintain // internal consistency after append (no repeating moveTos). if (!fVerbs.empty() && fVerbs.back() == SkPathVerb::kMove && !src.isEmpty()) {
SkASSERT(src.verbs().front() == SkPathVerb::kMove);
fVerbs.pop_back();
fPts.pop_back();
SkASSERT(fVerbs.empty() || fVerbs.back() != SkPathVerb::kMove);
}
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) { auto verbSpan = path.verbs(); if (verbSpan.empty()) { return *this;
}
auto verbs = verbSpan.end(); auto verbsBegin = verbSpan.begin(); auto pts = path.points().end() - 1; auto conicWeights = path.conicWeights().end();
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) { auto verbSpan = src.verbs(); if (verbSpan.empty()) { return *this;
}
auto verbs = verbSpan.end(); auto verbsBegin = verbSpan.begin(); auto pts = src.points().end(); auto conicWeights = src.conicWeights().end();
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;
}
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;
}
}
} else {
// Can we maintain our special case shape? if (!matrix.rectStaysRect() || !SkPathPriv::IsAxisAligned(fPts)) {
fType = SkPathIsAType::kGeneral; // lose convexity (just to be numerically safe) if (SkPathConvexity_IsConvex(fConvexity)) {
fConvexity = SkPathConvexity::kUnknown;
}
}
// If we're still a special case, check if we need to reverse our winding if (fType == SkPathIsAType::kOval || fType == SkPathIsAType::kRRect) { auto [dir, start] =
SkPathPriv::TransformDirAndStart(matrix, fType == SkPathIsAType::kRRect,
fIsA.fDirection, fIsA.fStartIndex);
fIsA.fDirection = dir;
fIsA.fStartIndex = start;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.