/* * 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 "SkMatrix.h" #include "SkRRect.h" #include "Test.h" static const SkScalar kWidth = 100.0f; static const SkScalar kHeight = 100.0f; static void test_inset(skiatest::Reporter* reporter) { SkRRect rr, rr2; SkRect r = { 0, 0, 100, 100 }; rr.setRect(r); rr.inset(-20, -20, &rr2); REPORTER_ASSERT(reporter, rr2.isRect()); rr.inset(20, 20, &rr2); REPORTER_ASSERT(reporter, rr2.isRect()); rr.inset(r.width()/2, r.height()/2, &rr2); REPORTER_ASSERT(reporter, rr2.isEmpty()); rr.setRectXY(r, 20, 20); rr.inset(19, 19, &rr2); REPORTER_ASSERT(reporter, rr2.isSimple()); rr.inset(20, 20, &rr2); REPORTER_ASSERT(reporter, rr2.isRect()); } // Test out the basic API entry points static void test_round_rect_basic(skiatest::Reporter* reporter) { // Test out initialization methods SkPoint zeroPt = { 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)); } SkRRect rr1_2; // construct the same RR using the most general set function SkVector rr1_2_radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }; rr1_2.setRectRadii(rect, rr1_2_radii); REPORTER_ASSERT(reporter, rr1_2 == rr1 && rr1_2.getType() == rr1.getType()); SkRRect rr1_3; // construct the same RR using the nine patch set function rr1_3.setNinePatch(rect, 0, 0, 0, 0); REPORTER_ASSERT(reporter, rr1_3 == rr1 && rr1_3.getType() == rr1.getType()); //---- 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)); } SkRRect rr2_2; // construct the same RR using the most general set function SkVector rr2_2_radii[4] = { { halfPoint.fX, halfPoint.fY }, { halfPoint.fX, halfPoint.fY }, { halfPoint.fX, halfPoint.fY }, { halfPoint.fX, halfPoint.fY } }; rr2_2.setRectRadii(rect, rr2_2_radii); REPORTER_ASSERT(reporter, rr2_2 == rr2 && rr2_2.getType() == rr2.getType()); SkRRect rr2_3; // construct the same RR using the nine patch set function rr2_3.setNinePatch(rect, halfPoint.fX, halfPoint.fY, halfPoint.fX, halfPoint.fY); REPORTER_ASSERT(reporter, rr2_3 == rr2 && rr2_3.getType() == rr2.getType()); //---- 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)); } SkRRect rr3_2; // construct the same RR using the most general set function SkVector rr3_2_radii[4] = { { 5, 5 }, { 5, 5 }, { 5, 5 }, { 5, 5 } }; rr3_2.setRectRadii(rect, rr3_2_radii); REPORTER_ASSERT(reporter, rr3_2 == rr3 && rr3_2.getType() == rr3.getType()); SkRRect rr3_3; // construct the same RR using the nine patch set function rr3_3.setNinePatch(rect, 5, 5, 5, 5); REPORTER_ASSERT(reporter, rr3_3 == rr3 && rr3_3.getType() == rr3.getType()); //---- SkRect ninePatchRadii = { 10, 9, 8, 7 }; SkRRect rr4; rr4.setNinePatch(rect, ninePatchRadii.fLeft, ninePatchRadii.fTop, ninePatchRadii.fRight, ninePatchRadii.fBottom); REPORTER_ASSERT(reporter, SkRRect::kNinePatch_Type == rr4.type()); REPORTER_ASSERT(reporter, rr4.rect() == rect); SkPoint rquad[4]; ninePatchRadii.toQuad(rquad); for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, rquad[i] == rr4.radii((SkRRect::Corner) i)); } SkRRect rr4_2; // construct the same RR using the most general set function SkVector rr4_2_radii[4] = { { 10, 9 }, { 8, 9 }, {8, 7 }, { 10, 7 } }; rr4_2.setRectRadii(rect, rr4_2_radii); REPORTER_ASSERT(reporter, rr4_2 == rr4 && rr4_2.getType() == rr4.getType()); //---- 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; //---- 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); //---- 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); //---- 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) { //---- 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); } // Test out the non-degenerate RR cases static void test_round_rect_general(skiatest::Reporter* reporter) { //---- SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); SkRRect rr1; rr1.setRectXY(rect, 20, 20); REPORTER_ASSERT(reporter, SkRRect::kSimple_Type == rr1.type()); //---- 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()); } // 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); } // Move a small box from the start position by (stepX, stepY) 'numSteps' times // testing for containment in 'rr' at each step. static void test_direction(skiatest::Reporter* reporter, const SkRRect &rr, SkScalar initX, int stepX, SkScalar initY, int stepY, int numSteps, const bool* contains) { SkScalar x = initX, y = initY; for (int i = 0; i < numSteps; ++i) { SkRect test = SkRect::MakeXYWH(x, y, stepX ? SkIntToScalar(stepX) : SK_Scalar1, stepY ? SkIntToScalar(stepY) : SK_Scalar1); test.sort(); REPORTER_ASSERT(reporter, contains[i] == rr.contains(test)); x += stepX; y += stepY; } } // Exercise the RR's contains rect method static void test_round_rect_contains_rect(skiatest::Reporter* reporter) { static const int kNumRRects = 4; static const SkVector gRadii[kNumRRects][4] = { { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }, // rect { { 20, 20 }, { 20, 20 }, { 20, 20 }, { 20, 20 } }, // circle { { 10, 10 }, { 10, 10 }, { 10, 10 }, { 10, 10 } }, // simple { { 0, 0 }, { 20, 20 }, { 10, 10 }, { 30, 30 } } // complex }; SkRRect rrects[kNumRRects]; for (int i = 0; i < kNumRRects; ++i) { rrects[i].setRectRadii(SkRect::MakeWH(40, 40), gRadii[i]); } // First test easy outs - boxes that are obviously out on // each corner and edge static const SkRect easyOuts[] = { { -5, -5, 5, 5 }, // NW { 15, -5, 20, 5 }, // N { 35, -5, 45, 5 }, // NE { 35, 15, 45, 20 }, // E { 35, 45, 35, 45 }, // SE { 15, 35, 20, 45 }, // S { -5, 35, 5, 45 }, // SW { -5, 15, 5, 20 } // W }; for (int i = 0; i < kNumRRects; ++i) { for (size_t j = 0; j < SK_ARRAY_COUNT(easyOuts); ++j) { REPORTER_ASSERT(reporter, !rrects[i].contains(easyOuts[j])); } } // Now test non-trivial containment. For each compass // point walk a 1x1 rect in from the edge of the bounding // rect static const int kNumSteps = 15; bool answers[kNumRRects][8][kNumSteps] = { // all the test rects are inside the degenerate rrect { // rect { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, // for the circle we expect 6 blocks to be out on the // corners (then the rest in) and only the first block // out on the vertical and horizontal axes (then // the rest in) { // circle { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, // for the simple round rect we expect 3 out on // the corners (then the rest in) and no blocks out // on the vertical and horizontal axes { // simple RR { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, }, // for the complex case the answer is different for each direction { // complex RR // all in for NW (rect) corner (same as rect case) { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // only first block out for N (same as circle case) { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // first 6 blocks out for NE (same as circle case) { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // only first block out for E (same as circle case) { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // first 3 blocks out for SE (same as simple case) { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // first two blocks out for S { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // first 9 blocks out for SW { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1 }, // first two blocks out for W (same as S) { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, } }; for (int i = 0; i < kNumRRects; ++i) { test_direction(reporter, rrects[i], 0, 1, 0, 1, kNumSteps, answers[i][0]); // NW test_direction(reporter, rrects[i], 19.5f, 0, 0, 1, kNumSteps, answers[i][1]); // N test_direction(reporter, rrects[i], 40, -1, 0, 1, kNumSteps, answers[i][2]); // NE test_direction(reporter, rrects[i], 40, -1, 19.5f, 0, kNumSteps, answers[i][3]); // E test_direction(reporter, rrects[i], 40, -1, 40, -1, kNumSteps, answers[i][4]); // SE test_direction(reporter, rrects[i], 19.5f, 0, 40, -1, kNumSteps, answers[i][5]); // S test_direction(reporter, rrects[i], 0, 1, 40, -1, kNumSteps, answers[i][6]); // SW test_direction(reporter, rrects[i], 0, 1, 19.5f, 0, kNumSteps, answers[i][7]); // W } } // 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(4); assert_transform_failure(reporter, orig, matrix); matrix.reset(); matrix.setPerspY(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(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 = 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); } } // Test out the case where an oval already off in space is translated/scaled // further off into space - yielding numerical issues when the rect & radii // are transformed separatly // BUG=skia:2696 static void test_issue_2696(skiatest::Reporter* reporter) { SkRRect rrect; SkRect r = { 28443.8594f, 53.1428604f, 28446.7148f, 56.0000038f }; rrect.setOval(r); SkMatrix xform; xform.setAll(2.44f, 0.0f, 485411.7f, 0.0f, 2.44f, -438.7f, 0.0f, 0.0f, 1.0f); SkRRect dst; bool success = rrect.transform(xform, &dst); REPORTER_ASSERT(reporter, success); SkScalar halfWidth = SkScalarHalf(dst.width()); SkScalar halfHeight = SkScalarHalf(dst.height()); for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.radii((SkRRect::Corner)i).fX, halfWidth)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.radii((SkRRect::Corner)i).fY, halfHeight)); } } DEF_TEST(RoundRect, 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); test_inset(reporter); test_round_rect_contains_rect(reporter); test_round_rect_transform(reporter); test_issue_2696(reporter); }