1 /*
2 * Copyright 2020 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 "include/core/SkPath.h"
9 #include "include/core/SkPathBuilder.h"
10 #include "include/core/SkPathTypes.h"
11 #include "include/core/SkPoint.h"
12 #include "include/core/SkRRect.h"
13 #include "include/core/SkRect.h"
14 #include "include/core/SkScalar.h"
15 #include "src/base/SkRandom.h"
16 #include "src/core/SkPathPriv.h"
17 #include "tests/Test.h"
18
19 #include <cstddef>
20 #include <cstdint>
21 #include <initializer_list>
22 #include <string>
23 #include <vector>
24
25 enum class SkPathConvexity;
26
is_empty(skiatest::Reporter * reporter,const SkPath & p)27 static void is_empty(skiatest::Reporter* reporter, const SkPath& p) {
28 REPORTER_ASSERT(reporter, p.getBounds().isEmpty());
29 REPORTER_ASSERT(reporter, p.countPoints() == 0);
30 }
31
DEF_TEST(pathbuilder,reporter)32 DEF_TEST(pathbuilder, reporter) {
33 SkPathBuilder b;
34
35 is_empty(reporter, b.snapshot());
36 is_empty(reporter, b.detach());
37
38 b.moveTo(10, 10).lineTo(20, 20).quadTo(30, 10, 10, 20);
39
40 SkPath p0 = b.snapshot();
41 SkPath p1 = b.snapshot();
42 SkPath p2 = b.detach();
43
44 // Builders should always precompute the path's bounds, so there is no race condition later
45 REPORTER_ASSERT(reporter, SkPathPriv::HasComputedBounds(p0));
46 REPORTER_ASSERT(reporter, SkPathPriv::HasComputedBounds(p1));
47 REPORTER_ASSERT(reporter, SkPathPriv::HasComputedBounds(p2));
48
49 REPORTER_ASSERT(reporter, p0.getBounds() == SkRect::MakeLTRB(10, 10, 30, 20));
50 REPORTER_ASSERT(reporter, p0.countPoints() == 4);
51
52 REPORTER_ASSERT(reporter, p0 == p1);
53 REPORTER_ASSERT(reporter, p0 == p2);
54
55 is_empty(reporter, b.snapshot());
56 is_empty(reporter, b.detach());
57 }
58
DEF_TEST(pathbuilder_filltype,reporter)59 DEF_TEST(pathbuilder_filltype, reporter) {
60 for (auto fillType : { SkPathFillType::kWinding,
61 SkPathFillType::kEvenOdd,
62 SkPathFillType::kInverseWinding,
63 SkPathFillType::kInverseEvenOdd }) {
64 SkPathBuilder b(fillType);
65
66 REPORTER_ASSERT(reporter, b.fillType() == fillType);
67
68 for (const SkPath& path : { b.snapshot(), b.detach() }) {
69 REPORTER_ASSERT(reporter, path.getFillType() == fillType);
70 is_empty(reporter, path);
71 }
72 }
73 }
74
check_points(const SkPath & path,const SkPoint expected[],size_t count)75 static bool check_points(const SkPath& path, const SkPoint expected[], size_t count) {
76 std::vector<SkPoint> iter_pts;
77
78 for (auto [v, p, w] : SkPathPriv::Iterate(path)) {
79 switch (v) {
80 case SkPathVerb::kMove:
81 iter_pts.push_back(p[0]);
82 break;
83 case SkPathVerb::kLine:
84 iter_pts.push_back(p[1]);
85 break;
86 case SkPathVerb::kQuad:
87 case SkPathVerb::kConic:
88 iter_pts.push_back(p[1]);
89 iter_pts.push_back(p[2]);
90 break;
91 case SkPathVerb::kCubic:
92 iter_pts.push_back(p[1]);
93 iter_pts.push_back(p[2]);
94 iter_pts.push_back(p[3]);
95 break;
96 case SkPathVerb::kClose:
97 break;
98 }
99 }
100 if (iter_pts.size() != count) {
101 return false;
102 }
103 for (size_t i = 0; i < count; ++i) {
104 if (iter_pts[i] != expected[i]) {
105 return false;
106 }
107 }
108 return true;
109 }
110
DEF_TEST(pathbuilder_missing_move,reporter)111 DEF_TEST(pathbuilder_missing_move, reporter) {
112 SkPathBuilder b;
113
114 b.lineTo(10, 10).lineTo(20, 30);
115 const SkPoint pts0[] = {
116 {0, 0}, {10, 10}, {20, 30},
117 };
118 REPORTER_ASSERT(reporter, check_points(b.snapshot(), pts0, std::size(pts0)));
119
120 b.reset().moveTo(20, 20).lineTo(10, 10).lineTo(20, 30).close().lineTo(60, 60);
121 const SkPoint pts1[] = {
122 {20, 20}, {10, 10}, {20, 30},
123 {20, 20}, {60, 60},
124 };
125 REPORTER_ASSERT(reporter, check_points(b.snapshot(), pts1, std::size(pts1)));
126 }
127
DEF_TEST(pathbuilder_addRect,reporter)128 DEF_TEST(pathbuilder_addRect, reporter) {
129 const SkRect r = { 10, 20, 30, 40 };
130
131 for (int i = 0; i < 4; ++i) {
132 for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
133 SkPathBuilder b;
134 b.addRect(r, dir, i);
135 auto bp = b.detach();
136
137 SkRect r2;
138 bool closed = false;
139 SkPathDirection dir2;
140 REPORTER_ASSERT(reporter, bp.isRect(&r2, &closed, &dir2));
141 REPORTER_ASSERT(reporter, r2 == r);
142 REPORTER_ASSERT(reporter, closed);
143 REPORTER_ASSERT(reporter, dir == dir2);
144
145 SkPath p;
146 p.addRect(r, dir, i);
147 REPORTER_ASSERT(reporter, p == bp);
148 }
149 }
150 }
151
is_eq(const SkPath & a,const SkPath & b)152 static bool is_eq(const SkPath& a, const SkPath& b) {
153 if (a != b) {
154 return false;
155 }
156
157 {
158 SkRect ra, rb;
159 bool is_a = a.isOval(&ra);
160 bool is_b = b.isOval(&rb);
161 if (is_a != is_b) {
162 return false;
163 }
164 if (is_a && (ra != rb)) {
165 return false;
166 }
167 }
168
169 {
170 SkRRect rra, rrb;
171 bool is_a = a.isRRect(&rra);
172 bool is_b = b.isRRect(&rrb);
173 if (is_a != is_b) {
174 return false;
175 }
176 if (is_a && (rra != rrb)) {
177 return false;
178 }
179 }
180
181 // getConvextity() should be sufficient to test, but internally we sometimes don't want
182 // to trigger computing it, so this is the stronger test for equality.
183 {
184 SkPathConvexity ca = SkPathPriv::GetConvexityOrUnknown(a),
185 cb = SkPathPriv::GetConvexityOrUnknown(b);
186 if (ca != cb) {
187 return false;
188 }
189 }
190
191 return true;
192 }
193
DEF_TEST(pathbuilder_addOval,reporter)194 DEF_TEST(pathbuilder_addOval, reporter) {
195 const SkRect r = { 10, 20, 30, 40 };
196 SkRect tmp;
197
198 for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
199 for (int i = 0; i < 4; ++i) {
200 auto bp = SkPathBuilder().addOval(r, dir, i).detach();
201 SkPath p;
202 p.addOval(r, dir, i);
203 REPORTER_ASSERT(reporter, is_eq(p, bp));
204 }
205 auto bp = SkPathBuilder().addOval(r, dir).detach();
206 SkPath p;
207 p.addOval(r, dir);
208 REPORTER_ASSERT(reporter, is_eq(p, bp));
209
210 // test negative case -- can't have any other segments
211 bp = SkPathBuilder().addOval(r, dir).lineTo(10, 10).detach();
212 REPORTER_ASSERT(reporter, !bp.isOval(&tmp));
213 bp = SkPathBuilder().lineTo(10, 10).addOval(r, dir).detach();
214 REPORTER_ASSERT(reporter, !bp.isOval(&tmp));
215 }
216 }
217
DEF_TEST(pathbuilder_addRRect,reporter)218 DEF_TEST(pathbuilder_addRRect, reporter) {
219 const SkRRect rr = SkRRect::MakeRectXY({ 10, 20, 30, 40 }, 5, 6);
220
221 for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
222 for (int i = 0; i < 4; ++i) {
223 SkPathBuilder b;
224 b.addRRect(rr, dir, i);
225 auto bp = b.detach();
226
227 SkPath p;
228 p.addRRect(rr, dir, i);
229 REPORTER_ASSERT(reporter, is_eq(p, bp));
230 }
231 auto bp = SkPathBuilder().addRRect(rr, dir).detach();
232 SkPath p;
233 p.addRRect(rr, dir);
234 REPORTER_ASSERT(reporter, is_eq(p, bp));
235
236 // test negative case -- can't have any other segments
237 SkRRect tmp;
238 bp = SkPathBuilder().addRRect(rr, dir).lineTo(10, 10).detach();
239 REPORTER_ASSERT(reporter, !bp.isRRect(&tmp));
240 bp = SkPathBuilder().lineTo(10, 10).addRRect(rr, dir).detach();
241 REPORTER_ASSERT(reporter, !bp.isRRect(&tmp));
242 }
243 }
244
DEF_TEST(pathbuilder_make,reporter)245 DEF_TEST(pathbuilder_make, reporter) {
246 constexpr int N = 100;
247 uint8_t vbs[N];
248 SkPoint pts[N];
249
250 SkRandom rand;
251 SkPathBuilder b;
252 b.moveTo(0, 0);
253 pts[0] = {0, 0}; vbs[0] = (uint8_t)SkPathVerb::kMove;
254 for (int i = 1; i < N; ++i) {
255 float x = rand.nextF();
256 float y = rand.nextF();
257 b.lineTo(x, y);
258 pts[i] = {x, y}; vbs[i] = (uint8_t)SkPathVerb::kLine;
259 }
260 auto p0 = b.detach();
261 auto p1 = SkPath::Make(pts, N, vbs, N, nullptr, 0, p0.getFillType());
262 REPORTER_ASSERT(reporter, p0 == p1);
263 }
264
DEF_TEST(pathbuilder_genid,r)265 DEF_TEST(pathbuilder_genid, r) {
266 SkPathBuilder builder;
267
268 builder.lineTo(10, 10);
269 auto p1 = builder.snapshot();
270
271 builder.lineTo(10, 20);
272 auto p2 = builder.snapshot();
273
274 REPORTER_ASSERT(r, p1.getGenerationID() != p2.getGenerationID());
275 }
276
DEF_TEST(pathbuilder_addPolygon,reporter)277 DEF_TEST(pathbuilder_addPolygon, reporter) {
278 SkPoint pts[] = {{1, 2}, {3, 4}, {5, 6}, {7, 8}};
279
280 auto addpoly = [](const SkPoint pts[], int count, bool isClosed) {
281 SkPathBuilder builder;
282 if (count > 0) {
283 builder.moveTo(pts[0]);
284 for (int i = 1; i < count; ++i) {
285 builder.lineTo(pts[i]);
286 }
287 if (isClosed) {
288 builder.close();
289 }
290 }
291 return builder.detach();
292 };
293
294 for (bool isClosed : {false, true}) {
295 for (size_t i = 0; i <= std::size(pts); ++i) {
296 auto path0 = SkPathBuilder().addPolygon(pts, i, isClosed).detach();
297 auto path1 = addpoly(pts, i, isClosed);
298 REPORTER_ASSERT(reporter, path0 == path1);
299 }
300 }
301 }
302
DEF_TEST(pathbuilder_addPath,reporter)303 DEF_TEST(pathbuilder_addPath, reporter) {
304 const auto p = SkPath()
305 .moveTo(10, 10)
306 .lineTo(100, 10)
307 .quadTo(200, 100, 100, 200)
308 .close()
309 .moveTo(200, 200)
310 .cubicTo(210, 200, 210, 300, 200, 300)
311 .conicTo(150, 250, 100, 200, 1.4f);
312
313 REPORTER_ASSERT(reporter, p == SkPathBuilder().addPath(p).detach());
314 }
315
316 /*
317 * If paths were immutable, we would not have to track this, but until that day, we need
318 * to ensure that paths are built correctly/consistently with this field, regardless of
319 * either the classic mutable apis, or via SkPathBuilder (SkPath::Polygon uses builder).
320 */
DEF_TEST(pathbuilder_lastmoveindex,reporter)321 DEF_TEST(pathbuilder_lastmoveindex, reporter) {
322 const SkPoint pts[] = {
323 {0, 1}, {2, 3}, {4, 5},
324 };
325 constexpr int N = (int)std::size(pts);
326
327 for (int ctrCount = 1; ctrCount < 4; ++ctrCount) {
328 const int lastMoveToIndex = (ctrCount - 1) * N;
329
330 for (bool isClosed : {false, true}) {
331 SkPath a, b;
332
333 SkPathBuilder builder;
334 for (int i = 0; i < ctrCount; ++i) {
335 builder.addPolygon(pts, N, isClosed); // new-school way
336 b.addPoly(pts, N, isClosed); // old-school way
337 }
338 a = builder.detach();
339
340 // We track the last moveTo verb index, and we invert it if the last verb was a close
341 const int expected = isClosed ? ~lastMoveToIndex : lastMoveToIndex;
342 const int a_last = SkPathPriv::LastMoveToIndex(a);
343 const int b_last = SkPathPriv::LastMoveToIndex(b);
344
345 REPORTER_ASSERT(reporter, a_last == expected);
346 REPORTER_ASSERT(reporter, b_last == expected);
347 }
348 }
349 }
350
assertIsMoveTo(skiatest::Reporter * reporter,SkPathPriv::RangeIter * iter,SkScalar x0,SkScalar y0)351 static void assertIsMoveTo(skiatest::Reporter* reporter, SkPathPriv::RangeIter* iter,
352 SkScalar x0, SkScalar y0) {
353 auto [v, pts, w] = *(*iter)++;
354 REPORTER_ASSERT(reporter, v == SkPathVerb::kMove, "%d != %d (move)",
355 (int)v, (int)SkPathVerb::kMove);
356 REPORTER_ASSERT(reporter, pts[0].fX == x0, "X mismatch %f != %f", pts[0].fX, x0);
357 REPORTER_ASSERT(reporter, pts[0].fY == y0, "Y mismatch %f != %f", pts[0].fY, y0);
358 }
359
assertIsLineTo(skiatest::Reporter * reporter,SkPathPriv::RangeIter * iter,SkScalar x1,SkScalar y1)360 static void assertIsLineTo(skiatest::Reporter* reporter, SkPathPriv::RangeIter* iter,
361 SkScalar x1, SkScalar y1) {
362 auto [v, pts, w] = *(*iter)++;
363 REPORTER_ASSERT(reporter, v == SkPathVerb::kLine, "%d != %d (line)",
364 (int)v, (int)SkPathVerb::kLine);
365 // pts[0] is the moveTo before this line. See pts_backset_for_verb in SkPath::RangeIter
366 REPORTER_ASSERT(reporter, pts[1].fX == x1, "X mismatch %f != %f", pts[1].fX, x1);
367 REPORTER_ASSERT(reporter, pts[1].fY == y1, "Y mismatch %f != %f", pts[1].fY, y1);
368 }
369
assertIsDone(skiatest::Reporter * reporter,SkPathPriv::RangeIter * iter,SkPath * p)370 static void assertIsDone(skiatest::Reporter* reporter, SkPathPriv::RangeIter* iter, SkPath* p) {
371 REPORTER_ASSERT(reporter, *iter == SkPathPriv::Iterate(*p).end(), "Iterator is not done yet");
372 }
373
DEF_TEST(SkPathBuilder_lineToMoveTo,reporter)374 DEF_TEST(SkPathBuilder_lineToMoveTo, reporter) {
375 SkPathBuilder pb;
376 pb.moveTo(5, -1);
377 pb.moveTo(20, 3);
378 pb.lineTo(7, 11);
379 pb.lineTo(8, 12);
380 pb.moveTo(2, 3);
381 pb.lineTo(20, 30);
382
383 SkPath result = pb.detach();
384
385 auto iter = SkPathPriv::Iterate(result).begin();
386 assertIsMoveTo(reporter, &iter, 5, -1);
387 assertIsMoveTo(reporter, &iter, 20, 3);
388 assertIsLineTo(reporter, &iter, 7, 11);
389 assertIsLineTo(reporter, &iter, 8, 12);
390 assertIsMoveTo(reporter, &iter, 2, 3);
391 assertIsLineTo(reporter, &iter, 20, 30);
392 assertIsDone(reporter, &iter, &result);
393 }
394
DEF_TEST(SkPathBuilder_arcToPtPtRad_invalidInputsResultInALine,reporter)395 DEF_TEST(SkPathBuilder_arcToPtPtRad_invalidInputsResultInALine, reporter) {
396 auto test = [&](const std::string& name, SkPoint start, SkPoint end, SkScalar radius,
397 SkPoint expectedLineTo) {
398 SkPathBuilder pb;
399 // Remember there is an implicit moveTo(0, 0) if arcTo is the first command called.
400 pb.arcTo(start, end, radius);
401 SkPath result = pb.detach();
402
403 reporter->push(name);
404 auto iter = SkPathPriv::Iterate(result).begin();
405 assertIsMoveTo(reporter, &iter, 0, 0);
406 assertIsLineTo(reporter, &iter, expectedLineTo.fX, expectedLineTo.fY);
407 assertIsDone(reporter, &iter, &result);
408 reporter->pop();
409 };
410 // From SkPathBuilder docs:
411 // Arc is contained by tangent from last SkPath point to p1, and tangent from p1 to p2. Arc
412 // is part of circle sized to radius, positioned so it touches both tangent lines.
413 // If the values cannot construct an arc, a line to the first point is constructed instead.
414 test("first point equals previous point", {0, 0}, {1, 2}, 1, {0, 0});
415 test("two points equal", {5, 7}, {5, 7}, 1, {5, 7});
416 test("radius is zero", {-3, 5}, {-7, 11}, 0, {-3, 5});
417 test("second point equals previous point", {5, 4}, {0, 0}, 1, {5, 4});
418 }
419