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
diff --git a/tests/RoundRectTest.cpp b/tests/RoundRectTest.cpp
index ec94c33..95e455d 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 @@
     }
 }
 
+// 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 @@
     test_round_rect_iffy_parameters(reporter);
     test_inset(reporter);
     test_round_rect_contains_rect(reporter);
+    test_round_rect_transform(reporter);
 }
 
 #include "TestClassDef.h"