SkRoundRect start

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



git-svn-id: http://skia.googlecode.com/svn/trunk@6595 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
robertphillips@google.com 2012-11-29 13:24:55 +00:00
parent 687c57c7d5
commit 5985e7c4d1
5 changed files with 812 additions and 0 deletions

View File

@ -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',

View File

@ -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',

215
include/core/SkRRect.h Normal file
View File

@ -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

286
src/core/SkRRect.cpp Normal file
View File

@ -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
///////////////////////////////////////////////////////////////////////////////

308
tests/RoundRectTest.cpp Normal file
View File

@ -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)