Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2018 Google Inc. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license that can be |
| 5 | * found in the LICENSE file. |
| 6 | */ |
| 7 | |
Mike Klein | c0bd9f9 | 2019-04-23 12:05:21 -0500 | [diff] [blame] | 8 | #include "tests/PathOpsExtendedTest.h" |
| 9 | #include "tests/PathOpsThreadedCommon.h" |
| 10 | #include "tests/Test.h" |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 11 | |
Mike Reed | 30bc527 | 2019-11-22 18:34:02 +0000 | [diff] [blame] | 12 | static SkPath build_squircle(SkPath::Verb verb, const SkRect& rect, SkPathDirection dir) { |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 13 | SkPath path; |
Mike Reed | 30bc527 | 2019-11-22 18:34:02 +0000 | [diff] [blame] | 14 | bool reverse = SkPathDirection::kCCW == dir; |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 15 | switch (verb) { |
| 16 | case SkPath::kLine_Verb: |
| 17 | path.addRect(rect, dir); |
| 18 | reverse = false; |
| 19 | break; |
| 20 | case SkPath::kQuad_Verb: |
| 21 | path.moveTo(rect.centerX(), rect.fTop); |
| 22 | path.quadTo(rect.fRight, rect.fTop, rect.fRight, rect.centerY()); |
| 23 | path.quadTo(rect.fRight, rect.fBottom, rect.centerX(), rect.fBottom); |
| 24 | path.quadTo(rect.fLeft, rect.fBottom, rect.fLeft, rect.centerY()); |
| 25 | path.quadTo(rect.fLeft, rect.fTop, rect.centerX(), rect.fTop); |
| 26 | break; |
| 27 | case SkPath::kConic_Verb: |
| 28 | path.addCircle(rect.centerX(), rect.centerY(), rect.width() / 2, dir); |
| 29 | reverse = false; |
| 30 | break; |
| 31 | case SkPath::kCubic_Verb: { |
| 32 | SkScalar aX14 = rect.fLeft + rect.width() * 1 / 4; |
| 33 | SkScalar aX34 = rect.fLeft + rect.width() * 3 / 4; |
| 34 | SkScalar aY14 = rect.fTop + rect.height() * 1 / 4; |
| 35 | SkScalar aY34 = rect.fTop + rect.height() * 3 / 4; |
| 36 | path.moveTo(rect.centerX(), rect.fTop); |
| 37 | path.cubicTo(aX34, rect.fTop, rect.fRight, aY14, rect.fRight, rect.centerY()); |
| 38 | path.cubicTo(rect.fRight, aY34, aX34, rect.fBottom, rect.centerX(), rect.fBottom); |
| 39 | path.cubicTo(aX14, rect.fBottom, rect.fLeft, aY34, rect.fLeft, rect.centerY()); |
| 40 | path.cubicTo(rect.fLeft, aY14, aX14, rect.fTop, rect.centerX(), rect.fTop); |
| 41 | } break; |
| 42 | default: |
| 43 | SkASSERT(0); |
| 44 | } |
| 45 | if (reverse) { |
| 46 | SkPath temp; |
| 47 | temp.reverseAddPath(path); |
| 48 | path.swap(temp); |
| 49 | } |
| 50 | return path; |
| 51 | } |
| 52 | |
| 53 | DEF_TEST(PathOpsAsWinding, reporter) { |
| 54 | SkPath test, result; |
| 55 | test.addRect({1, 2, 3, 4}); |
| 56 | // if test is winding |
| 57 | REPORTER_ASSERT(reporter, AsWinding(test, &result)); |
| 58 | REPORTER_ASSERT(reporter, test == result); |
| 59 | // if test is empty |
| 60 | test.reset(); |
Mike Reed | 7d34dc7 | 2019-11-26 12:17:17 -0500 | [diff] [blame] | 61 | test.setFillType(SkPathFillType::kEvenOdd); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 62 | REPORTER_ASSERT(reporter, AsWinding(test, &result)); |
| 63 | REPORTER_ASSERT(reporter, result.isEmpty()); |
Mike Reed | cf0e3c6 | 2019-12-03 16:26:15 -0500 | [diff] [blame] | 64 | REPORTER_ASSERT(reporter, result.getFillType() == SkPathFillType::kWinding); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 65 | // if test is convex |
| 66 | test.addCircle(5, 5, 10); |
| 67 | REPORTER_ASSERT(reporter, AsWinding(test, &result)); |
| 68 | REPORTER_ASSERT(reporter, result.isConvex()); |
Mike Reed | 7d34dc7 | 2019-11-26 12:17:17 -0500 | [diff] [blame] | 69 | test.setFillType(SkPathFillType::kWinding); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 70 | REPORTER_ASSERT(reporter, test == result); |
| 71 | // if test has infinity |
| 72 | test.reset(); |
| 73 | test.addRect({1, 2, 3, SK_ScalarInfinity}); |
Mike Reed | 7d34dc7 | 2019-11-26 12:17:17 -0500 | [diff] [blame] | 74 | test.setFillType(SkPathFillType::kEvenOdd); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 75 | REPORTER_ASSERT(reporter, !AsWinding(test, &result)); |
| 76 | // if test has only one contour |
| 77 | test.reset(); |
| 78 | SkPoint ell[] = {{0, 0}, {4, 0}, {4, 1}, {1, 1}, {1, 4}, {0, 4}}; |
| 79 | test.addPoly(ell, SK_ARRAY_COUNT(ell), true); |
Mike Reed | 7d34dc7 | 2019-11-26 12:17:17 -0500 | [diff] [blame] | 80 | test.setFillType(SkPathFillType::kEvenOdd); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 81 | REPORTER_ASSERT(reporter, AsWinding(test, &result)); |
| 82 | REPORTER_ASSERT(reporter, !result.isConvex()); |
Mike Reed | 7d34dc7 | 2019-11-26 12:17:17 -0500 | [diff] [blame] | 83 | test.setFillType(SkPathFillType::kWinding); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 84 | REPORTER_ASSERT(reporter, test == result); |
| 85 | // test two contours that do not overlap or share bounds |
| 86 | test.addRect({5, 2, 6, 3}); |
Mike Reed | 7d34dc7 | 2019-11-26 12:17:17 -0500 | [diff] [blame] | 87 | test.setFillType(SkPathFillType::kEvenOdd); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 88 | REPORTER_ASSERT(reporter, AsWinding(test, &result)); |
| 89 | REPORTER_ASSERT(reporter, !result.isConvex()); |
Mike Reed | 7d34dc7 | 2019-11-26 12:17:17 -0500 | [diff] [blame] | 90 | test.setFillType(SkPathFillType::kWinding); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 91 | REPORTER_ASSERT(reporter, test == result); |
| 92 | // test two contours that do not overlap but share bounds |
| 93 | test.reset(); |
| 94 | test.addPoly(ell, SK_ARRAY_COUNT(ell), true); |
| 95 | test.addRect({2, 2, 3, 3}); |
Mike Reed | 7d34dc7 | 2019-11-26 12:17:17 -0500 | [diff] [blame] | 96 | test.setFillType(SkPathFillType::kEvenOdd); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 97 | REPORTER_ASSERT(reporter, AsWinding(test, &result)); |
| 98 | REPORTER_ASSERT(reporter, !result.isConvex()); |
Mike Reed | 7d34dc7 | 2019-11-26 12:17:17 -0500 | [diff] [blame] | 99 | test.setFillType(SkPathFillType::kWinding); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 100 | REPORTER_ASSERT(reporter, test == result); |
| 101 | // test two contours that partially overlap |
| 102 | test.reset(); |
| 103 | test.addRect({0, 0, 3, 3}); |
| 104 | test.addRect({1, 1, 4, 4}); |
Mike Reed | 7d34dc7 | 2019-11-26 12:17:17 -0500 | [diff] [blame] | 105 | test.setFillType(SkPathFillType::kEvenOdd); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 106 | REPORTER_ASSERT(reporter, AsWinding(test, &result)); |
| 107 | REPORTER_ASSERT(reporter, !result.isConvex()); |
Mike Reed | 7d34dc7 | 2019-11-26 12:17:17 -0500 | [diff] [blame] | 108 | test.setFillType(SkPathFillType::kWinding); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 109 | REPORTER_ASSERT(reporter, test == result); |
| 110 | // test that result may be input |
| 111 | SkPath copy = test; |
Mike Reed | 7d34dc7 | 2019-11-26 12:17:17 -0500 | [diff] [blame] | 112 | test.setFillType(SkPathFillType::kEvenOdd); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 113 | REPORTER_ASSERT(reporter, AsWinding(test, &test)); |
| 114 | REPORTER_ASSERT(reporter, !test.isConvex()); |
| 115 | REPORTER_ASSERT(reporter, test == copy); |
| 116 | // test a in b, b in a, cw/ccw |
| 117 | constexpr SkRect rectA = {0, 0, 3, 3}; |
| 118 | constexpr SkRect rectB = {1, 1, 2, 2}; |
| 119 | const std::initializer_list<SkPoint> revBccw = {{1, 2}, {2, 2}, {2, 1}, {1, 1}}; |
| 120 | const std::initializer_list<SkPoint> revBcw = {{2, 1}, {2, 2}, {1, 2}, {1, 1}}; |
| 121 | for (bool aFirst : {false, true}) { |
Mike Reed | 30bc527 | 2019-11-22 18:34:02 +0000 | [diff] [blame] | 122 | for (auto dirA : {SkPathDirection::kCW, SkPathDirection::kCCW}) { |
| 123 | for (auto dirB : {SkPathDirection::kCW, SkPathDirection::kCCW}) { |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 124 | test.reset(); |
Mike Reed | 7d34dc7 | 2019-11-26 12:17:17 -0500 | [diff] [blame] | 125 | test.setFillType(SkPathFillType::kEvenOdd); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 126 | if (aFirst) { |
| 127 | test.addRect(rectA, dirA); |
| 128 | test.addRect(rectB, dirB); |
| 129 | } else { |
| 130 | test.addRect(rectB, dirB); |
| 131 | test.addRect(rectA, dirA); |
| 132 | } |
| 133 | SkPath original = test; |
| 134 | REPORTER_ASSERT(reporter, AsWinding(test, &result)); |
Mike Reed | cf0e3c6 | 2019-12-03 16:26:15 -0500 | [diff] [blame] | 135 | REPORTER_ASSERT(reporter, result.getFillType() == SkPathFillType::kWinding); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 136 | test.reset(); |
| 137 | if (aFirst) { |
| 138 | test.addRect(rectA, dirA); |
| 139 | } |
| 140 | if (dirA != dirB) { |
| 141 | test.addRect(rectB, dirB); |
| 142 | } else { |
Mike Reed | 30bc527 | 2019-11-22 18:34:02 +0000 | [diff] [blame] | 143 | test.addPoly(SkPathDirection::kCW == dirA ? revBccw : revBcw, true); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 144 | } |
| 145 | if (!aFirst) { |
| 146 | test.addRect(rectA, dirA); |
| 147 | } |
| 148 | REPORTER_ASSERT(reporter, test == result); |
| 149 | // test that result may be input |
| 150 | REPORTER_ASSERT(reporter, AsWinding(original, &original)); |
Mike Reed | cf0e3c6 | 2019-12-03 16:26:15 -0500 | [diff] [blame] | 151 | REPORTER_ASSERT(reporter, original.getFillType() == SkPathFillType::kWinding); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 152 | REPORTER_ASSERT(reporter, original == result); |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | // Test curve types with donuts. Create a donut with outer and hole in all directions. |
| 157 | // After converting to winding, all donuts should have a hole in the middle. |
| 158 | for (bool aFirst : {false, true}) { |
Mike Reed | 30bc527 | 2019-11-22 18:34:02 +0000 | [diff] [blame] | 159 | for (auto dirA : {SkPathDirection::kCW, SkPathDirection::kCCW}) { |
| 160 | for (auto dirB : {SkPathDirection::kCW, SkPathDirection::kCCW}) { |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 161 | for (auto curveA : { SkPath::kLine_Verb, SkPath::kQuad_Verb, |
| 162 | SkPath::kConic_Verb, SkPath::kCubic_Verb } ) { |
| 163 | SkPath pathA = build_squircle(curveA, rectA, dirA); |
| 164 | for (auto curveB : { SkPath::kLine_Verb, SkPath::kQuad_Verb, |
| 165 | SkPath::kConic_Verb, SkPath::kCubic_Verb } ) { |
| 166 | test = aFirst ? pathA : SkPath(); |
| 167 | test.addPath(build_squircle(curveB, rectB, dirB)); |
| 168 | if (!aFirst) { |
| 169 | test.addPath(pathA); |
| 170 | } |
Mike Reed | 7d34dc7 | 2019-11-26 12:17:17 -0500 | [diff] [blame] | 171 | test.setFillType(SkPathFillType::kEvenOdd); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 172 | REPORTER_ASSERT(reporter, AsWinding(test, &result)); |
Mike Reed | cf0e3c6 | 2019-12-03 16:26:15 -0500 | [diff] [blame] | 173 | REPORTER_ASSERT(reporter, result.getFillType() == SkPathFillType::kWinding); |
Cary Clark | 7d06e26 | 2018-08-16 11:53:54 -0400 | [diff] [blame] | 174 | for (SkScalar x = rectA.fLeft - 1; x <= rectA.fRight + 1; ++x) { |
| 175 | for (SkScalar y = rectA.fTop - 1; y <= rectA.fBottom + 1; ++y) { |
| 176 | bool evenOddContains = test.contains(x, y); |
| 177 | bool windingContains = result.contains(x, y); |
| 178 | REPORTER_ASSERT(reporter, evenOddContains == windingContains); |
| 179 | } |
| 180 | } |
| 181 | } |
| 182 | } |
| 183 | } |
| 184 | } |
| 185 | } |
| 186 | } |