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