|  | /* | 
|  | * 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(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 = 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); | 
|  | } |