blob: e92dc0d40b95960fd63780f354ea31c3a1eddd51 [file] [log] [blame]
Cary Clark7d06e262018-08-16 11:53:54 -04001/*
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 Kleinc0bd9f92019-04-23 12:05:21 -05008#include "tests/PathOpsExtendedTest.h"
9#include "tests/PathOpsThreadedCommon.h"
10#include "tests/Test.h"
Cary Clark7d06e262018-08-16 11:53:54 -040011
12static SkPath build_squircle(SkPath::Verb verb, const SkRect& rect, SkPath::Direction dir) {
13 SkPath path;
14 bool reverse = SkPath::kCCW_Direction == 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
53DEF_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(SkPath::kEvenOdd_FillType);
62 REPORTER_ASSERT(reporter, AsWinding(test, &result));
63 REPORTER_ASSERT(reporter, result.isEmpty());
64 REPORTER_ASSERT(reporter, result.getFillType() == SkPath::kWinding_FillType);
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(SkPath::kWinding_FillType);
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(SkPath::kEvenOdd_FillType);
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(SkPath::kEvenOdd_FillType);
81 REPORTER_ASSERT(reporter, AsWinding(test, &result));
82 REPORTER_ASSERT(reporter, !result.isConvex());
83 test.setFillType(SkPath::kWinding_FillType);
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(SkPath::kEvenOdd_FillType);
88 REPORTER_ASSERT(reporter, AsWinding(test, &result));
89 REPORTER_ASSERT(reporter, !result.isConvex());
90 test.setFillType(SkPath::kWinding_FillType);
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(SkPath::kEvenOdd_FillType);
97 REPORTER_ASSERT(reporter, AsWinding(test, &result));
98 REPORTER_ASSERT(reporter, !result.isConvex());
99 test.setFillType(SkPath::kWinding_FillType);
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(SkPath::kEvenOdd_FillType);
106 REPORTER_ASSERT(reporter, AsWinding(test, &result));
107 REPORTER_ASSERT(reporter, !result.isConvex());
108 test.setFillType(SkPath::kWinding_FillType);
109 REPORTER_ASSERT(reporter, test == result);
110 // test that result may be input
111 SkPath copy = test;
112 test.setFillType(SkPath::kEvenOdd_FillType);
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 : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
123 for (auto dirB : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
124 test.reset();
125 test.setFillType(SkPath::kEvenOdd_FillType);
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() == SkPath::kWinding_FillType);
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(SkPath::kCW_Direction == 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() == SkPath::kWinding_FillType);
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 : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
160 for (auto dirB : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
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(SkPath::kEvenOdd_FillType);
172 REPORTER_ASSERT(reporter, AsWinding(test, &result));
173 REPORTER_ASSERT(reporter, result.getFillType() == SkPath::kWinding_FillType);
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}