Much like SkPath::transform, it transforms an SkRRect based on an
SkMatrix. Unlike SkPath::transform, it will fail for matrices that
contain perspective or skewing.

Required by a future change (https://codereview.chromium.org/48623006)
to speed up drawing large blurry rounded rectangles by using ninepatches.

TODO: This could easily support 90 degree rotations, if desired.

BUG=https://b.corp.google.com/issue?id=11174385
R=reed@google.com, robertphillips@google.com

Review URL: https://codereview.chromium.org/52703003

git-svn-id: http://skia.googlecode.com/svn/trunk@12132 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
scroggo@google.com 2013-11-05 15:54:42 +00:00
Родитель a93f4e770f
Коммит 20e3cd2c9f
3 изменённых файлов: 296 добавлений и 2 удалений

Просмотреть файл

@ -12,6 +12,7 @@
#include "SkPoint.h"
class SkPath;
class SkMatrix;
// Path forward:
// core work
@ -259,6 +260,17 @@ public:
*/
size_t readFromMemory(const void* buffer, size_t length);
/**
* Transform by the specified matrix, and put the result in dst.
*
* @param matrix SkMatrix specifying the transform. Must only contain
* scale and/or translate, or this call will fail.
* @param dst SkRRect to store the result. It is an error to use this,
* which would make this function no longer const.
* @return true on success, false on failure. If false, dst is unmodified.
*/
bool transform(const SkMatrix& matrix, SkRRect* dst) const;
private:
SkRect fRect;
// Radii order is UL, UR, LR, LL. Use Corner enum to index into fRadii[]

Просмотреть файл

@ -6,6 +6,7 @@
*/
#include "SkRRect.h"
#include "SkMatrix.h"
///////////////////////////////////////////////////////////////////////////////
@ -233,6 +234,85 @@ void SkRRect::computeType() const {
fType = kComplex_Type;
}
static bool matrix_only_scale_and_translate(const SkMatrix& matrix) {
const SkMatrix::TypeMask m = (SkMatrix::TypeMask) (SkMatrix::kAffine_Mask
| SkMatrix::kPerspective_Mask);
return (matrix.getType() & m) == 0;
}
bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const {
if (NULL == dst) {
return false;
}
// Assert that the caller is not trying to do this in place, which
// would violate const-ness. Do not return false though, so that
// if they know what they're doing and want to violate it they can.
SkASSERT(dst != this);
if (matrix.isIdentity()) {
*dst = *this;
return true;
}
// If transform supported 90 degree rotations (which it could), we could
// use SkMatrix::rectStaysRect() to check for a valid transformation.
if (!matrix_only_scale_and_translate(matrix)) {
return false;
}
SkRect newRect;
if (!matrix.mapRect(&newRect, fRect)) {
return false;
}
// At this point, this is guaranteed to succeed, so we can modify dst.
dst->fRect = newRect;
// Now scale each corner
SkScalar xScale = matrix.getScaleX();
const bool flipX = xScale < 0;
if (flipX) {
xScale = -xScale;
}
SkScalar yScale = matrix.getScaleY();
const bool flipY = yScale < 0;
if (flipY) {
yScale = -yScale;
}
// Scale the radii without respecting the flip.
for (int i = 0; i < 4; ++i) {
dst->fRadii[i].fX = SkScalarMul(fRadii[i].fX, xScale);
dst->fRadii[i].fY = SkScalarMul(fRadii[i].fY, yScale);
}
// Now swap as necessary.
if (flipX) {
if (flipY) {
// Swap with opposite corners
SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerRight_Corner]);
SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerLeft_Corner]);
} else {
// Only swap in x
SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kUpperLeft_Corner]);
SkTSwap(dst->fRadii[kLowerRight_Corner], dst->fRadii[kLowerLeft_Corner]);
}
} else if (flipY) {
// Only swap in y
SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerLeft_Corner]);
SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerRight_Corner]);
}
// Since the only transforms that were allowed are scale and translate, the type
// remains unchanged.
dst->fType = fType;
SkDEBUGCODE(dst->validate();)
return true;
}
///////////////////////////////////////////////////////////////////////////////
void SkRRect::inset(SkScalar dx, SkScalar dy, SkRRect* dst) const {

Просмотреть файл

@ -6,10 +6,11 @@
*/
#include "Test.h"
#include "SkMatrix.h"
#include "SkRRect.h"
static const SkScalar kWidth = 100.0f;
static const SkScalar kHeight = 100.0f;
static const SkScalar kWidth = SkFloatToScalar(100.0f);
static const SkScalar kHeight = SkFloatToScalar(100.0f);
static void test_inset(skiatest::Reporter* reporter) {
SkRRect rr, rr2;
@ -352,6 +353,206 @@ static void test_round_rect_contains_rect(skiatest::Reporter* reporter) {
}
}
// Called for a matrix that should cause SkRRect::transform to fail.
static void assert_transform_failure(skiatest::Reporter* reporter, const SkRRect& orig,
const SkMatrix& matrix) {
// The test depends on the fact that the original is not empty.
SkASSERT(!orig.isEmpty());
SkRRect dst;
dst.setEmpty();
const SkRRect copyOfDst = dst;
const SkRRect copyOfOrig = orig;
bool success = orig.transform(matrix, &dst);
// This transform should fail.
REPORTER_ASSERT(reporter, !success);
// Since the transform failed, dst should be unchanged.
REPORTER_ASSERT(reporter, copyOfDst == dst);
// original should not be modified.
REPORTER_ASSERT(reporter, copyOfOrig == orig);
REPORTER_ASSERT(reporter, orig != dst);
}
#define GET_RADII \
const SkVector& origUL = orig.radii(SkRRect::kUpperLeft_Corner); \
const SkVector& origUR = orig.radii(SkRRect::kUpperRight_Corner); \
const SkVector& origLR = orig.radii(SkRRect::kLowerRight_Corner); \
const SkVector& origLL = orig.radii(SkRRect::kLowerLeft_Corner); \
const SkVector& dstUL = dst.radii(SkRRect::kUpperLeft_Corner); \
const SkVector& dstUR = dst.radii(SkRRect::kUpperRight_Corner); \
const SkVector& dstLR = dst.radii(SkRRect::kLowerRight_Corner); \
const SkVector& dstLL = dst.radii(SkRRect::kLowerLeft_Corner)
// Called to test various transforms on a single SkRRect.
static void test_transform_helper(skiatest::Reporter* reporter, const SkRRect& orig) {
SkRRect dst;
dst.setEmpty();
// The identity matrix will duplicate the rrect.
bool success = orig.transform(SkMatrix::I(), &dst);
REPORTER_ASSERT(reporter, success);
REPORTER_ASSERT(reporter, orig == dst);
// Skew and Perspective make transform fail.
SkMatrix matrix;
matrix.reset();
matrix.setSkewX(SkIntToScalar(2));
assert_transform_failure(reporter, orig, matrix);
matrix.reset();
matrix.setSkewY(SkIntToScalar(3));
assert_transform_failure(reporter, orig, matrix);
matrix.reset();
matrix.setPerspX(SkScalarToPersp(SkIntToScalar(4)));
assert_transform_failure(reporter, orig, matrix);
matrix.reset();
matrix.setPerspY(SkScalarToPersp(SkIntToScalar(5)));
assert_transform_failure(reporter, orig, matrix);
// Rotation fails.
matrix.reset();
matrix.setRotate(SkIntToScalar(90));
assert_transform_failure(reporter, orig, matrix);
matrix.setRotate(SkIntToScalar(37));
assert_transform_failure(reporter, orig, matrix);
// Translate will keep the rect moved, but otherwise the same.
matrix.reset();
SkScalar translateX = SkIntToScalar(32);
SkScalar translateY = SkIntToScalar(15);
matrix.setTranslateX(translateX);
matrix.setTranslateY(translateY);
dst.setEmpty();
success = orig.transform(matrix, &dst);
REPORTER_ASSERT(reporter, success);
for (int i = 0; i < 4; ++i) {
REPORTER_ASSERT(reporter,
orig.radii((SkRRect::Corner) i) == dst.radii((SkRRect::Corner) i));
}
REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width());
REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height());
REPORTER_ASSERT(reporter, dst.rect().left() == orig.rect().left() + translateX);
REPORTER_ASSERT(reporter, dst.rect().top() == orig.rect().top() + translateY);
// Keeping the translation, but adding skew will make transform fail.
matrix.setSkewY(SkIntToScalar(7));
assert_transform_failure(reporter, orig, matrix);
// Scaling in -x will flip the round rect horizontally.
matrix.reset();
matrix.setScaleX(SkIntToScalar(-1));
dst.setEmpty();
success = orig.transform(matrix, &dst);
REPORTER_ASSERT(reporter, success);
{
GET_RADII;
// Radii have swapped in x.
REPORTER_ASSERT(reporter, origUL == dstUR);
REPORTER_ASSERT(reporter, origUR == dstUL);
REPORTER_ASSERT(reporter, origLR == dstLL);
REPORTER_ASSERT(reporter, origLL == dstLR);
}
// Width and height remain the same.
REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width());
REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height());
// Right and left have swapped (sort of)
REPORTER_ASSERT(reporter, orig.rect().right() == -dst.rect().left());
// Top has stayed the same.
REPORTER_ASSERT(reporter, orig.rect().top() == dst.rect().top());
// Keeping the scale, but adding a persp will make transform fail.
matrix.setPerspX(SkScalarToPersp(SkIntToScalar(7)));
assert_transform_failure(reporter, orig, matrix);
// Scaling in -y will flip the round rect vertically.
matrix.reset();
matrix.setScaleY(SkIntToScalar(-1));
dst.setEmpty();
success = orig.transform(matrix, &dst);
REPORTER_ASSERT(reporter, success);
{
GET_RADII;
// Radii have swapped in y.
REPORTER_ASSERT(reporter, origUL == dstLL);
REPORTER_ASSERT(reporter, origUR == dstLR);
REPORTER_ASSERT(reporter, origLR == dstUR);
REPORTER_ASSERT(reporter, origLL == dstUL);
}
// Width and height remain the same.
REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width());
REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height());
// Top and bottom have swapped (sort of)
REPORTER_ASSERT(reporter, orig.rect().top() == -dst.rect().bottom());
// Left has stayed the same.
REPORTER_ASSERT(reporter, orig.rect().left() == dst.rect().left());
// Scaling in -x and -y will swap in both directions.
matrix.reset();
matrix.setScaleY(SkIntToScalar(-1));
matrix.setScaleX(SkIntToScalar(-1));
dst.setEmpty();
success = orig.transform(matrix, &dst);
REPORTER_ASSERT(reporter, success);
{
GET_RADII;
REPORTER_ASSERT(reporter, origUL == dstLR);
REPORTER_ASSERT(reporter, origUR == dstLL);
REPORTER_ASSERT(reporter, origLR == dstUL);
REPORTER_ASSERT(reporter, origLL == dstUR);
}
// Width and height remain the same.
REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width());
REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height());
REPORTER_ASSERT(reporter, orig.rect().top() == -dst.rect().bottom());
REPORTER_ASSERT(reporter, orig.rect().right() == -dst.rect().left());
// Scale in both directions.
SkScalar xScale = SkIntToScalar(3);
SkScalar yScale = SkFloatToScalar(3.2f);
matrix.reset();
matrix.setScaleX(xScale);
matrix.setScaleY(yScale);
dst.setEmpty();
success = orig.transform(matrix, &dst);
REPORTER_ASSERT(reporter, success);
// Radii are scaled.
for (int i = 0; i < 4; ++i) {
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.radii((SkRRect::Corner) i).fX,
SkScalarMul(orig.radii((SkRRect::Corner) i).fX, xScale)));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.radii((SkRRect::Corner) i).fY,
SkScalarMul(orig.radii((SkRRect::Corner) i).fY, yScale)));
}
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().width(),
SkScalarMul(orig.rect().width(), xScale)));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().height(),
SkScalarMul(orig.rect().height(), yScale)));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().left(),
SkScalarMul(orig.rect().left(), xScale)));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().top(),
SkScalarMul(orig.rect().top(), yScale)));
}
static void test_round_rect_transform(skiatest::Reporter* reporter) {
SkRRect rrect;
{
SkRect r = { 0, 0, kWidth, kHeight };
rrect.setRectXY(r, SkIntToScalar(4), SkIntToScalar(7));
test_transform_helper(reporter, rrect);
}
{
SkRect r = { SkIntToScalar(5), SkIntToScalar(15),
SkIntToScalar(27), SkIntToScalar(34) };
SkVector radii[4] = { { 0, SkIntToScalar(1) },
{ SkIntToScalar(2), SkIntToScalar(3) },
{ SkIntToScalar(4), SkIntToScalar(5) },
{ SkIntToScalar(6), SkIntToScalar(7) } };
rrect.setRectRadii(r, radii);
test_transform_helper(reporter, rrect);
}
}
static void TestRoundRect(skiatest::Reporter* reporter) {
test_round_rect_basic(reporter);
test_round_rect_rects(reporter);
@ -360,6 +561,7 @@ static void TestRoundRect(skiatest::Reporter* reporter) {
test_round_rect_iffy_parameters(reporter);
test_inset(reporter);
test_round_rect_contains_rect(reporter);
test_round_rect_transform(reporter);
}
#include "TestClassDef.h"