| /* | |
| * 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 "include/core/SkMatrix.h" | |
| #include "include/core/SkPath.h" | |
| #include "include/core/SkPoint.h" | |
| #include "include/core/SkRRect.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkScalar.h" | |
| #include "include/core/SkTypes.h" | |
| #include "include/pathops/SkPathOps.h" | |
| #include "src/base/SkRandom.h" | |
| #include "src/core/SkPointPriv.h" | |
| #include "src/core/SkRRectPriv.h" | |
| #include "tests/Test.h" | |
| #include <algorithm> | |
| #include <array> | |
| #include <cstddef> | |
| #include <cstdint> | |
| 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 < std::size(oooRects); ++i) { | |
| r.setRect(oooRects[i]); | |
| REPORTER_ASSERT(reporter, !r.isEmpty()); | |
| REPORTER_ASSERT(reporter, r.rect() == oooRects[i].makeSorted()); | |
| r.setOval(oooRects[i]); | |
| REPORTER_ASSERT(reporter, !r.isEmpty()); | |
| REPORTER_ASSERT(reporter, r.rect() == oooRects[i].makeSorted()); | |
| r.setRectXY(oooRects[i], 1, 2); | |
| REPORTER_ASSERT(reporter, !r.isEmpty()); | |
| REPORTER_ASSERT(reporter, r.rect() == oooRects[i].makeSorted()); | |
| r.setNinePatch(oooRects[i], 0, 1, 2, 3); | |
| REPORTER_ASSERT(reporter, !r.isEmpty()); | |
| REPORTER_ASSERT(reporter, r.rect() == oooRects[i].makeSorted()); | |
| r.setRectRadii(oooRects[i], radii); | |
| REPORTER_ASSERT(reporter, !r.isEmpty()); | |
| REPORTER_ASSERT(reporter, r.rect() == oooRects[i].makeSorted()); | |
| } | |
| for (size_t i = 0; i < std::size(emptyRects); ++i) { | |
| r.setRect(emptyRects[i]); | |
| REPORTER_ASSERT(reporter, r.isEmpty()); | |
| REPORTER_ASSERT(reporter, r.rect() == emptyRects[i]); | |
| r.setOval(emptyRects[i]); | |
| REPORTER_ASSERT(reporter, r.isEmpty()); | |
| REPORTER_ASSERT(reporter, r.rect() == emptyRects[i]); | |
| r.setRectXY(emptyRects[i], 1, 2); | |
| REPORTER_ASSERT(reporter, r.isEmpty()); | |
| REPORTER_ASSERT(reporter, r.rect() == emptyRects[i]); | |
| r.setNinePatch(emptyRects[i], 0, 1, 2, 3); | |
| REPORTER_ASSERT(reporter, r.isEmpty()); | |
| REPORTER_ASSERT(reporter, r.rect() == emptyRects[i]); | |
| r.setRectRadii(emptyRects[i], radii); | |
| REPORTER_ASSERT(reporter, r.isEmpty()); | |
| REPORTER_ASSERT(reporter, r.rect() == emptyRects[i]); | |
| } | |
| r.setRect({SK_ScalarNaN, 10, 10, 20}); | |
| REPORTER_ASSERT(reporter, r == SkRRect::MakeEmpty()); | |
| r.setRect({0, 10, 10, SK_ScalarInfinity}); | |
| REPORTER_ASSERT(reporter, r == SkRRect::MakeEmpty()); | |
| } | |
| 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 < std::size(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(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)); | |
| // a-----b d-----a | |
| // | | -> | | | |
| // | | Rotate 90 | | | |
| // d-----c c-----b | |
| matrix.reset(); | |
| matrix.setRotate(SkIntToScalar(90)); | |
| dst.setEmpty(); | |
| success = orig.transform(matrix, &dst); | |
| REPORTER_ASSERT(reporter, success); | |
| { | |
| GET_RADII; | |
| // Radii have cycled clockwise and swapped their x and y axis. | |
| REPORTER_ASSERT(reporter, dstUL.x() == origLL.y()); | |
| REPORTER_ASSERT(reporter, dstUL.y() == origLL.x()); | |
| REPORTER_ASSERT(reporter, dstUR.x() == origUL.y()); | |
| REPORTER_ASSERT(reporter, dstUR.y() == origUL.x()); | |
| REPORTER_ASSERT(reporter, dstLR.x() == origUR.y()); | |
| REPORTER_ASSERT(reporter, dstLR.y() == origUR.x()); | |
| REPORTER_ASSERT(reporter, dstLL.x() == origLR.y()); | |
| REPORTER_ASSERT(reporter, dstLL.y() == origLR.x()); | |
| } | |
| // Width and height would get swapped. | |
| REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().height()); | |
| REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().width()); | |
| // a-----b b-----a c-----b | |
| // | | -> | | -> | | | |
| // | | Flip X | | Rotate 90 | | | |
| // d-----c c-----d d-----a | |
| matrix.reset(); | |
| matrix.setRotate(SkIntToScalar(90)); | |
| matrix.postScale(SkIntToScalar(-1), SkIntToScalar(1)); | |
| dst.setEmpty(); | |
| success = orig.transform(matrix, &dst); | |
| REPORTER_ASSERT(reporter, success); | |
| { | |
| GET_RADII; | |
| REPORTER_ASSERT(reporter, dstUL.x() == origLR.y()); | |
| REPORTER_ASSERT(reporter, dstUL.y() == origLR.x()); | |
| REPORTER_ASSERT(reporter, dstUR.x() == origUR.y()); | |
| REPORTER_ASSERT(reporter, dstUR.y() == origUR.x()); | |
| REPORTER_ASSERT(reporter, dstLR.x() == origUL.y()); | |
| REPORTER_ASSERT(reporter, dstLR.y() == origUL.x()); | |
| REPORTER_ASSERT(reporter, dstLL.x() == origLL.y()); | |
| REPORTER_ASSERT(reporter, dstLL.y() == origLL.x()); | |
| } | |
| // Width and height would get swapped. | |
| REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().height()); | |
| REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().width()); | |
| // a-----b d-----a c-----b | |
| // | | -> | | -> | | | |
| // | | Rotate 90 | | Flip Y | | | |
| // d-----c c-----b d-----a | |
| // | |
| // This is the same as Flip X and Rotate 90. | |
| matrix.reset(); | |
| matrix.setScale(SkIntToScalar(1), SkIntToScalar(-1)); | |
| matrix.postRotate(SkIntToScalar(90)); | |
| SkRRect dst2; | |
| dst2.setEmpty(); | |
| success = orig.transform(matrix, &dst2); | |
| REPORTER_ASSERT(reporter, success); | |
| REPORTER_ASSERT(reporter, dst == dst2); | |
| // a-----b b-----c c-----b | |
| // | | -> | | -> | | | |
| // | | Rotate 270 | | Flip X | | | |
| // d-----c a-----d d-----a | |
| matrix.reset(); | |
| matrix.setScale(SkIntToScalar(-1), SkIntToScalar(1)); | |
| matrix.postRotate(SkIntToScalar(270)); | |
| dst2.setEmpty(); | |
| success = orig.transform(matrix, &dst2); | |
| REPORTER_ASSERT(reporter, success); | |
| REPORTER_ASSERT(reporter, dst == dst2); | |
| // a-----b d-----c c-----b | |
| // | | -> | | -> | | | |
| // | | Flip Y | | Rotate 270 | | | |
| // d-----c a-----b d-----a | |
| matrix.reset(); | |
| matrix.setRotate(SkIntToScalar(270)); | |
| matrix.postScale(SkIntToScalar(1), SkIntToScalar(-1)); | |
| dst2.setEmpty(); | |
| success = orig.transform(matrix, &dst2); | |
| REPORTER_ASSERT(reporter, success); | |
| REPORTER_ASSERT(reporter, dst == dst2); | |
| // a-----b d-----a a-----d | |
| // | | -> | | -> | | | |
| // | | Rotate 90 | | Flip X | | | |
| // d-----c c-----b b-----c | |
| matrix.reset(); | |
| matrix.setScale(SkIntToScalar(-1), SkIntToScalar(1)); | |
| matrix.postRotate(SkIntToScalar(90)); | |
| dst.setEmpty(); | |
| success = orig.transform(matrix, &dst); | |
| REPORTER_ASSERT(reporter, success); | |
| { | |
| GET_RADII; | |
| REPORTER_ASSERT(reporter, dstUL.x() == origUL.y()); | |
| REPORTER_ASSERT(reporter, dstUL.y() == origUL.x()); | |
| REPORTER_ASSERT(reporter, dstUR.x() == origLL.y()); | |
| REPORTER_ASSERT(reporter, dstUR.y() == origLL.x()); | |
| REPORTER_ASSERT(reporter, dstLR.x() == origLR.y()); | |
| REPORTER_ASSERT(reporter, dstLR.y() == origLR.x()); | |
| REPORTER_ASSERT(reporter, dstLL.x() == origUR.y()); | |
| REPORTER_ASSERT(reporter, dstLL.y() == origUR.x()); | |
| } | |
| // Width and height would get swapped. | |
| REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().height()); | |
| REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().width()); | |
| // a-----b d-----c a-----d | |
| // | | -> | | -> | | | |
| // | | Flip Y | | Rotate 90 | | | |
| // d-----c a-----b b-----c | |
| // This is the same as rotate 90 and flip x. | |
| matrix.reset(); | |
| matrix.setRotate(SkIntToScalar(90)); | |
| matrix.postScale(SkIntToScalar(1), SkIntToScalar(-1)); | |
| dst2.setEmpty(); | |
| success = orig.transform(matrix, &dst2); | |
| REPORTER_ASSERT(reporter, success); | |
| REPORTER_ASSERT(reporter, dst == dst2); | |
| // a-----b b-----a a-----d | |
| // | | -> | | -> | | | |
| // | | Flip X | | Rotate 270 | | | |
| // d-----c c-----d b-----c | |
| matrix.reset(); | |
| matrix.setRotate(SkIntToScalar(270)); | |
| matrix.postScale(SkIntToScalar(-1), SkIntToScalar(1)); | |
| dst2.setEmpty(); | |
| success = orig.transform(matrix, &dst2); | |
| REPORTER_ASSERT(reporter, success); | |
| REPORTER_ASSERT(reporter, dst == dst2); | |
| // a-----b b-----c a-----d | |
| // | | -> | | -> | | | |
| // | | Rotate 270 | | Flip Y | | | |
| // d-----c a-----d b-----c | |
| matrix.reset(); | |
| matrix.setScale(SkIntToScalar(1), SkIntToScalar(-1)); | |
| matrix.postRotate(SkIntToScalar(270)); | |
| dst2.setEmpty(); | |
| success = orig.transform(matrix, &dst2); | |
| REPORTER_ASSERT(reporter, success); | |
| REPORTER_ASSERT(reporter, dst == dst2); | |
| // a-----b b-----a c-----d b-----c | |
| // | | -> | | -> | | -> | | | |
| // | | Flip X | | Flip Y | | Rotate 90 | | | |
| // d-----c c-----d b-----a a-----d | |
| // | |
| // This is the same as rotation by 270. | |
| matrix.reset(); | |
| matrix.setRotate(SkIntToScalar(90)); | |
| matrix.postScale(SkIntToScalar(-1), SkIntToScalar(-1)); | |
| dst.setEmpty(); | |
| success = orig.transform(matrix, &dst); | |
| REPORTER_ASSERT(reporter, success); | |
| { | |
| GET_RADII; | |
| // Radii have cycled clockwise and swapped their x and y axis. | |
| REPORTER_ASSERT(reporter, dstUL.x() == origUR.y()); | |
| REPORTER_ASSERT(reporter, dstUL.y() == origUR.x()); | |
| REPORTER_ASSERT(reporter, dstUR.x() == origLR.y()); | |
| REPORTER_ASSERT(reporter, dstUR.y() == origLR.x()); | |
| REPORTER_ASSERT(reporter, dstLR.x() == origLL.y()); | |
| REPORTER_ASSERT(reporter, dstLR.y() == origLL.x()); | |
| REPORTER_ASSERT(reporter, dstLL.x() == origUL.y()); | |
| REPORTER_ASSERT(reporter, dstLL.y() == origUL.x()); | |
| } | |
| // Width and height would get swapped. | |
| REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().height()); | |
| REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().width()); | |
| // a-----b b-----c | |
| // | | -> | | | |
| // | | Rotate 270 | | | |
| // d-----c a-----d | |
| // | |
| dst2.setEmpty(); | |
| matrix.reset(); | |
| matrix.setRotate(SkIntToScalar(270)); | |
| success = orig.transform(matrix, &dst2); | |
| REPORTER_ASSERT(reporter, success); | |
| REPORTER_ASSERT(reporter, dst == dst2); | |
| // a-----b b-----a c-----d d-----a | |
| // | | -> | | -> | | -> | | | |
| // | | Flip X | | Flip Y | | Rotate 270 | | | |
| // d-----c c-----d b-----a c-----b | |
| // | |
| // This is the same as rotation by 90 degrees. | |
| matrix.reset(); | |
| matrix.setRotate(SkIntToScalar(270)); | |
| matrix.postScale(SkIntToScalar(-1), SkIntToScalar(-1)); | |
| dst.setEmpty(); | |
| success = orig.transform(matrix, &dst); | |
| REPORTER_ASSERT(reporter, success); | |
| matrix.reset(); | |
| matrix.setRotate(SkIntToScalar(90)); | |
| dst2.setEmpty(); | |
| success = orig.transform(matrix, &dst2); | |
| REPORTER_ASSERT(reporter, dst == dst2); | |
| } | |
| 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 shouldEqualSrc) { | |
| // 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)); | |
| REPORTER_ASSERT(reporter, size == SkRRect::kSizeInMemory); | |
| REPORTER_ASSERT(reporter, deserialized.isValid()); | |
| if (shouldEqualSrc) { | |
| REPORTER_ASSERT(reporter, rrect == deserialized); | |
| } | |
| } | |
| 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); | |
| *innerRadius = -10.f; | |
| test_read_rrect(reporter, rrect, false); | |
| } | |
| static void test_inner_bounds(skiatest::Reporter* reporter) { | |
| // Because InnerBounds() insets the computed bounds slightly to correct for numerical inaccuracy | |
| // when finding the maximum inscribed point on a curve, we use a larger epsilon for comparing | |
| // expected areas. | |
| static constexpr SkScalar kEpsilon = 0.005f; | |
| // Test that an empty rrect reports empty inner bounds | |
| REPORTER_ASSERT(reporter, SkRRectPriv::InnerBounds(SkRRect::MakeEmpty()).isEmpty()); | |
| // Test that a rect rrect reports itself as the inner bounds | |
| SkRect r = SkRect::MakeLTRB(0, 1, 2, 3); | |
| REPORTER_ASSERT(reporter, SkRRectPriv::InnerBounds(SkRRect::MakeRect(r)) == r); | |
| // Test that a circle rrect has an inner bounds area equal to 2*radius^2 | |
| float radius = 5.f; | |
| SkRect inner = SkRRectPriv::InnerBounds(SkRRect::MakeOval(SkRect::MakeWH(2.f * radius, | |
| 2.f * radius))); | |
| REPORTER_ASSERT(reporter, SkScalarNearlyEqual(inner.width() * inner.height(), | |
| 2.f * radius * radius, kEpsilon)); | |
| float width = 20.f; | |
| float height = 25.f; | |
| r = SkRect::MakeWH(width, height); | |
| // Test that a rrect with circular corners has an area equal to: | |
| float expectedArea = | |
| (2.f * radius * radius) + // area in the 4 circular corners | |
| (width-2.f*radius) * (height-2.f*radius) + // inner area excluding corners and edges | |
| SK_ScalarSqrt2 * radius * (width-2.f*radius) + // two horiz. rects between corners | |
| SK_ScalarSqrt2 * radius * (height-2.f*radius); // two vert. rects between corners | |
| inner = SkRRectPriv::InnerBounds(SkRRect::MakeRectXY(r, radius, radius)); | |
| REPORTER_ASSERT(reporter, SkScalarNearlyEqual(inner.width() * inner.height(), | |
| expectedArea, kEpsilon)); | |
| // Test that a rrect with a small y radius but large x radius selects the horizontal interior | |
| SkRRect rr = SkRRect::MakeRectXY(r, 2.f * radius, 0.1f * radius); | |
| REPORTER_ASSERT(reporter, SkRRectPriv::InnerBounds(rr) == | |
| SkRect::MakeLTRB(0.f, 0.1f * radius, width, height - 0.1f * radius)); | |
| // And vice versa with large y and small x radii | |
| rr = SkRRect::MakeRectXY(r, 0.1f * radius, 2.f * radius); | |
| REPORTER_ASSERT(reporter, SkRRectPriv::InnerBounds(rr) == | |
| SkRect::MakeLTRB(0.1f * radius, 0.f, width - 0.1f * radius, height)); | |
| // Test a variety of complex round rects produce a non-empty rect that is at least contained, | |
| // and larger than the inner area avoiding all corners. | |
| SkRandom rng; | |
| for (int i = 0; i < 1000; ++i) { | |
| float maxRadiusX = rng.nextRangeF(0.f, 40.f); | |
| float maxRadiusY = rng.nextRangeF(0.f, 40.f); | |
| float innerWidth = rng.nextRangeF(0.f, 40.f); | |
| float innerHeight = rng.nextRangeF(0.f, 40.f); | |
| SkVector radii[4] = {{rng.nextRangeF(0.f, maxRadiusX), rng.nextRangeF(0.f, maxRadiusY)}, | |
| {rng.nextRangeF(0.f, maxRadiusX), rng.nextRangeF(0.f, maxRadiusY)}, | |
| {rng.nextRangeF(0.f, maxRadiusX), rng.nextRangeF(0.f, maxRadiusY)}, | |
| {rng.nextRangeF(0.f, maxRadiusX), rng.nextRangeF(0.f, maxRadiusY)}}; | |
| float maxLeft = std::max(radii[0].fX, radii[3].fX); | |
| float maxTop = std::max(radii[0].fY, radii[1].fY); | |
| float maxRight = std::max(radii[1].fX, radii[2].fX); | |
| float maxBottom = std::max(radii[2].fY, radii[3].fY); | |
| SkRect outer = SkRect::MakeWH(maxLeft + maxRight + innerWidth, | |
| maxTop + maxBottom + innerHeight); | |
| rr.setRectRadii(outer, radii); | |
| SkRect maxInner = SkRRectPriv::InnerBounds(rr); | |
| // Test upper limit on the size of 'maxInner' | |
| REPORTER_ASSERT(reporter, outer.contains(maxInner)); | |
| REPORTER_ASSERT(reporter, rr.contains(maxInner)); | |
| // Test lower limit on the size of 'maxInner' | |
| inner = SkRect::MakeXYWH(maxLeft, maxTop, innerWidth, innerHeight); | |
| inner.inset(kEpsilon, kEpsilon); | |
| if (inner.isSorted()) { | |
| REPORTER_ASSERT(reporter, maxInner.contains(inner)); | |
| } else { | |
| // Flipped from the inset, just test two points of inner | |
| float midX = maxLeft + 0.5f * innerWidth; | |
| float midY = maxTop + 0.5f * innerHeight; | |
| REPORTER_ASSERT(reporter, maxInner.contains(midX, maxTop)); | |
| REPORTER_ASSERT(reporter, maxInner.contains(midX, maxTop + innerHeight)); | |
| REPORTER_ASSERT(reporter, maxInner.contains(maxLeft, midY)); | |
| REPORTER_ASSERT(reporter, maxInner.contains(maxLeft + innerWidth, midY)); | |
| } | |
| } | |
| } | |
| namespace { | |
| // Helper to test expected intersection, relying on the fact that all round rect intersections | |
| // will have their bounds equal to the intersection of the bounds of the input round rects, and | |
| // their corner radii will be a one of A's, B's, or rectangular. | |
| enum CornerChoice : uint8_t { | |
| kA, kB, kRect | |
| }; | |
| static void verify_success(skiatest::Reporter* reporter, const SkRRect& a, const SkRRect& b, | |
| CornerChoice tl, CornerChoice tr, CornerChoice br, CornerChoice bl) { | |
| static const SkRRect kRect = SkRRect::MakeEmpty(); // has (0,0) for all corners | |
| // Compute expected round rect intersection given bounds of A and B, and the specified | |
| // corner choices for the 4 corners. | |
| SkRect expectedBounds; | |
| SkAssertResult(expectedBounds.intersect(a.rect(), b.rect())); | |
| SkVector radii[4] = { | |
| (tl == kA ? a : (tl == kB ? b : kRect)).radii(SkRRect::kUpperLeft_Corner), | |
| (tr == kA ? a : (tr == kB ? b : kRect)).radii(SkRRect::kUpperRight_Corner), | |
| (br == kA ? a : (br == kB ? b : kRect)).radii(SkRRect::kLowerRight_Corner), | |
| (bl == kA ? a : (bl == kB ? b : kRect)).radii(SkRRect::kLowerLeft_Corner) | |
| }; | |
| SkRRect expected; | |
| expected.setRectRadii(expectedBounds, radii); | |
| SkRRect actual = SkRRectPriv::ConservativeIntersect(a, b); | |
| // Intersections are commutative so ba and ab should be the same | |
| REPORTER_ASSERT(reporter, actual == SkRRectPriv::ConservativeIntersect(b, a)); | |
| // Intersection of the result with either A or B should remain the intersection | |
| REPORTER_ASSERT(reporter, actual == SkRRectPriv::ConservativeIntersect(actual, a)); | |
| REPORTER_ASSERT(reporter, actual == SkRRectPriv::ConservativeIntersect(actual, b)); | |
| // Bounds of intersection round rect should equal intersection of bounds of a and b | |
| REPORTER_ASSERT(reporter, actual.rect() == expectedBounds); | |
| // Use PathOps to confirm that the explicit round rect is correct. | |
| SkPath aPath, bPath, expectedPath; | |
| aPath.addRRect(a); | |
| bPath.addRRect(b); | |
| SkAssertResult(Op(aPath, bPath, kIntersect_SkPathOp, &expectedPath)); | |
| // The isRRect() heuristics in SkPath are based on having called addRRect(), so a path from | |
| // path ops that is a rounded rectangle will return false. However, if test XOR expected is | |
| // empty, then we know that the shapes were the same. | |
| SkPath testPath; | |
| testPath.addRRect(actual); | |
| SkPath empty; | |
| SkAssertResult(Op(testPath, expectedPath, kXOR_SkPathOp, &empty)); | |
| REPORTER_ASSERT(reporter, empty.isEmpty()); | |
| } | |
| static void verify_failure(skiatest::Reporter* reporter, const SkRRect& a, const SkRRect& b) { | |
| SkRRect intersection = SkRRectPriv::ConservativeIntersect(a, b); | |
| // Expected the intersection to fail (no intersection or complex intersection is not | |
| // disambiguated). | |
| REPORTER_ASSERT(reporter, intersection.isEmpty()); | |
| REPORTER_ASSERT(reporter, SkRRectPriv::ConservativeIntersect(b, a).isEmpty()); | |
| } | |
| } // namespace | |
| static void test_conservative_intersection(skiatest::Reporter* reporter) { | |
| // Helper to inline making an inset round rect | |
| auto make_inset = [](const SkRRect& r, float dx, float dy) { | |
| SkRRect i = r; | |
| i.inset(dx, dy); | |
| return i; | |
| }; | |
| // A is a wide, short round rect | |
| SkRRect a = SkRRect::MakeRectXY({0.f, 4.f, 16.f, 12.f}, 2.f, 2.f); | |
| // B is a narrow, tall round rect | |
| SkRRect b = SkRRect::MakeRectXY({4.f, 0.f, 12.f, 16.f}, 3.f, 3.f); | |
| // NOTE: As positioned by default, A and B intersect as the rectangle {4, 4, 12, 12}. | |
| // There is a 2 px buffer between the corner curves of A and the vertical edges of B, and | |
| // a 1 px buffer between the corner curves of B and the horizontal edges of A. Since the shapes | |
| // form a symmetric rounded cross, we can easily test edge and corner combinations by simply | |
| // flipping signs and/or swapping x and y offsets. | |
| // Successful intersection operations: | |
| // - for clarity these are formed by moving A around to intersect with B in different ways. | |
| // - the expected bounds of the round rect intersection is calculated automatically | |
| // in check_success, so all we have to specify are the expected corner radii | |
| // A and B intersect as a rectangle | |
| verify_success(reporter, a, b, kRect, kRect, kRect, kRect); | |
| // Move A to intersect B on a vertical edge, preserving two corners of A inside B | |
| verify_success(reporter, a.makeOffset(6.f, 0.f), b, kA, kRect, kRect, kA); | |
| verify_success(reporter, a.makeOffset(-6.f, 0.f), b, kRect, kA, kA, kRect); | |
| // Move B to intersect A on a horizontal edge, preserving two corners of B inside A | |
| verify_success(reporter, a, b.makeOffset(0.f, 6.f), kB, kB, kRect, kRect); | |
| verify_success(reporter, a, b.makeOffset(0.f, -6.f), kRect, kRect, kB, kB); | |
| // Move A to intersect B on a corner, preserving one corner of A and one of B | |
| verify_success(reporter, a.makeOffset(-7.f, -8.f), b, kB, kRect, kA, kRect); // TL of B | |
| verify_success(reporter, a.makeOffset(7.f, -8.f), b, kRect, kB, kRect, kA); // TR of B | |
| verify_success(reporter, a.makeOffset(7.f, 8.f), b, kA, kRect, kB, kRect); // BR of B | |
| verify_success(reporter, a.makeOffset(-7.f, 8.f), b, kRect, kA, kRect, kB); // BL of B | |
| // An inset is contained inside the original (note that SkRRect::inset modifies radii too) so | |
| // is returned unmodified when intersected. | |
| verify_success(reporter, a, make_inset(a, 1.f, 1.f), kB, kB, kB, kB); | |
| verify_success(reporter, make_inset(b, 2.f, 2.f), b, kA, kA, kA, kA); | |
| // A rectangle exactly matching the corners of the rrect bounds keeps the rrect radii, | |
| // regardless of whether or not it's the 1st or 2nd arg to ConservativeIntersect. | |
| SkRRect c = SkRRect::MakeRectXY({0.f, 0.f, 10.f, 10.f}, 2.f, 2.f); | |
| SkRRect cT = SkRRect::MakeRect({0.f, 0.f, 10.f, 5.f}); | |
| verify_success(reporter, c, cT, kA, kA, kRect, kRect); | |
| verify_success(reporter, cT, c, kB, kB, kRect, kRect); | |
| SkRRect cB = SkRRect::MakeRect({0.f, 5.f, 10.f, 10.}); | |
| verify_success(reporter, c, cB, kRect, kRect, kA, kA); | |
| verify_success(reporter, cB, c, kRect, kRect, kB, kB); | |
| SkRRect cL = SkRRect::MakeRect({0.f, 0.f, 5.f, 10.f}); | |
| verify_success(reporter, c, cL, kA, kRect, kRect, kA); | |
| verify_success(reporter, cL, c, kB, kRect, kRect, kB); | |
| SkRRect cR = SkRRect::MakeRect({5.f, 0.f, 10.f, 10.f}); | |
| verify_success(reporter, c, cR, kRect, kA, kA, kRect); | |
| verify_success(reporter, cR, c, kRect, kB, kB, kRect); | |
| // Failed intersection operations: | |
| // A and B's bounds do not intersect | |
| verify_failure(reporter, a.makeOffset(32.f, 0.f), b); | |
| // A and B's bounds intersect, but corner curves do not -> no intersection | |
| verify_failure(reporter, a.makeOffset(11.5f, -11.5f), b); | |
| // A is empty -> no intersection | |
| verify_failure(reporter, SkRRect::MakeEmpty(), b); | |
| // A is contained in B, but is too close to the corner curves for the conservative | |
| // approximations to construct a valid round rect intersection. | |
| verify_failure(reporter, make_inset(b, 0.3f, 0.3f), b); | |
| // A intersects a straight edge, but not far enough for B to contain A's corners | |
| verify_failure(reporter, a.makeOffset(2.5f, 0.f), b); | |
| verify_failure(reporter, a.makeOffset(-2.5f, 0.f), b); | |
| // And vice versa for B into A | |
| verify_failure(reporter, a, b.makeOffset(0.f, 1.5f)); | |
| verify_failure(reporter, a, b.makeOffset(0.f, -1.5f)); | |
| // A intersects a straight edge and part of B's corner | |
| verify_failure(reporter, a.makeOffset(5.f, -2.f), b); | |
| verify_failure(reporter, a.makeOffset(-5.f, -2.f), b); | |
| verify_failure(reporter, a.makeOffset(5.f, 2.f), b); | |
| verify_failure(reporter, a.makeOffset(-5.f, 2.f), b); | |
| // And vice versa | |
| verify_failure(reporter, a, b.makeOffset(3.f, -5.f)); | |
| verify_failure(reporter, a, b.makeOffset(-3.f, -5.f)); | |
| verify_failure(reporter, a, b.makeOffset(3.f, 5.f)); | |
| verify_failure(reporter, a, b.makeOffset(-3.f, 5.f)); | |
| // A intersects B on a corner, but the corner curves overlap each other | |
| verify_failure(reporter, a.makeOffset(8.f, 10.f), b); | |
| verify_failure(reporter, a.makeOffset(-8.f, 10.f), b); | |
| verify_failure(reporter, a.makeOffset(8.f, -10.f), b); | |
| verify_failure(reporter, a.makeOffset(-8.f, -10.f), b); | |
| // Another variant of corners overlapping, this is two circles of radius r that overlap by r | |
| // pixels (e.g. the leftmost point of the right circle touches the center of the left circle). | |
| // The key difference with the above case is that the intersection of the circle bounds have | |
| // corners that are contained in both circles, but because it is only r wide, can not satisfy | |
| // all corners having radii = r. | |
| float r = 100.f; | |
| a = SkRRect::MakeOval(SkRect::MakeWH(2*r, 2*r)); | |
| verify_failure(reporter, a, a.makeOffset(r, 0.f)); | |
| } | |
| 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); | |
| test_inner_bounds(reporter); | |
| test_conservative_intersection(reporter); | |
| } | |
| DEF_TEST(RRect_fuzzer_regressions, r) { | |
| { | |
| unsigned char buf[] = { | |
| 0x0a, 0x00, 0x00, 0xff, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, | |
| 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, | |
| 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, | |
| 0x7f, 0x7f, 0x7f, 0x02, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x02, 0x00 | |
| }; | |
| REPORTER_ASSERT(r, sizeof(buf) == SkRRect{}.readFromMemory(buf, sizeof(buf))); | |
| } | |
| { | |
| unsigned char buf[] = { | |
| 0x5d, 0xff, 0xff, 0x5d, 0x0a, 0x60, 0x0a, 0x0a, 0x0a, 0x7e, 0x0a, 0x5a, | |
| 0x0a, 0x12, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, | |
| 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x00, 0x00, 0x00, 0x0a, | |
| 0x0a, 0x0a, 0x0a, 0x26, 0x0a, 0x0a, 0x0a, 0x0a, 0xff, 0xff, 0x0a, 0x0a | |
| }; | |
| REPORTER_ASSERT(r, sizeof(buf) == SkRRect{}.readFromMemory(buf, sizeof(buf))); | |
| } | |
| { | |
| unsigned char buf[] = { | |
| 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x04, 0xdd, 0xdd, 0x15, | |
| 0xfe, 0x00, 0x00, 0x04, 0x05, 0x7e, 0x00, 0x00, 0x00, 0xff, 0x08, 0x04, | |
| 0xff, 0xff, 0xfe, 0xfe, 0xff, 0x32, 0x32, 0x32, 0x32, 0x00, 0x32, 0x32, | |
| 0x04, 0xdd, 0x3d, 0x1c, 0xfe, 0x89, 0x04, 0x0a, 0x0e, 0x05, 0x7e, 0x0a | |
| }; | |
| REPORTER_ASSERT(r, sizeof(buf) == SkRRect{}.readFromMemory(buf, sizeof(buf))); | |
| } | |
| } |