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, 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 (120, 20) -> (130, 20)
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 (120, 20) -> (130, 20)
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, TwoLineSegmentsRightTurnStrokeVerticesMiterJoin) {
858 flutter::DlPathBuilder path_builder;
859 path_builder.MoveTo({20, 20});
860 path_builder.LineTo({30, 20});
861 path_builder.LineTo({30, 30});
862 flutter::DlPath path = path_builder.TakePath();
863
864 auto points = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
865 path,
866 {
867 .width = 10.0f,
868 .cap = Cap::kButt,
869 .join = Join::kMiter,
870 .miter_limit = 4.0f,
871 },
872 1.0f);
873
874 std::vector<Point> expected = {
875 // The points for the first segment (20, 20) -> (30, 20)
876 Point(20, 25),
877 Point(20, 15),
878 Point(30, 25),
879 Point(30, 15),
880
881 // And one point makes a Miter
882 Point(35, 15),
883
884 // The points for the second segment (120, 20) -> (130, 20)
885 Point(25, 20),
886 Point(35, 20),
887 Point(25, 30),
888 Point(35, 30),
889 };
890
891 EXPECT_EQ(points, expected);
892}
893
894TEST(EntityGeometryTest, TwoLineSegmentsLeftTurnStrokeVerticesMiterJoin) {
895 flutter::DlPathBuilder path_builder;
896 path_builder.MoveTo({20, 20});
897 path_builder.LineTo({30, 20});
898 path_builder.LineTo({30, 10});
899 flutter::DlPath path = path_builder.TakePath();
900
901 auto points = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
902 path,
903 {
904 .width = 10.0f,
905 .cap = Cap::kButt,
906 .join = Join::kMiter,
907 .miter_limit = 4.0f,
908 },
909 1.0f);
910
911 std::vector<Point> expected = {
912 // The points for the first segment (20, 20) -> (30, 20)
913 Point(20, 25),
914 Point(20, 15),
915 Point(30, 25),
916 Point(30, 15),
917
918 // And one point makes a Miter
919 Point(35, 25),
920
921 // The points for the second segment (120, 20) -> (130, 20)
922 Point(35, 20),
923 Point(25, 20),
924 Point(35, 10),
925 Point(25, 10),
926 };
927
928 EXPECT_EQ(points, expected);
929}
930
931TEST(EntityGeometryTest, TinyQuadGeneratesCaps) {
932 flutter::DlPathBuilder path_builder;
933 path_builder.MoveTo({20, 20});
934 path_builder.QuadraticCurveTo({20.125, 20}, {20.250, 20});
935 flutter::DlPath path = path_builder.TakePath();
936
937 auto points = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
938 path,
939 {
940 .width = 4.0f,
941 .cap = Cap::kSquare,
942 .join = Join::kBevel,
943 .miter_limit = 4.0f,
944 },
945 1.0f);
946
947 std::vector<Point> expected = {
948 // The points for the opening square cap
949 Point(18, 22),
950 Point(18, 18),
951
952 // The points for the start of the curve
953 Point(20, 22),
954 Point(20, 18),
955
956 // The points for the end of the curve
957 Point(20.25, 22),
958 Point(20.25, 18),
959
960 // The points for the closing square cap
961 Point(22.25, 22),
962 Point(22.25, 18),
963 };
964
965 EXPECT_EQ(points, expected);
966}
967
968TEST(EntityGeometryTest, TinyConicGeneratesCaps) {
969 flutter::DlPathBuilder path_builder;
970 path_builder.MoveTo({20, 20});
971 path_builder.ConicCurveTo({20.125, 20}, {20.250, 20}, 0.6);
972 flutter::DlPath path = path_builder.TakePath();
973
974 auto points = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
975 path,
976 {
977 .width = 4.0f,
978 .cap = Cap::kSquare,
979 .join = Join::kBevel,
980 .miter_limit = 4.0f,
981 },
982 1.0f);
983
984 std::vector<Point> expected = {
985 // The points for the opening square cap
986 Point(18, 22),
987 Point(18, 18),
988
989 // The points for the start of the curve
990 Point(20, 22),
991 Point(20, 18),
992
993 // The points for the end of the curve
994 Point(20.25, 22),
995 Point(20.25, 18),
996
997 // The points for the closing square cap
998 Point(22.25, 22),
999 Point(22.25, 18),
1000 };
1001
1002 EXPECT_EQ(points, expected);
1003}
1004
1005TEST(EntityGeometryTest, TinyCubicGeneratesCaps) {
1006 flutter::DlPathBuilder path_builder;
1007 path_builder.MoveTo({20, 20});
1008 path_builder.CubicCurveTo({20.0625, 20}, {20.125, 20}, {20.250, 20});
1009 flutter::DlPath path = path_builder.TakePath();
1010
1011 auto points = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1012 path,
1013 {
1014 .width = 4.0f,
1015 .cap = Cap::kSquare,
1016 .join = Join::kBevel,
1017 .miter_limit = 4.0f,
1018 },
1019 1.0f);
1020
1021 std::vector<Point> expected = {
1022 // The points for the opening square cap
1023 Point(18, 22),
1024 Point(18, 18),
1025
1026 // The points for the start of the curve
1027 Point(20, 22),
1028 Point(20, 18),
1029
1030 // The points for the end of the curve
1031 Point(20.25, 22),
1032 Point(20.25, 18),
1033
1034 // The points for the closing square cap
1035 Point(22.25, 22),
1036 Point(22.25, 18),
1037 };
1038
1039 EXPECT_EQ(points, expected);
1040}
1041
1042TEST(EntityGeometryTest, TwoLineSegmentsMiterLimit) {
1043 // degrees is the angle that the line deviates from "straight ahead"
1044 for (int degrees = 10; degrees < 180; degrees += 10) {
1045 // Start with a width of 2 since line widths of 1 usually decide
1046 // that they don't need join geometry at a scale of 1.0
1047 for (int width = 2; width <= 10; width++) {
1048 Degrees d(degrees);
1049 Radians r(d);
1050 Point pixel_delta = Point(std::cos(r.radians), std::sin(r.radians));
1051
1052 if (pixel_delta.GetDistance(Point(1, 0)) * width < 1.0f) {
1053 // Some combinations of angle and width result in a join that is
1054 // less than a pixel in size. We don't care about compliance on
1055 // such a small join delta (and, in fact, the implementation may
1056 // decide to elide those small joins).
1057 continue;
1058 }
1059
1060 // Miter limits are based on angle between the vectors/segments
1061 Degrees between(180 - degrees);
1062 Radians r_between(between);
1063 Scalar limit = 1.0f / std::sin(r_between.radians / 2.0f);
1064
1065 flutter::DlPathBuilder path_builder;
1066 path_builder.MoveTo(Point(20, 20));
1067 path_builder.LineTo(Point(30, 20));
1068 path_builder.LineTo(Point(30, 20) + pixel_delta * 10.0f);
1069 flutter::DlPath path = path_builder.TakePath();
1070
1071 // Miter limit too small (99% of required) to allow a miter
1072 auto points1 =
1073 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1074 path,
1075 {
1076 .width = static_cast<Scalar>(width),
1077 .cap = Cap::kButt,
1078 .join = Join::kMiter,
1079 .miter_limit = limit * 0.99f,
1080 },
1081 1.0f);
1082 EXPECT_EQ(points1.size(), 8u)
1083 << "degrees: " << degrees << ", width: " << width << ", "
1084 << points1[4];
1085
1086 // Miter limit large enough (101% of required) to allow a miter
1087 auto points2 =
1088 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1089 path,
1090 {
1091 .width = static_cast<Scalar>(width),
1092 .cap = Cap::kButt,
1093 .join = Join::kMiter,
1094 .miter_limit = limit * 1.01f,
1095 },
1096 1.0f);
1097 EXPECT_EQ(points2.size(), 9u)
1098 << "degrees: " << degrees << ", width: " << width;
1099 EXPECT_LE(points2[4].GetDistance({30, 20}), width * limit * 1.05f)
1100 << "degrees: " << degrees << ", width: " << width << ", "
1101 << points2[4];
1102 }
1103 }
1104}
1105
1106TEST(EntityGeometryTest, TwoLineSegments180DegreeJoins) {
1107 // First, create a path that doubles back on itself.
1108 flutter::DlPathBuilder path_builder;
1109 path_builder.MoveTo(Point(10, 10));
1110 path_builder.LineTo(Point(100, 10));
1111 path_builder.LineTo(Point(10, 10));
1112 flutter::DlPath path = path_builder.TakePath();
1113
1114 auto points_bevel =
1115 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1116 path,
1117 {
1118 .width = 20.0f,
1119 .cap = Cap::kButt,
1120 .join = Join::kBevel,
1121 .miter_limit = 4.0f,
1122 },
1123 1.0f);
1124 // Generates no join - because it is a bevel join
1125 EXPECT_EQ(points_bevel.size(), 8u);
1126
1127 auto points_miter =
1128 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1129 path,
1130 {
1131 .width = 20.0f,
1132 .cap = Cap::kButt,
1133 .join = Join::kMiter,
1134 .miter_limit = 400.0f,
1135 },
1136 1.0f);
1137 // Generates no join - even with a very large miter limit
1138 EXPECT_EQ(points_miter.size(), 8u);
1139
1140 auto points_round =
1141 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1142 path,
1143 {
1144 .width = 20.0f,
1145 .cap = Cap::kButt,
1146 .join = Join::kRound,
1147 .miter_limit = 4.0f,
1148 },
1149 1.0f);
1150 // Generates lots of join points - to round off the 180 degree bend
1151 EXPECT_EQ(points_round.size(), 19u);
1152}
1153
1154TEST(EntityGeometryTest, TightQuadratic180DegreeJoins) {
1155 // First, create a mild quadratic that helps us verify how many points
1156 // should normally be on a quad with 2 legs of length 90.
1157 flutter::DlPathBuilder path_builder_refrence;
1158 path_builder_refrence.MoveTo(Point(10, 10));
1159 path_builder_refrence.QuadraticCurveTo(Point(100, 10), Point(100, 100));
1160 flutter::DlPath path_reference = path_builder_refrence.TakePath();
1161
1162 auto points_bevel_reference =
1163 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1164 path_reference,
1165 {
1166 .width = 20.0f,
1167 .cap = Cap::kButt,
1168 .join = Join::kBevel,
1169 .miter_limit = 4.0f,
1170 },
1171 1.0f);
1172 // Generates no joins because the curve is smooth
1173 EXPECT_EQ(points_bevel_reference.size(), 74u);
1174
1175 // Now create a path that doubles back on itself with a quadratic.
1176 flutter::DlPathBuilder path_builder;
1177 path_builder.MoveTo(Point(10, 10));
1178 path_builder.QuadraticCurveTo(Point(100, 10), Point(10, 10));
1179 flutter::DlPath path = path_builder.TakePath();
1180
1181 auto points_bevel =
1182 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1183 path,
1184 {
1185 .width = 20.0f,
1186 .cap = Cap::kButt,
1187 .join = Join::kBevel,
1188 .miter_limit = 4.0f,
1189 },
1190 1.0f);
1191 // Generates round join because it is in the middle of a curved segment
1192 EXPECT_GT(points_bevel.size(), points_bevel_reference.size());
1193
1194 auto points_miter =
1195 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1196 path,
1197 {
1198 .width = 20.0f,
1199 .cap = Cap::kButt,
1200 .join = Join::kMiter,
1201 .miter_limit = 400.0f,
1202 },
1203 1.0f);
1204 // Generates round join because it is in the middle of a curved segment
1205 EXPECT_GT(points_miter.size(), points_bevel_reference.size());
1206
1207 auto points_round =
1208 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1209 path,
1210 {
1211 .width = 20.0f,
1212 .cap = Cap::kButt,
1213 .join = Join::kRound,
1214 .miter_limit = 4.0f,
1215 },
1216 1.0f);
1217 // Generates round join because it is in the middle of a curved segment
1218 EXPECT_GT(points_round.size(), points_bevel_reference.size());
1219}
1220
1221TEST(EntityGeometryTest, TightConic180DegreeJoins) {
1222 // First, create a mild conic that helps us verify how many points
1223 // should normally be on a quad with 2 legs of length 90.
1224 flutter::DlPathBuilder path_builder_refrence;
1225 path_builder_refrence.MoveTo(Point(10, 10));
1226 path_builder_refrence.ConicCurveTo(Point(100, 10), Point(100, 100), 0.9f);
1227 flutter::DlPath path_reference = path_builder_refrence.TakePath();
1228
1229 auto points_bevel_reference =
1230 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1231 path_reference,
1232 {
1233 .width = 20.0f,
1234 .cap = Cap::kButt,
1235 .join = Join::kBevel,
1236 .miter_limit = 4.0f,
1237 },
1238 1.0f);
1239 // Generates no joins because the curve is smooth
1240 EXPECT_EQ(points_bevel_reference.size(), 78u);
1241
1242 // Now create a path that doubles back on itself with a conic.
1243 flutter::DlPathBuilder path_builder;
1244 path_builder.MoveTo(Point(10, 10));
1245 path_builder.QuadraticCurveTo(Point(100, 10), Point(10, 10));
1246 flutter::DlPath path = path_builder.TakePath();
1247
1248 auto points_bevel =
1249 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1250 path,
1251 {
1252 .width = 20.0f,
1253 .cap = Cap::kButt,
1254 .join = Join::kBevel,
1255 .miter_limit = 4.0f,
1256 },
1257 1.0f);
1258 // Generates round join because it is in the middle of a curved segment
1259 EXPECT_GT(points_bevel.size(), points_bevel_reference.size());
1260
1261 auto points_miter =
1262 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1263 path,
1264 {
1265 .width = 20.0f,
1266 .cap = Cap::kButt,
1267 .join = Join::kMiter,
1268 .miter_limit = 400.0f,
1269 },
1270 1.0f);
1271 // Generates round join because it is in the middle of a curved segment
1272 EXPECT_GT(points_miter.size(), points_bevel_reference.size());
1273
1274 auto points_round =
1275 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1276 path,
1277 {
1278 .width = 20.0f,
1279 .cap = Cap::kButt,
1280 .join = Join::kRound,
1281 .miter_limit = 4.0f,
1282 },
1283 1.0f);
1284 // Generates round join because it is in the middle of a curved segment
1285 EXPECT_GT(points_round.size(), points_bevel_reference.size());
1286}
1287
1288TEST(EntityGeometryTest, TightCubic180DegreeJoins) {
1289 // First, create a mild cubic that helps us verify how many points
1290 // should normally be on a quad with 3 legs of length ~50.
1291 flutter::DlPathBuilder path_builder_reference;
1292 path_builder_reference.MoveTo(Point(10, 10));
1293 path_builder_reference.CubicCurveTo(Point(60, 10), Point(100, 40),
1294 Point(100, 90));
1295 flutter::DlPath path_reference = path_builder_reference.TakePath();
1296
1297 auto points_reference =
1298 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1299 path_reference,
1300 {
1301 .width = 20.0f,
1302 .cap = Cap::kButt,
1303 .join = Join::kBevel,
1304 .miter_limit = 4.0f,
1305 },
1306 1.0f);
1307 // Generates no joins because the curve is smooth
1308 EXPECT_EQ(points_reference.size(), 76u);
1309
1310 // Now create a path that doubles back on itself with a cubic.
1311 flutter::DlPathBuilder path_builder;
1312 path_builder.MoveTo(Point(10, 10));
1313 path_builder.CubicCurveTo(Point(60, 10), Point(100, 40), Point(60, 10));
1314 flutter::DlPath path = path_builder.TakePath();
1315
1316 auto points_bevel =
1317 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1318 path,
1319 {
1320 .width = 20.0f,
1321 .cap = Cap::kButt,
1322 .join = Join::kBevel,
1323 .miter_limit = 4.0f,
1324 },
1325 1.0f);
1326 // Generates round join because it is in the middle of a curved segment
1327 EXPECT_GT(points_bevel.size(), points_reference.size());
1328
1329 auto points_miter =
1330 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1331 path,
1332 {
1333 .width = 20.0f,
1334 .cap = Cap::kButt,
1335 .join = Join::kMiter,
1336 .miter_limit = 400.0f,
1337 },
1338 1.0f);
1339 // Generates round join because it is in the middle of a curved segment
1340 EXPECT_GT(points_miter.size(), points_reference.size());
1341
1342 auto points_round =
1343 ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices(
1344 path,
1345 {
1346 .width = 20.0f,
1347 .cap = Cap::kButt,
1348 .join = Join::kRound,
1349 .miter_limit = 4.0f,
1350 },
1351 1.0f);
1352 // Generates round join because it is in the middle of a curved segment
1353 EXPECT_GT(points_round.size(), points_reference.size());
1354}
1355
1356TEST(EntityGeometryTest, RotatedFilledCircleGeometryCoverage) {
1357 Point center = Point(50, 50);
1358 auto geometry = Geometry::MakeCircle(center, 50);
1359 Rect circle_bounds = Rect::MakeLTRB(0, 0, 100, 100);
1360 ASSERT_EQ(geometry->GetCoverage({}).value_or(Rect()), circle_bounds);
1361
1362 Matrix transform45 = Matrix::MakeTranslation(center) *
1363 Matrix::MakeRotationZ(Degrees(45)) *
1364 Matrix::MakeTranslation(-center);
1365
1366 EXPECT_TRUE(geometry->GetCoverage(transform45).has_value());
1367 Rect bounds = geometry->GetCoverage(transform45).value_or(Rect());
1368 EXPECT_TRUE(bounds.Contains(circle_bounds))
1369 << "geometry bounds: " << bounds << std::endl
1370 << " circle bounds: " << circle_bounds;
1371}
1372
1373TEST(EntityGeometryTest, RotatedStrokedCircleGeometryCoverage) {
1374 Point center = Point(50, 50);
1375 auto geometry = Geometry::MakeStrokedCircle(center, 50, 10);
1376 Rect circle_bounds = Rect::MakeLTRB(0, 0, 100, 100).Expand(5);
1377 ASSERT_EQ(geometry->GetCoverage({}).value_or(Rect()), circle_bounds);
1378
1379 Matrix transform45 = Matrix::MakeTranslation(center) *
1380 Matrix::MakeRotationZ(Degrees(45)) *
1381 Matrix::MakeTranslation(-center);
1382
1383 EXPECT_TRUE(geometry->GetCoverage(transform45).has_value());
1384 Rect bounds = geometry->GetCoverage(transform45).value_or(Rect());
1385 EXPECT_TRUE(bounds.Contains(circle_bounds))
1386 << "geometry bounds: " << bounds << std::endl
1387 << " circle bounds: " << circle_bounds;
1388}
1389
1390} // namespace testing
1391} // 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().