Make SkRRect work with axis aligned rotation transforms

This patch allows SkRRect::trasform() work with any axis aligned matrix
transform meaning it now works with a rotation of 90 or 270 degrees.

Adds relevant unit tests.

Bug: skia:8944
Change-Id: I63678ec0e3556c181517526de55d84666afeb681
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/205860
Reviewed-by: Robert Phillips <robertphillips@google.com>
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Malay Keshav 2019-04-03 13:28:05 -07:00 committed by Skia Commit-Bot
parent e21616804a
commit e4628b1072
3 changed files with 245 additions and 10 deletions

View File

@ -454,7 +454,7 @@ public:
/** Transforms by SkRRect by matrix, storing result in dst.
Returns true if SkRRect transformed can be represented by another SkRRect.
Returns false if matrix contains transformations other than scale and translate.
Returns false if matrix contains transformations that are not axis aligned.
Asserts in debug builds if SkRRect equals dst.

View File

@ -389,9 +389,7 @@ bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const {
return true;
}
// If transform supported 90 degree rotations (which it could), we could
// use SkMatrix::rectStaysRect() to check for a valid transformation.
if (!matrix.isScaleTranslate()) {
if (!matrix.preservesAxisAlignment()) {
return false;
}
@ -411,7 +409,7 @@ bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const {
// At this point, this is guaranteed to succeed, so we can modify dst.
dst->fRect = newRect;
// Since the only transforms that were allowed are scale and translate, the type
// Since the only transforms that were allowed are axis aligned, the type
// remains unchanged.
dst->fType = fType;
@ -430,11 +428,37 @@ bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const {
// Now scale each corner
SkScalar xScale = matrix.getScaleX();
SkScalar yScale = matrix.getScaleY();
// There is a rotation of 90 (Clockwise 90) or 270 (Counter clockwise 90).
// 180 degrees rotations are simply flipX with a flipY and would come under
// a scale transform.
if (!matrix.isScaleTranslate()) {
const bool isClockwise = matrix.getSkewX() < 0;
// The matrix location for scale changes if there is a rotation.
xScale = matrix.getSkewY() * (isClockwise ? 1 : -1);
yScale = matrix.getSkewX() * (isClockwise ? -1 : 1);
const int dir = isClockwise ? 3 : 1;
for (int i = 0; i < 4; ++i) {
const int src = (i + dir) >= 4 ? (i + dir) % 4 : (i + dir);
// Swap X and Y axis for the radii.
dst->fRadii[i].fX = fRadii[src].fY;
dst->fRadii[i].fY = fRadii[src].fX;
}
} else {
for (int i = 0; i < 4; ++i) {
dst->fRadii[i].fX = fRadii[i].fX;
dst->fRadii[i].fY = fRadii[i].fY;
}
}
const bool flipX = xScale < 0;
if (flipX) {
xScale = -xScale;
}
SkScalar yScale = matrix.getScaleY();
const bool flipY = yScale < 0;
if (flipY) {
yScale = -yScale;
@ -442,8 +466,8 @@ bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const {
// Scale the radii without respecting the flip.
for (int i = 0; i < 4; ++i) {
dst->fRadii[i].fX = fRadii[i].fX * xScale;
dst->fRadii[i].fY = fRadii[i].fY * yScale;
dst->fRadii[i].fX *= xScale;
dst->fRadii[i].fY *= yScale;
}
// Now swap as necessary.

View File

@ -570,8 +570,6 @@ static void test_transform_helper(skiatest::Reporter* reporter, const SkRRect& o
// Rotation fails.
matrix.reset();
matrix.setRotate(SkIntToScalar(90));
assert_transform_failure(reporter, orig, matrix);
matrix.setRotate(SkIntToScalar(37));
assert_transform_failure(reporter, orig, matrix);
@ -689,6 +687,219 @@ static void test_transform_helper(skiatest::Reporter* reporter, const SkRRect& o
orig.rect().left() * xScale));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().top(),
orig.rect().top() * yScale));
// a-----b d-----a
// | | -> | |
// | | Rotate 90 | |
// d-----c c-----b
matrix.reset();
matrix.setRotate(SkIntToScalar(90));
dst.setEmpty();
success = orig.transform(matrix, &dst);
REPORTER_ASSERT(reporter, success);
{
GET_RADII;
// Radii have cycled clockwise and swapped their x and y axis.
REPORTER_ASSERT(reporter, dstUL.x() == origLL.y());
REPORTER_ASSERT(reporter, dstUL.y() == origLL.x());
REPORTER_ASSERT(reporter, dstUR.x() == origUL.y());
REPORTER_ASSERT(reporter, dstUR.y() == origUL.x());
REPORTER_ASSERT(reporter, dstLR.x() == origUR.y());
REPORTER_ASSERT(reporter, dstLR.y() == origUR.x());
REPORTER_ASSERT(reporter, dstLL.x() == origLR.y());
REPORTER_ASSERT(reporter, dstLL.y() == origLR.x());
}
// Width and height would get swapped.
REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().height());
REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().width());
// a-----b b-----a c-----b
// | | -> | | -> | |
// | | Flip X | | Rotate 90 | |
// d-----c c-----d d-----a
matrix.reset();
matrix.setRotate(SkIntToScalar(90));
matrix.postScale(SkIntToScalar(-1), SkIntToScalar(1));
dst.setEmpty();
success = orig.transform(matrix, &dst);
REPORTER_ASSERT(reporter, success);
{
GET_RADII;
REPORTER_ASSERT(reporter, dstUL.x() == origLR.y());
REPORTER_ASSERT(reporter, dstUL.y() == origLR.x());
REPORTER_ASSERT(reporter, dstUR.x() == origUR.y());
REPORTER_ASSERT(reporter, dstUR.y() == origUR.x());
REPORTER_ASSERT(reporter, dstLR.x() == origUL.y());
REPORTER_ASSERT(reporter, dstLR.y() == origUL.x());
REPORTER_ASSERT(reporter, dstLL.x() == origLL.y());
REPORTER_ASSERT(reporter, dstLL.y() == origLL.x());
}
// Width and height would get swapped.
REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().height());
REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().width());
// a-----b d-----a c-----b
// | | -> | | -> | |
// | | Rotate 90 | | Flip Y | |
// d-----c c-----b d-----a
//
// This is the same as Flip X and Rotate 90.
matrix.reset();
matrix.setScale(SkIntToScalar(1), SkIntToScalar(-1));
matrix.postRotate(SkIntToScalar(90));
SkRRect dst2;
dst2.setEmpty();
success = orig.transform(matrix, &dst2);
REPORTER_ASSERT(reporter, success);
REPORTER_ASSERT(reporter, dst == dst2);
// a-----b b-----c c-----b
// | | -> | | -> | |
// | | Rotate 270 | | Flip X | |
// d-----c a-----d d-----a
matrix.reset();
matrix.setScale(SkIntToScalar(-1), SkIntToScalar(1));
matrix.postRotate(SkIntToScalar(270));
dst2.setEmpty();
success = orig.transform(matrix, &dst2);
REPORTER_ASSERT(reporter, success);
REPORTER_ASSERT(reporter, dst == dst2);
// a-----b d-----c c-----b
// | | -> | | -> | |
// | | Flip Y | | Rotate 270 | |
// d-----c a-----b d-----a
matrix.reset();
matrix.setRotate(SkIntToScalar(270));
matrix.postScale(SkIntToScalar(1), SkIntToScalar(-1));
dst2.setEmpty();
success = orig.transform(matrix, &dst2);
REPORTER_ASSERT(reporter, success);
REPORTER_ASSERT(reporter, dst == dst2);
// a-----b d-----a a-----d
// | | -> | | -> | |
// | | Rotate 90 | | Flip X | |
// d-----c c-----b b-----c
matrix.reset();
matrix.setScale(SkIntToScalar(-1), SkIntToScalar(1));
matrix.postRotate(SkIntToScalar(90));
dst.setEmpty();
success = orig.transform(matrix, &dst);
REPORTER_ASSERT(reporter, success);
{
GET_RADII;
REPORTER_ASSERT(reporter, dstUL.x() == origUL.y());
REPORTER_ASSERT(reporter, dstUL.y() == origUL.x());
REPORTER_ASSERT(reporter, dstUR.x() == origLL.y());
REPORTER_ASSERT(reporter, dstUR.y() == origLL.x());
REPORTER_ASSERT(reporter, dstLR.x() == origLR.y());
REPORTER_ASSERT(reporter, dstLR.y() == origLR.x());
REPORTER_ASSERT(reporter, dstLL.x() == origUR.y());
REPORTER_ASSERT(reporter, dstLL.y() == origUR.x());
}
// Width and height would get swapped.
REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().height());
REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().width());
// a-----b d-----c a-----d
// | | -> | | -> | |
// | | Flip Y | | Rotate 90 | |
// d-----c a-----b b-----c
// This is the same as rotate 90 and flip x.
matrix.reset();
matrix.setRotate(SkIntToScalar(90));
matrix.postScale(SkIntToScalar(1), SkIntToScalar(-1));
dst2.setEmpty();
success = orig.transform(matrix, &dst2);
REPORTER_ASSERT(reporter, success);
REPORTER_ASSERT(reporter, dst == dst2);
// a-----b b-----a a-----d
// | | -> | | -> | |
// | | Flip X | | Rotate 270 | |
// d-----c c-----d b-----c
matrix.reset();
matrix.setRotate(SkIntToScalar(270));
matrix.postScale(SkIntToScalar(-1), SkIntToScalar(1));
dst2.setEmpty();
success = orig.transform(matrix, &dst2);
REPORTER_ASSERT(reporter, success);
REPORTER_ASSERT(reporter, dst == dst2);
// a-----b b-----c a-----d
// | | -> | | -> | |
// | | Rotate 270 | | Flip Y | |
// d-----c a-----d b-----c
matrix.reset();
matrix.setScale(SkIntToScalar(1), SkIntToScalar(-1));
matrix.postRotate(SkIntToScalar(270));
dst2.setEmpty();
success = orig.transform(matrix, &dst2);
REPORTER_ASSERT(reporter, success);
REPORTER_ASSERT(reporter, dst == dst2);
// a-----b b-----a c-----d b-----c
// | | -> | | -> | | -> | |
// | | Flip X | | Flip Y | | Rotate 90 | |
// d-----c c-----d b-----a a-----d
//
// This is the same as rotation by 270.
matrix.reset();
matrix.setRotate(SkIntToScalar(90));
matrix.postScale(SkIntToScalar(-1), SkIntToScalar(-1));
dst.setEmpty();
success = orig.transform(matrix, &dst);
REPORTER_ASSERT(reporter, success);
{
GET_RADII;
// Radii have cycled clockwise and swapped their x and y axis.
REPORTER_ASSERT(reporter, dstUL.x() == origUR.y());
REPORTER_ASSERT(reporter, dstUL.y() == origUR.x());
REPORTER_ASSERT(reporter, dstUR.x() == origLR.y());
REPORTER_ASSERT(reporter, dstUR.y() == origLR.x());
REPORTER_ASSERT(reporter, dstLR.x() == origLL.y());
REPORTER_ASSERT(reporter, dstLR.y() == origLL.x());
REPORTER_ASSERT(reporter, dstLL.x() == origUL.y());
REPORTER_ASSERT(reporter, dstLL.y() == origUL.x());
}
// Width and height would get swapped.
REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().height());
REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().width());
// a-----b b-----c
// | | -> | |
// | | Rotate 270 | |
// d-----c a-----d
//
dst2.setEmpty();
matrix.reset();
matrix.setRotate(SkIntToScalar(270));
success = orig.transform(matrix, &dst2);
REPORTER_ASSERT(reporter, success);
REPORTER_ASSERT(reporter, dst == dst2);
// a-----b b-----a c-----d d-----a
// | | -> | | -> | | -> | |
// | | Flip X | | Flip Y | | Rotate 270 | |
// d-----c c-----d b-----a c-----b
//
// This is the same as rotation by 90 degrees.
matrix.reset();
matrix.setRotate(SkIntToScalar(270));
matrix.postScale(SkIntToScalar(-1), SkIntToScalar(-1));
dst.setEmpty();
success = orig.transform(matrix, &dst);
REPORTER_ASSERT(reporter, success);
matrix.reset();
matrix.setRotate(SkIntToScalar(90));
dst2.setEmpty();
success = orig.transform(matrix, &dst2);
REPORTER_ASSERT(reporter, dst == dst2);
}
static void test_round_rect_transform(skiatest::Reporter* reporter) {