Altered arcTo's canonical points to (usually) be convex

https://codereview.appspot.com/6709051/

This will require rebaselining of: degeneratesegments, shadertext & shadertext2



git-svn-id: http://skia.googlecode.com/svn/trunk@5997 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/bench/PathBench.cpp b/bench/PathBench.cpp
index 4a69d5a..3b3c52d 100644
--- a/bench/PathBench.cpp
+++ b/bench/PathBench.cpp
@@ -672,6 +672,8 @@
 };
 
 // Chrome creates its own round rects with each corner possibly being different
+// Note: PathTest::test_arb_round_rect_is_convex performs almost exactly
+// the same test (but with no drawing)
 class ArbRoundRectBench : public SkBenchmark {
 protected:
     SkString            fName;
@@ -727,8 +729,7 @@
         add_corner_arc(path, r, xCorner, yCorner, 90);
         add_corner_arc(path, r, xCorner, yCorner, 180);
 
-        // TODO: re-enable once arcTo convexity issue is resolved
-        //SkASSERT(path->isConvex());
+        SkASSERT(path->isConvex());
     }
 
     virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
@@ -741,6 +742,9 @@
             paint.setAntiAlias(true);
 
             SkScalar radius = rand.nextUScalar1() * 30;
+            if (radius < SK_Scalar1) {
+                continue;
+            }
             r.fLeft = rand.nextUScalar1() * 300;
             r.fTop =  rand.nextUScalar1() * 300;
             r.fRight =  r.fLeft + 2 * radius;
diff --git a/src/core/SkGeometry.cpp b/src/core/SkGeometry.cpp
index 1c1a4f1..d2aaeff 100644
--- a/src/core/SkGeometry.cpp
+++ b/src/core/SkGeometry.cpp
@@ -1254,28 +1254,47 @@
     return false;
 }
 
+#ifdef SK_SCALAR_IS_FLOAT
+// Due to floating point issues (i.e., 1.0f - SK_ScalarRoot2Over2 !=
+// SK_ScalarRoot2Over2 - SK_ScalarTanPIOver8) a cruder root2over2 
+// approximation is required to make the quad circle points convex. The 
+// root of the problem is that with the root2over2 value in SkScalar.h
+// the arcs really are ever so slightly concave. Some alternative fixes
+// to this problem (besides just arbitrarily pushing out the mid-point as
+// is done here) are:
+//    Adjust all the points (not just the middle) to both better approximate
+//             the curve and remain convex
+//    Switch over to using cubics rather then quads
+//    Use a different method to create the mid-point (e.g., compute
+//             the two side points, average them, then move it out as needed
+    #define SK_ScalarRoot2Over2_QuadCircle    0.7072f
+#else
+    #define SK_ScalarRoot2Over2_QuadCircle    SK_FixedRoot2Over2
+#endif
+
+
 static const SkPoint gQuadCirclePts[kSkBuildQuadArcStorage] = {
-    { SK_Scalar1,           0               },
-    { SK_Scalar1,           SK_ScalarTanPIOver8 },
-    { SK_ScalarRoot2Over2,  SK_ScalarRoot2Over2 },
-    { SK_ScalarTanPIOver8,  SK_Scalar1          },
+    { SK_Scalar1,                      0                                  },
+    { SK_Scalar1,                      SK_ScalarTanPIOver8                },
+    { SK_ScalarRoot2Over2_QuadCircle,  SK_ScalarRoot2Over2_QuadCircle     },
+    { SK_ScalarTanPIOver8,             SK_Scalar1                         },
 
-    { 0,                    SK_Scalar1      },
-    { -SK_ScalarTanPIOver8, SK_Scalar1  },
-    { -SK_ScalarRoot2Over2, SK_ScalarRoot2Over2 },
-    { -SK_Scalar1,          SK_ScalarTanPIOver8 },
+    { 0,                               SK_Scalar1                         },
+    { -SK_ScalarTanPIOver8,            SK_Scalar1                         },
+    { -SK_ScalarRoot2Over2_QuadCircle, SK_ScalarRoot2Over2_QuadCircle     },
+    { -SK_Scalar1,                     SK_ScalarTanPIOver8                },
 
-    { -SK_Scalar1,          0               },
-    { -SK_Scalar1,          -SK_ScalarTanPIOver8    },
-    { -SK_ScalarRoot2Over2, -SK_ScalarRoot2Over2    },
-    { -SK_ScalarTanPIOver8, -SK_Scalar1     },
+    { -SK_Scalar1,                     0                                  },
+    { -SK_Scalar1,                     -SK_ScalarTanPIOver8               },
+    { -SK_ScalarRoot2Over2_QuadCircle, -SK_ScalarRoot2Over2_QuadCircle    },
+    { -SK_ScalarTanPIOver8,            -SK_Scalar1                        },
 
-    { 0,                    -SK_Scalar1     },
-    { SK_ScalarTanPIOver8,  -SK_Scalar1     },
-    { SK_ScalarRoot2Over2,  -SK_ScalarRoot2Over2    },
-    { SK_Scalar1,           -SK_ScalarTanPIOver8    },
+    { 0,                               -SK_Scalar1                        },
+    { SK_ScalarTanPIOver8,             -SK_Scalar1                        },
+    { SK_ScalarRoot2Over2_QuadCircle,  -SK_ScalarRoot2Over2_QuadCircle    },
+    { SK_Scalar1,                      -SK_ScalarTanPIOver8               },
 
-    { SK_Scalar1,           0   }
+    { SK_Scalar1,                      0                                  }
 };
 
 int SkBuildQuadArc(const SkVector& uStart, const SkVector& uStop,
diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp
index 8197e68..6c8a400 100644
--- a/tests/PathTest.cpp
+++ b/tests/PathTest.cpp
@@ -77,6 +77,72 @@
     REPORTER_ASSERT(reporter, path.getBounds().isEmpty());
 }
 
+static void add_corner_arc(SkPath* path, const SkRect& rect, 
+                           SkScalar xIn, SkScalar yIn, 
+                           int startAngle)
+{
+
+    SkScalar rx = SkMinScalar(rect.width(), xIn);
+    SkScalar ry = SkMinScalar(rect.height(), yIn);
+
+    SkRect arcRect;
+    arcRect.set(-rx, -ry, rx, ry);
+    switch (startAngle) {
+    case 0:
+        arcRect.offset(rect.fRight - arcRect.fRight, rect.fBottom - arcRect.fBottom);
+        break;
+    case 90:
+        arcRect.offset(rect.fLeft - arcRect.fLeft, rect.fBottom - arcRect.fBottom);
+        break;
+    case 180:
+        arcRect.offset(rect.fLeft - arcRect.fLeft, rect.fTop - arcRect.fTop);
+        break;
+    case 270:
+        arcRect.offset(rect.fRight - arcRect.fRight, rect.fTop - arcRect.fTop);
+        break;
+    default:
+        break;
+    }
+
+    path->arcTo(arcRect, SkIntToScalar(startAngle), SkIntToScalar(90), false);
+}
+
+static void make_arb_round_rect(SkPath* path, const SkRect& r, 
+                                SkScalar xCorner, SkScalar yCorner) {
+    // we are lazy here and use the same x & y for each corner
+    add_corner_arc(path, r, xCorner, yCorner, 270);
+    add_corner_arc(path, r, xCorner, yCorner, 0);
+    add_corner_arc(path, r, xCorner, yCorner, 90);
+    add_corner_arc(path, r, xCorner, yCorner, 180);
+}
+
+// Chrome creates its own round rects with each corner possibly being different.
+// Performance will suffer if they are not convex.
+// Note: PathBench::ArbRoundRectBench performs almost exactly
+// the same test (but with drawing)
+static void test_arb_round_rect_is_convex(skiatest::Reporter* reporter) {
+    SkRandom rand;
+    SkRect r;
+
+    for (int i = 0; i < 5000; ++i) {
+
+        SkScalar radius = rand.nextUScalar1() * 30;
+        if (radius < SK_Scalar1) {
+            continue;
+        }
+        r.fLeft = rand.nextUScalar1() * 300;
+        r.fTop =  rand.nextUScalar1() * 300;
+        r.fRight =  r.fLeft + 2 * radius;
+        r.fBottom = r.fTop + 2 * radius;
+
+        SkPath temp;
+
+        make_arb_round_rect(&temp, r, r.width() / 10, r.height() / 15);
+
+        REPORTER_ASSERT(reporter, temp.isConvex());
+    }
+}
+
 static void test_rect_isfinite(skiatest::Reporter* reporter) {
     const SkScalar inf = SK_ScalarInfinity;
     const SkScalar nan = SK_ScalarNaN;
@@ -1616,6 +1682,7 @@
     test_isfinite(reporter);
     test_isfinite_after_transform(reporter);
     test_tricky_cubic(reporter);
+    test_arb_round_rect_is_convex(reporter);
 }
 
 #include "TestClassDef.h"