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 
8 #include "tests/PathOpsExtendedTest.h"
9 #include "tests/PathOpsThreadedCommon.h"
10 #include "tests/Test.h"
11 
build_squircle(SkPath::Verb verb,const SkRect & rect,SkPathDirection dir)12 static SkPath build_squircle(SkPath::Verb verb, const SkRect& rect, SkPathDirection dir) {
13     SkPath path;
14     bool reverse = SkPathDirection::kCCW == dir;
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 
DEF_TEST(PathOpsAsWinding,reporter)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();
61     test.setFillType(SkPathFillType::kEvenOdd);
62     REPORTER_ASSERT(reporter, AsWinding(test, &result));
63     REPORTER_ASSERT(reporter, result.isEmpty());
64     REPORTER_ASSERT(reporter, result.getFillType() == SkPathFillType::kWinding);
65     // if test is convex
66     test.addCircle(5, 5, 10);
67     REPORTER_ASSERT(reporter, AsWinding(test, &result));
68     REPORTER_ASSERT(reporter, result.isConvex());
69     test.setFillType(SkPathFillType::kWinding);
70     REPORTER_ASSERT(reporter, test == result);
71     // if test has infinity
72     test.reset();
73     test.addRect({1, 2, 3, SK_ScalarInfinity});
74     test.setFillType(SkPathFillType::kEvenOdd);
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);
80     test.setFillType(SkPathFillType::kEvenOdd);
81     REPORTER_ASSERT(reporter, AsWinding(test, &result));
82     REPORTER_ASSERT(reporter, !result.isConvex());
83     test.setFillType(SkPathFillType::kWinding);
84     REPORTER_ASSERT(reporter, test == result);
85     // test two contours that do not overlap or share bounds
86     test.addRect({5, 2, 6, 3});
87     test.setFillType(SkPathFillType::kEvenOdd);
88     REPORTER_ASSERT(reporter, AsWinding(test, &result));
89     REPORTER_ASSERT(reporter, !result.isConvex());
90     test.setFillType(SkPathFillType::kWinding);
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});
96     test.setFillType(SkPathFillType::kEvenOdd);
97     REPORTER_ASSERT(reporter, AsWinding(test, &result));
98     REPORTER_ASSERT(reporter, !result.isConvex());
99     test.setFillType(SkPathFillType::kWinding);
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});
105     test.setFillType(SkPathFillType::kEvenOdd);
106     REPORTER_ASSERT(reporter, AsWinding(test, &result));
107     REPORTER_ASSERT(reporter, !result.isConvex());
108     test.setFillType(SkPathFillType::kWinding);
109     REPORTER_ASSERT(reporter, test == result);
110     // test that result may be input
111     SkPath copy = test;
112     test.setFillType(SkPathFillType::kEvenOdd);
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}) {
122         for (auto dirA : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
123             for (auto dirB : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
124                 test.reset();
125                 test.setFillType(SkPathFillType::kEvenOdd);
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));
135                 REPORTER_ASSERT(reporter, result.getFillType() == SkPathFillType::kWinding);
136                 test.reset();
137                 if (aFirst) {
138                     test.addRect(rectA, dirA);
139                 }
140                 if (dirA != dirB) {
141                     test.addRect(rectB, dirB);
142                 } else {
143                     test.addPoly(SkPathDirection::kCW == dirA ? revBccw : revBcw, true);
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));
151                 REPORTER_ASSERT(reporter, original.getFillType() == SkPathFillType::kWinding);
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}) {
159         for (auto dirA : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
160             for (auto dirB : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
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                         }
171                         test.setFillType(SkPathFillType::kEvenOdd);
172                         REPORTER_ASSERT(reporter, AsWinding(test, &result));
173                        REPORTER_ASSERT(reporter, result.getFillType() == SkPathFillType::kWinding);
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 }
187