Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
PathBuilderTest.cpp
Go to the documentation of this file.
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
13#include "include/core/SkRect.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
25enum class SkPathConvexity;
26
27static 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
32DEF_TEST(pathbuilder, reporter) {
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
48
49 REPORTER_ASSERT(reporter, p0.getBounds() == SkRect::MakeLTRB(10, 10, 30, 20));
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
59DEF_TEST(pathbuilder_filltype, reporter) {
60 for (auto fillType : { SkPathFillType::kWinding,
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
75static 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) {
81 iter_pts.push_back(p[0]);
82 break;
84 iter_pts.push_back(p[1]);
85 break;
88 iter_pts.push_back(p[1]);
89 iter_pts.push_back(p[2]);
90 break;
92 iter_pts.push_back(p[1]);
93 iter_pts.push_back(p[2]);
94 iter_pts.push_back(p[3]);
95 break;
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
111DEF_TEST(pathbuilder_missing_move, reporter) {
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
128DEF_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}) {
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
152static 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 {
186 if (ca != cb) {
187 return false;
188 }
189 }
190
191 return true;
192}
193
194DEF_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);
204 }
205 auto bp = SkPathBuilder().addOval(r, dir).detach();
206 SkPath p;
207 p.addOval(r, dir);
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
218DEF_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) {
224 b.addRRect(rr, dir, i);
225 auto bp = b.detach();
226
227 SkPath p;
228 p.addRRect(rr, dir, i);
230 }
231 auto bp = SkPathBuilder().addRRect(rr, dir).detach();
232 SkPath p;
233 p.addRRect(rr, dir);
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
245DEF_TEST(pathbuilder_make, reporter) {
246 constexpr int N = 100;
247 uint8_t vbs[N];
248 SkPoint pts[N];
249
250 SkRandom rand;
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
265DEF_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
277DEF_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
303DEF_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 */
321DEF_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
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
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
371 REPORTER_ASSERT(reporter, *iter == SkPathPriv::Iterate(*p).end(), "Iterator is not done yet");
372}
373
374DEF_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
395DEF_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}
#define test(name)
reporter
int count
static void is_empty(skiatest::Reporter *reporter, const SkPath &p)
static bool is_eq(const SkPath &a, const SkPath &b)
static void assertIsMoveTo(skiatest::Reporter *reporter, SkPathPriv::RangeIter *iter, SkScalar x0, SkScalar y0)
static bool check_points(const SkPath &path, const SkPoint expected[], size_t count)
static void assertIsDone(skiatest::Reporter *reporter, SkPathPriv::RangeIter *iter, SkPath *p)
static void assertIsLineTo(skiatest::Reporter *reporter, SkPathPriv::RangeIter *iter, SkScalar x1, SkScalar y1)
static SkPath path1()
SkPathConvexity
Definition SkPathEnums.h:13
SkPathDirection
Definition SkPathTypes.h:34
@ kClose
SkPath::RawIter returns 0 points.
@ kCubic
SkPath::RawIter returns 4 points.
@ kConic
SkPath::RawIter returns 3 points + 1 weight.
@ kQuad
SkPath::RawIter returns 3 points.
@ kMove
SkPath::RawIter returns 1 point.
@ kLine
SkPath::RawIter returns 2 points.
#define DEF_TEST(name, reporter)
Definition Test.h:312
#define REPORTER_ASSERT(r, cond,...)
Definition Test.h:286
Type::kYUV Type::kRGBA() int(0.7 *637)
#define N
Definition beziers.cpp:19
SkPathBuilder & arcTo(const SkRect &oval, SkScalar startAngleDeg, SkScalar sweepAngleDeg, bool forceMoveTo)
SkPathBuilder & lineTo(SkPoint pt)
SkPathBuilder & addRect(const SkRect &, SkPathDirection, unsigned startIndex)
SkPathBuilder & addRRect(const SkRRect &, SkPathDirection, unsigned startIndex)
SkPathBuilder & moveTo(SkPoint pt)
SkPathBuilder & addOval(const SkRect &, SkPathDirection, unsigned startIndex)
SkPathBuilder & addPolygon(const SkPoint pts[], int count, bool isClosed)
static int LastMoveToIndex(const SkPath &path)
Definition SkPathPriv.h:348
static bool HasComputedBounds(const SkPath &path)
Definition SkPathPriv.h:223
static SkPathConvexity GetConvexityOrUnknown(const SkPath &path)
Definition SkPathPriv.h:410
SkPath::RangeIter RangeIter
Definition SkPathPriv.h:164
SkPath snapshot() const
Definition SkPath.h:142
int countPoints() const
Definition SkPath.cpp:525
SkPath & moveTo(SkScalar x, SkScalar y)
Definition SkPath.cpp:678
SkPath & lineTo(SkScalar x, SkScalar y)
Definition SkPath.cpp:718
static SkPath Make(const SkPoint[], int pointCount, const uint8_t[], int verbCount, const SkScalar[], int conicWeightCount, SkPathFillType, bool isVolatile=false)
Definition SkPath.cpp:3501
SkPath & quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2)
Definition SkPath.cpp:736
SkPath detach()
Definition SkPath.h:147
const SkRect & getBounds() const
Definition SkPath.cpp:420
SkPath & cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar x3, SkScalar y3)
Definition SkPath.cpp:789
SkPath & conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar w)
Definition SkPath.cpp:756
SkPath & close()
Definition SkPath.cpp:813
static SkRRect MakeRectXY(const SkRect &rect, SkScalar xRad, SkScalar yRad)
Definition SkRRect.h:180
float nextF()
Definition SkRandom.h:55
float SkScalar
Definition extension.cpp:12
static bool b
struct MyStruct a[10]
glong glong end
GAsyncResult * result
const char * name
Definition fuchsia.cc:50
double y
double x
SkScalar w
SkPath::RangeIter end()
Definition SkPathPriv.h:187
SkPath::RangeIter begin()
Definition SkPathPriv.h:186
static constexpr SkRect MakeLTRB(float l, float t, float r, float b)
Definition SkRect.h:646