/* * 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());
SkPath SkPathBuilder::make(sk_sp<SkPathRef> pr) const { auto convexity = SkPathConvexity::kUnknown;
SkPathFirstDirection dir = SkPathFirstDirection::kUnknown;
switch (fIsA) { case kIsA_Oval:
pr->setIsOval(fIsACCW, fIsAStart);
convexity = SkPathConvexity::kConvex;
dir = fIsACCW ? SkPathFirstDirection::kCCW : SkPathFirstDirection::kCW; break; case kIsA_RRect:
pr->setIsRRect(fIsACCW, fIsAStart);
convexity = SkPathConvexity::kConvex;
dir = fIsACCW ? SkPathFirstDirection::kCCW : SkPathFirstDirection::kCW; 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, convexity, 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. const uint8_t* start = path.fPathRef->verbsBegin(); const uint8_t* stop = path.fPathRef->verbsEnd(); if (start < stop) {
SkASSERT(fLastMoveIndex >= 0); // peek at the last verb, to know if our last contour is closed constbool isClosed = (stop[-1] == (uint8_t)SkPathVerb::kClose);
path.fLastMoveToIndex = isClosed ? ~fLastMoveIndex : fLastMoveIndex;
}
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;
// 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();
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/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(endPt);
}
// 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, (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.
fPts.back() = endPt; return *this;
}
if (rrect.isRect() || rrect.isEmpty()) { // degenerate(rect) => radii points are collapsing
this->addRect(bounds, dir, (index + 1) / 2);
} elseif (rrect.isOval()) { // degenerate(oval) => line points are collapsing
this->addOval(bounds, dir, index / 2);
} else { // we start with a conic on odd indices when moving CW vs. even indices when moving CCW constbool startsWithConic = ((index & 1) == (dir == SkPathDirection::kCW)); const SkScalar weight = SK_ScalarRoot2Over2;
RRectPointIterator rrectIter(rrect, dir, index); // Corner iterator indices follow the collapsed radii model, // adjusted such that the start pt is "behind" the radii start pt. constunsigned rectStartIndex = index / 2 + (dir == SkPathDirection::kCW ? 0 : 1);
RectPointIterator rectIter(bounds, dir, rectStartIndex);
this->moveTo(rrectIter.current()); if (startsWithConic) { for (unsigned i = 0; i < 3; ++i) {
this->conicTo(rectIter.next(), rrectIter.next(), weight);
this->lineTo(rrectIter.next());
}
this->conicTo(rectIter.next(), rrectIter.next(), weight); // final lineTo handled by close().
} else { for (unsigned i = 0; i < 4; ++i) {
this->lineTo(rrectIter.next());
this->conicTo(rectIter.next(), rrectIter.next(), weight);
}
}
this->close();
}
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.