| Flutter Impeller |

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