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:
parent
687c57c7d5
commit
5985e7c4d1
@ -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',
|
||||
|
@ -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
215
include/core/SkRRect.h
Normal 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
286
src/core/SkRRect.cpp
Normal 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
308
tests/RoundRectTest.cpp
Normal 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)
|
Loading…
Reference in New Issue
Block a user