Add RRect GM

https://codereview.appspot.com/6945063/



git-svn-id: http://skia.googlecode.com/svn/trunk@6866 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gm/rrects.cpp b/gm/rrects.cpp
new file mode 100644
index 0000000..9d26282
--- /dev/null
+++ b/gm/rrects.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkRRect.h"
+
+namespace skiagm {
+
+///////////////////////////////////////////////////////////////////////////////
+
+class RRectGM : public GM {
+public:
+    RRectGM(bool doAA, bool doClip) : fDoAA(doAA), fDoClip(doClip) {
+        this->setBGColor(0xFFDDDDDD);
+        this->setUpRRects();
+    }
+
+protected:
+    SkString onShortName() {
+        SkString name("rrect");
+        if (fDoClip) {
+            name.append("_clip");
+        }
+        if (fDoAA) {
+            name.append("_aa");
+        } else {
+            name.append("_bw");
+        }
+
+        return name;
+    }
+
+    virtual SkISize onISize() { return make_isize(kImageWidth, kImageHeight); }
+
+    virtual void onDraw(SkCanvas* canvas) {
+
+        SkPaint paint;
+        // when clipping the AA is pushed into the clip operation
+        paint.setAntiAlias(fDoClip ? false : fDoAA); 
+
+        static const SkRect kMaxTileBound = SkRect::MakeWH(SkIntToScalar(kTileX), SkIntToScalar(kTileY));
+
+        int curRRect = 0;
+        for (int y = 1; y < kImageHeight; y += kTileY) {
+            for (int x = 1; x < kImageWidth; x += kTileX) {
+                if (curRRect >= kNumRRects) {
+                    break;
+                }
+                SkASSERT(kMaxTileBound.contains(fRRects[curRRect].getBounds()));
+
+                canvas->save();
+                    canvas->translate(SkIntToScalar(x), SkIntToScalar(y));
+                    if (fDoClip) {
+                        canvas->clipRRect(fRRects[curRRect], SkRegion::kReplace_Op, fDoAA);
+                        canvas->drawRect(kMaxTileBound, paint);
+                    } else {
+                        canvas->drawRRect(fRRects[curRRect], paint);
+                    }
+                    ++curRRect;
+                canvas->restore();
+            }
+        }
+    }
+
+    void setUpRRects() {
+        // each RRect must fit in a 0x0 -> (kTileX-2)x(kTileY-2) block. These will be tiled across 
+        // the screen in kTileX x kTileY tiles. The extra empty pixels on each side are for AA.
+
+        // simple cases
+        fRRects[0].setRect(SkRect::MakeWH(kTileX-2, kTileY-2));
+        fRRects[1].setOval(SkRect::MakeWH(kTileX-2, kTileY-2));
+        fRRects[2].setRectXY(SkRect::MakeWH(kTileX-2, kTileY-2), 10, 10);
+
+        // The first complex case needs special handling since it is a square
+        fRRects[kNumSimpleCases].setRectRadii(SkRect::MakeWH(kTileY-2, kTileY-2), gRadii[0]);
+        for (int i = 1; i < SK_ARRAY_COUNT(gRadii); ++i) {
+            fRRects[kNumSimpleCases+i].setRectRadii(SkRect::MakeWH(kTileX-2, kTileY-2), gRadii[i]);
+        }
+    }
+
+private:
+    bool fDoAA;
+    bool fDoClip;   // use clipRRect & drawRect instead of drawRRect
+
+    static const int kImageWidth = 640;
+    static const int kImageHeight = 480;
+
+    static const int kTileX = 80;
+    static const int kTileY = 40;
+
+    static const int kNumSimpleCases = 3;
+    static const int kNumComplexCases = 19;
+    static const SkVector RRectGM::gRadii[kNumComplexCases][4];
+
+    static const int kNumRRects = kNumSimpleCases + kNumComplexCases;
+    SkRRect fRRects[kNumRRects];
+
+    typedef GM INHERITED;
+};
+
+// Radii for the various test cases. Order is UL, UR, LR, LL
+const SkVector RRectGM::gRadii[kNumComplexCases][4] = {
+    // a circle
+    { { kTileY, kTileY }, { kTileY, kTileY }, { kTileY, kTileY }, { kTileY, kTileY } },
+
+    // odd ball cases
+    { { 8, 8 }, { 32, 32 }, { 8, 8 }, { 32, 32 } },
+    { { 16, 8 }, { 8, 16 }, { 16, 8 }, { 8, 16 } },
+    { { 0, 0 }, { 16, 16 }, { 8, 8 }, { 32, 32 } },
+
+    // UL
+    { { 30, 30 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
+    { { 30, 15 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
+    { { 15, 30 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
+
+    // UR
+    { { 0, 0 }, { 30, 30 }, { 0, 0 }, { 0, 0 } },
+    { { 0, 0 }, { 30, 15 }, { 0, 0 }, { 0, 0 } },
+    { { 0, 0 }, { 15, 30 }, { 0, 0 }, { 0, 0 } },
+
+    // LR
+    { { 0, 0 }, { 0, 0 }, { 30, 30 }, { 0, 0 } },
+    { { 0, 0 }, { 0, 0 }, { 30, 15 }, { 0, 0 } },
+    { { 0, 0 }, { 0, 0 }, { 15, 30 }, { 0, 0 } },
+
+    // LL
+    { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 30, 30 } },
+    { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 30, 15 } },
+    { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 15, 30 } },
+
+    // over-sized radii
+    { { 0, 0 }, { 100, 400 }, { 0, 0 }, { 0, 0 } },
+    { { 0, 0 }, { 400, 400 }, { 0, 0 }, { 0, 0 } },
+    { { 400, 400 }, { 400, 400 }, { 400, 400 }, { 400, 400 } },
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+DEF_GM( return new RRectGM(false, false); )
+DEF_GM( return new RRectGM(true, false); )
+DEF_GM( return new RRectGM(false, true); )
+DEF_GM( return new RRectGM(true, true); )
+
+}
+
+
diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi
index 8c026aa..1553e74 100644
--- a/gyp/gmslides.gypi
+++ b/gyp/gmslides.gypi
@@ -63,6 +63,7 @@
     '../gm/poly2poly.cpp',
     '../gm/quadpaths.cpp',
     '../gm/rrect.cpp',
+    '../gm/rrects.cpp',
     '../gm/samplerstress.cpp',
     '../gm/shaderbounds.cpp',
     '../gm/shadertext.cpp',
diff --git a/include/core/SkPath.h b/include/core/SkPath.h
index 9d7e6a1..3b5424f 100644
--- a/include/core/SkPath.h
+++ b/include/core/SkPath.h
@@ -662,14 +662,20 @@
      *  @param radii Array of 8 scalars, 4 [X,Y] pairs for each corner
      *  @param dir  The direction to wind the rectangle's contour. Cannot be
      *              kUnknown_Direction.
+     * Note: The radii here now go through the same constraint handling as the
+     *       SkRRect radii (i.e., either radii at a corner being 0 implies a
+     *       sqaure corner and oversized radii are proportionally scaled down).
      */
     void addRoundRect(const SkRect& rect, const SkScalar radii[],
                       Direction dir = kCW_Direction);
 
     /**
-     *  Add a SkRRect contour to the path
+     *  Add an SkRRect contour to the path
+     *  @param rrect The rounded rect to add as a closed contour
+     *  @param dir   The winding direction for the new contour. Cannot be
+     *               kUnknown_Direction.
      */
-    void addRRect(const SkRRect&, Direction dir = kCW_Direction);
+    void addRRect(const SkRRect& rrect, Direction dir = kCW_Direction);
 
     /**
      *  Add a new contour made of just lines. This is just a fast version of
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index ae1d187..b9f3286 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -1035,8 +1035,12 @@
 static void add_corner_arc(SkPath* path, const SkRect& rect,
                            SkScalar rx, SkScalar ry, int startAngle,
                            SkPath::Direction dir, bool forceMoveTo) {
-    rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
-    ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
+    // 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);
@@ -1063,32 +1067,20 @@
     path->arcTo(r, start, sweep, forceMoveTo);
 }
 
-void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
+void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
                           Direction dir) {
-    assert_known_direction(dir);
-
-    // abort before we invoke SkAutoPathBoundsUpdate()
-    if (rect.isEmpty()) {
-        return;
-    }
-
-    SkAutoPathBoundsUpdate apbu(this, rect);
-
-    if (kCW_Direction == dir) {
-        add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
-        add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
-        add_corner_arc(this, rect, rad[4], rad[5],   0, dir, false);
-        add_corner_arc(this, rect, rad[6], rad[7],  90, dir, false);
-    } else {
-        add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
-        add_corner_arc(this, rect, rad[6], rad[7],  90, dir, false);
-        add_corner_arc(this, rect, rad[4], rad[5],   0, dir, false);
-        add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
-    }
-    this->close();
+    SkRRect rrect;
+    rrect.setRectRadii(rect, (const SkVector*) radii);
+    this->addRRect(rrect, dir);
 }
 
 void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
+    assert_known_direction(dir);
+
+    if (rrect.isEmpty()) {
+        return;
+    }
+
     const SkRect& bounds = rrect.getBounds();
 
     if (rrect.isRect()) {
@@ -1099,7 +1091,20 @@
         const SkVector& rad = rrect.getSimpleRadii();
         this->addRoundRect(bounds, rad.x(), rad.y(), dir);
     } else {
-        this->addRoundRect(bounds, (const SkScalar*)&rrect.fRadii[0], dir);
+        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);
+        } 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->close();
     }
 }