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