Index: src/core/SkPath.cpp |
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp |
index c77a25b4aa6dca74c6ac2988ba6c85ddc2f50216..40ca50b5caf99b4c93f6f9380a9ea62a9f2b7793 100644 |
--- a/src/core/SkPath.cpp |
+++ b/src/core/SkPath.cpp |
@@ -1257,6 +1257,113 @@ void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, |
} |
} |
+// 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. |
+void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge, |
+ SkPath::Direction arcSweep, SkScalar x, SkScalar y) { |
+ 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; |
+ } |
+ // 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; |
+ } |
+ rx = SkScalarAbs(rx); |
+ ry = SkScalarAbs(ry); |
+ SkVector midPointDistance = srcPts[0] - srcPts[1]; |
+ midPointDistance *= 0.5f; |
+ |
+ SkMatrix pointTransform; |
+ pointTransform.setRotate(-angle); |
+ |
+ SkPoint transformedMidPoint; |
+ pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1); |
+ 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, (int) SK_ARRAY_COUNT(unitPts)); |
+ SkVector delta = unitPts[1] - unitPts[0]; |
+ |
+ SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY; |
+ SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f); |
+ |
+ SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared); |
+ if (SkToBool(arcSweep) != 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) { // arcSweep flipped from the original implementation |
+ thetaArc += SK_ScalarPI * 2; |
+ } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation |
+ thetaArc -= SK_ScalarPI * 2; |
+ } |
+ pointTransform.setRotate(angle); |
+ pointTransform.preScale(rx, ry); |
+ |
+ int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2))); |
+ SkScalar thetaWidth = thetaArc / segments; |
+ SkScalar t = SkScalarTan(0.5f * thetaWidth); |
+ if (!SkScalarIsFinite(t)) { |
+ return; |
+ } |
+ SkScalar startTheta = theta1; |
+ SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf); |
+ for (int i = 0; i < segments; ++i) { |
+ SkScalar endTheta = startTheta + thetaWidth; |
+ SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta); |
+ |
+ 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) SK_ARRAY_COUNT(unitPts)); |
+ this->conicTo(mapped[0], mapped[1], w); |
+ startTheta = endTheta; |
+ } |
+} |
+ |
+void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc, |
+ SkPath::Direction sweep, SkScalar dx, SkScalar dy) { |
+ SkPoint currentPoint; |
+ this->getLastPt(¤tPoint); |
+ this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy); |
+} |
+ |
void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) { |
if (oval.isEmpty() || 0 == sweepAngle) { |
return; |