use quads for mixed radius rrects

Create a specialized version of adding a pair of corner quads
that avoids the overhead of the full arc machinery.

This is on the way to changing Chrome to calling Skia directly to create fully general round rects rather than rolling their own.

R=robertphillips@google.com, reed@google.com

Author: caryclark@google.com

Review URL: https://codereview.chromium.org/60203002

git-svn-id: http://skia.googlecode.com/svn/trunk@12190 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index c480624..873f433 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -950,41 +950,6 @@
                           &matrix, pts);
 }
 
-static void add_corner_arc(SkPath* path, const SkRect& rect,
-                           SkScalar rx, SkScalar ry, int startAngle,
-                           SkPath::Direction dir, bool forceMoveTo) {
-    // 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
-    // these conservative asserts.
-    SkASSERT(0 <= rx && rx <= rect.width());
-    SkASSERT(0 <= ry && ry <= rect.height());
-
-    SkRect   r;
-    r.set(-rx, -ry, rx, ry);
-
-    switch (startAngle) {
-        case   0:
-            r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
-            break;
-        case  90:
-            r.offset(rect.fLeft - r.fLeft,   rect.fBottom - r.fBottom);
-            break;
-        case 180: r.offset(rect.fLeft - r.fLeft,   rect.fTop - r.fTop); break;
-        case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
-        default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
-    }
-
-    SkScalar start = SkIntToScalar(startAngle);
-    SkScalar sweep = SkIntToScalar(90);
-    if (SkPath::kCCW_Direction == dir) {
-        start += sweep;
-        sweep = -sweep;
-    }
-
-    path->arcTo(r, start, sweep, forceMoveTo);
-}
-
 void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
                           Direction dir) {
     SkRRect rrect;
@@ -992,6 +957,131 @@
     this->addRRect(rrect, dir);
 }
 
+/* The inline clockwise and counterclockwise round rect quad approximations
+   make it easier to see the symmetry patterns used by add corner quads.
+Clockwise                                                     corner value
+    path->lineTo(rect.fLeft,           rect.fTop    + ry);    0 upper left
+    path->quadTo(rect.fLeft,           rect.fTop    + offPtY,
+                 rect.fLeft  + midPtX, rect.fTop    + midPtY);
+    path->quadTo(rect.fLeft  + offPtX, rect.fTop,
+                 rect.fLeft  + rx,     rect.fTop);
+
+    path->lineTo(rect.fRight - rx,     rect.fTop);            1 upper right
+    path->quadTo(rect.fRight - offPtX, rect.fTop,
+                 rect.fRight - midPtX, rect.fTop    + midPtY);
+    path->quadTo(rect.fRight,          rect.fTop    + offPtY,
+                 rect.fRight,          rect.fTop    + ry);
+
+    path->lineTo(rect.fRight,          rect.fBottom - ry);    2 lower right
+    path->quadTo(rect.fRight,          rect.fBottom - offPtY,
+                 rect.fRight - midPtX, rect.fBottom - midPtY);
+    path->quadTo(rect.fRight - offPtX, rect.fBottom,
+                 rect.fRight - rx,     rect.fBottom);
+
+    path->lineTo(rect.fLeft  + rx,     rect.fBottom);         3 lower left
+    path->quadTo(rect.fLeft  + offPtX, rect.fBottom,
+                 rect.fLeft  + midPtX, rect.fBottom - midPtY);
+    path->quadTo(rect.fLeft,           rect.fBottom - offPtY,
+                 rect.fLeft,           rect.fBottom - ry);
+
+Counterclockwise
+    path->lineTo(rect.fLeft,           rect.fBottom - ry);    3 lower left
+    path->quadTo(rect.fLeft,           rect.fBottom - offPtY,
+                 rect.fLeft  + midPtX, rect.fBottom - midPtY);
+    path->quadTo(rect.fLeft  + offPtX, rect.fBottom,
+                 rect.fLeft  + rx,     rect.fBottom);
+
+    path->lineTo(rect.fRight - rx,     rect.fBottom);         2 lower right
+    path->quadTo(rect.fRight - offPtX, rect.fBottom,
+                 rect.fRight - midPtX, rect.fBottom - midPtY);
+    path->quadTo(rect.fRight,          rect.fBottom - offPtY,
+                 rect.fRight,          rect.fBottom - ry);
+
+    path->lineTo(rect.fRight,          rect.fTop    + ry);    1 upper right
+    path->quadTo(rect.fRight,          rect.fTop    + offPtY,
+                 rect.fRight - midPtX, rect.fTop    + midPtY);
+    path->quadTo(rect.fRight - offPtX, rect.fTop,
+                 rect.fRight - rx,     rect.fTop);
+
+    path->lineTo(rect.fLeft  + rx,     rect.fTop);            0 upper left
+    path->quadTo(rect.fLeft  + offPtX, rect.fTop,
+                 rect.fLeft  + midPtX, rect.fTop    + midPtY);
+    path->quadTo(rect.fLeft,           rect.fTop    + offPtY,
+                 rect.fLeft,           rect.fTop    + ry);
+*/
+static void add_corner_quads(SkPath* path, const SkRRect& rrect,
+                             SkRRect::Corner corner, SkPath::Direction dir) {
+    const SkRect& rect = rrect.rect();
+    const SkVector& radii = rrect.radii(corner);
+    SkScalar rx = radii.fX;
+    SkScalar ry = radii.fY;
+    // The mid point of the quadratic arc approximation is half way between the two
+    // control points.
+    SkScalar midPtX = rx - rx * (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
+    SkScalar midPtY = ry - ry * (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
+    SkScalar offPtX = rx - rx * SK_ScalarTanPIOver8;
+    SkScalar offPtY = ry - ry * SK_ScalarTanPIOver8;
+    static const int kCornerPts = 5;
+    SkScalar xOff[kCornerPts];
+    SkScalar yOff[kCornerPts];
+
+    if ((corner & 1) == (dir == SkPath::kCCW_Direction)) {  // corners always alternate direction
+        SkASSERT(dir == SkPath::kCCW_Direction
+             ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
+             : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
+        xOff[0] = xOff[1] = 0;
+        xOff[2] = midPtX;
+        xOff[3] = offPtX;
+        xOff[4] = rx;
+        yOff[0] = ry;
+        yOff[1] = offPtY;
+        yOff[2] = midPtY;
+        yOff[3] = yOff[4] = 0;
+    } else {
+        xOff[0] = rx;
+        xOff[1] = offPtX;
+        xOff[2] = midPtX;
+        xOff[3] = xOff[4] = 0;
+        yOff[0] = yOff[1] = 0;
+        yOff[2] = midPtY;
+        yOff[3] = offPtY;
+        yOff[4] = ry;
+    }
+    if ((corner - 1) & 2) {
+        SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
+        for (int i = 0; i < kCornerPts; ++i) {
+            xOff[i] = rect.fLeft + xOff[i];
+        }
+    } else {
+        SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
+        for (int i = 0; i < kCornerPts; ++i) {
+            xOff[i] = rect.fRight - xOff[i];
+        }
+    }
+    if (corner < SkRRect::kLowerRight_Corner) {
+        for (int i = 0; i < kCornerPts; ++i) {
+            yOff[i] = rect.fTop + yOff[i];
+        }
+    } else {
+        for (int i = 0; i < kCornerPts; ++i) {
+            yOff[i] = rect.fBottom - yOff[i];
+        }
+    }
+
+    SkPoint lastPt;
+    SkAssertResult(path->getLastPt(&lastPt));
+    if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
+        path->lineTo(xOff[0], yOff[0]);
+    }
+    if (rx || ry) {
+        path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
+        path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
+    } else {
+        path->lineTo(xOff[2], yOff[2]);
+        path->lineTo(xOff[4], yOff[4]);
+    }
+}
+
 void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
     assert_known_direction(dir);
 
@@ -1005,22 +1095,32 @@
         this->addRect(bounds, dir);
     } else if (rrect.isOval()) {
         this->addOval(bounds, dir);
+#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
     } else if (rrect.isSimple()) {
         const SkVector& rad = rrect.getSimpleRadii();
         this->addRoundRect(bounds, rad.x(), rad.y(), dir);
+#endif
     } else {
-        SkAutoPathBoundsUpdate apbu(this, bounds);
+        fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
 
+        SkAutoPathBoundsUpdate apbu(this, bounds);
+        SkAutoDisableDirectionCheck(this);
+
+        this->incReserve(21);
         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);
+            this->moveTo(bounds.fLeft,
+                         bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
+            add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
+            add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
+            add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
+            add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
         } 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);
+            this->moveTo(bounds.fLeft,
+                         bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
+            add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
+            add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
+            add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
+            add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
         }
         this->close();
     }
@@ -1056,6 +1156,7 @@
         return;
     }
 
+#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
     SkScalar    w = rect.width();
     SkScalar    halfW = SkScalarHalf(w);
     SkScalar    h = rect.height();
@@ -1083,133 +1184,68 @@
     } else if (skip_vert) {
         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
-// The mid point of the quadratic arc approximation is half way between the two
-// control points. The float epsilon adjustment moves the on curve point out by
-// two bits, distributing the convex test error between the round rect approximation
-// and the convex cross product sign equality test.
-    SkScalar    midPtX = rx * (SK_Scalar1 + SK_ScalarTanPIOver8 + FLT_EPSILON * 4) / 2;
-    SkScalar    midPtY = ry * (SK_Scalar1 + SK_ScalarTanPIOver8 + FLT_EPSILON * 4) / 2;
-
-    SkScalar    offPtX = rx * SK_ScalarTanPIOver8;
-    SkScalar    offPtY = ry * SK_ScalarTanPIOver8;
-
-    this->incReserve(21);
-#endif
     this->moveTo(rect.fRight - rx, rect.fTop);                  // top-right
     if (dir == kCCW_Direction) {
         if (!skip_hori) {
             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
-        this->quadTo(rect.fLeft + rx - offPtX, rect.fTop,
-                     rect.fLeft + rx - midPtX, rect.fTop + ry - midPtY);
-        this->quadTo(rect.fLeft, rect.fTop + ry - offPtY,
-                     rect.fLeft, rect.fTop + ry);
-#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
-        this->quadTo(rect.fLeft, rect.fBottom - ry + offPtY,
-                     rect.fLeft + rx - midPtX, rect.fBottom - ry + midPtY);
-        this->quadTo(rect.fLeft + rx - offPtX, rect.fBottom,
-                     rect.fLeft + rx, rect.fBottom);
-#endif
         if (!skip_hori) {
             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
-        this->quadTo(rect.fRight - rx + offPtX, rect.fBottom,
-                     rect.fRight - rx + midPtX, rect.fBottom - ry + midPtY);
-        this->quadTo(rect.fRight, rect.fBottom - ry + offPtY,
-                     rect.fRight, rect.fBottom - ry);
-#endif
         if (!skip_vert) {
             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
-        this->quadTo(rect.fRight, rect.fTop + ry - offPtY,
-                     rect.fRight - rx + midPtX, rect.fTop + ry - midPtY);
-        this->quadTo(rect.fRight - rx + offPtX, rect.fTop,
-                     rect.fRight - rx, rect.fTop);
-#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
-        this->quadTo(rect.fRight - rx + offPtX, rect.fTop,
-                     rect.fRight - rx + midPtX, rect.fTop + ry - midPtY);
-        this->quadTo(rect.fRight, rect.fTop + ry - offPtY,
-                     rect.fRight, rect.fTop + ry);
-#endif
         if (!skip_vert) {
             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
-        this->quadTo(rect.fRight, rect.fBottom - ry + offPtY,
-                     rect.fRight - rx + midPtX, rect.fBottom - ry + midPtY);
-        this->quadTo(rect.fRight - rx + offPtX, rect.fBottom,
-                     rect.fRight - rx, rect.fBottom);
-#endif
         if (!skip_hori) {
             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
-        this->quadTo(rect.fLeft + rx - offPtX, rect.fBottom,
-                     rect.fLeft + rx - midPtX, rect.fBottom - ry + midPtY);
-        this->quadTo(rect.fLeft, rect.fBottom - ry + offPtY,
-                     rect.fLeft, rect.fBottom - ry);
-#endif
         if (!skip_vert) {
             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
-        this->quadTo(rect.fLeft, rect.fTop + ry - offPtY,
-                     rect.fLeft + rx - midPtX, rect.fTop + ry - midPtY);
-        this->quadTo(rect.fLeft + rx - offPtX, rect.fTop,
-                     rect.fLeft + rx, rect.fTop);
-#endif
         if (!skip_hori) {
             this->lineTo(rect.fRight - rx, rect.fTop);          // top
         }
     }
     this->close();
+#else
+    SkRRect rrect;
+    rrect.setRectXY(rect, rx, ry);
+    this->addRRect(rrect, dir);
+#endif
 }
 
 void SkPath::addOval(const SkRect& oval, Direction dir) {