From c3823848483eba33c3fa36187de7713e87651c1c Mon Sep 17 00:00:00 2001 From: Cary Clark Date: Tue, 5 Dec 2017 11:10:06 -0500 Subject: keep SVG arcs axis aligned Computing the arc width introduces rounding errors that cause the arc to exceed 1/4 circle and cause integer anchored arcs to start outside their marks. A round rect may lose convexity as a result. Check if arcTo() inputs are integers and arc is 90 degrees; if so, output conics which are axis-aligned on integers as well. This is triggered when using SVG to represent a round rect. Possible future enhancements are recorded in bug.skia.org/7383 R=reed@google.com,djsollen@google.com Change-Id: I6609456fcefabcda6c9560a044533ecb5cda2d31 Reviewed-on: https://skia-review.googlesource.com/79423 Reviewed-by: Derek Sollenberger Commit-Queue: Derek Sollenberger --- src/core/SkPath.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp index c433424635..ed3f466a4a 100644 --- a/src/core/SkPath.cpp +++ b/src/core/SkPath.cpp @@ -1417,7 +1417,8 @@ void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arc pointTransform.setRotate(angle); pointTransform.preScale(rx, ry); - int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2))); + // 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 (!SkScalarIsFinite(t)) { @@ -1425,6 +1426,12 @@ void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arc } 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; SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta); @@ -1435,6 +1442,17 @@ void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arc unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta); SkPoint mapped[2]; pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(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) { + SkScalar* mappedScalars = &mapped[0].fX; + for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) { + mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]); + } + } this->conicTo(mapped[0], mapped[1], w); startTheta = endTheta; } -- cgit v1.2.3