Flutter Engine
The Flutter Engine
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
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);
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) {
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);
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
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
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) {
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) {
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
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
Definition: FontMgrTest.cpp:39
int count
Definition: FontMgrTest.cpp:50
static void is_empty(skiatest::Reporter *reporter, const SkPath &p)
DEF_TEST(pathbuilder, reporter)
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 REPORTER_ASSERT(r, cond,...)
Definition: Test.h:286
#define N
Definition: beziers.cpp:19
SkPathBuilder & arcTo(const SkRect &oval, SkScalar startAngleDeg, SkScalar sweepAngleDeg, bool forceMoveTo)
SkPathBuilder & lineTo(SkPoint pt)
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
Definition: SkPath.h:59
int countPoints() const
Definition: SkPath.cpp:535
SkPath & moveTo(SkScalar x, SkScalar y)
Definition: SkPath.cpp:688
SkPath & lineTo(SkScalar x, SkScalar y)
Definition: SkPath.cpp:728
static SkPath Make(const SkPoint[], int pointCount, const uint8_t[], int verbCount, const SkScalar[], int conicWeightCount, SkPathFillType, bool isVolatile=false)
Definition: SkPath.cpp:3569
SkPath & quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2)
Definition: SkPath.cpp:746
const SkRect & getBounds() const
Definition: SkPath.cpp:430
SkPath & cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar x3, SkScalar y3)
Definition: SkPath.cpp:799
SkPath & conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar w)
Definition: SkPath.cpp:766
SkPath & close()
Definition: SkPath.cpp:823
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
double y
double x
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets dir
Definition: switches.h:145
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
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