|  | /* | 
|  | * Copyright 2015 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 "SkPath.h" | 
|  | #include "SkPathRef.h" | 
|  | #include "SkRRect.h" | 
|  | #include "Test.h" | 
|  |  | 
|  | static SkRRect path_contains_rrect(skiatest::Reporter* reporter, const SkPath& path, | 
|  | SkPath::Direction* dir, unsigned* start) { | 
|  | SkRRect out; | 
|  | REPORTER_ASSERT(reporter, path.isRRect(&out, dir, start)); | 
|  | SkPath recreatedPath; | 
|  | recreatedPath.addRRect(out, *dir, *start); | 
|  | REPORTER_ASSERT(reporter, path == recreatedPath); | 
|  | // Test that rotations/mirrors of the rrect path are still rrect paths and the returned | 
|  | // parameters for the transformed paths are correct. | 
|  | static const SkMatrix kMatrices[] = { | 
|  | SkMatrix::MakeScale(1, 1), | 
|  | SkMatrix::MakeScale(-1, 1), | 
|  | SkMatrix::MakeScale(1, -1), | 
|  | SkMatrix::MakeScale(-1, -1), | 
|  | }; | 
|  | for (auto& m : kMatrices) { | 
|  | SkPath xformed; | 
|  | path.transform(m, &xformed); | 
|  | SkRRect xrr = SkRRect::MakeRect(SkRect::MakeEmpty()); | 
|  | SkPath::Direction xd = SkPath::kCCW_Direction; | 
|  | unsigned xs = ~0U; | 
|  | REPORTER_ASSERT(reporter, xformed.isRRect(&xrr, &xd, &xs)); | 
|  | recreatedPath.reset(); | 
|  | recreatedPath.addRRect(xrr, xd, xs); | 
|  | REPORTER_ASSERT(reporter, recreatedPath == xformed); | 
|  | } | 
|  | return out; | 
|  | } | 
|  |  | 
|  | static SkRRect inner_path_contains_rrect(skiatest::Reporter* reporter, const SkRRect& in, | 
|  | SkPath::Direction dir, unsigned start) { | 
|  | switch (in.getType()) { | 
|  | case SkRRect::kEmpty_Type: | 
|  | case SkRRect::kRect_Type: | 
|  | case SkRRect::kOval_Type: | 
|  | return in; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | SkPath path; | 
|  | path.addRRect(in, dir, start); | 
|  | SkPath::Direction outDir; | 
|  | unsigned outStart; | 
|  | SkRRect rrect = path_contains_rrect(reporter, path, &outDir, &outStart); | 
|  | REPORTER_ASSERT(reporter, outDir == dir && outStart == start); | 
|  | return rrect; | 
|  | } | 
|  |  | 
|  | static void path_contains_rrect_check(skiatest::Reporter* reporter, const SkRRect& in, | 
|  | SkPath::Direction dir, unsigned start) { | 
|  | SkRRect out = inner_path_contains_rrect(reporter, in, dir, start); | 
|  | if (in != out) { | 
|  | SkDebugf(""); | 
|  | } | 
|  | REPORTER_ASSERT(reporter, in == out); | 
|  | } | 
|  |  | 
|  | static void path_contains_rrect_nocheck(skiatest::Reporter* reporter, const SkRRect& in, | 
|  | SkPath::Direction dir, unsigned start) { | 
|  | SkRRect out = inner_path_contains_rrect(reporter, in, dir, start); | 
|  | if (in == out) { | 
|  | SkDebugf(""); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void path_contains_rrect_check(skiatest::Reporter* reporter, const SkRect& r, | 
|  | SkVector v[4], SkPath::Direction dir, unsigned start) { | 
|  | SkRRect rrect; | 
|  | rrect.setRectRadii(r, v); | 
|  | path_contains_rrect_check(reporter, rrect, dir, start); | 
|  | } | 
|  |  | 
|  | class ForceIsRRect_Private { | 
|  | public: | 
|  | ForceIsRRect_Private(SkPath* path, SkPath::Direction dir, unsigned start) { | 
|  | path->fPathRef->setIsRRect(true, dir == SkPath::kCCW_Direction, start); | 
|  | } | 
|  | }; | 
|  |  | 
|  | static void force_path_contains_rrect(skiatest::Reporter* reporter, SkPath& path, | 
|  | SkPath::Direction dir, unsigned start) { | 
|  | ForceIsRRect_Private force_rrect(&path, dir, start); | 
|  | SkPath::Direction outDir; | 
|  | unsigned outStart; | 
|  | path_contains_rrect(reporter, path, &outDir, &outStart); | 
|  | REPORTER_ASSERT(reporter, outDir == dir && outStart == start); | 
|  | } | 
|  |  | 
|  | static void test_undetected_paths(skiatest::Reporter* reporter) { | 
|  | // We use a dummy path to get the exact conic weight used by SkPath for a circular arc. This | 
|  | // allows our local, hand-crafted, artisanal round rect paths below to exactly match the | 
|  | // factory made corporate paths produced by SkPath. | 
|  | SkPath dummyPath; | 
|  | dummyPath.addCircle(0, 0, 10); | 
|  | SkPath::RawIter iter(dummyPath); | 
|  | SkPoint dummyPts[4]; | 
|  | SkPath::Verb v = iter.next(dummyPts); | 
|  | REPORTER_ASSERT(reporter, SkPath::kMove_Verb == v); | 
|  | v = iter.next(dummyPts); | 
|  | REPORTER_ASSERT(reporter, SkPath::kConic_Verb == v); | 
|  | const SkScalar weight = iter.conicWeight(); | 
|  |  | 
|  | SkPath path; | 
|  | path.moveTo(0, 62.5f); | 
|  | path.lineTo(0, 3.5f); | 
|  | path.conicTo(0, 0, 3.5f, 0, weight); | 
|  | path.lineTo(196.5f, 0); | 
|  | path.conicTo(200, 0, 200, 3.5f, weight); | 
|  | path.lineTo(200, 62.5f); | 
|  | path.conicTo(200, 66, 196.5f, 66, weight); | 
|  | path.lineTo(3.5f, 66); | 
|  | path.conicTo(0, 66, 0, 62.5, weight); | 
|  | path.close(); | 
|  | force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6); | 
|  |  | 
|  | path.reset(); | 
|  | path.moveTo(0, 81.5f); | 
|  | path.lineTo(0, 3.5f); | 
|  | path.conicTo(0, 0, 3.5f, 0, weight); | 
|  | path.lineTo(149.5, 0); | 
|  | path.conicTo(153, 0, 153, 3.5f, weight); | 
|  | path.lineTo(153, 81.5f); | 
|  | path.conicTo(153, 85, 149.5f, 85, weight); | 
|  | path.lineTo(3.5f, 85); | 
|  | path.conicTo(0, 85, 0, 81.5f, weight); | 
|  | path.close(); | 
|  | force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6); | 
|  |  | 
|  | path.reset(); | 
|  | path.moveTo(14, 1189); | 
|  | path.lineTo(14, 21); | 
|  | path.conicTo(14, 14, 21, 14, weight); | 
|  | path.lineTo(1363, 14); | 
|  | path.conicTo(1370, 14, 1370, 21, weight); | 
|  | path.lineTo(1370, 1189); | 
|  | path.conicTo(1370, 1196, 1363, 1196, weight); | 
|  | path.lineTo(21, 1196); | 
|  | path.conicTo(14, 1196, 14, 1189, weight); | 
|  | path.close(); | 
|  | force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6); | 
|  |  | 
|  | path.reset(); | 
|  | path.moveTo(14, 1743); | 
|  | path.lineTo(14, 21); | 
|  | path.conicTo(14, 14, 21, 14, weight); | 
|  | path.lineTo(1363, 14); | 
|  | path.conicTo(1370, 14, 1370, 21, weight); | 
|  | path.lineTo(1370, 1743); | 
|  | path.conicTo(1370, 1750, 1363, 1750, weight); | 
|  | path.lineTo(21, 1750); | 
|  | path.conicTo(14, 1750, 14, 1743, weight); | 
|  | path.close(); | 
|  | force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6); | 
|  | } | 
|  |  | 
|  | static const SkScalar kWidth = 100.0f; | 
|  | static const SkScalar kHeight = 100.0f; | 
|  |  | 
|  | static void test_tricky_radii(skiatest::Reporter* reporter) { | 
|  | for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { | 
|  | for (int start = 0; start < 8; ++start) { | 
|  | { | 
|  | // 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); | 
|  | path_contains_rrect_check(reporter, rr, dir, start); | 
|  | } | 
|  |  | 
|  | { | 
|  | // 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); | 
|  | path_contains_rrect_nocheck(reporter, rr, dir, start); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void test_empty_crbug_458524(skiatest::Reporter* reporter) { | 
|  | for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { | 
|  | for (int start = 0; start < 8; ++start) { | 
|  | SkRRect rr; | 
|  | const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 }; | 
|  | const SkScalar rad = 40; | 
|  | rr.setRectXY(bounds, rad, rad); | 
|  | path_contains_rrect_check(reporter, rr, dir, start); | 
|  |  | 
|  | SkRRect other; | 
|  | SkMatrix matrix; | 
|  | matrix.setScale(0, 1); | 
|  | rr.transform(matrix, &other); | 
|  | path_contains_rrect_check(reporter, rr, dir, start); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void test_inset(skiatest::Reporter* reporter) { | 
|  | for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { | 
|  | for (int start = 0; start < 8; ++start) { | 
|  | SkRRect rr, rr2; | 
|  | SkRect r = { 0, 0, 100, 100 }; | 
|  |  | 
|  | rr.setRect(r); | 
|  | rr.inset(-20, -20, &rr2); | 
|  | path_contains_rrect_check(reporter, rr, dir, start); | 
|  |  | 
|  | rr.inset(20, 20, &rr2); | 
|  | path_contains_rrect_check(reporter, rr, dir, start); | 
|  |  | 
|  | rr.inset(r.width()/2, r.height()/2, &rr2); | 
|  | path_contains_rrect_check(reporter, rr, dir, start); | 
|  |  | 
|  | rr.setRectXY(r, 20, 20); | 
|  | rr.inset(19, 19, &rr2); | 
|  | path_contains_rrect_check(reporter, rr, dir, start); | 
|  | rr.inset(20, 20, &rr2); | 
|  | path_contains_rrect_check(reporter, rr, dir, start); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static void test_9patch_rrect(skiatest::Reporter* reporter, | 
|  | const SkRect& rect, | 
|  | SkScalar l, SkScalar t, SkScalar r, SkScalar b, | 
|  | bool checkRadii) { | 
|  | for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { | 
|  | for (int start = 0; start < 8; ++start) { | 
|  | SkRRect rr; | 
|  | rr.setNinePatch(rect, l, t, r, b); | 
|  | if (checkRadii) { | 
|  | path_contains_rrect_check(reporter, rr, dir, start); | 
|  | } else { | 
|  | path_contains_rrect_nocheck(reporter, rr, dir, start); | 
|  | } | 
|  |  | 
|  | 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); | 
|  | if (checkRadii) { | 
|  | path_contains_rrect_check(reporter, rr, dir, start); | 
|  | } else { | 
|  | path_contains_rrect_nocheck(reporter, rr, dir, start); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Test out the basic API entry points | 
|  | static void test_round_rect_basic(skiatest::Reporter* reporter) { | 
|  | for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { | 
|  | for (int start = 0; start < 8; ++start) { | 
|  | //---- | 
|  | SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); | 
|  |  | 
|  | SkRRect rr1; | 
|  | rr1.setRect(rect); | 
|  | path_contains_rrect_check(reporter, rr1, dir, start); | 
|  |  | 
|  | 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); | 
|  | path_contains_rrect_check(reporter, rr1_2, dir, start); | 
|  | SkRRect rr1_3;  // construct the same RR using the nine patch set function | 
|  | rr1_3.setNinePatch(rect, 0, 0, 0, 0); | 
|  | path_contains_rrect_check(reporter, rr1_2, dir, start); | 
|  |  | 
|  | //---- | 
|  | SkPoint halfPoint = { SkScalarHalf(kWidth), SkScalarHalf(kHeight) }; | 
|  | SkRRect rr2; | 
|  | rr2.setOval(rect); | 
|  | path_contains_rrect_check(reporter, rr2, dir, start); | 
|  |  | 
|  | 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); | 
|  | path_contains_rrect_check(reporter, rr2_2, dir, start); | 
|  | 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); | 
|  | path_contains_rrect_check(reporter, rr2_3, dir, start); | 
|  |  | 
|  | //---- | 
|  | SkPoint p = { 5, 5 }; | 
|  | SkRRect rr3; | 
|  | rr3.setRectXY(rect, p.fX, p.fY); | 
|  | path_contains_rrect_check(reporter, rr3, dir, start); | 
|  |  | 
|  | 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); | 
|  | path_contains_rrect_check(reporter, rr3_2, dir, start); | 
|  | SkRRect rr3_3;  // construct the same RR using the nine patch set function | 
|  | rr3_3.setNinePatch(rect, 5, 5, 5, 5); | 
|  | path_contains_rrect_check(reporter, rr3_3, dir, start); | 
|  |  | 
|  | //---- | 
|  | 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); | 
|  | path_contains_rrect_check(reporter, rr5, dir, start); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Test out the cases when the RR degenerates to a rect | 
|  | static void test_round_rect_rects(skiatest::Reporter* reporter) { | 
|  | for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { | 
|  | for (int start = 0; start < 8; ++start) { | 
|  | //---- | 
|  | SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); | 
|  | SkRRect rr1; | 
|  | rr1.setRectXY(rect, 0, 0); | 
|  |  | 
|  | path_contains_rrect_check(reporter, rr1, dir, start); | 
|  |  | 
|  | //---- | 
|  | SkPoint radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }; | 
|  |  | 
|  | SkRRect rr2; | 
|  | rr2.setRectRadii(rect, radii); | 
|  |  | 
|  | path_contains_rrect_check(reporter, rr2, dir, start); | 
|  |  | 
|  | //---- | 
|  | SkPoint radii2[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } }; | 
|  |  | 
|  | SkRRect rr3; | 
|  | rr3.setRectRadii(rect, radii2); | 
|  | path_contains_rrect_check(reporter, rr3, dir, start); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Test out the cases when the RR degenerates to an oval | 
|  | static void test_round_rect_ovals(skiatest::Reporter* reporter) { | 
|  | for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { | 
|  | for (int start = 0; start < 8; ++start) { | 
|  | //---- | 
|  | SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); | 
|  | SkRRect rr1; | 
|  | rr1.setRectXY(rect, SkScalarHalf(kWidth), SkScalarHalf(kHeight)); | 
|  |  | 
|  | path_contains_rrect_check(reporter, rr1, dir, start); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Test out the non-degenerate RR cases | 
|  | static void test_round_rect_general(skiatest::Reporter* reporter) { | 
|  | for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { | 
|  | for (int start = 0; start < 8; ++start) { | 
|  | //---- | 
|  | SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); | 
|  | SkRRect rr1; | 
|  | rr1.setRectXY(rect, 20, 20); | 
|  |  | 
|  | path_contains_rrect_check(reporter, rr1, dir, start); | 
|  |  | 
|  | //---- | 
|  | SkPoint radii[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } }; | 
|  |  | 
|  | SkRRect rr2; | 
|  | rr2.setRectRadii(rect, radii); | 
|  |  | 
|  | path_contains_rrect_check(reporter, rr2, dir, start); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void test_round_rect_iffy_parameters(skiatest::Reporter* reporter) { | 
|  | for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { | 
|  | for (int start = 0; start < 8; ++start) { | 
|  | 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); | 
|  | path_contains_rrect_nocheck(reporter, rr1, dir, start); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void set_radii(SkVector radii[4], int index, float rad) { | 
|  | sk_bzero(radii, sizeof(SkVector) * 4); | 
|  | radii[index].set(rad, rad); | 
|  | } | 
|  |  | 
|  | static void test_skbug_3239(skiatest::Reporter* reporter) { | 
|  | const float min = SkBits2Float(0xcb7f16c8); /* -16717512.000000 */ | 
|  | const float max = SkBits2Float(0x4b7f1c1d); /*  16718877.000000 */ | 
|  | const float big = SkBits2Float(0x4b7f1bd7); /*  16718807.000000 */ | 
|  |  | 
|  | const float rad = 33436320; | 
|  |  | 
|  | const SkRect rectx = SkRect::MakeLTRB(min, min, max, big); | 
|  | const SkRect recty = SkRect::MakeLTRB(min, min, big, max); | 
|  |  | 
|  | for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { | 
|  | for (int start = 0; start < 8; ++start) { | 
|  | SkVector radii[4]; | 
|  | for (int i = 0; i < 4; ++i) { | 
|  | set_radii(radii, i, rad); | 
|  | path_contains_rrect_check(reporter, rectx, radii, dir, start); | 
|  | path_contains_rrect_check(reporter, recty, radii, dir, start); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void test_mix(skiatest::Reporter* reporter) { | 
|  | for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { | 
|  | for (int start = 0; start < 8; ++start) { | 
|  | // Test out mixed degenerate and non-degenerate geometry with Conics | 
|  | const SkVector radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 100, 100 } }; | 
|  | SkRect r = SkRect::MakeWH(100, 100); | 
|  | SkRRect rr; | 
|  | rr.setRectRadii(r, radii); | 
|  | path_contains_rrect_check(reporter, rr, dir, start); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | DEF_TEST(RoundRectInPath, reporter) { | 
|  | test_tricky_radii(reporter); | 
|  | test_empty_crbug_458524(reporter); | 
|  | test_inset(reporter); | 
|  | test_round_rect_basic(reporter); | 
|  | test_round_rect_rects(reporter); | 
|  | test_round_rect_ovals(reporter); | 
|  | test_round_rect_general(reporter); | 
|  | test_undetected_paths(reporter); | 
|  | test_round_rect_iffy_parameters(reporter); | 
|  | test_skbug_3239(reporter); | 
|  | test_mix(reporter); | 
|  | } |