Flutter Impeller
geometry_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 <memory>
6 
7 #include "flutter/display_list/geometry/dl_path_builder.h"
8 #include "flutter/testing/testing.h"
9 #include "gtest/gtest.h"
17 #include "impeller/renderer/testing/mocks.h"
18 
19 inline ::testing::AssertionResult SolidVerticesNear(
20  std::vector<impeller::Point> a,
21  std::vector<impeller::Point> b) {
22  if (a.size() != b.size()) {
23  return ::testing::AssertionFailure() << "Colors length does not match";
24  }
25  for (auto i = 0u; i < b.size(); i++) {
26  if (!PointNear(a[i], b[i])) {
27  return ::testing::AssertionFailure() << "Positions are not equal.";
28  }
29  }
30  return ::testing::AssertionSuccess();
31 }
32 
33 inline ::testing::AssertionResult TextureVerticesNear(
34  std::vector<impeller::TextureFillVertexShader::PerVertexData> a,
35  std::vector<impeller::TextureFillVertexShader::PerVertexData> b) {
36  if (a.size() != b.size()) {
37  return ::testing::AssertionFailure() << "Colors length does not match";
38  }
39  for (auto i = 0u; i < b.size(); i++) {
40  if (!PointNear(a[i].position, b[i].position)) {
41  return ::testing::AssertionFailure() << "Positions are not equal.";
42  }
43  if (!PointNear(a[i].texture_coords, b[i].texture_coords)) {
44  return ::testing::AssertionFailure() << "Texture coords are not equal.";
45  }
46  }
47  return ::testing::AssertionSuccess();
48 }
49 
50 #define EXPECT_SOLID_VERTICES_NEAR(a, b) \
51  EXPECT_PRED2(&::SolidVerticesNear, a, b)
52 #define EXPECT_TEXTURE_VERTICES_NEAR(a, b) \
53  EXPECT_PRED2(&::TextureVerticesNear, a, b)
54 
55 namespace impeller {
56 
58  public:
59  static std::vector<Point> GenerateSolidStrokeVertices(
60  const PathSource& path,
61  const StrokeParameters& stroke,
62  Scalar scale) {
63  // We could create a single Tessellator instance for the whole suite,
64  // but we don't really need performance for unit tests.
65  Tessellator tessellator;
66  return StrokePathGeometry::GenerateSolidStrokeVertices( //
67  tessellator, path, stroke, scale);
68  }
69 };
70 
71 namespace testing {
72 
73 TEST(EntityGeometryTest, RectGeometryCoversArea) {
74  auto geometry = Geometry::MakeRect(Rect::MakeLTRB(0, 0, 100, 100));
75  ASSERT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(0, 0, 100, 100)));
76  ASSERT_FALSE(geometry->CoversArea({}, Rect::MakeLTRB(-1, 0, 100, 100)));
77  ASSERT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(1, 1, 100, 100)));
78  ASSERT_TRUE(geometry->CoversArea({}, Rect()));
79 }
80 
81 TEST(EntityGeometryTest, FillPathGeometryCoversArea) {
82  auto path = flutter::DlPathBuilder{}
83  .AddRect(Rect::MakeLTRB(0, 0, 100, 100))
84  .TakePath();
85  auto geometry = Geometry::MakeFillPath(
86  path, /* inner rect */ Rect::MakeLTRB(0, 0, 100, 100));
87  ASSERT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(0, 0, 100, 100)));
88  ASSERT_FALSE(geometry->CoversArea({}, Rect::MakeLTRB(-1, 0, 100, 100)));
89  ASSERT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(1, 1, 100, 100)));
90  ASSERT_TRUE(geometry->CoversArea({}, Rect()));
91 }
92 
93 TEST(EntityGeometryTest, FillPathGeometryCoversAreaNoInnerRect) {
94  auto path = flutter::DlPathBuilder{}
95  .AddRect(Rect::MakeLTRB(0, 0, 100, 100))
96  .TakePath();
97  auto geometry = Geometry::MakeFillPath(path);
98  ASSERT_FALSE(geometry->CoversArea({}, Rect::MakeLTRB(0, 0, 100, 100)));
99  ASSERT_FALSE(geometry->CoversArea({}, Rect::MakeLTRB(-1, 0, 100, 100)));
100  ASSERT_FALSE(geometry->CoversArea({}, Rect::MakeLTRB(1, 1, 100, 100)));
101  ASSERT_FALSE(geometry->CoversArea({}, Rect()));
102 }
103 
104 TEST(EntityGeometryTest, FillArcGeometryCoverage) {
105  Rect oval_bounds = Rect::MakeLTRB(100, 100, 200, 200);
106  Matrix transform45 = Matrix::MakeTranslation(oval_bounds.GetCenter()) *
108  Matrix::MakeTranslation(-oval_bounds.GetCenter());
109 
110  { // Sweeps <=-360 or >=360
111  for (int start = -720; start <= 720; start += 10) {
112  for (int sweep = 360; sweep <= 720; sweep += 30) {
113  std::string label =
114  "start: " + std::to_string(start) + " + " + std::to_string(sweep);
115  auto geometry = Geometry::MakeFilledArc(oval_bounds, Degrees(start),
116  Degrees(sweep), false);
117  EXPECT_EQ(geometry->GetCoverage({}), oval_bounds)
118  << "start: " << start << ", sweep: " << sweep;
119  geometry = Geometry::MakeFilledArc(oval_bounds, Degrees(start),
120  Degrees(-sweep), false);
121  EXPECT_EQ(geometry->GetCoverage({}), oval_bounds)
122  << "start: " << start << ", sweep: " << -sweep;
123  geometry = Geometry::MakeFilledArc(oval_bounds, Degrees(start),
124  Degrees(-sweep), true);
125  EXPECT_EQ(geometry->GetCoverage({}), oval_bounds)
126  << "start: " << start << ", sweep: " << -sweep << ", with center";
127  }
128  }
129  }
130  { // Sweep from late in one quadrant to earlier in same quadrant
131  for (int start = 60; start < 360; start += 90) {
132  auto geometry = Geometry::MakeFilledArc(oval_bounds, Degrees(start),
133  Degrees(330), false);
134  EXPECT_EQ(geometry->GetCoverage({}), oval_bounds)
135  << "start: " << start << " without center";
136  geometry = Geometry::MakeFilledArc(oval_bounds, Degrees(start),
137  Degrees(330), true);
138  EXPECT_EQ(geometry->GetCoverage({}), oval_bounds)
139  << "start: " << start << " with center";
140  }
141  }
142  { // Sweep from early in one quadrant backwards to later in same quadrant
143  for (int start = 30; start < 360; start += 90) {
144  auto geometry = Geometry::MakeFilledArc(oval_bounds, Degrees(start),
145  Degrees(-330), false);
146  EXPECT_EQ(geometry->GetCoverage({}), oval_bounds)
147  << "start: " << start << " without center";
148  geometry = Geometry::MakeFilledArc(oval_bounds, Degrees(start),
149  Degrees(-330), true);
150  EXPECT_EQ(geometry->GetCoverage({}), oval_bounds)
151  << "start: " << start << " with center";
152  }
153  }
154  { // Sweep past each quadrant axis individually, no center
155  for (int start = -360; start <= 720; start += 360) {
156  { // Quadrant 0
157  auto geometry = Geometry::MakeFilledArc(
158  oval_bounds, Degrees(start - 45), Degrees(90), false);
159  Rect expected_bounds = Rect::MakeLTRB(150 + 50 * kSqrt2Over2, //
160  150 - 50 * kSqrt2Over2, //
161  200, //
162  150 + 50 * kSqrt2Over2);
163  EXPECT_RECT_NEAR(geometry->GetCoverage({}).value_or(Rect()),
164  expected_bounds)
165  << "start: " << start - 45;
166  }
167  { // Quadrant 1
168  auto geometry = Geometry::MakeFilledArc(
169  oval_bounds, Degrees(start + 45), Degrees(90), false);
170  Rect expected_bounds = Rect::MakeLTRB(150 - 50 * kSqrt2Over2, //
171  150 + 50 * kSqrt2Over2, //
172  150 + 50 * kSqrt2Over2, //
173  200);
174  EXPECT_RECT_NEAR(geometry->GetCoverage({}).value_or(Rect()),
175  expected_bounds)
176  << "start: " << start + 45;
177  }
178  { // Quadrant 2
179  auto geometry = Geometry::MakeFilledArc(
180  oval_bounds, Degrees(start + 135), Degrees(90), false);
181  Rect expected_bounds = Rect::MakeLTRB(100, //
182  150 - 50 * kSqrt2Over2, //
183  150 - 50 * kSqrt2Over2, //
184  150 + 50 * kSqrt2Over2);
185  EXPECT_RECT_NEAR(geometry->GetCoverage({}).value_or(Rect()),
186  expected_bounds)
187  << "start: " << start + 135;
188  }
189  { // Quadrant 3
190  auto geometry = Geometry::MakeFilledArc(
191  oval_bounds, Degrees(start + 225), Degrees(90), false);
192  Rect expected_bounds = Rect::MakeLTRB(150 - 50 * kSqrt2Over2, //
193  100, //
194  150 + 50 * kSqrt2Over2, //
195  150 - 50 * kSqrt2Over2);
196  EXPECT_RECT_NEAR(geometry->GetCoverage({}).value_or(Rect()),
197  expected_bounds)
198  << "start: " << start + 225;
199  }
200  }
201  }
202  { // Sweep past each quadrant axis individually, including the center
203  for (int start = -360; start <= 720; start += 360) {
204  { // Quadrant 0
205  auto geometry = Geometry::MakeFilledArc(
206  oval_bounds, Degrees(start - 45), Degrees(90), true);
207  Rect expected_bounds = Rect::MakeLTRB(150, //
208  150 - 50 * kSqrt2Over2, //
209  200, //
210  150 + 50 * kSqrt2Over2);
211  EXPECT_RECT_NEAR(geometry->GetCoverage({}).value_or(Rect()),
212  expected_bounds)
213  << "start: " << start - 45;
214  }
215  { // Quadrant 1
216  auto geometry = Geometry::MakeFilledArc(
217  oval_bounds, Degrees(start + 45), Degrees(90), true);
218  Rect expected_bounds = Rect::MakeLTRB(150 - 50 * kSqrt2Over2, //
219  150, //
220  150 + 50 * kSqrt2Over2, //
221  200);
222  EXPECT_RECT_NEAR(geometry->GetCoverage({}).value_or(Rect()),
223  expected_bounds)
224  << "start: " << start + 45;
225  }
226  { // Quadrant 2
227  auto geometry = Geometry::MakeFilledArc(
228  oval_bounds, Degrees(start + 135), Degrees(90), true);
229  Rect expected_bounds = Rect::MakeLTRB(100, //
230  150 - 50 * kSqrt2Over2, //
231  150, //
232  150 + 50 * kSqrt2Over2);
233  EXPECT_RECT_NEAR(geometry->GetCoverage({}).value_or(Rect()),
234  expected_bounds)
235  << "start: " << start + 135;
236  }
237  { // Quadrant 3
238  auto geometry = Geometry::MakeFilledArc(
239  oval_bounds, Degrees(start + 225), Degrees(90), true);
240  Rect expected_bounds = Rect::MakeLTRB(150 - 50 * kSqrt2Over2, //
241  100, //
242  150 + 50 * kSqrt2Over2, //
243  150);
244  EXPECT_RECT_NEAR(geometry->GetCoverage({}).value_or(Rect()),
245  expected_bounds)
246  << "start: " << start + 225;
247  }
248  }
249  }
250  { // 45 degree tilted full circle
251  auto geometry =
252  Geometry::MakeFilledArc(oval_bounds, Degrees(0), Degrees(360), false);
253  ASSERT_TRUE(oval_bounds.TransformBounds(transform45).Contains(oval_bounds));
254 
255  EXPECT_TRUE(geometry->GetCoverage(transform45)
256  .value_or(Rect())
257  .Contains(oval_bounds));
258  }
259  { // 45 degree tilted mostly full circle
260  auto geometry =
261  Geometry::MakeFilledArc(oval_bounds, Degrees(3), Degrees(359), false);
262  ASSERT_TRUE(oval_bounds.TransformBounds(transform45).Contains(oval_bounds));
263 
264  EXPECT_TRUE(geometry->GetCoverage(transform45)
265  .value_or(Rect())
266  .Contains(oval_bounds));
267  }
268 }
269 
270 TEST(EntityGeometryTest, StrokeArcGeometryCoverage) {
271  Rect oval_bounds = Rect::MakeLTRB(100, 100, 200, 200);
272  Rect expanded_bounds = Rect::MakeLTRB(95, 95, 205, 205);
273  Rect squared_bounds = Rect::MakeLTRB(100 - 5 * kSqrt2, 100 - 5 * kSqrt2,
274  200 + 5 * kSqrt2, 200 + 5 * kSqrt2);
275  Matrix transform45 = Matrix::MakeTranslation(oval_bounds.GetCenter()) *
277  Matrix::MakeTranslation(-oval_bounds.GetCenter());
278 
279  StrokeParameters butt_params = {
280  .width = 10.0f,
281  .cap = Cap::kButt,
282  };
283  StrokeParameters square_params = {
284  .width = 10.0f,
285  .cap = Cap::kSquare,
286  };
287 
288  { // Sweeps <=-360 or >=360
289  for (int start = -720; start <= 720; start += 10) {
290  for (int sweep = 360; sweep <= 720; sweep += 30) {
291  std::string label =
292  "start: " + std::to_string(start) + " + " + std::to_string(sweep);
293  auto geometry = Geometry::MakeStrokedArc(oval_bounds, Degrees(start),
294  Degrees(sweep), butt_params);
295  EXPECT_EQ(geometry->GetCoverage({}), expanded_bounds)
296  << "start: " << start << ", sweep: " << sweep;
297  geometry = Geometry::MakeStrokedArc(oval_bounds, Degrees(start),
298  Degrees(-sweep), butt_params);
299  EXPECT_EQ(geometry->GetCoverage({}), expanded_bounds)
300  << "start: " << start << ", sweep: " << -sweep;
301  geometry = Geometry::MakeStrokedArc(oval_bounds, Degrees(start),
302  Degrees(-sweep), square_params);
303  EXPECT_EQ(geometry->GetCoverage({}), expanded_bounds)
304  << "start: " << start << ", sweep: " << -sweep << ", square caps";
305  }
306  }
307  }
308  { // Sweep from late in one quadrant to earlier in same quadrant
309  for (int start = 60; start < 360; start += 90) {
310  auto geometry = Geometry::MakeStrokedArc(oval_bounds, Degrees(start),
311  Degrees(330), butt_params);
312  EXPECT_EQ(geometry->GetCoverage({}), expanded_bounds)
313  << "start: " << start << ", butt caps";
314  geometry = Geometry::MakeStrokedArc(oval_bounds, Degrees(start),
315  Degrees(330), square_params);
316  EXPECT_EQ(geometry->GetCoverage({}), squared_bounds)
317  << "start: " << start << ", square caps";
318  }
319  }
320  { // Sweep from early in one quadrant backwards to later in same quadrant
321  for (int start = 30; start < 360; start += 90) {
322  auto geometry = Geometry::MakeStrokedArc(oval_bounds, Degrees(start),
323  Degrees(-330), butt_params);
324  EXPECT_EQ(geometry->GetCoverage({}), expanded_bounds)
325  << "start: " << start << " without center";
326  geometry = Geometry::MakeStrokedArc(oval_bounds, Degrees(start),
327  Degrees(-330), square_params);
328  EXPECT_EQ(geometry->GetCoverage({}), squared_bounds)
329  << "start: " << start << " with center";
330  }
331  }
332  { // Sweep past each quadrant axis individually with butt caps
333  for (int start = -360; start <= 720; start += 360) {
334  { // Quadrant 0
335  auto geometry = Geometry::MakeStrokedArc(
336  oval_bounds, Degrees(start - 45), Degrees(90), butt_params);
337  Rect expected_bounds = Rect::MakeLTRB(150 + 50 * kSqrt2Over2 - 5, //
338  150 - 50 * kSqrt2Over2 - 5, //
339  205, //
340  150 + 50 * kSqrt2Over2 + 5);
341  EXPECT_RECT_NEAR(geometry->GetCoverage({}).value_or(Rect()),
342  expected_bounds)
343  << "start: " << start - 45;
344  }
345  { // Quadrant 1
346  auto geometry = Geometry::MakeStrokedArc(
347  oval_bounds, Degrees(start + 45), Degrees(90), butt_params);
348  Rect expected_bounds = Rect::MakeLTRB(150 - 50 * kSqrt2Over2 - 5, //
349  150 + 50 * kSqrt2Over2 - 5, //
350  150 + 50 * kSqrt2Over2 + 5, //
351  205);
352  EXPECT_RECT_NEAR(geometry->GetCoverage({}).value_or(Rect()),
353  expected_bounds)
354  << "start: " << start + 45;
355  }
356  { // Quadrant 2
357  auto geometry = Geometry::MakeStrokedArc(
358  oval_bounds, Degrees(start + 135), Degrees(90), butt_params);
359  Rect expected_bounds = Rect::MakeLTRB(95, //
360  150 - 50 * kSqrt2Over2 - 5, //
361  150 - 50 * kSqrt2Over2 + 5, //
362  150 + 50 * kSqrt2Over2 + 5);
363  EXPECT_RECT_NEAR(geometry->GetCoverage({}).value_or(Rect()),
364  expected_bounds)
365  << "start: " << start + 135;
366  }
367  { // Quadrant 3
368  auto geometry = Geometry::MakeStrokedArc(
369  oval_bounds, Degrees(start + 225), Degrees(90), butt_params);
370  Rect expected_bounds = Rect::MakeLTRB(150 - 50 * kSqrt2Over2 - 5, //
371  95, //
372  150 + 50 * kSqrt2Over2 + 5, //
373  150 - 50 * kSqrt2Over2 + 5);
374  EXPECT_RECT_NEAR(geometry->GetCoverage({}).value_or(Rect()),
375  expected_bounds)
376  << "start: " << start + 225;
377  }
378  }
379  }
380  { // Sweep past each quadrant axis individually with square caps
381  Scalar pad = 5 * kSqrt2;
382  for (int start = -360; start <= 720; start += 360) {
383  { // Quadrant 0
384  auto geometry = Geometry::MakeStrokedArc(
385  oval_bounds, Degrees(start - 45), Degrees(90), square_params);
386  Rect expected_bounds = Rect::MakeLTRB(150 + 50 * kSqrt2Over2 - pad, //
387  150 - 50 * kSqrt2Over2 - pad, //
388  200 + pad, //
389  150 + 50 * kSqrt2Over2 + pad);
390  EXPECT_RECT_NEAR(geometry->GetCoverage({}).value_or(Rect()),
391  expected_bounds)
392  << "start: " << start - 45;
393  }
394  { // Quadrant 1
395  auto geometry = Geometry::MakeStrokedArc(
396  oval_bounds, Degrees(start + 45), Degrees(90), square_params);
397  Rect expected_bounds = Rect::MakeLTRB(150 - 50 * kSqrt2Over2 - pad, //
398  150 + 50 * kSqrt2Over2 - pad, //
399  150 + 50 * kSqrt2Over2 + pad, //
400  200 + pad);
401  EXPECT_RECT_NEAR(geometry->GetCoverage({}).value_or(Rect()),
402  expected_bounds)
403  << "start: " << start + 45;
404  }
405  { // Quadrant 2
406  auto geometry = Geometry::MakeStrokedArc(
407  oval_bounds, Degrees(start + 135), Degrees(90), square_params);
408  Rect expected_bounds = Rect::MakeLTRB(100 - pad, //
409  150 - 50 * kSqrt2Over2 - pad, //
410  150 - 50 * kSqrt2Over2 + pad, //
411  150 + 50 * kSqrt2Over2 + pad);
412  EXPECT_RECT_NEAR(geometry->GetCoverage({}).value_or(Rect()),
413  expected_bounds)
414  << "start: " << start + 135;
415  }
416  { // Quadrant 3
417  auto geometry = Geometry::MakeStrokedArc(
418  oval_bounds, Degrees(start + 225), Degrees(90), square_params);
419  Rect expected_bounds = Rect::MakeLTRB(150 - 50 * kSqrt2Over2 - pad, //
420  100 - pad, //
421  150 + 50 * kSqrt2Over2 + pad, //
422  150 - 50 * kSqrt2Over2 + pad);
423  EXPECT_RECT_NEAR(geometry->GetCoverage({}).value_or(Rect()),
424  expected_bounds)
425  << "start: " << start + 225;
426  }
427  }
428  }
429  { // 45 degree tilted full circle, butt caps
430  auto geometry = Geometry::MakeStrokedArc( //
431  oval_bounds, Degrees(0), Degrees(360), butt_params);
432  ASSERT_TRUE(
433  oval_bounds.TransformBounds(transform45).Contains(expanded_bounds));
434 
435  EXPECT_TRUE(geometry->GetCoverage(transform45)
436  .value_or(Rect())
437  .Contains(expanded_bounds));
438  }
439  { // 45 degree tilted full circle, square caps
440  auto geometry = Geometry::MakeStrokedArc( //
441  oval_bounds, Degrees(0), Degrees(360), square_params);
442  ASSERT_TRUE(
443  oval_bounds.TransformBounds(transform45).Contains(expanded_bounds));
444 
445  EXPECT_TRUE(geometry->GetCoverage(transform45)
446  .value_or(Rect())
447  .Contains(squared_bounds));
448  }
449  { // 45 degree tilted mostly full circle, butt caps
450  auto geometry = Geometry::MakeStrokedArc( //
451  oval_bounds, Degrees(3), Degrees(359), butt_params);
452  ASSERT_TRUE(
453  oval_bounds.TransformBounds(transform45).Contains(expanded_bounds));
454 
455  EXPECT_TRUE(geometry->GetCoverage(transform45)
456  .value_or(Rect())
457  .Contains(expanded_bounds));
458  }
459  { // 45 degree tilted mostly full circle, square caps
460  auto geometry = Geometry::MakeStrokedArc( //
461  oval_bounds, Degrees(3), Degrees(359), square_params);
462  ASSERT_TRUE(
463  oval_bounds.TransformBounds(transform45).Contains(expanded_bounds));
464 
465  EXPECT_TRUE(geometry->GetCoverage(transform45)
466  .value_or(Rect())
467  .Contains(squared_bounds));
468  }
469 }
470 
471 TEST(EntityGeometryTest, FillRoundRectGeometryCoversArea) {
472  Rect bounds = Rect::MakeLTRB(100, 100, 200, 200);
473  RoundRect round_rect =
475  .top_left = Size(1, 11),
476  .top_right = Size(2, 12),
477  .bottom_left = Size(3, 13),
478  .bottom_right = Size(4, 14),
479  });
480  FillRoundRectGeometry geom(round_rect);
481 
482  // Tall middle rect should barely be covered.
483  EXPECT_TRUE(geom.CoversArea({}, Rect::MakeLTRB(103, 100, 196, 200)));
484  EXPECT_FALSE(geom.CoversArea({}, Rect::MakeLTRB(102, 100, 196, 200)));
485  EXPECT_FALSE(geom.CoversArea({}, Rect::MakeLTRB(103, 99, 196, 200)));
486  EXPECT_FALSE(geom.CoversArea({}, Rect::MakeLTRB(103, 100, 197, 200)));
487  EXPECT_FALSE(geom.CoversArea({}, Rect::MakeLTRB(103, 100, 196, 201)));
488 
489  // Wide middle rect should barely be covered.
490  EXPECT_TRUE(geom.CoversArea({}, Rect::MakeLTRB(100, 112, 200, 186)));
491  EXPECT_FALSE(geom.CoversArea({}, Rect::MakeLTRB(99, 112, 200, 186)));
492  EXPECT_FALSE(geom.CoversArea({}, Rect::MakeLTRB(100, 111, 200, 186)));
493  EXPECT_FALSE(geom.CoversArea({}, Rect::MakeLTRB(100, 112, 201, 186)));
494  EXPECT_FALSE(geom.CoversArea({}, Rect::MakeLTRB(100, 112, 200, 187)));
495 }
496 
497 TEST(EntityGeometryTest, LineGeometryCoverage) {
498  {
499  auto geometry = Geometry::MakeLine( //
500  {10, 10}, {20, 10}, {.width = 2, .cap = Cap::kButt});
501  EXPECT_EQ(geometry->GetCoverage({}), Rect::MakeLTRB(10, 9, 20, 11));
502  EXPECT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(10, 9, 20, 11)));
503  }
504 
505  {
506  auto geometry = Geometry::MakeLine( //
507  {10, 10}, {20, 10}, {.width = 2, .cap = Cap::kSquare});
508  EXPECT_EQ(geometry->GetCoverage({}), Rect::MakeLTRB(9, 9, 21, 11));
509  EXPECT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(9, 9, 21, 11)));
510  }
511 
512  {
513  auto geometry = Geometry::MakeLine( //
514  {10, 10}, {10, 20}, {.width = 2, .cap = Cap::kButt});
515  EXPECT_EQ(geometry->GetCoverage({}), Rect::MakeLTRB(9, 10, 11, 20));
516  EXPECT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(9, 10, 11, 20)));
517  }
518 
519  {
520  auto geometry = Geometry::MakeLine( //
521  {10, 10}, {10, 20}, {.width = 2, .cap = Cap::kSquare});
522  EXPECT_EQ(geometry->GetCoverage({}), Rect::MakeLTRB(9, 9, 11, 21));
523  EXPECT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(9, 9, 11, 21)));
524  }
525 }
526 
527 TEST(EntityGeometryTest, RoundRectGeometryCoversArea) {
528  auto geometry =
529  Geometry::MakeRoundRect(Rect::MakeLTRB(0, 0, 100, 100), Size(20, 20));
530  EXPECT_FALSE(geometry->CoversArea({}, Rect::MakeLTRB(15, 15, 85, 85)));
531  EXPECT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(20, 20, 80, 80)));
532  EXPECT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(30, 1, 70, 99)));
533  EXPECT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(1, 30, 99, 70)));
534 }
535 
536 TEST(EntityGeometryTest, GeometryResultHasReasonableDefaults) {
537  GeometryResult result;
538  EXPECT_EQ(result.type, PrimitiveType::kTriangleStrip);
539  EXPECT_EQ(result.transform, Matrix());
540  EXPECT_EQ(result.mode, GeometryResult::Mode::kNormal);
541 }
542 
543 TEST(EntityGeometryTest, AlphaCoverageStrokePaths) {
544  auto matrix = Matrix::MakeScale(Vector2{3.0, 3.0});
545  EXPECT_EQ(Geometry::MakeStrokePath({}, {.width = 0.5f})
546  ->ComputeAlphaCoverage(matrix),
547  1.0f);
548  EXPECT_NEAR(Geometry::MakeStrokePath({}, {.width = 0.1f})
549  ->ComputeAlphaCoverage(matrix),
550  0.6, 0.05);
551  EXPECT_NEAR(Geometry::MakeStrokePath({}, {.width = 0.05})
552  ->ComputeAlphaCoverage(matrix),
553  0.3, 0.05);
554  EXPECT_NEAR(Geometry::MakeStrokePath({}, {.width = 0.01})
555  ->ComputeAlphaCoverage(matrix),
556  0.1, 0.1);
557  EXPECT_NEAR(Geometry::MakeStrokePath({}, {.width = 0.0000005f})
558  ->ComputeAlphaCoverage(matrix),
559  1e-05, 0.001);
560  EXPECT_EQ(Geometry::MakeStrokePath({}, {.width = 0.0f})
561  ->ComputeAlphaCoverage(matrix),
562  1.0f);
563  EXPECT_EQ(Geometry::MakeStrokePath({}, {.width = 40.0f})
564  ->ComputeAlphaCoverage(matrix),
565  1.0f);
566 }
567 
568 TEST(EntityGeometryTest, SimpleTwoLineStrokeVerticesButtCap) {
569  flutter::DlPathBuilder path_builder;
570  path_builder.MoveTo({20, 20});
571  path_builder.LineTo({30, 20});
572  path_builder.MoveTo({120, 20});
573  path_builder.LineTo({130, 20});
574  flutter::DlPath path = path_builder.TakePath();
575 
577  path,
578  {
579  .width = 10.0f,
580  .cap = Cap::kButt,
581  .join = Join::kBevel,
582  .miter_limit = 4.0f,
583  },
584  1.0f);
585 
586  std::vector<Point> expected = {
587  // The points for the first segment (20, 20) -> (30, 20)
588  Point(20, 25),
589  Point(20, 15),
590  Point(30, 25),
591  Point(30, 15),
592 
593  // The glue points that allow us to "pick up the pen" between segments
594  Point(30, 20),
595  Point(30, 20),
596  Point(120, 20),
597  Point(120, 20),
598 
599  // The points for the second segment (120, 20) -> (130, 20)
600  Point(120, 25),
601  Point(120, 15),
602  Point(130, 25),
603  Point(130, 15),
604  };
605 
606  EXPECT_EQ(points, expected);
607 }
608 
609 TEST(EntityGeometryTest, SimpleTwoLineStrokeVerticesRoundCap) {
610  flutter::DlPathBuilder path_builder;
611  path_builder.MoveTo({20, 20});
612  path_builder.LineTo({30, 20});
613  path_builder.MoveTo({120, 20});
614  path_builder.LineTo({130, 20});
615  flutter::DlPath path = path_builder.TakePath();
616 
618  path,
619  {
620  .width = 10.0f,
621  .cap = Cap::kRound,
622  .join = Join::kBevel,
623  .miter_limit = 4.0f,
624  },
625  1.0f);
626 
627  size_t count = points.size();
628  ASSERT_TRUE((count & 0x1) == 0x0); // Should always be even
629 
630  // For a scale factor of 1.0 and a stroke width of 10.0 we currently
631  // generate 40 total points for the 2 line segments based on the number
632  // of quadrant circle divisions for a radius of 5.0
633  //
634  // If the number of points changes because of a change in the way we
635  // compute circle divisions, we need to recompute the circular offsets
636  ASSERT_EQ(points.size(), 40u);
637 
638  // Compute the indicated circular end cap offset based on the current
639  // step out of 4 divisions [1, 2, 3] (not 0 or 4) based on whether this
640  // is the left or right side of the path and whether this is a backwards
641  // (starting) cap or a forwards (ending) cap.
642  auto offset = [](int step, bool left, bool backwards) -> Point {
643  Radians angle(kPiOver2 * (step / 4.0f));
644  Point along = Point(5.0f, 0.0f) * std::cos(angle.radians);
645  Point across = Point(0.0f, 5.0f) * std::sin(angle.radians);
646  Point center = backwards ? -along : along;
647  return left ? center + across : center - across;
648  };
649 
650  // The points for the first segment (20, 20) -> (30, 20)
651  EXPECT_EQ(points[0], Point(15, 20));
652  EXPECT_EQ(points[1], Point(20, 20) + offset(1, true, true));
653  EXPECT_EQ(points[2], Point(20, 20) + offset(1, false, true));
654  EXPECT_EQ(points[3], Point(20, 20) + offset(2, true, true));
655  EXPECT_EQ(points[4], Point(20, 20) + offset(2, false, true));
656  EXPECT_EQ(points[5], Point(20, 20) + offset(3, true, true));
657  EXPECT_EQ(points[6], Point(20, 20) + offset(3, false, true));
658  EXPECT_EQ(points[7], Point(20, 25));
659  EXPECT_EQ(points[8], Point(20, 15));
660  EXPECT_EQ(points[9], Point(30, 25));
661  EXPECT_EQ(points[10], Point(30, 15));
662  EXPECT_EQ(points[11], Point(30, 20) + offset(3, true, false));
663  EXPECT_EQ(points[12], Point(30, 20) + offset(3, false, false));
664  EXPECT_EQ(points[13], Point(30, 20) + offset(2, true, false));
665  EXPECT_EQ(points[14], Point(30, 20) + offset(2, false, false));
666  EXPECT_EQ(points[15], Point(30, 20) + offset(1, true, false));
667  EXPECT_EQ(points[16], Point(30, 20) + offset(1, false, false));
668  EXPECT_EQ(points[17], Point(35, 20));
669 
670  // The glue points that allow us to "pick up the pen" between segments
671  EXPECT_EQ(points[18], Point(30, 20));
672  EXPECT_EQ(points[19], Point(30, 20));
673  EXPECT_EQ(points[20], Point(120, 20));
674  EXPECT_EQ(points[21], Point(120, 20));
675 
676  // The points for the second segment (120, 20) -> (130, 20)
677  EXPECT_EQ(points[22], Point(115, 20));
678  EXPECT_EQ(points[23], Point(120, 20) + offset(1, true, true));
679  EXPECT_EQ(points[24], Point(120, 20) + offset(1, false, true));
680  EXPECT_EQ(points[25], Point(120, 20) + offset(2, true, true));
681  EXPECT_EQ(points[26], Point(120, 20) + offset(2, false, true));
682  EXPECT_EQ(points[27], Point(120, 20) + offset(3, true, true));
683  EXPECT_EQ(points[28], Point(120, 20) + offset(3, false, true));
684  EXPECT_EQ(points[29], Point(120, 25));
685  EXPECT_EQ(points[30], Point(120, 15));
686  EXPECT_EQ(points[31], Point(130, 25));
687  EXPECT_EQ(points[32], Point(130, 15));
688  EXPECT_EQ(points[33], Point(130, 20) + offset(3, true, false));
689  EXPECT_EQ(points[34], Point(130, 20) + offset(3, false, false));
690  EXPECT_EQ(points[35], Point(130, 20) + offset(2, true, false));
691  EXPECT_EQ(points[36], Point(130, 20) + offset(2, false, false));
692  EXPECT_EQ(points[37], Point(130, 20) + offset(1, true, false));
693  EXPECT_EQ(points[38], Point(130, 20) + offset(1, false, false));
694  EXPECT_EQ(points[39], Point(135, 20));
695 }
696 
697 TEST(EntityGeometryTest, SimpleTwoLineStrokeVerticesSquareCap) {
698  flutter::DlPathBuilder path_builder;
699  path_builder.MoveTo({20, 20});
700  path_builder.LineTo({30, 20});
701  path_builder.MoveTo({120, 20});
702  path_builder.LineTo({130, 20});
703  flutter::DlPath path = path_builder.TakePath();
704 
706  path,
707  {
708  .width = 10.0f,
709  .cap = Cap::kSquare,
710  .join = Join::kBevel,
711  .miter_limit = 4.0f,
712  },
713  1.0f);
714 
715  // clang-format off
716  std::vector<Point> expected = {
717  // The points for the first segment (20, 20) -> (30, 20)
718  Point(15, 25),
719  Point(15, 15),
720  Point(20, 25),
721  Point(20, 15),
722  Point(30, 25),
723  Point(30, 15),
724  Point(35, 25),
725  Point(35, 15),
726 
727  // The glue points that allow us to "pick up the pen" between segments
728  Point(30, 20),
729  Point(30, 20),
730  Point(120, 20),
731  Point(120, 20),
732 
733  // The points for the second segment (120, 20) -> (130, 20)
734  Point(115, 25),
735  Point(115, 15),
736  Point(120, 25),
737  Point(120, 15),
738  Point(130, 25),
739  Point(130, 15),
740  Point(135, 25),
741  Point(135, 15),
742  };
743  // clang-format on
744 
745  EXPECT_EQ(points, expected);
746 }
747 
748 TEST(EntityGeometryTest, TwoLineSegmentsRightTurnStrokeVerticesBevelJoin) {
749  flutter::DlPathBuilder path_builder;
750  path_builder.MoveTo({20, 20});
751  path_builder.LineTo({30, 20});
752  path_builder.LineTo({30, 30});
753  flutter::DlPath path = path_builder.TakePath();
754 
756  path,
757  {
758  .width = 10.0f,
759  .cap = Cap::kButt,
760  .join = Join::kBevel,
761  .miter_limit = 4.0f,
762  },
763  1.0f);
764 
765  std::vector<Point> expected = {
766  // The points for the first segment (20, 20) -> (30, 20)
767  Point(20, 25),
768  Point(20, 15),
769  Point(30, 25),
770  Point(30, 15),
771 
772  // The points for the second segment (120, 20) -> (130, 20)
773  Point(25, 20),
774  Point(35, 20),
775  Point(25, 30),
776  Point(35, 30),
777  };
778 
779  EXPECT_EQ(points, expected);
780 }
781 
782 TEST(EntityGeometryTest, TwoLineSegmentsLeftTurnStrokeVerticesBevelJoin) {
783  flutter::DlPathBuilder path_builder;
784  path_builder.MoveTo({20, 20});
785  path_builder.LineTo({30, 20});
786  path_builder.LineTo({30, 10});
787  flutter::DlPath path = path_builder.TakePath();
788 
790  path,
791  {
792  .width = 10.0f,
793  .cap = Cap::kButt,
794  .join = Join::kBevel,
795  .miter_limit = 4.0f,
796  },
797  1.0f);
798 
799  std::vector<Point> expected = {
800  // The points for the first segment (20, 20) -> (30, 20)
801  Point(20, 25),
802  Point(20, 15),
803  Point(30, 25),
804  Point(30, 15),
805 
806  // The points for the second segment (120, 20) -> (130, 20)
807  Point(35, 20),
808  Point(25, 20),
809  Point(35, 10),
810  Point(25, 10),
811  };
812 
813  EXPECT_EQ(points, expected);
814 }
815 
816 TEST(EntityGeometryTest, TwoLineSegmentsRightTurnStrokeVerticesMiterJoin) {
817  flutter::DlPathBuilder path_builder;
818  path_builder.MoveTo({20, 20});
819  path_builder.LineTo({30, 20});
820  path_builder.LineTo({30, 30});
821  flutter::DlPath path = path_builder.TakePath();
822 
824  path,
825  {
826  .width = 10.0f,
827  .cap = Cap::kButt,
828  .join = Join::kMiter,
829  .miter_limit = 4.0f,
830  },
831  1.0f);
832 
833  std::vector<Point> expected = {
834  // The points for the first segment (20, 20) -> (30, 20)
835  Point(20, 25),
836  Point(20, 15),
837  Point(30, 25),
838  Point(30, 15),
839 
840  // And one point makes a Miter
841  Point(35, 15),
842 
843  // The points for the second segment (120, 20) -> (130, 20)
844  Point(25, 20),
845  Point(35, 20),
846  Point(25, 30),
847  Point(35, 30),
848  };
849 
850  EXPECT_EQ(points, expected);
851 }
852 
853 TEST(EntityGeometryTest, TwoLineSegmentsLeftTurnStrokeVerticesMiterJoin) {
854  flutter::DlPathBuilder path_builder;
855  path_builder.MoveTo({20, 20});
856  path_builder.LineTo({30, 20});
857  path_builder.LineTo({30, 10});
858  flutter::DlPath path = path_builder.TakePath();
859 
861  path,
862  {
863  .width = 10.0f,
864  .cap = Cap::kButt,
865  .join = Join::kMiter,
866  .miter_limit = 4.0f,
867  },
868  1.0f);
869 
870  std::vector<Point> expected = {
871  // The points for the first segment (20, 20) -> (30, 20)
872  Point(20, 25),
873  Point(20, 15),
874  Point(30, 25),
875  Point(30, 15),
876 
877  // And one point makes a Miter
878  Point(35, 25),
879 
880  // The points for the second segment (120, 20) -> (130, 20)
881  Point(35, 20),
882  Point(25, 20),
883  Point(35, 10),
884  Point(25, 10),
885  };
886 
887  EXPECT_EQ(points, expected);
888 }
889 
890 TEST(EntityGeometryTest, TinyQuadGeneratesCaps) {
891  flutter::DlPathBuilder path_builder;
892  path_builder.MoveTo({20, 20});
893  path_builder.QuadraticCurveTo({20.125, 20}, {20.250, 20});
894  flutter::DlPath path = path_builder.TakePath();
895 
897  path,
898  {
899  .width = 4.0f,
900  .cap = Cap::kSquare,
901  .join = Join::kBevel,
902  .miter_limit = 4.0f,
903  },
904  1.0f);
905 
906  std::vector<Point> expected = {
907  // The points for the opening square cap
908  Point(18, 22),
909  Point(18, 18),
910 
911  // The points for the start of the curve
912  Point(20, 22),
913  Point(20, 18),
914 
915  // The points for the end of the curve
916  Point(20.25, 22),
917  Point(20.25, 18),
918 
919  // The points for the closing square cap
920  Point(22.25, 22),
921  Point(22.25, 18),
922  };
923 
924  EXPECT_EQ(points, expected);
925 }
926 
927 TEST(EntityGeometryTest, TinyConicGeneratesCaps) {
928  flutter::DlPathBuilder path_builder;
929  path_builder.MoveTo({20, 20});
930  path_builder.ConicCurveTo({20.125, 20}, {20.250, 20}, 0.6);
931  flutter::DlPath path = path_builder.TakePath();
932 
934  path,
935  {
936  .width = 4.0f,
937  .cap = Cap::kSquare,
938  .join = Join::kBevel,
939  .miter_limit = 4.0f,
940  },
941  1.0f);
942 
943  std::vector<Point> expected = {
944  // The points for the opening square cap
945  Point(18, 22),
946  Point(18, 18),
947 
948  // The points for the start of the curve
949  Point(20, 22),
950  Point(20, 18),
951 
952  // The points for the end of the curve
953  Point(20.25, 22),
954  Point(20.25, 18),
955 
956  // The points for the closing square cap
957  Point(22.25, 22),
958  Point(22.25, 18),
959  };
960 
961  EXPECT_EQ(points, expected);
962 }
963 
964 TEST(EntityGeometryTest, TinyCubicGeneratesCaps) {
965  flutter::DlPathBuilder path_builder;
966  path_builder.MoveTo({20, 20});
967  path_builder.CubicCurveTo({20.0625, 20}, {20.125, 20}, {20.250, 20});
968  flutter::DlPath path = path_builder.TakePath();
969 
971  path,
972  {
973  .width = 4.0f,
974  .cap = Cap::kSquare,
975  .join = Join::kBevel,
976  .miter_limit = 4.0f,
977  },
978  1.0f);
979 
980  std::vector<Point> expected = {
981  // The points for the opening square cap
982  Point(18, 22),
983  Point(18, 18),
984 
985  // The points for the start of the curve
986  Point(20, 22),
987  Point(20, 18),
988 
989  // The points for the end of the curve
990  Point(20.25, 22),
991  Point(20.25, 18),
992 
993  // The points for the closing square cap
994  Point(22.25, 22),
995  Point(22.25, 18),
996  };
997 
998  EXPECT_EQ(points, expected);
999 }
1000 
1001 TEST(EntityGeometryTest, TwoLineSegmentsMiterLimit) {
1002  // degrees is the angle that the line deviates from "straight ahead"
1003  for (int degrees = 10; degrees < 180; degrees += 10) {
1004  // Start with a width of 2 since line widths of 1 usually decide
1005  // that they don't need join geometry at a scale of 1.0
1006  for (int width = 2; width <= 10; width++) {
1007  Degrees d(degrees);
1008  Radians r(d);
1009  Point pixel_delta = Point(std::cos(r.radians), std::sin(r.radians));
1010 
1011  if (pixel_delta.GetDistance(Point(1, 0)) * width < 1.0f) {
1012  // Some combinations of angle and width result in a join that is
1013  // less than a pixel in size. We don't care about compliance on
1014  // such a small join delta (and, in fact, the implementation may
1015  // decide to elide those small joins).
1016  continue;
1017  }
1018 
1019  // Miter limits are based on angle between the vectors/segments
1020  Degrees between(180 - degrees);
1021  Radians r_between(between);
1022  Scalar limit = 1.0f / std::sin(r_between.radians / 2.0f);
1023 
1024  flutter::DlPathBuilder path_builder;
1025  path_builder.MoveTo(Point(20, 20));
1026  path_builder.LineTo(Point(30, 20));
1027  path_builder.LineTo(Point(30, 20) + pixel_delta * 10.0f);
1028  flutter::DlPath path = path_builder.TakePath();
1029 
1030  // Miter limit too small (99% of required) to allow a miter
1031  auto points1 =
1033  path,
1034  {
1035  .width = static_cast<Scalar>(width),
1036  .cap = Cap::kButt,
1037  .join = Join::kMiter,
1038  .miter_limit = limit * 0.99f,
1039  },
1040  1.0f);
1041  EXPECT_EQ(points1.size(), 8u)
1042  << "degrees: " << degrees << ", width: " << width << ", "
1043  << points1[4];
1044 
1045  // Miter limit large enough (101% of required) to allow a miter
1046  auto points2 =
1048  path,
1049  {
1050  .width = static_cast<Scalar>(width),
1051  .cap = Cap::kButt,
1052  .join = Join::kMiter,
1053  .miter_limit = limit * 1.01f,
1054  },
1055  1.0f);
1056  EXPECT_EQ(points2.size(), 9u)
1057  << "degrees: " << degrees << ", width: " << width;
1058  EXPECT_LE(points2[4].GetDistance({30, 20}), width * limit * 1.05f)
1059  << "degrees: " << degrees << ", width: " << width << ", "
1060  << points2[4];
1061  }
1062  }
1063 }
1064 
1065 TEST(EntityGeometryTest, TwoLineSegments180DegreeJoins) {
1066  // First, create a path that doubles back on itself.
1067  flutter::DlPathBuilder path_builder;
1068  path_builder.MoveTo(Point(10, 10));
1069  path_builder.LineTo(Point(100, 10));
1070  path_builder.LineTo(Point(10, 10));
1071  flutter::DlPath path = path_builder.TakePath();
1072 
1073  auto points_bevel =
1075  path,
1076  {
1077  .width = 20.0f,
1078  .cap = Cap::kButt,
1079  .join = Join::kBevel,
1080  .miter_limit = 4.0f,
1081  },
1082  1.0f);
1083  // Generates no join - because it is a bevel join
1084  EXPECT_EQ(points_bevel.size(), 8u);
1085 
1086  auto points_miter =
1088  path,
1089  {
1090  .width = 20.0f,
1091  .cap = Cap::kButt,
1092  .join = Join::kMiter,
1093  .miter_limit = 400.0f,
1094  },
1095  1.0f);
1096  // Generates no join - even with a very large miter limit
1097  EXPECT_EQ(points_miter.size(), 8u);
1098 
1099  auto points_round =
1101  path,
1102  {
1103  .width = 20.0f,
1104  .cap = Cap::kButt,
1105  .join = Join::kRound,
1106  .miter_limit = 4.0f,
1107  },
1108  1.0f);
1109  // Generates lots of join points - to round off the 180 degree bend
1110  EXPECT_EQ(points_round.size(), 19u);
1111 }
1112 
1113 TEST(EntityGeometryTest, TightQuadratic180DegreeJoins) {
1114  // First, create a mild quadratic that helps us verify how many points
1115  // should normally be on a quad with 2 legs of length 90.
1116  flutter::DlPathBuilder path_builder_refrence;
1117  path_builder_refrence.MoveTo(Point(10, 10));
1118  path_builder_refrence.QuadraticCurveTo(Point(100, 10), Point(100, 100));
1119  flutter::DlPath path_reference = path_builder_refrence.TakePath();
1120 
1121  auto points_bevel_reference =
1123  path_reference,
1124  {
1125  .width = 20.0f,
1126  .cap = Cap::kButt,
1127  .join = Join::kBevel,
1128  .miter_limit = 4.0f,
1129  },
1130  1.0f);
1131  // Generates no joins because the curve is smooth
1132  EXPECT_EQ(points_bevel_reference.size(), 74u);
1133 
1134  // Now create a path that doubles back on itself with a quadratic.
1135  flutter::DlPathBuilder path_builder;
1136  path_builder.MoveTo(Point(10, 10));
1137  path_builder.QuadraticCurveTo(Point(100, 10), Point(10, 10));
1138  flutter::DlPath path = path_builder.TakePath();
1139 
1140  auto points_bevel =
1142  path,
1143  {
1144  .width = 20.0f,
1145  .cap = Cap::kButt,
1146  .join = Join::kBevel,
1147  .miter_limit = 4.0f,
1148  },
1149  1.0f);
1150  // Generates round join because it is in the middle of a curved segment
1151  EXPECT_GT(points_bevel.size(), points_bevel_reference.size());
1152 
1153  auto points_miter =
1155  path,
1156  {
1157  .width = 20.0f,
1158  .cap = Cap::kButt,
1159  .join = Join::kMiter,
1160  .miter_limit = 400.0f,
1161  },
1162  1.0f);
1163  // Generates round join because it is in the middle of a curved segment
1164  EXPECT_GT(points_miter.size(), points_bevel_reference.size());
1165 
1166  auto points_round =
1168  path,
1169  {
1170  .width = 20.0f,
1171  .cap = Cap::kButt,
1172  .join = Join::kRound,
1173  .miter_limit = 4.0f,
1174  },
1175  1.0f);
1176  // Generates round join because it is in the middle of a curved segment
1177  EXPECT_GT(points_round.size(), points_bevel_reference.size());
1178 }
1179 
1180 TEST(EntityGeometryTest, TightConic180DegreeJoins) {
1181  // First, create a mild conic that helps us verify how many points
1182  // should normally be on a quad with 2 legs of length 90.
1183  flutter::DlPathBuilder path_builder_refrence;
1184  path_builder_refrence.MoveTo(Point(10, 10));
1185  path_builder_refrence.ConicCurveTo(Point(100, 10), Point(100, 100), 0.9f);
1186  flutter::DlPath path_reference = path_builder_refrence.TakePath();
1187 
1188  auto points_bevel_reference =
1190  path_reference,
1191  {
1192  .width = 20.0f,
1193  .cap = Cap::kButt,
1194  .join = Join::kBevel,
1195  .miter_limit = 4.0f,
1196  },
1197  1.0f);
1198  // Generates no joins because the curve is smooth
1199  EXPECT_EQ(points_bevel_reference.size(), 78u);
1200 
1201  // Now create a path that doubles back on itself with a conic.
1202  flutter::DlPathBuilder path_builder;
1203  path_builder.MoveTo(Point(10, 10));
1204  path_builder.QuadraticCurveTo(Point(100, 10), Point(10, 10));
1205  flutter::DlPath path = path_builder.TakePath();
1206 
1207  auto points_bevel =
1209  path,
1210  {
1211  .width = 20.0f,
1212  .cap = Cap::kButt,
1213  .join = Join::kBevel,
1214  .miter_limit = 4.0f,
1215  },
1216  1.0f);
1217  // Generates round join because it is in the middle of a curved segment
1218  EXPECT_GT(points_bevel.size(), points_bevel_reference.size());
1219 
1220  auto points_miter =
1222  path,
1223  {
1224  .width = 20.0f,
1225  .cap = Cap::kButt,
1226  .join = Join::kMiter,
1227  .miter_limit = 400.0f,
1228  },
1229  1.0f);
1230  // Generates round join because it is in the middle of a curved segment
1231  EXPECT_GT(points_miter.size(), points_bevel_reference.size());
1232 
1233  auto points_round =
1235  path,
1236  {
1237  .width = 20.0f,
1238  .cap = Cap::kButt,
1239  .join = Join::kRound,
1240  .miter_limit = 4.0f,
1241  },
1242  1.0f);
1243  // Generates round join because it is in the middle of a curved segment
1244  EXPECT_GT(points_round.size(), points_bevel_reference.size());
1245 }
1246 
1247 TEST(EntityGeometryTest, TightCubic180DegreeJoins) {
1248  // First, create a mild cubic that helps us verify how many points
1249  // should normally be on a quad with 3 legs of length ~50.
1250  flutter::DlPathBuilder path_builder_reference;
1251  path_builder_reference.MoveTo(Point(10, 10));
1252  path_builder_reference.CubicCurveTo(Point(60, 10), Point(100, 40),
1253  Point(100, 90));
1254  flutter::DlPath path_reference = path_builder_reference.TakePath();
1255 
1256  auto points_reference =
1258  path_reference,
1259  {
1260  .width = 20.0f,
1261  .cap = Cap::kButt,
1262  .join = Join::kBevel,
1263  .miter_limit = 4.0f,
1264  },
1265  1.0f);
1266  // Generates no joins because the curve is smooth
1267  EXPECT_EQ(points_reference.size(), 80u);
1268 
1269  // Now create a path that doubles back on itself with a cubic.
1270  flutter::DlPathBuilder path_builder;
1271  path_builder.MoveTo(Point(10, 10));
1272  path_builder.CubicCurveTo(Point(60, 10), Point(100, 40), Point(60, 10));
1273  flutter::DlPath path = path_builder.TakePath();
1274 
1275  auto points_bevel =
1277  path,
1278  {
1279  .width = 20.0f,
1280  .cap = Cap::kButt,
1281  .join = Join::kBevel,
1282  .miter_limit = 4.0f,
1283  },
1284  1.0f);
1285  // Generates round join because it is in the middle of a curved segment
1286  EXPECT_GT(points_bevel.size(), points_reference.size());
1287 
1288  auto points_miter =
1290  path,
1291  {
1292  .width = 20.0f,
1293  .cap = Cap::kButt,
1294  .join = Join::kMiter,
1295  .miter_limit = 400.0f,
1296  },
1297  1.0f);
1298  // Generates round join because it is in the middle of a curved segment
1299  EXPECT_GT(points_miter.size(), points_reference.size());
1300 
1301  auto points_round =
1303  path,
1304  {
1305  .width = 20.0f,
1306  .cap = Cap::kButt,
1307  .join = Join::kRound,
1308  .miter_limit = 4.0f,
1309  },
1310  1.0f);
1311  // Generates round join because it is in the middle of a curved segment
1312  EXPECT_GT(points_round.size(), points_reference.size());
1313 }
1314 
1315 TEST(EntityGeometryTest, RotatedFilledCircleGeometryCoverage) {
1316  Point center = Point(50, 50);
1317  auto geometry = Geometry::MakeCircle(center, 50);
1318  Rect circle_bounds = Rect::MakeLTRB(0, 0, 100, 100);
1319  ASSERT_EQ(geometry->GetCoverage({}).value_or(Rect()), circle_bounds);
1320 
1321  Matrix transform45 = Matrix::MakeTranslation(center) *
1323  Matrix::MakeTranslation(-center);
1324 
1325  EXPECT_TRUE(geometry->GetCoverage(transform45).has_value());
1326  Rect bounds = geometry->GetCoverage(transform45).value_or(Rect());
1327  EXPECT_TRUE(bounds.Contains(circle_bounds))
1328  << "geometry bounds: " << bounds << std::endl
1329  << " circle bounds: " << circle_bounds;
1330 }
1331 
1332 TEST(EntityGeometryTest, RotatedStrokedCircleGeometryCoverage) {
1333  Point center = Point(50, 50);
1334  auto geometry = Geometry::MakeStrokedCircle(center, 50, 10);
1335  Rect circle_bounds = Rect::MakeLTRB(0, 0, 100, 100).Expand(5);
1336  ASSERT_EQ(geometry->GetCoverage({}).value_or(Rect()), circle_bounds);
1337 
1338  Matrix transform45 = Matrix::MakeTranslation(center) *
1340  Matrix::MakeTranslation(-center);
1341 
1342  EXPECT_TRUE(geometry->GetCoverage(transform45).has_value());
1343  Rect bounds = geometry->GetCoverage(transform45).value_or(Rect());
1344  EXPECT_TRUE(bounds.Contains(circle_bounds))
1345  << "geometry bounds: " << bounds << std::endl
1346  << " circle bounds: " << circle_bounds;
1347 }
1348 
1349 } // namespace testing
1350 } // namespace impeller
A Geometry class that produces fillable vertices from any |RoundRect| object regardless of radii unif...
static std::unique_ptr< Geometry > MakeFillPath(const flutter::DlPath &path, std::optional< Rect > inner_rect=std::nullopt)
Definition: geometry.cc:62
static std::unique_ptr< Geometry > MakeRect(const Rect &rect)
Definition: geometry.cc:83
static std::unique_ptr< Geometry > MakeFilledArc(const Rect &oval_bounds, Degrees start, Degrees sweep, bool include_center)
Definition: geometry.cc:108
static std::unique_ptr< Geometry > MakeStrokePath(const flutter::DlPath &path, const StrokeParameters &stroke={})
Definition: geometry.cc:68
static std::unique_ptr< Geometry > MakeCircle(const Point &center, Scalar radius)
Definition: geometry.cc:97
static std::unique_ptr< Geometry > MakeRoundRect(const Rect &rect, const Size &radii)
Definition: geometry.cc:125
static std::unique_ptr< Geometry > MakeLine(const Point &p0, const Point &p1, const StrokeParameters &stroke)
Definition: geometry.cc:91
static std::unique_ptr< Geometry > MakeStrokedCircle(const Point &center, Scalar radius, Scalar stroke_width)
Definition: geometry.cc:102
static std::unique_ptr< Geometry > MakeStrokedArc(const Rect &oval_bounds, Degrees start, Degrees sweep, const StrokeParameters &stroke)
Definition: geometry.cc:116
static std::vector< Point > GenerateSolidStrokeVertices(const PathSource &path, const StrokeParameters &stroke, Scalar scale)
A utility that generates triangles of the specified fill type given a polyline. This happens on the C...
Definition: tessellator.h:37
inline ::testing::AssertionResult SolidVerticesNear(std::vector< impeller::Point > a, std::vector< impeller::Point > b)
inline ::testing::AssertionResult TextureVerticesNear(std::vector< impeller::TextureFillVertexShader::PerVertexData > a, std::vector< impeller::TextureFillVertexShader::PerVertexData > b)
inline ::testing::AssertionResult PointNear(impeller::Point a, impeller::Point b)
#define EXPECT_RECT_NEAR(a, b)
TEST(AllocationSizeTest, CanCreateTypedAllocations)
float Scalar
Definition: scalar.h:19
constexpr float kSqrt2Over2
Definition: constants.h:51
TRect< Scalar > Rect
Definition: rect.h:792
TPoint< Scalar > Point
Definition: point.h:327
constexpr float kPiOver2
Definition: constants.h:32
flutter::DlPath DlPath
Definition: dl_dispatcher.h:29
TSize< Scalar > Size
Definition: size.h:159
constexpr float kSqrt2
Definition: constants.h:47
PrimitiveType type
Definition: geometry.h:37
@ kNormal
The geometry has no overlapping triangles.
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
static Matrix MakeRotationZ(Radians r)
Definition: matrix.h:223
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
Scalar radians
Definition: scalar.h:45
static RoundRect MakeRectRadii(const Rect &rect, const RoundingRadii &radii)
Definition: round_rect.cc:9
A structure to store all of the parameters related to stroking a path or basic geometry object.
constexpr Type GetDistance(const TPoint &p) const
Definition: point.h:200
constexpr TRect< T > Expand(T left, T top, T right, T bottom) const
Returns a rectangle with expanded edges. Negative expansion results in shrinking.
Definition: rect.h:622
constexpr TRect TransformBounds(const Matrix &transform) const
Creates a new bounding box that contains this transformed rectangle.
Definition: rect.h:476
constexpr bool Contains(const TPoint< Type > &p) const
Returns true iff the provided point |p| is inside the half-open interior of this rectangle.
Definition: rect.h:235
constexpr Point GetCenter() const
Get the center point as a |Point|.
Definition: rect.h:386
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
const size_t start
std::vector< Point > points