Replace cubic round rect corners with quads

https://codereview.chromium.org/26372006/



git-svn-id: http://skia.googlecode.com/svn/trunk@11835 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index d5c8d98..2e8837b 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -899,9 +899,73 @@
     SkDEBUGCODE(this->validate();)
 }
 
+#include "SkGeometry.h"
+
+static int build_arc_points(const SkRect& oval, SkScalar startAngle,
+                            SkScalar sweepAngle,
+                            SkPoint pts[kSkBuildQuadArcStorage]) {
+
+    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).
+        pts[0].set(oval.fRight, oval.centerY());
+        return 1;
+    } else if (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.
+        pts[0].set(oval.fRight, oval.fTop);
+        return 1;
+    }
+
+    SkVector start, stop;
+
+    start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
+    stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
+                             &stop.fX);
+
+    /*  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 (start == stop) {
+        SkScalar sw = SkScalarAbs(sweepAngle);
+        if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
+            SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
+            // 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;
+                stop.fY = SkScalarSinCos(stopRad, &stop.fX);
+            } while (start == stop);
+        }
+    }
+
+    SkMatrix    matrix;
+
+    matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
+    matrix.postTranslate(oval.centerX(), oval.centerY());
+
+    return SkBuildQuadArc(start, stop,
+                          sweepAngle > 0 ? kCW_SkRotationDirection :
+                                           kCCW_SkRotationDirection,
+                          &matrix, pts);
+}
+
 static void add_corner_arc(SkPath* path, const SkRect& rect,
                            SkScalar rx, SkScalar ry, int startAngle,
-                           SkPath::Direction dir, bool forceMoveTo) {
+                           SkPath::Direction dir, bool addArcTo,
+                           bool forceMoveTo = false) {
     // These two asserts are not sufficient, since really we want to know
     // that the pair of radii (e.g. left and right, or top and bottom) sum
     // to <= dimension, but we don't have that data here, so we just have
@@ -931,7 +995,15 @@
         sweep = -sweep;
     }
 
-    path->arcTo(r, start, sweep, forceMoveTo);
+    if (addArcTo) {
+        path->arcTo(r, start, sweep, forceMoveTo);
+    } else {
+        SkPoint pts[kSkBuildQuadArcStorage];
+        int count = build_arc_points(r, start, sweep, pts);
+        for (int i = 1; i < count; i += 2) {
+            path->quadTo(pts[i], pts[i+1]);
+        }
+    }
 }
 
 void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
@@ -961,15 +1033,15 @@
         SkAutoPathBoundsUpdate apbu(this, bounds);
 
         if (kCW_Direction == dir) {
-            add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
-            add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
-            add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY,   0, dir, false);
-            add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY,  90, dir, false);
+            add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true, true);
+            add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, true);
+            add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY,   0, dir, true);
+            add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY,  90, dir, true);
         } else {
-            add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
-            add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY,  90, dir, false);
-            add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY,   0, dir, false);
-            add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
+            add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true, true);
+            add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY,  90, dir, true);
+            add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY,   0, dir, true);
+            add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, true);
         }
         this->close();
     }
@@ -989,7 +1061,9 @@
     return true;
 }
 
+#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
 #define CUBIC_ARC_FACTOR    ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
+#endif
 
 void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
                           Direction dir) {
@@ -1031,60 +1105,96 @@
         ry = halfH;
     }
 
+#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
     SkScalar    sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
     SkScalar    sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
 
     this->incReserve(17);
+#else
+    this->incReserve(13);
+#endif
     this->moveTo(rect.fRight - rx, rect.fTop);
     if (dir == kCCW_Direction) {
         if (!skip_hori) {
-            this->lineTo(rect.fLeft + rx, rect.fTop);       // top
+            this->lineTo(rect.fLeft + rx, rect.fTop);           // top
         }
+#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
         this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
                       rect.fLeft, rect.fTop + ry - sy,
                       rect.fLeft, rect.fTop + ry);          // top-left
+#else
+        add_corner_arc(this, rect, rx, ry, 180, dir, false);    // top-left
+#endif
         if (!skip_vert) {
             this->lineTo(rect.fLeft, rect.fBottom - ry);        // left
         }
+#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
         this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
                       rect.fLeft + rx - sx, rect.fBottom,
                       rect.fLeft + rx, rect.fBottom);       // bot-left
+#else
+        add_corner_arc(this, rect, rx, ry, 90, dir, false);     // bot-left
+#endif
         if (!skip_hori) {
-            this->lineTo(rect.fRight - rx, rect.fBottom);   // bottom
+            this->lineTo(rect.fRight - rx, rect.fBottom);       // bottom
         }
+#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
         this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
                       rect.fRight, rect.fBottom - ry + sy,
                       rect.fRight, rect.fBottom - ry);      // bot-right
+#else
+        add_corner_arc(this, rect, rx, ry, 0, dir, false);      // bot-right
+#endif
         if (!skip_vert) {
-            this->lineTo(rect.fRight, rect.fTop + ry);
+            this->lineTo(rect.fRight, rect.fTop + ry);          // right
         }
+#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
         this->cubicTo(rect.fRight, rect.fTop + ry - sy,
                       rect.fRight - rx + sx, rect.fTop,
                       rect.fRight - rx, rect.fTop);         // top-right
+#else
+        add_corner_arc(this, rect, rx, ry, 270, dir, false);    // top-right
+#endif
     } else {
+#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
         this->cubicTo(rect.fRight - rx + sx, rect.fTop,
                       rect.fRight, rect.fTop + ry - sy,
                       rect.fRight, rect.fTop + ry);         // top-right
+#else
+        add_corner_arc(this, rect, rx, ry, 270, dir, false);    // top-right
+#endif
         if (!skip_vert) {
-            this->lineTo(rect.fRight, rect.fBottom - ry);
+            this->lineTo(rect.fRight, rect.fBottom - ry);       // right
         }
+#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
         this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
                       rect.fRight - rx + sx, rect.fBottom,
                       rect.fRight - rx, rect.fBottom);      // bot-right
+#else
+        add_corner_arc(this, rect, rx, ry, 0, dir, false);      // bot-right
+#endif
         if (!skip_hori) {
-            this->lineTo(rect.fLeft + rx, rect.fBottom);    // bottom
+            this->lineTo(rect.fLeft + rx, rect.fBottom);        // bottom
         }
+#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
         this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
                       rect.fLeft, rect.fBottom - ry + sy,
                       rect.fLeft, rect.fBottom - ry);       // bot-left
+#else
+        add_corner_arc(this, rect, rx, ry, 90, dir, false);     // bot-left
+#endif
         if (!skip_vert) {
-            this->lineTo(rect.fLeft, rect.fTop + ry);       // left
+            this->lineTo(rect.fLeft, rect.fTop + ry);           // left
         }
+#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
         this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
                       rect.fLeft + rx - sx, rect.fTop,
                       rect.fLeft + rx, rect.fTop);          // top-left
+#else
+        add_corner_arc(this, rect, rx, ry, 180, dir, false);    // top-left
+#endif
         if (!skip_hori) {
-            this->lineTo(rect.fRight - rx, rect.fTop);      // top
+            this->lineTo(rect.fRight - rx, rect.fTop);          // top
         }
     }
     this->close();
@@ -1172,69 +1282,6 @@
     }
 }
 
-#include "SkGeometry.h"
-
-static int build_arc_points(const SkRect& oval, SkScalar startAngle,
-                            SkScalar sweepAngle,
-                            SkPoint pts[kSkBuildQuadArcStorage]) {
-
-    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).
-        pts[0].set(oval.fRight, oval.centerY());
-        return 1;
-    } else if (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.
-        pts[0].set(oval.fRight, oval.fTop);
-        return 1;
-    }
-
-    SkVector start, stop;
-
-    start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
-    stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
-                             &stop.fX);
-
-    /*  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 (start == stop) {
-        SkScalar sw = SkScalarAbs(sweepAngle);
-        if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
-            SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
-            // 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;
-                stop.fY = SkScalarSinCos(stopRad, &stop.fX);
-            } while (start == stop);
-        }
-    }
-
-    SkMatrix    matrix;
-
-    matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
-    matrix.postTranslate(oval.centerX(), oval.centerY());
-
-    return SkBuildQuadArc(start, stop,
-                          sweepAngle > 0 ? kCW_SkRotationDirection :
-                                           kCCW_SkRotationDirection,
-                          &matrix, pts);
-}
-
 void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
                    bool forceMoveTo) {
     if (oval.width() < 0 || oval.height() < 0) {
@@ -1255,8 +1302,7 @@
     }
 }
 
-void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
-                    SkScalar sweepAngle) {
+void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
     if (oval.isEmpty() || 0 == sweepAngle) {
         return;
     }