SkRoundRect start

https://codereview.appspot.com/6815058/



git-svn-id: http://skia.googlecode.com/svn/trunk@6595 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gyp/core.gypi b/gyp/core.gypi
index 4626a72..6251ce6 100644
--- a/gyp/core.gypi
+++ b/gyp/core.gypi
@@ -136,6 +136,7 @@
         '<(skia_src_path)/core/SkRegion.cpp',
         '<(skia_src_path)/core/SkRegionPriv.h',
         '<(skia_src_path)/core/SkRegion_path.cpp',
+        '<(skia_src_path)/core/SkRRect.cpp',
         '<(skia_src_path)/core/SkRTree.h',
         '<(skia_src_path)/core/SkRTree.cpp',
         '<(skia_src_path)/core/SkScalar.cpp',
@@ -242,6 +243,7 @@
         '<(skia_include_path)/core/SkRect.h',
         '<(skia_include_path)/core/SkRefCnt.h',
         '<(skia_include_path)/core/SkRegion.h',
+        '<(skia_include_path)/core/SkRRect.h',
         '<(skia_include_path)/core/SkScalar.h',
         '<(skia_include_path)/core/SkScalarCompare.h',
         '<(skia_include_path)/core/SkShader.h',
diff --git a/gyp/tests.gyp b/gyp/tests.gyp
index 85f58ea..7e8a19e 100644
--- a/gyp/tests.gyp
+++ b/gyp/tests.gyp
@@ -79,6 +79,7 @@
         '../tests/RefCntTest.cpp',
         '../tests/RefDictTest.cpp',
         '../tests/RegionTest.cpp',
+        '../tests/RoundRectTest.cpp',
         '../tests/RTreeTest.cpp',
         '../tests/ScalarTest.cpp',
         '../tests/ShaderOpacityTest.cpp',
diff --git a/include/core/SkRRect.h b/include/core/SkRRect.h
new file mode 100644
index 0000000..125b787
--- /dev/null
+++ b/include/core/SkRRect.h
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkRRect_DEFINED
+#define SkRRect_DEFINED
+
+#include "SkRect.h"
+#include "SkPoint.h"
+
+// Path forward:
+//   core work
+//      add validate method (all radii positive, all radii sums < rect size, etc.)
+//      add contains(SkRect&)  - for clip stack
+//      add contains(SkRRect&) - for clip stack
+//      add heart rect computation (max rect inside RR)
+//      add 9patch rect computation
+//      add growToInclude(SkPath&)
+//   analysis
+//      use growToInclude to fit skp round rects & generate stats (RRs vs. real paths)
+//      check on # of rectorus's the RRs could handle
+//   rendering work
+//      add entry points (clipRRect, drawRRect) - plumb down to SkDevice
+//      update SkPath.addRRect() to take an SkRRect - only use quads
+//          -- alternatively add addRRectToPath here
+//      add GM and bench
+//   clipping opt
+//      update SkClipStack to perform logic with RRs
+//   further out
+//      add RR rendering shader to Ganesh (akin to cicle drawing code)
+//          - only for simple RRs
+//      detect and triangulate RRectorii rather than falling back to SW in Ganesh
+//      
+
+/** \class SkRRect
+
+    The SkRRect class represents a rounded rect with a potentially different 
+    radii for each corner. It does not have a constructor so must be 
+    initialized with one of the initialization functions (e.g., setEmpty, 
+    setRectRadii, etc.)
+
+    This class is intended to roughly match CSS' border-*-*-radius capabilities. 
+    This means:
+        If either of a corner's radii are 0 the corner will be square. 
+        Negative radii are not allowed (they are clamped to zero).
+        If the corner curves overlap they will be proportionally reduced to fit.
+*/
+class SK_API SkRRect {
+public:
+    /** 
+     * Enum to capture the various possible subtypes of RR. Accessed
+     * by type(). The subtypes become progressively less restrictive.
+     */
+    enum Type {
+        // !< The RR is empty
+        kEmpty_Type,
+
+        //!< The RR is actually a (non-empty) rect (i.e., at least one radius
+        //!< at each corner is zero)
+        kRect_Type,  
+
+        //!< The RR is actually a (non-empty) oval (i.e., all x radii are equal 
+        //!< and >= width/2 and all the y radii are equal and >= height/2
+        kOval_Type,  
+
+        //!< The RR is non-empty and all the x radii are equal & all y radii 
+        //!< are equal but it is not an oval (i.e., there are lines between 
+        //!< the curves) nor a rect (i.e., both radii are non-zero)
+        kSimple_Type,  
+
+        //!< A fully general (non-empty) RR. Some of the x and/or y radii are
+        //!< different from the others and there must be one corner where
+        //!< both radii are non-zero.
+        kComplex_Type,
+    };
+
+    /** 
+     * Returns the RR's sub type.
+     */
+    Type type() const {
+        SkDEBUGCODE(this->validate();)
+
+        if (kUnknown_Type == fType) {
+            this->computeType();
+        }
+        SkASSERT(kUnknown_Type != fType);
+        return fType;
+    }
+
+    /** 
+     * Set this RR to the empty rectangle (0,0,0,0) with 0 x & y radii.
+     */
+    void setEmpty() { 
+        fRect.setEmpty();
+        memset(fRadii, 0, sizeof(fRadii));
+        fType = kEmpty_Type;
+
+        SkDEBUGCODE(this->validate();)
+    }
+
+    /** 
+     * Set this RR to match the supplied rect. All radii will be 0.
+     */
+    void setRect(const SkRect& rect) {
+        if (rect.isEmpty()) {
+            this->setEmpty();
+            return;
+        }
+
+        fRect = rect;
+        memset(fRadii, 0, sizeof(fRadii));
+        fType = kRect_Type;
+
+        SkDEBUGCODE(this->validate();)
+    }
+
+    /** 
+     * Set this RR to match the supplied oval. All x radii will equal half the
+     * width and all y radii will equal half the height.
+     */
+    void setOval(const SkRect& oval) {
+        if (oval.isEmpty()) {
+            this->setEmpty();
+            return;
+        }
+
+        SkScalar xRad = SkScalarHalf(oval.width());
+        SkScalar yRad = SkScalarHalf(oval.height());
+
+        fRect = oval;
+        for (int i = 0; i < 4; ++i) {
+            fRadii[i].set(xRad, yRad);
+        }
+        fType = kOval_Type;
+
+        SkDEBUGCODE(this->validate();)
+    }
+
+    /** 
+     * Initialize the RR with the same radii for all four corners.
+     */
+    void setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad);
+
+    /** 
+     * Initialize the RR with potentially different radii for all four corners.
+     */
+    void setRectRadii(const SkRect& rect, const SkVector radii[4]);
+
+    // The radii are stored in UL, UR, LR, LL order. 
+    enum Corner {
+        kUpperLeft_Corner,
+        kUpperRight_Corner,
+        kLowerRight_Corner,
+        kLowerLeft_Corner
+    };
+
+    const SkRect& rect() const { return fRect; }
+    const SkVector& radii(Corner corner) const { return fRadii[corner]; }
+
+    friend bool operator==(const SkRRect& a, const SkRRect& b) {
+        return a.fRect == b.fRect &&
+               SkScalarsEqual((SkScalar*) a.fRadii, (SkScalar*) b.fRadii, 8);
+    }
+
+    friend bool operator!=(const SkRRect& a, const SkRRect& b) {
+        return a.fRect != b.fRect ||
+               !SkScalarsEqual((SkScalar*) a.fRadii, (SkScalar*) b.fRadii, 8);
+    }
+
+    /**
+     *  Returns true if (p.fX,p.fY) is inside the RR, and the RR
+     *  is not empty.
+     *
+     *  Contains treats the left and top differently from the right and bottom.
+     *  The left and top coordinates of the RR are themselves considered
+     *  to be inside, while the right and bottom are not. All the points on the
+     *  edges of the corners are considered to be inside.
+     */
+    bool contains(const SkPoint& p) const {
+        return contains(p.fX, p.fY);
+    }
+
+    /**
+     *  Returns true if (x,y) is inside the RR, and the RR
+     *  is not empty.
+     *
+     *  Contains treats the left and top differently from the right and bottom.
+     *  The left and top coordinates of the RR are themselves considered
+     *  to be inside, while the right and bottom are not. All the points on the
+     *  edges of the corners are considered to be inside.
+     */
+    bool contains(SkScalar x, SkScalar y) const;
+
+    SkDEBUGCODE(void validate() const;)
+
+private:
+    enum {
+        //!< Internal indicator that the sub type must be computed.
+        kUnknown_Type = -1
+    };
+
+    SkRect fRect;
+    // Radii order is UL, UR, LR, LL. Use Corner enum to index into fRadii[]
+    SkVector fRadii[4];     
+    mutable Type fType;
+    // TODO: add padding so we can use memcpy for flattening and not copy
+    // uninitialized data
+
+    void computeType() const;
+};
+
+#endif
\ No newline at end of file
diff --git a/src/core/SkRRect.cpp b/src/core/SkRRect.cpp
new file mode 100644
index 0000000..b4214ca
--- /dev/null
+++ b/src/core/SkRRect.cpp
@@ -0,0 +1,286 @@
+/*
+ * 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 "SkRRect.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkRRect::setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) {
+    if (rect.isEmpty()) {
+        this->setEmpty();
+        return;
+    }
+
+    if (xRad <= 0 || yRad <= 0) {
+        // all corners are square in this case
+        this->setRect(rect);
+        return;
+    }
+
+    if (rect.width() < xRad+xRad || rect.height() < yRad+yRad) {
+        SkScalar scale = SkMinScalar(SkScalarDiv(rect.width(), xRad + xRad),
+                                     SkScalarDiv(rect.height(), yRad + yRad));
+        SkASSERT(scale < SK_Scalar1);
+        xRad = SkScalarMul(xRad, scale);
+        yRad = SkScalarMul(yRad, scale);
+    }
+
+    fRect = rect;
+    for (int i = 0; i < 4; ++i) {
+        fRadii[i].set(xRad, yRad);
+    }
+    fType = kSimple_Type;
+    if (xRad >= SkScalarHalf(fRect.width()) && yRad >= SkScalarHalf(fRect.height())) {
+        fType = kOval_Type;
+        // TODO: try asserting they are already W/2 & H/2 already
+        xRad = SkScalarHalf(fRect.width());
+        yRad = SkScalarHalf(fRect.height());
+    }
+
+    SkDEBUGCODE(this->validate();)
+}
+
+void SkRRect::setRectRadii(const SkRect& rect, const SkVector radii[4]) {
+    if (rect.isEmpty()) {
+        this->setEmpty();
+        return;
+    }
+
+    fRect = rect;
+    memcpy(fRadii, radii, sizeof(fRadii));
+
+    bool allCornersSquare = true;
+
+    // Clamp negative radii to zero
+    for (int i = 0; i < 4; ++i) {
+        if (fRadii[i].fX <= 0 || fRadii[i].fY <= 0) {
+            // In this case we are being a little fast & loose. Since one of
+            // the radii is 0 the corner is square. However, the other radii
+            // could still be non-zero and play in the global scale factor
+            // computation.
+            fRadii[i].fX = 0;
+            fRadii[i].fY = 0;
+        } else {
+            allCornersSquare = false;
+        }
+    }
+
+    if (allCornersSquare) {
+        this->setRect(rect);
+        return;
+    }
+
+    // Proportionally scale down all radii to fit. Find the minimum ratio
+    // of a side and the radii on that side (for all four sides) and use 
+    // that to scale down _all_ the radii. This algorithm is from the
+    // W3 spec (http://www.w3.org/TR/css3-background/) section 5.5 - Overlapping
+    // Curves:
+    // "Let f = min(Li/Si), where i is one of { top, right, bottom, left }, 
+    //   Si is the sum of the two corresponding radii of the corners on side i,
+    //   and Ltop = Lbottom = the width of the box, 
+    //   and Lleft = Lright = the height of the box. 
+    // If f < 1, then all corner radii are reduced by multiplying them by f."
+    SkScalar scale = SK_Scalar1;
+
+    if (fRadii[0].fX + fRadii[1].fX > rect.width()) {
+        scale = SkMinScalar(scale,
+                            SkScalarDiv(rect.width(), fRadii[0].fX + fRadii[1].fX));
+    }
+    if (fRadii[1].fY + fRadii[2].fY > rect.height()) {
+        scale = SkMinScalar(scale,
+                            SkScalarDiv(rect.height(), fRadii[1].fY + fRadii[2].fY));
+    }
+    if (fRadii[2].fX + fRadii[3].fX > rect.width()) {
+        scale = SkMinScalar(scale,
+                            SkScalarDiv(rect.width(), fRadii[2].fX + fRadii[3].fX));
+    }
+    if (fRadii[3].fY + fRadii[0].fY > rect.height()) {
+        scale = SkMinScalar(scale,
+                            SkScalarDiv(rect.height(), fRadii[3].fY + fRadii[0].fY));
+    }
+
+    if (scale < SK_Scalar1) {
+        for (int i = 0; i < 4; ++i) {
+            fRadii[i].fX = SkScalarMul(fRadii[i].fX, scale);
+            fRadii[i].fY = SkScalarMul(fRadii[i].fY, scale);
+        }
+    }
+
+    // At this point we're either oval, simple, or complex (not empty or rect)
+    // but we lazily resolve the type to avoid the work if the information
+    // isn't required.
+    fType = (SkRRect::Type) kUnknown_Type;
+
+    SkDEBUGCODE(this->validate();)
+}
+
+bool SkRRect::contains(SkScalar x, SkScalar y) const {
+    SkDEBUGCODE(this->validate();)
+
+    if (kEmpty_Type == this->type()) {
+        return false;
+    }
+
+    if (!fRect.contains(x, y)) {
+        return false;
+    }
+
+    if (kRect_Type == this->type()) {
+        // the 'fRect' test above was sufficient
+        return true;
+    }
+
+    // We know the point is inside the RR's bounds. The only way it can
+    // be out is if it outside one of the corners
+    SkPoint canonicalPt; // (x,y) translated to one of the quadrants
+    int index;
+
+    if (kOval_Type == this->type()) {
+        canonicalPt.set(x - fRect.centerX(), y - fRect.centerY());
+        index = kUpperLeft_Corner;  // any corner will do in this case
+    } else {
+        if (x < fRect.fLeft + fRadii[kUpperLeft_Corner].fX && 
+            y < fRect.fTop + fRadii[kUpperLeft_Corner].fY) {
+            // UL corner
+            index = kUpperLeft_Corner;
+            canonicalPt.set(x - (fRect.fLeft + fRadii[kUpperLeft_Corner].fX),
+                            y - (fRect.fTop + fRadii[kUpperLeft_Corner].fY));
+            SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY < 0);
+        } else if (x < fRect.fLeft + fRadii[kLowerLeft_Corner].fX &&
+                   y > fRect.fBottom - fRadii[kLowerLeft_Corner].fY) {
+            // LL corner
+            index = kLowerLeft_Corner;
+            canonicalPt.set(x - (fRect.fLeft + fRadii[kLowerLeft_Corner].fX),
+                            y - (fRect.fBottom - fRadii[kLowerLeft_Corner].fY));
+            SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY > 0);
+        } else if (x > fRect.fRight - fRadii[kUpperRight_Corner].fX &&
+                   y < fRect.fTop + fRadii[kUpperRight_Corner].fY) {
+            // UR corner
+            index = kUpperRight_Corner;
+            canonicalPt.set(x - (fRect.fRight - fRadii[kUpperRight_Corner].fX),
+                            y - (fRect.fTop + fRadii[kUpperRight_Corner].fY));
+            SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY < 0);
+        } else if (x > fRect.fRight - fRadii[kLowerRight_Corner].fX &&
+                   y > fRect.fBottom - fRadii[kLowerRight_Corner].fY) {
+            // LR corner
+            index = kLowerRight_Corner;
+            canonicalPt.set(x - (fRect.fRight - fRadii[kLowerRight_Corner].fX),
+                            y - (fRect.fBottom - fRadii[kLowerRight_Corner].fY));
+            SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY > 0);
+        } else {
+            // not in any of the corners
+            return true;
+        }
+    }
+
+    // A point is in an ellipse (in standard position) if:
+    //      x^2     y^2
+    //     ----- + ----- <= 1
+    //      a^2     b^2
+    SkScalar dist =  SkScalarDiv(SkScalarSquare(canonicalPt.fX), SkScalarSquare(fRadii[index].fX)) +
+                     SkScalarDiv(SkScalarSquare(canonicalPt.fY), SkScalarSquare(fRadii[index].fY));
+    return dist <= SK_Scalar1;
+}
+
+// There is a simplified version of this method in setRectXY
+void SkRRect::computeType() const {
+    SkDEBUGCODE(this->validate();)
+
+    if (fRect.isEmpty()) {
+        fType = kEmpty_Type;
+        return;
+    }
+
+    bool allRadiiEqual = true; // are all x radii equal and all y radii?
+    bool allCornersSquare = 0 == fRadii[0].fX || 0 == fRadii[0].fY;
+
+    for (int i = 1; i < 4; ++i) {
+        if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
+            // if either radius is zero the corner is square so both have to
+            // be non-zero to have a rounded corner
+            allCornersSquare = false;
+        }
+        if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
+            allRadiiEqual = false;
+        }
+    }
+
+    if (allCornersSquare) {
+        fType = kRect_Type;
+        return;
+    }
+
+    if (allRadiiEqual) {
+        if (fRadii[0].fX >= SkScalarHalf(fRect.width()) &&
+            fRadii[0].fY >= SkScalarHalf(fRect.height())) {
+            fType = kOval_Type;
+        } else {
+            fType = kSimple_Type;
+        }
+        return;
+    }
+
+    fType = kComplex_Type;
+}
+
+#ifdef SK_DEBUG
+void SkRRect::validate() const {
+    bool allRadiiZero = (0 == fRadii[0].fX && 0 == fRadii[0].fY);
+    bool allCornersSquare = (0 == fRadii[0].fX || 0 == fRadii[0].fY);
+    bool allRadiiSame = true;
+
+    for (int i = 1; i < 4; ++i) {
+        if (0 != fRadii[i].fX || 0 != fRadii[i].fY) {
+            allRadiiZero = false;
+        }
+
+        if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
+            allRadiiSame = false;
+        }
+
+        if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
+            allCornersSquare = false;
+        }
+    }
+
+    switch (fType) {
+        case kEmpty_Type:
+            SkASSERT(fRect.isEmpty());
+            SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare);
+
+            SkASSERT(0 == fRect.fLeft && 0 == fRect.fTop && 
+                     0 == fRect.fRight && 0 == fRect.fBottom);
+            break;
+        case kRect_Type:
+            SkASSERT(!fRect.isEmpty());
+            SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare);
+            break;
+        case kOval_Type:
+            SkASSERT(!fRect.isEmpty());
+            SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare);
+
+            for (int i = 0; i < 4; ++i) {
+                SkASSERT(SkScalarNearlyEqual(fRadii[i].fX, SkScalarHalf(fRect.width())));
+                SkASSERT(SkScalarNearlyEqual(fRadii[i].fY, SkScalarHalf(fRect.height())));
+            }
+            break;
+        case kSimple_Type:
+            SkASSERT(!fRect.isEmpty());
+            SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare);
+            break;
+        case kComplex_Type:
+            SkASSERT(!fRect.isEmpty());
+            SkASSERT(!allRadiiZero && !allRadiiSame && !allCornersSquare);
+            break;
+        case kUnknown_Type:
+            // no limits on this
+            break;
+    }
+}
+#endif // SK_DEBUG
+
+///////////////////////////////////////////////////////////////////////////////
diff --git a/tests/RoundRectTest.cpp b/tests/RoundRectTest.cpp
new file mode 100644
index 0000000..fd4780c
--- /dev/null
+++ b/tests/RoundRectTest.cpp
@@ -0,0 +1,308 @@
+/*
+ * 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 "Test.h"
+#include "SkRRect.h"
+
+static const SkScalar kWidth = 100.0f;
+static const SkScalar kHeight = 100.0f;
+
+// Test out the basic API entry points
+static void test_round_rect_basic(skiatest::Reporter* reporter) {
+    // Test out initialization methods
+    SkPoint zeroPt = { 0.0, 0.0 };
+    SkRRect empty;
+    
+    empty.setEmpty();
+
+    REPORTER_ASSERT(reporter, SkRRect::kEmpty_Type == empty.type());
+    REPORTER_ASSERT(reporter, empty.rect().isEmpty());
+
+    for (int i = 0; i < 4; ++i) {
+        REPORTER_ASSERT(reporter, zeroPt == empty.radii((SkRRect::Corner) i));
+    }
+
+    //----
+    SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
+
+    SkRRect rr1;
+    rr1.setRect(rect);
+
+    REPORTER_ASSERT(reporter, SkRRect::kRect_Type == rr1.type());
+    REPORTER_ASSERT(reporter, rr1.rect() == rect);
+
+    for (int i = 0; i < 4; ++i) {
+        REPORTER_ASSERT(reporter, zeroPt == rr1.radii((SkRRect::Corner) i));
+    }
+
+    //----
+    SkPoint halfPoint = { SkScalarHalf(kWidth), SkScalarHalf(kHeight) };
+    SkRRect rr2;
+    rr2.setOval(rect);
+
+    REPORTER_ASSERT(reporter, SkRRect::kOval_Type == rr2.type());
+    REPORTER_ASSERT(reporter, rr2.rect() == rect);
+
+    for (int i = 0; i < 4; ++i) {
+        REPORTER_ASSERT(reporter, 
+                        rr2.radii((SkRRect::Corner) i).equalsWithinTolerance(halfPoint));
+    }
+
+    //----
+    SkPoint p = { 5, 5 };
+    SkRRect rr3;
+    rr3.setRectXY(rect, p.fX, p.fY);
+
+    REPORTER_ASSERT(reporter, SkRRect::kSimple_Type == rr3.type());
+    REPORTER_ASSERT(reporter, rr3.rect() == rect);
+
+    for (int i = 0; i < 4; ++i) {
+        REPORTER_ASSERT(reporter, p == rr3.radii((SkRRect::Corner) i));
+    }
+    
+    //----
+    SkPoint radii[4] = { { 5, 5 }, { 5, 5 }, { 5, 5 }, { 5, 5 } };
+
+    SkRRect rr4;
+    rr4.setRectRadii(rect, radii);
+
+    REPORTER_ASSERT(reporter, SkRRect::kSimple_Type == rr4.type());
+    REPORTER_ASSERT(reporter, rr4.rect() == rect);
+
+    for (int i = 0; i < 4; ++i) {
+        REPORTER_ASSERT(reporter, radii[i] == rr4.radii((SkRRect::Corner) i));
+    }
+
+    //----
+    SkPoint radii2[4] = { { 0, 0 }, { 0, 0 }, { 50, 50 }, { 20, 50 } };
+
+    SkRRect rr5;
+    rr5.setRectRadii(rect, radii2);
+
+    REPORTER_ASSERT(reporter, SkRRect::kComplex_Type == rr5.type());
+    REPORTER_ASSERT(reporter, rr5.rect() == rect);
+
+    for (int i = 0; i < 4; ++i) {
+        REPORTER_ASSERT(reporter, radii2[i] == rr5.radii((SkRRect::Corner) i));
+    }
+
+    // Test out == & !=
+    REPORTER_ASSERT(reporter, empty != rr3);
+    REPORTER_ASSERT(reporter, rr3 == rr4);
+    REPORTER_ASSERT(reporter, rr4 != rr5);
+}
+
+// Test out the cases when the RR degenerates to a rect
+static void test_round_rect_rects(skiatest::Reporter* reporter) {
+    SkRect r;
+    static const SkPoint pts[] = { 
+        // Upper Left
+        { -SK_Scalar1, -SK_Scalar1 },               // out
+        { SK_Scalar1, SK_Scalar1 },                 // in
+        // Upper Right
+        { SkIntToScalar(101), -SK_Scalar1},         // out
+        { SkIntToScalar(99), SK_Scalar1 },          // in
+        // Lower Right
+        { SkIntToScalar(101), SkIntToScalar(101) }, // out
+        { SkIntToScalar(99), SkIntToScalar(99) },   // in
+        // Lower Left
+        { -SK_Scalar1, SkIntToScalar(101) },        // out
+        { SK_Scalar1, SkIntToScalar(99) },          // in
+        // Middle
+        { SkIntToScalar(50), SkIntToScalar(50) }    // in
+    };
+    static const bool isIn[] = { false, true, false, true, false, true, false, true, true };
+
+    SkASSERT(SK_ARRAY_COUNT(pts) == SK_ARRAY_COUNT(isIn));
+
+    //----
+    SkRRect empty;
+    
+    empty.setEmpty();
+
+    REPORTER_ASSERT(reporter, SkRRect::kEmpty_Type == empty.type());
+    r = empty.rect();
+    REPORTER_ASSERT(reporter, 0 == r.fLeft && 0 == r.fTop && 0 == r.fRight && 0 == r.fBottom);
+
+    //----
+    SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
+    SkRRect rr1;
+    rr1.setRectXY(rect, 0, 0);
+
+    REPORTER_ASSERT(reporter, SkRRect::kRect_Type == rr1.type());
+    r = rr1.rect();
+    REPORTER_ASSERT(reporter, rect == r);
+    for (int i = 0; i < SK_ARRAY_COUNT(pts); ++i) {
+        REPORTER_ASSERT(reporter, isIn[i] == rr1.contains(pts[i].fX, pts[i].fY));
+    }
+
+    //----
+    SkPoint radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
+
+    SkRRect rr2;
+    rr2.setRectRadii(rect, radii);
+
+    REPORTER_ASSERT(reporter, SkRRect::kRect_Type == rr2.type());
+    r = rr2.rect();
+    REPORTER_ASSERT(reporter, rect == r);
+    for (int i = 0; i < SK_ARRAY_COUNT(pts); ++i) {
+        REPORTER_ASSERT(reporter, isIn[i] == rr2.contains(pts[i].fX, pts[i].fY));
+    }
+
+    //----
+    SkPoint radii2[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
+
+    SkRRect rr3;
+    rr3.setRectRadii(rect, radii2);
+    REPORTER_ASSERT(reporter, SkRRect::kComplex_Type == rr3.type());
+}
+
+// Test out the cases when the RR degenerates to an oval
+static void test_round_rect_ovals(skiatest::Reporter* reporter) {
+    static const SkScalar kEps = 0.1f;
+    static const SkScalar kWidthTol = SkScalarHalf(kWidth) * (SK_Scalar1 - SK_ScalarRoot2Over2);
+    static const SkScalar kHeightTol = SkScalarHalf(kHeight) * (SK_Scalar1 - SK_ScalarRoot2Over2);
+    static const SkPoint pts[] = { 
+        // Upper Left
+        { kWidthTol - kEps, kHeightTol - kEps },       // out
+        { kWidthTol + kEps, kHeightTol + kEps },       // in
+        // Upper Right
+        { kWidth + kEps - kWidthTol, kHeightTol - kEps },     // out
+        { kWidth - kEps - kWidthTol, kHeightTol + kEps },      // in
+        // Lower Right
+        { kWidth + kEps - kWidthTol, kHeight + kEps - kHeightTol },   // out
+        { kWidth - kEps - kWidthTol, kHeight - kEps - kHeightTol },   // in
+        // Lower Left
+        { kWidthTol - kEps, kHeight + kEps - kHeightTol },     //out
+        { kWidthTol + kEps, kHeight - kEps - kHeightTol },     // in
+        // Middle
+        { SkIntToScalar(50), SkIntToScalar(50) } // in
+    };
+    static const bool isIn[] = { false, true, false, true, false, true, false, true, true };
+
+    SkASSERT(SK_ARRAY_COUNT(pts) == SK_ARRAY_COUNT(isIn));
+
+    //----
+    SkRect oval;
+    SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
+    SkRRect rr1;
+    rr1.setRectXY(rect, SkScalarHalf(kWidth), SkScalarHalf(kHeight));
+
+    REPORTER_ASSERT(reporter, SkRRect::kOval_Type == rr1.type());
+    oval = rr1.rect();
+    REPORTER_ASSERT(reporter, oval == rect);
+    for (int i = 0; i < SK_ARRAY_COUNT(pts); ++i) {
+        REPORTER_ASSERT(reporter, isIn[i] == rr1.contains(pts[i].fX, pts[i].fY));
+    }
+}
+
+// Test out the non-degenerate RR cases
+static void test_round_rect_general(skiatest::Reporter* reporter) {
+    static const SkScalar kEps = 0.1f;
+    static const SkScalar kDist20 = 20 * (SK_Scalar1 - SK_ScalarRoot2Over2);
+    static const SkPoint pts[] = { 
+        // Upper Left
+        { kDist20 - kEps, kDist20 - kEps },       // out
+        { kDist20 + kEps, kDist20 + kEps },       // in
+        // Upper Right
+        { kWidth + kEps - kDist20, kDist20 - kEps },     // out
+        { kWidth - kEps - kDist20, kDist20 + kEps },      // in
+        // Lower Right
+        { kWidth + kEps - kDist20, kHeight + kEps - kDist20 },   // out
+        { kWidth - kEps - kDist20, kHeight - kEps - kDist20 },   // in
+        // Lower Left
+        { kDist20 - kEps, kHeight + kEps - kDist20 },     //out
+        { kDist20 + kEps, kHeight - kEps - kDist20 },     // in
+        // Middle
+        { SkIntToScalar(50), SkIntToScalar(50) } // in
+    };
+    static const bool isIn[] = { false, true, false, true, false, true, false, true, true };
+
+    SkASSERT(SK_ARRAY_COUNT(pts) == SK_ARRAY_COUNT(isIn));
+
+    //----
+    SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
+    SkRRect rr1;
+    rr1.setRectXY(rect, 20, 20);
+
+    REPORTER_ASSERT(reporter, SkRRect::kSimple_Type == rr1.type());
+    for (int i = 0; i < SK_ARRAY_COUNT(pts); ++i) {
+        REPORTER_ASSERT(reporter, isIn[i] == rr1.contains(pts[i].fX, pts[i].fY));
+    }
+
+    //----
+    static const SkScalar kDist50 = 50*(SK_Scalar1 - SK_ScalarRoot2Over2);
+    static const SkPoint pts2[] = { 
+        // Upper Left
+        { -SK_Scalar1, -SK_Scalar1 },           // out
+        { SK_Scalar1, SK_Scalar1 },             // in
+        // Upper Right
+        { kWidth + kEps - kDist20, kDist20 - kEps },     // out
+        { kWidth - kEps - kDist20, kDist20 + kEps },     // in
+        // Lower Right
+        { kWidth + kEps - kDist50, kHeight + kEps - kDist50 },   // out
+        { kWidth - kEps - kDist50, kHeight - kEps - kDist50 },   // in
+        // Lower Left
+        { kDist20 - kEps, kHeight + kEps - kDist50 },     // out
+        { kDist20 + kEps, kHeight - kEps - kDist50 },     // in
+        // Middle
+        { SkIntToScalar(50), SkIntToScalar(50) }  // in
+    };
+
+    SkASSERT(SK_ARRAY_COUNT(pts2) == SK_ARRAY_COUNT(isIn));
+
+    SkPoint radii[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
+
+    SkRRect rr2;
+    rr2.setRectRadii(rect, radii);
+
+    REPORTER_ASSERT(reporter, SkRRect::kComplex_Type == rr2.type());
+    for (int i = 0; i < SK_ARRAY_COUNT(pts); ++i) {
+        REPORTER_ASSERT(reporter, isIn[i] == rr2.contains(pts2[i].fX, pts2[i].fY));
+    }
+}
+
+// Test out questionable-parameter handling
+static void test_round_rect_iffy_parameters(skiatest::Reporter* reporter) {
+
+    // When the radii exceed the base rect they are proportionally scaled down
+    // to fit
+    SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
+    SkPoint radii[4] = { { 50, 100 }, { 100, 50 }, { 50, 100 }, { 100, 50 } };
+
+    SkRRect rr1;
+    rr1.setRectRadii(rect, radii);
+
+    REPORTER_ASSERT(reporter, SkRRect::kComplex_Type == rr1.type());
+
+    const SkPoint& p = rr1.radii(SkRRect::kUpperLeft_Corner);
+
+    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(p.fX, 33.33333f));
+    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(p.fY, 66.66666f));
+
+    // Negative radii should be capped at zero
+    SkRRect rr2;
+    rr2.setRectXY(rect, -10, -20);
+
+    REPORTER_ASSERT(reporter, SkRRect::kRect_Type == rr2.type());
+
+    const SkPoint& p2 = rr2.radii(SkRRect::kUpperLeft_Corner);
+
+    REPORTER_ASSERT(reporter, 0.0f == p2.fX);
+    REPORTER_ASSERT(reporter, 0.0f == p2.fY);
+}
+
+static void TestRoundRect(skiatest::Reporter* reporter) {
+    test_round_rect_basic(reporter);
+    test_round_rect_rects(reporter);
+    test_round_rect_ovals(reporter);
+    test_round_rect_general(reporter);
+    test_round_rect_iffy_parameters(reporter);
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("RoundRect", TestRoundRectClass, TestRoundRect)