Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
geometry_unittests.cc File Reference

Go to the source code of this file.

Classes

class  impeller::ImpellerEntityUnitTestAccessor
 

Namespaces

namespace  impeller
 
namespace  impeller::testing
 

Macros

#define EXPECT_SOLID_VERTICES_NEAR(a, b)    EXPECT_PRED2(&::SolidVerticesNear, a, b)
 
#define EXPECT_TEXTURE_VERTICES_NEAR(a, b)    EXPECT_PRED2(&::TextureVerticesNear, a, b)
 

Functions

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)
 
 impeller::testing::TEST (EntityGeometryTest, RectGeometryCoversArea)
 
 impeller::testing::TEST (EntityGeometryTest, UberSDFGeometryPaddingIsAdjustedByInverseMaxBasis)
 
 impeller::testing::TEST (EntityGeometryTest, FillPathGeometryCoversArea)
 
 impeller::testing::TEST (EntityGeometryTest, FillPathGeometryCoversAreaNoInnerRect)
 
 impeller::testing::TEST (EntityGeometryTest, FillArcGeometryCoverage)
 
 impeller::testing::TEST (EntityGeometryTest, StrokeArcGeometryCoverage)
 
 impeller::testing::TEST (EntityGeometryTest, FillRoundRectGeometryCoversArea)
 
 impeller::testing::TEST (EntityGeometryTest, LineGeometryCoverage)
 
 impeller::testing::TEST (EntityGeometryTest, RoundRectGeometryCoversArea)
 
 impeller::testing::TEST (EntityGeometryTest, GeometryResultHasReasonableDefaults)
 
 impeller::testing::TEST (EntityGeometryTest, AlphaCoverageStrokePaths)
 
 impeller::testing::TEST (EntityGeometryTest, SimpleTwoLineStrokeVerticesButtCap)
 
 impeller::testing::TEST (EntityGeometryTest, SimpleTwoLineStrokeVerticesRoundCap)
 
 impeller::testing::TEST (EntityGeometryTest, SimpleTwoLineStrokeVerticesSquareCap)
 
 impeller::testing::TEST (EntityGeometryTest, TwoLineSegmentsRightTurnStrokeVerticesBevelJoin)
 
 impeller::testing::TEST (EntityGeometryTest, TwoLineSegmentsLeftTurnStrokeVerticesBevelJoin)
 
 impeller::testing::TEST (EntityGeometryTest, TwoLineSegmentsInDifferentContoursAreNotJoined)
 
 impeller::testing::TEST (EntityGeometryTest, TwoLineSegmentsRightTurnStrokeVerticesMiterJoin)
 
 impeller::testing::TEST (EntityGeometryTest, TwoLineSegmentsLeftTurnStrokeVerticesMiterJoin)
 
 impeller::testing::TEST (EntityGeometryTest, TinyQuadGeneratesCaps)
 
 impeller::testing::TEST (EntityGeometryTest, TinyConicGeneratesCaps)
 
 impeller::testing::TEST (EntityGeometryTest, TinyCubicGeneratesCaps)
 
 impeller::testing::TEST (EntityGeometryTest, TwoLineSegmentsMiterLimit)
 
 impeller::testing::TEST (EntityGeometryTest, TwoLineSegments180DegreeJoins)
 
 impeller::testing::TEST (EntityGeometryTest, TightQuadratic180DegreeJoins)
 
 impeller::testing::TEST (EntityGeometryTest, TightConic180DegreeJoins)
 
 impeller::testing::TEST (EntityGeometryTest, TightCubic180DegreeJoins)
 
 impeller::testing::TEST (EntityGeometryTest, RotatedFilledCircleGeometryCoverage)
 
 impeller::testing::TEST (EntityGeometryTest, RotatedStrokedCircleGeometryCoverage)
 

Macro Definition Documentation

◆ EXPECT_SOLID_VERTICES_NEAR

#define EXPECT_SOLID_VERTICES_NEAR (   a,
 
)     EXPECT_PRED2(&::SolidVerticesNear, a, b)

Definition at line 53 of file geometry_unittests.cc.

58 {
59
60class ImpellerEntityUnitTestAccessor {
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
74namespace testing {
75
76TEST(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
84TEST(EntityGeometryTest, UberSDFGeometryPaddingIsAdjustedByInverseMaxBasis) {
85 UberSDFGeometry geometry(UberSDFParameters::MakeRect(
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
122TEST(EntityGeometryTest, FillPathGeometryCoversArea) {
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
134TEST(EntityGeometryTest, FillPathGeometryCoversAreaNoInnerRect) {
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
145TEST(EntityGeometryTest, FillArcGeometryCoverage) {
146 Rect oval_bounds = Rect::MakeLTRB(100, 100, 200, 200);
147 Matrix transform45 = Matrix::MakeTranslation(oval_bounds.GetCenter()) *
148 Matrix::MakeRotationZ(Degrees(45)) *
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
311TEST(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()) *
317 Matrix::MakeRotationZ(Degrees(45)) *
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
512TEST(EntityGeometryTest, FillRoundRectGeometryCoversArea) {
513 Rect bounds = Rect::MakeLTRB(100, 100, 200, 200);
514 RoundRect round_rect =
515 RoundRect::MakeRectRadii(bounds, RoundingRadii{
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
538TEST(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
568TEST(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
577TEST(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
584TEST(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
609TEST(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
617 auto points = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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
650TEST(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
658 auto points = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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
738TEST(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
746 auto points = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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
789TEST(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
796 auto points = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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
823TEST(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
830 auto points = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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
857TEST(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
869 auto points = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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
904TEST(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
911 auto points = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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
941TEST(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
948 auto points = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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
978TEST(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
984 auto points = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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
1015TEST(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
1021 auto points = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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
1052TEST(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
1058 auto points = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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
1089TEST(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 =
1120 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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 =
1135 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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
1153TEST(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 =
1162 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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 =
1175 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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 =
1188 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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
1201TEST(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 =
1210 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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 =
1229 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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 =
1242 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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 =
1255 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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
1268TEST(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 =
1277 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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 =
1296 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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 =
1309 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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 =
1322 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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
1335TEST(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 =
1345 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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 =
1364 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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 =
1377 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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 =
1390 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
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
1403TEST(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) *
1410 Matrix::MakeRotationZ(Degrees(45)) *
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
1420TEST(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) *
1427 Matrix::MakeRotationZ(Degrees(45)) *
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
TEST(AsciiTableTest, Simple)
DlPathBuilder & LineTo(DlPoint p2)
Draw a line from the current point to the indicated point p2.
DlPathBuilder & MoveTo(DlPoint p2)
Start a new contour that will originate at the indicated point p2.
const DlPath TakePath()
Returns the path constructed by this path builder and resets its internal state to the default state ...
DlPathBuilder & ConicCurveTo(DlPoint cp, DlPoint p2, DlScalar weight)
Draw a conic curve (a rational quadratic bezier curve) from the current point to the indicated point ...
DlPathBuilder & AddRect(const DlRect &rect)
Append a closed rectangular contour to the path.
DlPathBuilder & QuadraticCurveTo(DlPoint cp, DlPoint p2)
Draw a quadratic bezier curve from the current point to the indicated point p2, using the indicated p...
DlPathBuilder & CubicCurveTo(DlPoint cp1, DlPoint cp2, DlPoint p2)
Draw a cubic bezier curve from the current point to the indicated point p2, using the indicated point...
auto & d
Definition main.cc:28
#define EXPECT_RECT_NEAR(a, b)
bool Contains(const Container &container, const Value &value)
constexpr float kSqrt2
Definition math.h:45
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition switch_defs.h:52
float Scalar
Definition scalar.h:19
TRect< Scalar > Rect
Definition rect.h:822
TPoint< Scalar > Point
Definition point.h:426
IRect64 IRect
Definition rect.h:825
TSize< Scalar > Size
Definition size.h:159
int32_t width
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
const size_t start
std::vector< Point > points

◆ EXPECT_TEXTURE_VERTICES_NEAR

#define EXPECT_TEXTURE_VERTICES_NEAR (   a,
 
)     EXPECT_PRED2(&::TextureVerticesNear, a, b)

Definition at line 55 of file geometry_unittests.cc.

Function Documentation

◆ SolidVerticesNear()

inline ::testing::AssertionResult SolidVerticesNear ( std::vector< impeller::Point a,
std::vector< impeller::Point b 
)

Definition at line 22 of file geometry_unittests.cc.

24 {
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}
inline ::testing::AssertionResult PointNear(impeller::Point a, impeller::Point b)

References i, and PointNear().

◆ TextureVerticesNear()

inline ::testing::AssertionResult TextureVerticesNear ( std::vector< impeller::TextureFillVertexShader::PerVertexData >  a,
std::vector< impeller::TextureFillVertexShader::PerVertexData >  b 
)

Definition at line 36 of file geometry_unittests.cc.

38 {
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}

References i, and PointNear().