Flutter Impeller
path_unittests.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "gtest/gtest.h"
6 
7 #include "flutter/testing/testing.h"
13 
14 namespace impeller {
15 namespace testing {
16 
17 TEST(PathTest, CubicPathComponentPolylineDoesNotIncludePointOne) {
18  CubicPathComponent component({10, 10}, {20, 35}, {35, 20}, {40, 40});
19  std::vector<Point> polyline;
20  component.AppendPolylinePoints(1.0f, polyline);
21  ASSERT_NE(polyline.front().x, 10);
22  ASSERT_NE(polyline.front().y, 10);
23  ASSERT_EQ(polyline.back().x, 40);
24  ASSERT_EQ(polyline.back().y, 40);
25 }
26 
27 TEST(PathTest, EmptyPathWithContour) {
28  PathBuilder builder;
29  auto path = builder.TakePath();
30 
31  EXPECT_TRUE(path.IsEmpty());
32 }
33 
34 TEST(PathTest, PathCreatePolyLineDoesNotDuplicatePoints) {
35  PathBuilder builder;
36  builder.MoveTo({10, 10});
37  builder.LineTo({20, 20});
38  builder.LineTo({30, 30});
39  builder.MoveTo({40, 40});
40  builder.LineTo({50, 50});
41 
42  auto polyline = builder.TakePath().CreatePolyline(1.0f);
43 
44  ASSERT_EQ(polyline.contours.size(), 2u);
45  ASSERT_EQ(polyline.points->size(), 5u);
46  ASSERT_EQ(polyline.GetPoint(0).x, 10);
47  ASSERT_EQ(polyline.GetPoint(1).x, 20);
48  ASSERT_EQ(polyline.GetPoint(2).x, 30);
49  ASSERT_EQ(polyline.GetPoint(3).x, 40);
50  ASSERT_EQ(polyline.GetPoint(4).x, 50);
51 }
52 
53 TEST(PathTest, PathSingleContour) {
54  // Closed shapes.
55  {
56  Path path = PathBuilder{}.AddCircle({100, 100}, 50).TakePath();
57  EXPECT_TRUE(path.IsSingleContour());
58  }
59 
60  {
61  Path path =
62  PathBuilder{}.AddOval(Rect::MakeXYWH(100, 100, 100, 100)).TakePath();
63 
64  EXPECT_TRUE(path.IsSingleContour());
65  }
66 
67  {
68  Path path =
69  PathBuilder{}.AddRect(Rect::MakeXYWH(100, 100, 100, 100)).TakePath();
70 
71  EXPECT_TRUE(path.IsSingleContour());
72  }
73 
74  {
75  Path path = PathBuilder{}
77  Rect::MakeXYWH(100, 100, 100, 100), 10))
78  .TakePath();
79 
80  EXPECT_TRUE(path.IsSingleContour());
81  }
82 
83  // Open shapes.
84  {
85  Point p(100, 100);
86  Path path = PathBuilder{}.AddLine(p, {200, 100}).TakePath();
87 
88  EXPECT_TRUE(path.IsSingleContour());
89  }
90 
91  {
92  Path path =
93  PathBuilder{}
94  .AddCubicCurve({100, 100}, {100, 50}, {100, 150}, {200, 100})
95  .TakePath();
96 
97  EXPECT_TRUE(path.IsSingleContour());
98  }
99 
100  {
101  Path path = PathBuilder{}
102  .AddQuadraticCurve({100, 100}, {100, 50}, {200, 100})
103  .TakePath();
104 
105  EXPECT_TRUE(path.IsSingleContour());
106  }
107 }
108 
109 TEST(PathTest, PathSingleContourDoubleShapes) {
110  // Closed shapes.
111  {
112  Path path = PathBuilder{}
113  .AddCircle({100, 100}, 50)
114  .AddCircle({100, 100}, 50)
115  .TakePath();
116  EXPECT_FALSE(path.IsSingleContour());
117  }
118 
119  {
120  Path path = PathBuilder{}
121  .AddOval(Rect::MakeXYWH(100, 100, 100, 100))
122  .AddOval(Rect::MakeXYWH(100, 100, 100, 100))
123  .TakePath();
124 
125  EXPECT_FALSE(path.IsSingleContour());
126  }
127 
128  {
129  Path path = PathBuilder{}
130  .AddRect(Rect::MakeXYWH(100, 100, 100, 100))
131  .AddRect(Rect::MakeXYWH(100, 100, 100, 100))
132  .TakePath();
133 
134  EXPECT_FALSE(path.IsSingleContour());
135  }
136 
137  {
138  Path path = PathBuilder{}
140  Rect::MakeXYWH(100, 100, 100, 100), 10))
142  Rect::MakeXYWH(100, 100, 100, 100), 10))
143  .TakePath();
144 
145  EXPECT_FALSE(path.IsSingleContour());
146  }
147 
148  {
149  Path path = PathBuilder{}
151  Rect::MakeXYWH(100, 100, 100, 100), Size(10, 20)))
153  Rect::MakeXYWH(100, 100, 100, 100), Size(10, 20)))
154  .TakePath();
155 
156  EXPECT_FALSE(path.IsSingleContour());
157  }
158 
159  // Open shapes.
160  {
161  Point p(100, 100);
162  Path path =
163  PathBuilder{}.AddLine(p, {200, 100}).AddLine(p, {200, 100}).TakePath();
164 
165  EXPECT_FALSE(path.IsSingleContour());
166  }
167 
168  {
169  Path path =
170  PathBuilder{}
171  .AddCubicCurve({100, 100}, {100, 50}, {100, 150}, {200, 100})
172  .AddCubicCurve({100, 100}, {100, 50}, {100, 150}, {200, 100})
173  .TakePath();
174 
175  EXPECT_FALSE(path.IsSingleContour());
176  }
177 
178  {
179  Path path = PathBuilder{}
180  .AddQuadraticCurve({100, 100}, {100, 50}, {200, 100})
181  .Close()
182  .AddQuadraticCurve({100, 100}, {100, 50}, {200, 100})
183  .TakePath();
184 
185  EXPECT_FALSE(path.IsSingleContour());
186  }
187 }
188 
189 TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) {
190  // Closed shapes.
191  {
192  Path path = PathBuilder{}.AddCircle({100, 100}, 50).TakePath();
193  ContourComponent contour;
194  path.GetContourComponentAtIndex(0, contour);
195  EXPECT_POINT_NEAR(contour.destination, Point(100, 50));
196  EXPECT_TRUE(contour.IsClosed());
197  }
198 
199  {
200  Path path =
201  PathBuilder{}.AddOval(Rect::MakeXYWH(100, 100, 100, 100)).TakePath();
202  ContourComponent contour;
203  path.GetContourComponentAtIndex(0, contour);
204  EXPECT_POINT_NEAR(contour.destination, Point(150, 100));
205  EXPECT_TRUE(contour.IsClosed());
206  }
207 
208  {
209  Path path =
210  PathBuilder{}.AddRect(Rect::MakeXYWH(100, 100, 100, 100)).TakePath();
211  ContourComponent contour;
212  path.GetContourComponentAtIndex(0, contour);
213  EXPECT_POINT_NEAR(contour.destination, Point(100, 100));
214  EXPECT_TRUE(contour.IsClosed());
215  }
216 
217  {
218  Path path = PathBuilder{}
220  Rect::MakeXYWH(100, 100, 100, 100), 10))
221  .TakePath();
222  ContourComponent contour;
223  path.GetContourComponentAtIndex(0, contour);
224  EXPECT_POINT_NEAR(contour.destination, Point(110, 100));
225  EXPECT_TRUE(contour.IsClosed());
226  }
227 
228  {
229  Path path = PathBuilder{}
231  Rect::MakeXYWH(100, 100, 100, 100), Size(10, 20)))
232  .TakePath();
233  ContourComponent contour;
234  path.GetContourComponentAtIndex(0, contour);
235  EXPECT_POINT_NEAR(contour.destination, Point(110, 100));
236  EXPECT_TRUE(contour.IsClosed());
237  }
238 
239  // Open shapes.
240  {
241  Point p(100, 100);
242  Path path = PathBuilder{}.AddLine(p, {200, 100}).TakePath();
243  ContourComponent contour;
244  path.GetContourComponentAtIndex(0, contour);
245  ASSERT_POINT_NEAR(contour.destination, p);
246  ASSERT_FALSE(contour.IsClosed());
247  }
248 
249  {
250  Path path =
251  PathBuilder{}
252  .AddCubicCurve({100, 100}, {100, 50}, {100, 150}, {200, 100})
253  .TakePath();
254  ContourComponent contour;
255  path.GetContourComponentAtIndex(0, contour);
256  ASSERT_POINT_NEAR(contour.destination, Point(100, 100));
257  ASSERT_FALSE(contour.IsClosed());
258  }
259 
260  {
261  Path path = PathBuilder{}
262  .AddQuadraticCurve({100, 100}, {100, 50}, {200, 100})
263  .TakePath();
264  ContourComponent contour;
265  path.GetContourComponentAtIndex(0, contour);
266  ASSERT_POINT_NEAR(contour.destination, Point(100, 100));
267  ASSERT_FALSE(contour.IsClosed());
268  }
269 }
270 
271 TEST(PathTest, PathCreatePolylineGeneratesCorrectContourData) {
273  .AddLine({100, 100}, {200, 100})
274  .MoveTo({100, 200})
275  .LineTo({150, 250})
276  .LineTo({200, 200})
277  .Close()
278  .TakePath()
279  .CreatePolyline(1.0f);
280  ASSERT_EQ(polyline.points->size(), 6u);
281  ASSERT_EQ(polyline.contours.size(), 2u);
282  ASSERT_EQ(polyline.contours[0].is_closed, false);
283  ASSERT_EQ(polyline.contours[0].start_index, 0u);
284  ASSERT_EQ(polyline.contours[1].is_closed, true);
285  ASSERT_EQ(polyline.contours[1].start_index, 2u);
286 }
287 
288 TEST(PathTest, PolylineGetContourPointBoundsReturnsCorrectRanges) {
290  .AddLine({100, 100}, {200, 100})
291  .MoveTo({100, 200})
292  .LineTo({150, 250})
293  .LineTo({200, 200})
294  .Close()
295  .TakePath()
296  .CreatePolyline(1.0f);
297  size_t a1, a2, b1, b2;
298  std::tie(a1, a2) = polyline.GetContourPointBounds(0);
299  std::tie(b1, b2) = polyline.GetContourPointBounds(1);
300  ASSERT_EQ(a1, 0u);
301  ASSERT_EQ(a2, 2u);
302  ASSERT_EQ(b1, 2u);
303  ASSERT_EQ(b2, 6u);
304 }
305 
306 TEST(PathTest, PathAddRectPolylineHasCorrectContourData) {
308  .AddRect(Rect::MakeLTRB(50, 60, 70, 80))
309  .TakePath()
310  .CreatePolyline(1.0f);
311  ASSERT_EQ(polyline.contours.size(), 1u);
312  ASSERT_TRUE(polyline.contours[0].is_closed);
313  ASSERT_EQ(polyline.contours[0].start_index, 0u);
314  ASSERT_EQ(polyline.points->size(), 5u);
315  ASSERT_EQ(polyline.GetPoint(0), Point(50, 60));
316  ASSERT_EQ(polyline.GetPoint(1), Point(70, 60));
317  ASSERT_EQ(polyline.GetPoint(2), Point(70, 80));
318  ASSERT_EQ(polyline.GetPoint(3), Point(50, 80));
319  ASSERT_EQ(polyline.GetPoint(4), Point(50, 60));
320 }
321 
322 TEST(PathTest, PathPolylineDuplicatesAreRemovedForSameContour) {
324  PathBuilder{}
325  .MoveTo({50, 50})
326  .LineTo({50, 50}) // Insert duplicate at beginning of contour.
327  .LineTo({100, 50})
328  .LineTo({100, 50}) // Insert duplicate at contour join.
329  .LineTo({100, 100})
330  .Close() // Implicitly insert duplicate {50, 50} across contours.
331  .LineTo({0, 50})
332  .LineTo({0, 100})
333  .LineTo({0, 100}) // Insert duplicate at end of contour.
334  .TakePath()
335  .CreatePolyline(1.0f);
336  ASSERT_EQ(polyline.contours.size(), 2u);
337  ASSERT_EQ(polyline.contours[0].start_index, 0u);
338  ASSERT_TRUE(polyline.contours[0].is_closed);
339  ASSERT_EQ(polyline.contours[1].start_index, 4u);
340  ASSERT_FALSE(polyline.contours[1].is_closed);
341  ASSERT_EQ(polyline.points->size(), 7u);
342  ASSERT_EQ(polyline.GetPoint(0), Point(50, 50));
343  ASSERT_EQ(polyline.GetPoint(1), Point(100, 50));
344  ASSERT_EQ(polyline.GetPoint(2), Point(100, 100));
345  ASSERT_EQ(polyline.GetPoint(3), Point(50, 50));
346  ASSERT_EQ(polyline.GetPoint(4), Point(50, 50));
347  ASSERT_EQ(polyline.GetPoint(5), Point(0, 50));
348  ASSERT_EQ(polyline.GetPoint(6), Point(0, 100));
349 }
350 
351 TEST(PathTest, PolylineBufferReuse) {
352  auto point_buffer = std::make_unique<std::vector<Point>>();
353  auto point_buffer_address = reinterpret_cast<uintptr_t>(point_buffer.get());
355  PathBuilder{}
356  .MoveTo({50, 50})
357  .LineTo({100, 100})
358  .TakePath()
359  .CreatePolyline(
360  1.0f, std::move(point_buffer),
361  [point_buffer_address](
362  Path::Polyline::PointBufferPtr point_buffer) {
363  ASSERT_EQ(point_buffer->size(), 0u);
364  ASSERT_EQ(point_buffer_address,
365  reinterpret_cast<uintptr_t>(point_buffer.get()));
366  });
367 }
368 
369 TEST(PathTest, PolylineFailsWithNullptrBuffer) {
370  EXPECT_DEATH_IF_SUPPORTED(PathBuilder{}
371  .MoveTo({50, 50})
372  .LineTo({100, 100})
373  .TakePath()
374  .CreatePolyline(1.0f, nullptr),
375  "");
376 }
377 
378 TEST(PathTest, PathShifting) {
379  PathBuilder builder{};
380  auto path =
381  builder.AddLine(Point(0, 0), Point(10, 10))
382  .AddQuadraticCurve(Point(10, 10), Point(15, 15), Point(20, 20))
383  .AddCubicCurve(Point(20, 20), Point(25, 25), Point(-5, -5),
384  Point(30, 30))
385  .Close()
386  .Shift(Point(1, 1))
387  .TakePath();
388 
389  ContourComponent contour;
390  LinearPathComponent linear;
392  CubicPathComponent cubic;
393 
394  ASSERT_TRUE(path.GetContourComponentAtIndex(0, contour));
395  ASSERT_TRUE(path.GetLinearComponentAtIndex(1, linear));
396  ASSERT_TRUE(path.GetQuadraticComponentAtIndex(3, quad));
397  ASSERT_TRUE(path.GetCubicComponentAtIndex(5, cubic));
398 
399  EXPECT_EQ(contour.destination, Point(1, 1));
400 
401  EXPECT_EQ(linear.p1, Point(1, 1));
402  EXPECT_EQ(linear.p2, Point(11, 11));
403 
404  EXPECT_EQ(quad.cp, Point(16, 16));
405  EXPECT_EQ(quad.p1, Point(11, 11));
406  EXPECT_EQ(quad.p2, Point(21, 21));
407 
408  EXPECT_EQ(cubic.cp1, Point(26, 26));
409  EXPECT_EQ(cubic.cp2, Point(-4, -4));
410  EXPECT_EQ(cubic.p1, Point(21, 21));
411  EXPECT_EQ(cubic.p2, Point(31, 31));
412 }
413 
414 TEST(PathTest, PathBuilderWillComputeBounds) {
415  PathBuilder builder;
416  auto path_1 = builder.AddLine({0, 0}, {1, 1}).TakePath();
417 
418  ASSERT_EQ(path_1.GetBoundingBox().value_or(Rect::MakeMaximum()),
419  Rect::MakeLTRB(0, 0, 1, 1));
420 
421  auto path_2 = builder.AddLine({-1, -1}, {1, 1}).TakePath();
422 
423  // Verify that PathBuilder recomputes the bounds.
424  ASSERT_EQ(path_2.GetBoundingBox().value_or(Rect::MakeMaximum()),
425  Rect::MakeLTRB(-1, -1, 1, 1));
426 
427  // PathBuilder can set the bounds to whatever it wants
428  auto path_3 = builder.AddLine({0, 0}, {1, 1})
429  .SetBounds(Rect::MakeLTRB(0, 0, 100, 100))
430  .TakePath();
431 
432  ASSERT_EQ(path_3.GetBoundingBox().value_or(Rect::MakeMaximum()),
433  Rect::MakeLTRB(0, 0, 100, 100));
434 }
435 
436 TEST(PathTest, PathHorizontalLine) {
437  PathBuilder builder;
438  auto path = builder.HorizontalLineTo(10).TakePath();
439 
440  LinearPathComponent linear;
441  path.GetLinearComponentAtIndex(1, linear);
442 
443  EXPECT_EQ(linear.p1, Point(0, 0));
444  EXPECT_EQ(linear.p2, Point(10, 0));
445 }
446 
447 TEST(PathTest, PathVerticalLine) {
448  PathBuilder builder;
449  auto path = builder.VerticalLineTo(10).TakePath();
450 
451  LinearPathComponent linear;
452  path.GetLinearComponentAtIndex(1, linear);
453 
454  EXPECT_EQ(linear.p1, Point(0, 0));
455  EXPECT_EQ(linear.p2, Point(0, 10));
456 }
457 
458 TEST(PathTest, QuadradicPath) {
459  PathBuilder builder;
460  auto path = builder.QuadraticCurveTo(Point(10, 10), Point(20, 20)).TakePath();
461 
463  path.GetQuadraticComponentAtIndex(1, quad);
464 
465  EXPECT_EQ(quad.p1, Point(0, 0));
466  EXPECT_EQ(quad.cp, Point(10, 10));
467  EXPECT_EQ(quad.p2, Point(20, 20));
468 }
469 
470 TEST(PathTest, CubicPath) {
471  PathBuilder builder;
472  auto path =
473  builder.CubicCurveTo(Point(10, 10), Point(-10, -10), Point(20, 20))
474  .TakePath();
475 
476  CubicPathComponent cubic;
477  path.GetCubicComponentAtIndex(1, cubic);
478 
479  EXPECT_EQ(cubic.p1, Point(0, 0));
480  EXPECT_EQ(cubic.cp1, Point(10, 10));
481  EXPECT_EQ(cubic.cp2, Point(-10, -10));
482  EXPECT_EQ(cubic.p2, Point(20, 20));
483 }
484 
485 TEST(PathTest, BoundingBoxCubic) {
486  PathBuilder builder;
487  auto path =
488  builder.AddCubicCurve({120, 160}, {25, 200}, {220, 260}, {220, 40})
489  .TakePath();
490  auto box = path.GetBoundingBox();
491  Rect expected = Rect::MakeXYWH(93.9101, 40, 126.09, 158.862);
492  ASSERT_TRUE(box.has_value());
493  ASSERT_RECT_NEAR(box.value_or(Rect::MakeMaximum()), expected);
494 }
495 
496 TEST(PathTest, BoundingBoxOfCompositePathIsCorrect) {
497  PathBuilder builder;
498  builder.AddRoundRect(
499  RoundRect::MakeRectRadius(Rect::MakeXYWH(10, 10, 300, 300), 50));
500  auto path = builder.TakePath();
501  auto actual = path.GetBoundingBox();
502  Rect expected = Rect::MakeXYWH(10, 10, 300, 300);
503 
504  ASSERT_TRUE(actual.has_value());
505  ASSERT_RECT_NEAR(actual.value_or(Rect::MakeMaximum()), expected);
506 }
507 
508 TEST(PathTest, ExtremaOfCubicPathComponentIsCorrect) {
509  CubicPathComponent cubic{{11.769268, 252.883148},
510  {-6.2857933, 204.356461},
511  {-4.53997231, 156.552902},
512  {17.0067291, 109.472488}};
513  auto points = cubic.Extrema();
514 
515  ASSERT_EQ(points.size(), static_cast<size_t>(3));
516  ASSERT_POINT_NEAR(points[2], cubic.Solve(0.455916));
517 }
518 
519 TEST(PathTest, PathGetBoundingBoxForCubicWithNoDerivativeRootsIsCorrect) {
520  PathBuilder builder;
521  // Straight diagonal line.
522  builder.AddCubicCurve({0, 1}, {2, 3}, {4, 5}, {6, 7});
523  auto path = builder.TakePath();
524  auto actual = path.GetBoundingBox();
525  auto expected = Rect::MakeLTRB(0, 1, 6, 7);
526 
527  ASSERT_TRUE(actual.has_value());
528  ASSERT_RECT_NEAR(actual.value_or(Rect::MakeMaximum()), expected);
529 }
530 
531 TEST(PathTest, EmptyPath) {
532  auto path = PathBuilder{}.TakePath();
533  ASSERT_EQ(path.GetComponentCount(), 1u);
534 
536  path.GetContourComponentAtIndex(0, c);
538 
539  Path::Polyline polyline = path.CreatePolyline(1.0f);
540  ASSERT_TRUE(polyline.points->empty());
541  ASSERT_TRUE(polyline.contours.empty());
542 }
543 
544 TEST(PathTest, SimplePath) {
545  PathBuilder builder;
546 
547  auto path = builder.AddLine({0, 0}, {100, 100})
548  .AddQuadraticCurve({100, 100}, {200, 200}, {300, 300})
549  .AddCubicCurve({300, 300}, {400, 400}, {500, 500}, {600, 600})
550  .TakePath();
551 
552  EXPECT_EQ(path.GetComponentCount(), 6u);
553  EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kLinear), 1u);
554  EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kQuadratic), 1u);
555  EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kCubic), 1u);
556  EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kContour), 3u);
557 
558  {
559  LinearPathComponent linear;
560  EXPECT_TRUE(path.GetLinearComponentAtIndex(1, linear));
561 
562  Point p1(0, 0);
563  Point p2(100, 100);
564  EXPECT_EQ(linear.p1, p1);
565  EXPECT_EQ(linear.p2, p2);
566  }
567 
568  {
570  EXPECT_TRUE(path.GetQuadraticComponentAtIndex(3, quad));
571 
572  Point p1(100, 100);
573  Point cp(200, 200);
574  Point p2(300, 300);
575  EXPECT_EQ(quad.p1, p1);
576  EXPECT_EQ(quad.cp, cp);
577  EXPECT_EQ(quad.p2, p2);
578  }
579 
580  {
581  CubicPathComponent cubic;
582  EXPECT_TRUE(path.GetCubicComponentAtIndex(5, cubic));
583 
584  Point p1(300, 300);
585  Point cp1(400, 400);
586  Point cp2(500, 500);
587  Point p2(600, 600);
588  EXPECT_EQ(cubic.p1, p1);
589  EXPECT_EQ(cubic.cp1, cp1);
590  EXPECT_EQ(cubic.cp2, cp2);
591  EXPECT_EQ(cubic.p2, p2);
592  }
593 
594  {
595  ContourComponent contour;
596  EXPECT_TRUE(path.GetContourComponentAtIndex(0, contour));
597 
598  Point p1(0, 0);
599  EXPECT_EQ(contour.destination, p1);
600  EXPECT_FALSE(contour.IsClosed());
601  }
602 
603  {
604  ContourComponent contour;
605  EXPECT_TRUE(path.GetContourComponentAtIndex(2, contour));
606 
607  Point p1(100, 100);
608  EXPECT_EQ(contour.destination, p1);
609  EXPECT_FALSE(contour.IsClosed());
610  }
611 
612  {
613  ContourComponent contour;
614  EXPECT_TRUE(path.GetContourComponentAtIndex(4, contour));
615 
616  Point p1(300, 300);
617  EXPECT_EQ(contour.destination, p1);
618  EXPECT_FALSE(contour.IsClosed());
619  }
620 }
621 
622 TEST(PathTest, RepeatCloseDoesNotAddNewLines) {
623  PathBuilder builder;
624  auto path = builder.LineTo({0, 10})
625  .LineTo({10, 10})
626  .Close() // Returns to (0, 0)
627  .Close() // No Op
628  .Close() // Still No op
629  .TakePath();
630 
631  EXPECT_EQ(path.GetComponentCount(), 5u);
632  EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kLinear), 3u);
633  EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kContour), 2u);
634 }
635 
636 TEST(PathTest, CloseAfterMoveDoesNotAddNewLines) {
637  PathBuilder builder;
638  auto path = builder.LineTo({0, 10})
639  .LineTo({10, 10})
640  .MoveTo({30, 30}) // Moves to (30, 30)
641  .Close() // No Op
642  .Close() // Still No op
643  .TakePath();
644 
645  EXPECT_EQ(path.GetComponentCount(), 4u);
646  EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kLinear), 2u);
647  EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kContour), 2u);
648 }
649 
650 TEST(PathTest, CloseAtOriginDoesNotAddNewLineSegment) {
651  PathBuilder builder;
652  // Create a path that has a current position at the origin when close is
653  // called. This should not insert a new line segment
654  auto path = builder.LineTo({10, 0})
655  .LineTo({10, 10})
656  .LineTo({0, 10})
657  .LineTo({0, 0})
658  .Close()
659  .TakePath();
660 
661  EXPECT_EQ(path.GetComponentCount(), 6u);
662  EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kLinear), 4u);
663  EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kContour), 2u);
664 }
665 
666 TEST(PathTest, CanBeCloned) {
667  PathBuilder builder;
668  builder.MoveTo({10, 10});
669  builder.LineTo({20, 20});
670  builder.SetBounds(Rect::MakeLTRB(0, 0, 100, 100));
672 
673  auto path_a = builder.TakePath(FillType::kOdd);
674  // NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
675  auto path_b = path_a;
676 
677  EXPECT_EQ(path_a.GetBoundingBox(), path_b.GetBoundingBox());
678  EXPECT_EQ(path_a.GetFillType(), path_b.GetFillType());
679  EXPECT_EQ(path_a.IsConvex(), path_b.IsConvex());
680 
681  auto poly_a = path_a.CreatePolyline(1.0);
682  auto poly_b = path_b.CreatePolyline(1.0);
683 
684  ASSERT_EQ(poly_a.points->size(), poly_b.points->size());
685  ASSERT_EQ(poly_a.contours.size(), poly_b.contours.size());
686 
687  for (auto i = 0u; i < poly_a.points->size(); i++) {
688  EXPECT_EQ((*poly_a.points)[i], (*poly_b.points)[i]);
689  }
690 
691  for (auto i = 0u; i < poly_a.contours.size(); i++) {
692  EXPECT_EQ(poly_a.contours[i].start_index, poly_b.contours[i].start_index);
693  EXPECT_EQ(poly_a.contours[i].start_direction,
694  poly_b.contours[i].start_direction);
695  }
696 }
697 
698 TEST(PathTest, FanTessellation) {
699  Path path = PathBuilder{}
701  Rect::MakeLTRB(0, 0, 100, 100), 10))
702  .TakePath();
703  auto [points, contours] = path.CountStorage(1.0);
704 
705  std::vector<Point> point_storage(points);
706  std::vector<uint16_t> index_storage(points + (contours - 1));
707 
708  FanVertexWriter writer(point_storage.data(), index_storage.data());
709  path.WritePolyline(1.0, writer);
710 
711  EXPECT_LE(writer.GetIndexCount(), index_storage.size());
712  EXPECT_EQ(point_storage[0], Point(10, 0));
713 }
714 
715 // Filled Paths without an explicit close should still be closed
716 TEST(PathTest, FanTessellationUnclosedPath) {
717  // Create a rectangle that lacks an explicit close.
718  Path path = PathBuilder{}
719  .LineTo({100, 0})
720  .LineTo({100, 100})
721  .LineTo({0, 100})
722  .TakePath();
723 
724  std::vector<Point> expected = {{0, 0}, {100, 0}, {100, 100},
725  {0, 100}, {0, 0}, {0, 0}};
726  std::vector<uint16_t> expected_indices = {0, 1, 2, 3, 0xFFFF, 0};
727 
728  auto [points, contours] = path.CountStorage(1.0);
729 
730  std::vector<Point> point_storage(points);
731  std::vector<uint16_t> index_storage(points + (contours - 1));
732 
733  FanVertexWriter writer(point_storage.data(), index_storage.data());
734  path.WritePolyline(1.0, writer);
735 
736  EXPECT_LE(index_storage, expected_indices);
737  EXPECT_EQ(point_storage, expected);
738 }
739 
740 // Filled Paths without an explicit close should still be closed
741 TEST(PathTest, StripTessellationUnclosedPath) {
742  // Create a rectangle that lacks an explicit close.
743  Path path = PathBuilder{}
744  .LineTo({100, 0})
745  .LineTo({100, 100})
746  .LineTo({0, 100})
747  .TakePath();
748 
749  std::vector<Point> expected = {{0, 0}, {100, 0}, {100, 100},
750  {0, 100}, {0, 0}, {0, 0}};
751  std::vector<uint16_t> expected_indices = {0, 1, 3, 2, 0xFFFF, 0};
752 
753  auto [points, contours] = path.CountStorage(1.0);
754 
755  std::vector<Point> point_storage(points);
756  std::vector<uint16_t> index_storage(points + (contours - 1));
757 
758  StripVertexWriter writer(point_storage.data(), index_storage.data());
759  path.WritePolyline(1.0, writer);
760 
761  EXPECT_LE(index_storage, expected_indices);
762  EXPECT_EQ(point_storage, expected);
763 }
764 
765 TEST(PathTest, FanTessellationMultiContour) {
766  PathBuilder builder{};
767  for (auto i = 0; i < 10; i++) {
768  builder.AddRoundRect(
769  RoundRect::MakeRectRadius(Rect::MakeLTRB(0 + i, 0 + i, 100, 100), 10));
770  }
771  auto path = builder.TakePath();
772  auto [points, contours] = path.CountStorage(1.0);
773 
774  std::vector<Point> point_storage(points);
775  std::vector<uint16_t> index_storage(points + (contours - 1));
776 
777  FanVertexWriter writer(point_storage.data(), index_storage.data());
778  path.WritePolyline(1.0, writer);
779 
780  EXPECT_LE(writer.GetIndexCount(), index_storage.size());
781  EXPECT_EQ(point_storage[0], Point(10, 0));
782 }
783 
784 TEST(PathTest, StripTessellation) {
785  Path path = PathBuilder{}
787  Rect::MakeLTRB(0, 0, 100, 100), 10))
788  .TakePath();
789  auto [points, contours] = path.CountStorage(1.0);
790 
791  std::vector<Point> point_storage(points);
792  std::vector<uint16_t> index_storage(points + (contours - 1));
793 
794  StripVertexWriter writer(point_storage.data(), index_storage.data());
795  path.WritePolyline(1.0, writer);
796 
797  EXPECT_LE(writer.GetIndexCount(), index_storage.size());
798  EXPECT_EQ(point_storage[0], Point(10, 0));
799 }
800 
801 TEST(PathTest, StripTessellationMultiContour) {
802  PathBuilder builder{};
803  for (auto i = 0; i < 10; i++) {
804  builder.AddRoundRect(
805  RoundRect::MakeRectRadius(Rect::MakeLTRB(0 + i, 0 + i, 100, 100), 10));
806  }
807  auto path = builder.TakePath();
808  auto [points, contours] = path.CountStorage(1.0);
809 
810  std::vector<Point> point_storage(points);
811  std::vector<uint16_t> index_storage(points + (contours - 1));
812 
813  StripVertexWriter writer(point_storage.data(), index_storage.data());
814  path.WritePolyline(1.0, writer);
815 
816  EXPECT_LE(writer.GetIndexCount(), index_storage.size());
817  EXPECT_EQ(point_storage[0], Point(10, 0));
818 }
819 
820 TEST(PathTest, PathBuilderDoesNotMutateCopiedPaths) {
821  auto test_isolation =
822  [](const std::function<void(PathBuilder & builder)>& mutator,
823  bool will_close, Point mutation_offset, const std::string& label) {
824  PathBuilder builder;
825  builder.MoveTo({10, 10});
826  builder.LineTo({20, 20});
827  builder.LineTo({20, 10});
828 
829  auto verify_path = [](const Path& path, bool is_mutated, bool is_closed,
830  Point offset, const std::string& label) {
831  if (is_mutated) {
832  // We can only test the initial state before the mutator did
833  // its work. We have >= 3 components and the first 3 components
834  // will match what we saw before the mutation.
835  EXPECT_GE(path.GetComponentCount(), 3u) << label;
836  } else {
837  EXPECT_EQ(path.GetComponentCount(), 3u) << label;
838  }
839  {
840  ContourComponent contour;
841  EXPECT_TRUE(path.GetContourComponentAtIndex(0, contour)) << label;
842  EXPECT_EQ(contour.destination, offset + Point(10, 10)) << label;
843  EXPECT_EQ(contour.IsClosed(), is_closed) << label;
844  }
845  {
846  LinearPathComponent line;
847  EXPECT_TRUE(path.GetLinearComponentAtIndex(1, line)) << label;
848  EXPECT_EQ(line.p1, offset + Point(10, 10)) << label;
849  EXPECT_EQ(line.p2, offset + Point(20, 20)) << label;
850  }
851  {
852  LinearPathComponent line;
853  EXPECT_TRUE(path.GetLinearComponentAtIndex(2, line)) << label;
854  EXPECT_EQ(line.p1, offset + Point(20, 20)) << label;
855  EXPECT_EQ(line.p2, offset + Point(20, 10)) << label;
856  }
857  };
858 
859  auto path1 = builder.CopyPath();
860  verify_path(path1, false, false, {},
861  "Initial Path1 state before " + label);
862 
863  for (int i = 0; i < 10; i++) {
864  auto path = builder.CopyPath();
865  verify_path(
866  path, false, false, {},
867  "Extra CopyPath #" + std::to_string(i + 1) + " for " + label);
868  }
869  mutator(builder);
870  verify_path(path1, false, false, {},
871  "Path1 state after subsequent " + label);
872 
873  auto path2 = builder.CopyPath();
874  verify_path(path1, false, false, {},
875  "Path1 state after subsequent " + label + " and CopyPath");
876  verify_path(path2, true, will_close, mutation_offset,
877  "Initial Path2 state with subsequent " + label);
878  };
879 
880  test_isolation(
881  [](PathBuilder& builder) { //
883  },
884  false, {}, "SetConvex");
885 
886  test_isolation(
887  [](PathBuilder& builder) { //
889  },
890  false, {}, "SetUnknownConvex");
891 
892  test_isolation(
893  [](PathBuilder& builder) { //
894  builder.Close();
895  },
896  true, {}, "Close");
897 
898  test_isolation(
899  [](PathBuilder& builder) {
900  builder.MoveTo({20, 30}, false);
901  },
902  false, {}, "Absolute MoveTo");
903 
904  test_isolation(
905  [](PathBuilder& builder) {
906  builder.MoveTo({20, 30}, true);
907  },
908  false, {}, "Relative MoveTo");
909 
910  test_isolation(
911  [](PathBuilder& builder) {
912  builder.LineTo({20, 30}, false);
913  },
914  false, {}, "Absolute LineTo");
915 
916  test_isolation(
917  [](PathBuilder& builder) {
918  builder.LineTo({20, 30}, true);
919  },
920  false, {}, "Relative LineTo");
921 
922  test_isolation(
923  [](PathBuilder& builder) { //
924  builder.HorizontalLineTo(100, false);
925  },
926  false, {}, "Absolute HorizontalLineTo");
927 
928  test_isolation(
929  [](PathBuilder& builder) { //
930  builder.HorizontalLineTo(100, true);
931  },
932  false, {}, "Relative HorizontalLineTo");
933 
934  test_isolation(
935  [](PathBuilder& builder) { //
936  builder.VerticalLineTo(100, false);
937  },
938  false, {}, "Absolute VerticalLineTo");
939 
940  test_isolation(
941  [](PathBuilder& builder) { //
942  builder.VerticalLineTo(100, true);
943  },
944  false, {}, "Relative VerticalLineTo");
945 
946  test_isolation(
947  [](PathBuilder& builder) {
948  builder.QuadraticCurveTo({20, 30}, {30, 20}, false);
949  },
950  false, {}, "Absolute QuadraticCurveTo");
951 
952  test_isolation(
953  [](PathBuilder& builder) {
954  builder.QuadraticCurveTo({20, 30}, {30, 20}, true);
955  },
956  false, {}, "Relative QuadraticCurveTo");
957 
958  test_isolation(
959  [](PathBuilder& builder) {
960  builder.CubicCurveTo({20, 30}, {30, 20}, {30, 30}, false);
961  },
962  false, {}, "Absolute CubicCurveTo");
963 
964  test_isolation(
965  [](PathBuilder& builder) {
966  builder.CubicCurveTo({20, 30}, {30, 20}, {30, 30}, true);
967  },
968  false, {}, "Relative CubicCurveTo");
969 
970  test_isolation(
971  [](PathBuilder& builder) {
972  builder.AddLine({100, 100}, {150, 100});
973  },
974  false, {}, "AddLine");
975 
976  test_isolation(
977  [](PathBuilder& builder) {
978  builder.AddRect(Rect::MakeLTRB(100, 100, 120, 120));
979  },
980  false, {}, "AddRect");
981 
982  test_isolation(
983  [](PathBuilder& builder) {
984  builder.AddOval(Rect::MakeLTRB(100, 100, 120, 120));
985  },
986  false, {}, "AddOval");
987 
988  test_isolation(
989  [](PathBuilder& builder) {
990  builder.AddCircle({100, 100}, 20);
991  },
992  false, {}, "AddCircle");
993 
994  test_isolation(
995  [](PathBuilder& builder) {
996  builder.AddArc(Rect::MakeLTRB(100, 100, 120, 120), Degrees(10),
997  Degrees(170));
998  },
999  false, {}, "AddArc");
1000 
1001  test_isolation(
1002  [](PathBuilder& builder) {
1003  builder.AddQuadraticCurve({100, 100}, {150, 100}, {150, 150});
1004  },
1005  false, {}, "AddQuadraticCurve");
1006 
1007  test_isolation(
1008  [](PathBuilder& builder) {
1009  builder.AddCubicCurve({100, 100}, {150, 100}, {100, 150}, {150, 150});
1010  },
1011  false, {}, "AddCubicCurve");
1012 
1013  test_isolation(
1014  [](PathBuilder& builder) {
1015  builder.Shift({23, 42});
1016  },
1017  false, {23, 42}, "Shift");
1018 }
1019 
1020 } // namespace testing
1021 } // namespace impeller
A vertex writer that generates a triangle fan and requires primitive restart.
size_t GetIndexCount() const
PathBuilder & AddRect(Rect rect)
Path TakePath(FillType fill=FillType::kNonZero)
Definition: path_builder.cc:28
PathBuilder & AddArc(const Rect &oval_bounds, Radians start, Radians sweep, bool use_center=false)
PathBuilder & AddRoundRect(RoundRect rect)
PathBuilder & LineTo(Point point, bool relative=false)
Insert a line from the current position to point.
Definition: path_builder.cc:64
PathBuilder & MoveTo(Point point, bool relative=false)
Definition: path_builder.cc:45
PathBuilder & SetBounds(Rect bounds)
Set the bounding box that will be used by Path.GetBoundingBox in place of performing the computation.
PathBuilder & AddOval(const Rect &rect)
PathBuilder & AddCircle(const Point &center, Scalar radius)
PathBuilder & Close()
Definition: path_builder.cc:52
Path CopyPath(FillType fill=FillType::kNonZero)
Definition: path_builder.cc:19
PathBuilder & VerticalLineTo(Scalar y, bool relative=false)
Definition: path_builder.cc:79
PathBuilder & Shift(Point offset)
Transform the existing path segments and contours by the given offset.
PathBuilder & AddLine(const Point &p1, const Point &p2)
Move to point p1, then insert a line from p1 to p2.
PathBuilder & AddQuadraticCurve(Point p1, Point cp, Point p2)
Move to point p1, then insert a quadradic curve from p1 to p2 with the control point cp.
PathBuilder & CubicCurveTo(Point controlPoint1, Point controlPoint2, Point point, bool relative=false)
Insert a cubic curve from the curren position to point using the control points controlPoint1 and con...
PathBuilder & AddCubicCurve(Point p1, Point cp1, Point cp2, Point p2)
Move to point p1, then insert a cubic curve from p1 to p2 with control points cp1 and cp2.
PathBuilder & HorizontalLineTo(Scalar x, bool relative=false)
Definition: path_builder.cc:71
PathBuilder & SetConvexity(Convexity value)
Definition: path_builder.cc:97
PathBuilder & QuadraticCurveTo(Point controlPoint, Point point, bool relative=false)
Insert a quadradic curve from the current position to point using the control point controlPoint.
Definition: path_builder.cc:87
Paths are lightweight objects that describe a collection of linear, quadratic, or cubic segments....
Definition: path.h:53
size_t GetComponentCount(std::optional< ComponentType > type={}) const
Definition: path.cc:34
bool GetContourComponentAtIndex(size_t index, ContourComponent &contour) const
Definition: path.cc:237
Polyline CreatePolyline(Scalar scale, Polyline::PointBufferPtr point_buffer=std::make_unique< std::vector< Point >>(), Polyline::ReclaimPointBufferCallback reclaim=nullptr) const
Definition: path.cc:338
bool IsSingleContour() const
Whether the line contains a single contour.
Definition: path.cc:62
bool GetLinearComponentAtIndex(size_t index, LinearPathComponent &linear) const
Definition: path.cc:178
void WritePolyline(Scalar scale, VertexWriter &writer) const
Definition: path.cc:105
std::optional< Rect > GetBoundingBox() const
Definition: path.cc:436
std::pair< size_t, size_t > CountStorage(Scalar scale) const
Determine required storage for points and number of contours.
Definition: path.cc:67
A vertex writer that generates a triangle strip and requires primitive restart.
#define ASSERT_RECT_NEAR(a, b)
#define ASSERT_POINT_NEAR(a, b)
#define EXPECT_POINT_NEAR(a, b)
TEST(AllocationSizeTest, CanCreateTypedAllocations)
TPoint< Scalar > Point
Definition: point.h:327
TSize< Scalar > Size
Definition: size.h:171
void MoveTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:20
void LineTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:24
void Close(PathBuilder *builder)
Definition: tessellator.cc:38
const Path::Polyline & polyline
SeparatedVector2 offset
constexpr bool IsClosed() const
std::vector< Point > Extrema() const
std::unique_ptr< std::vector< Point > > PointBufferPtr
Definition: path.h:114
constexpr static RoundRect MakeRectRadius(const Rect &rect, Scalar radius)
Definition: round_rect.h:26
constexpr static RoundRect MakeRectXY(const Rect &rect, Scalar x_radius, Scalar y_radius)
Definition: round_rect.h:30
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
constexpr static TRect MakeMaximum()
Definition: rect.h:188