aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar scroggo@google.com <scroggo@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2013-11-05 15:54:42 +0000
committerGravatar scroggo@google.com <scroggo@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2013-11-05 15:54:42 +0000
commit20e3cd2c9fbc049eae8bcedc591c2cc8d4bed656 (patch)
tree12b4d1d2461bdca4dc3284a7c27e8d7a02bad022
parenta93f4e770f22f913197bef82dc19078e12bee76b (diff)
Add SkRRect::transform.
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
-rw-r--r--include/core/SkRRect.h12
-rw-r--r--src/core/SkRRect.cpp80
-rw-r--r--tests/RoundRectTest.cpp206
3 files changed, 296 insertions, 2 deletions
diff --git a/include/core/SkRRect.h b/include/core/SkRRect.h
index 3c6386f1c6..66c433f43b 100644
--- a/include/core/SkRRect.h
+++ b/include/core/SkRRect.h
@@ -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[]
diff --git a/src/core/SkRRect.cpp b/src/core/SkRRect.cpp
index bcbf37ec59..e5296d4e3a 100644
--- a/src/core/SkRRect.cpp
+++ b/src/core/SkRRect.cpp
@@ -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 {
diff --git a/tests/RoundRectTest.cpp b/tests/RoundRectTest.cpp
index ec94c33968..95e455d29b 100644
--- a/tests/RoundRectTest.cpp
+++ b/tests/RoundRectTest.cpp
@@ -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"