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)