| /* |
| * 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 "SkPointPriv.h" |
| #include "SkRRect.h" |
| #include "Test.h" |
| |
| static void test_tricky_radii(skiatest::Reporter* reporter) { |
| { |
| // crbug.com/458522 |
| SkRRect rr; |
| const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 }; |
| const SkScalar rad = 12814; |
| const SkVector vec[] = { { rad, rad }, { 0, rad }, { rad, rad }, { 0, rad } }; |
| rr.setRectRadii(bounds, vec); |
| } |
| |
| { |
| // crbug.com//463920 |
| SkRect r = SkRect::MakeLTRB(0, 0, 1009, 33554432.0); |
| SkVector radii[4] = { |
| { 13.0f, 8.0f }, { 170.0f, 2.0 }, { 256.0f, 33554432.0 }, { 110.0f, 5.0f } |
| }; |
| SkRRect rr; |
| rr.setRectRadii(r, radii); |
| |
| REPORTER_ASSERT(reporter, (double) rr.radii(SkRRect::kUpperRight_Corner).fY + |
| (double) rr.radii(SkRRect::kLowerRight_Corner).fY <= |
| rr.height()); |
| } |
| } |
| |
| static void test_empty_crbug_458524(skiatest::Reporter* reporter) { |
| SkRRect rr; |
| const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 }; |
| const SkScalar rad = 40; |
| rr.setRectXY(bounds, rad, rad); |
| |
| SkRRect other; |
| SkMatrix matrix; |
| matrix.setScale(0, 1); |
| rr.transform(matrix, &other); |
| REPORTER_ASSERT(reporter, SkRRect::kEmpty_Type == other.getType()); |
| } |
| |
| // Test that all the SkRRect entry points correctly handle un-sorted and |
| // zero-sized input rects |
| static void test_empty(skiatest::Reporter* reporter) { |
| static const SkRect oooRects[] = { // out of order |
| { 100, 0, 0, 100 }, // ooo horizontal |
| { 0, 100, 100, 0 }, // ooo vertical |
| { 100, 100, 0, 0 }, // ooo both |
| }; |
| |
| static const SkRect emptyRects[] = { |
| { 100, 100, 100, 200 }, // empty horizontal |
| { 100, 100, 200, 100 }, // empty vertical |
| { 100, 100, 100, 100 }, // empty both |
| { 0, 0, 0, 0 } // setEmpty-empty |
| }; |
| |
| static const SkVector radii[4] = { { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 } }; |
| |
| SkRRect r; |
| |
| for (size_t i = 0; i < SK_ARRAY_COUNT(oooRects); ++i) { |
| r.setRect(oooRects[i]); |
| REPORTER_ASSERT(reporter, !r.isEmpty()); |
| |
| r.setOval(oooRects[i]); |
| REPORTER_ASSERT(reporter, !r.isEmpty()); |
| |
| r.setRectXY(oooRects[i], 1, 2); |
| REPORTER_ASSERT(reporter, !r.isEmpty()); |
| |
| r.setNinePatch(oooRects[i], 0, 1, 2, 3); |
| REPORTER_ASSERT(reporter, !r.isEmpty()); |
| |
| r.setRectRadii(oooRects[i], radii); |
| REPORTER_ASSERT(reporter, !r.isEmpty()); |
| } |
| |
| for (size_t i = 0; i < SK_ARRAY_COUNT(emptyRects); ++i) { |
| r.setRect(emptyRects[i]); |
| REPORTER_ASSERT(reporter, r.isEmpty()); |
| |
| r.setOval(emptyRects[i]); |
| REPORTER_ASSERT(reporter, r.isEmpty()); |
| |
| r.setRectXY(emptyRects[i], 1, 2); |
| REPORTER_ASSERT(reporter, r.isEmpty()); |
| |
| r.setNinePatch(emptyRects[i], 0, 1, 2, 3); |
| REPORTER_ASSERT(reporter, r.isEmpty()); |
| |
| r.setRectRadii(emptyRects[i], radii); |
| REPORTER_ASSERT(reporter, r.isEmpty()); |
| } |
| } |
| |
| 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()); |
| } |
| |
| |
| static void test_9patch_rrect(skiatest::Reporter* reporter, |
| const SkRect& rect, |
| SkScalar l, SkScalar t, SkScalar r, SkScalar b, |
| bool checkRadii) { |
| SkRRect rr; |
| rr.setNinePatch(rect, l, t, r, b); |
| |
| REPORTER_ASSERT(reporter, SkRRect::kNinePatch_Type == rr.type()); |
| REPORTER_ASSERT(reporter, rr.rect() == rect); |
| |
| if (checkRadii) { |
| // This test doesn't hold if the radii will be rescaled by SkRRect |
| SkRect ninePatchRadii = { l, t, r, b }; |
| SkPoint rquad[4]; |
| ninePatchRadii.toQuad(rquad); |
| for (int i = 0; i < 4; ++i) { |
| REPORTER_ASSERT(reporter, rquad[i] == rr.radii((SkRRect::Corner) i)); |
| } |
| } |
| SkRRect rr2; // construct the same RR using the most general set function |
| SkVector radii[4] = { { l, t }, { r, t }, { r, b }, { l, b } }; |
| rr2.setRectRadii(rect, radii); |
| REPORTER_ASSERT(reporter, rr2 == rr && rr2.getType() == rr.getType()); |
| } |
| |
| // 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, |
| SkPointPriv::EqualsWithinTolerance(rr2.radii((SkRRect::Corner) i), |
| 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()); |
| |
| //---- |
| test_9patch_rrect(reporter, rect, 10, 9, 8, 7, true); |
| |
| { |
| // Test out the rrect from skia:3466 |
| SkRect rect2 = SkRect::MakeLTRB(0.358211994f, 0.755430222f, 0.872866154f, 0.806214333f); |
| |
| test_9patch_rrect(reporter, |
| rect2, |
| 0.926942348f, 0.642850280f, 0.529063463f, 0.587844372f, |
| false); |
| } |
| |
| //---- |
| 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 != 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, |
| orig.radii((SkRRect::Corner) i).fX * xScale)); |
| REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.radii((SkRRect::Corner) i).fY, |
| orig.radii((SkRRect::Corner) i).fY * yScale)); |
| } |
| REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().width(), |
| orig.rect().width() * xScale)); |
| REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().height(), |
| orig.rect().height() * yScale)); |
| REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().left(), |
| orig.rect().left() * xScale)); |
| REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().top(), |
| 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)); |
| } |
| } |
| |
| void test_read_rrect(skiatest::Reporter* reporter, const SkRRect& rrect, bool shouldSucceed) { |
| // It would be cleaner to call rrect.writeToMemory into a buffer. However, writeToMemory asserts |
| // that the rrect is valid and our caller may have fiddled with the internals of rrect to make |
| // it invalid. |
| const void* buffer = reinterpret_cast<const void*>(&rrect); |
| SkRRect deserialized; |
| size_t size = deserialized.readFromMemory(buffer, sizeof(SkRRect)); |
| if (shouldSucceed) { |
| REPORTER_ASSERT(reporter, size == SkRRect::kSizeInMemory); |
| if (size) { |
| REPORTER_ASSERT(reporter, rrect == deserialized); |
| REPORTER_ASSERT(reporter, rrect.getType() == deserialized.getType()); |
| } |
| } else { |
| REPORTER_ASSERT(reporter, !size); |
| } |
| } |
| |
| static void test_read(skiatest::Reporter* reporter) { |
| static const SkRect kRect = {10.f, 10.f, 20.f, 20.f}; |
| static const SkRect kNaNRect = {10.f, 10.f, 20.f, SK_ScalarNaN}; |
| static const SkRect kInfRect = {10.f, 10.f, SK_ScalarInfinity, 20.f}; |
| SkRRect rrect; |
| |
| test_read_rrect(reporter, SkRRect::MakeEmpty(), true); |
| test_read_rrect(reporter, SkRRect::MakeRect(kRect), true); |
| // These get coerced to empty. |
| test_read_rrect(reporter, SkRRect::MakeRect(kInfRect), true); |
| test_read_rrect(reporter, SkRRect::MakeRect(kNaNRect), true); |
| |
| rrect.setRect(kRect); |
| SkRect* innerRect = reinterpret_cast<SkRect*>(&rrect); |
| SkASSERT(*innerRect == kRect); |
| *innerRect = kInfRect; |
| test_read_rrect(reporter, rrect, false); |
| *innerRect = kNaNRect; |
| test_read_rrect(reporter, rrect, false); |
| |
| test_read_rrect(reporter, SkRRect::MakeOval(kRect), true); |
| test_read_rrect(reporter, SkRRect::MakeOval(kInfRect), true); |
| test_read_rrect(reporter, SkRRect::MakeOval(kNaNRect), true); |
| rrect.setOval(kRect); |
| *innerRect = kInfRect; |
| test_read_rrect(reporter, rrect, false); |
| *innerRect = kNaNRect; |
| test_read_rrect(reporter, rrect, false); |
| |
| test_read_rrect(reporter, SkRRect::MakeRectXY(kRect, 5.f, 5.f), true); |
| // rrect should scale down the radii to make this legal |
| test_read_rrect(reporter, SkRRect::MakeRectXY(kRect, 5.f, 400.f), true); |
| |
| static const SkVector kRadii[4] = {{0.5f, 1.f}, {1.5f, 2.f}, {2.5f, 3.f}, {3.5f, 4.f}}; |
| rrect.setRectRadii(kRect, kRadii); |
| test_read_rrect(reporter, rrect, true); |
| SkScalar* innerRadius = reinterpret_cast<SkScalar*>(&rrect) + 6; |
| SkASSERT(*innerRadius == 1.5f); |
| *innerRadius = 400.f; |
| test_read_rrect(reporter, rrect, false); |
| *innerRadius = SK_ScalarInfinity; |
| test_read_rrect(reporter, rrect, false); |
| *innerRadius = SK_ScalarNaN; |
| test_read_rrect(reporter, rrect, false); |
| } |
| |
| 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); |
| test_tricky_radii(reporter); |
| test_empty_crbug_458524(reporter); |
| test_empty(reporter); |
| test_read(reporter); |
| } |