Flutter Impeller
aiks_dl_path_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 "display_list/dl_sampling_options.h"
6 #include "display_list/dl_tile_mode.h"
7 #include "display_list/effects/dl_color_source.h"
8 #include "display_list/effects/dl_mask_filter.h"
10 
11 #include "flutter/display_list/dl_blend_mode.h"
12 #include "flutter/display_list/dl_builder.h"
13 #include "flutter/display_list/dl_color.h"
14 #include "flutter/display_list/dl_paint.h"
15 #include "flutter/display_list/effects/dl_color_filter.h"
16 #include "flutter/display_list/geometry/dl_path_builder.h"
17 #include "flutter/testing/testing.h"
21 
22 namespace impeller {
23 namespace testing {
24 
25 using namespace flutter;
26 
27 TEST_P(AiksTest, RotateColorFilteredPath) {
28  DisplayListBuilder builder;
29  builder.Transform(DlMatrix::MakeTranslation(DlPoint(300, 300)) *
30  DlMatrix::MakeRotationZ(DlDegrees(90)));
31 
32  DlPathBuilder arrow_stem;
33  DlPathBuilder arrow_head;
34 
35  arrow_stem.MoveTo(DlPoint(120, 190)).LineTo(DlPoint(120, 50));
36  arrow_head.MoveTo(DlPoint(50, 120))
37  .LineTo(DlPoint(120, 190))
38  .LineTo(DlPoint(190, 120));
39 
40  auto filter =
41  DlColorFilter::MakeBlend(DlColor::kAliceBlue(), DlBlendMode::kSrcIn);
42 
43  DlPaint paint;
44  paint.setStrokeWidth(15.0);
45  paint.setStrokeCap(DlStrokeCap::kRound);
46  paint.setStrokeJoin(DlStrokeJoin::kRound);
47  paint.setDrawStyle(DlDrawStyle::kStroke);
48  paint.setColorFilter(filter);
49  paint.setColor(DlColor::kBlack());
50 
51  builder.DrawPath(arrow_stem.TakePath(), paint);
52  builder.DrawPath(arrow_head.TakePath(), paint);
53 
54  auto dl = builder.Build();
55  ASSERT_TRUE(OpenPlaygroundHere(dl));
56 }
57 
58 TEST_P(AiksTest, CanRenderStrokes) {
59  DisplayListBuilder builder;
60  DlPaint paint;
61  paint.setColor(DlColor::kRed());
62  paint.setStrokeWidth(20);
63  paint.setDrawStyle(DlDrawStyle::kStroke);
64 
65  builder.DrawPath(DlPath::MakeLine(DlPoint(200, 100), DlPoint(800, 100)),
66  paint);
67 
68  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
69 }
70 
71 TEST_P(AiksTest, CanRenderCurvedStrokes) {
72  DisplayListBuilder builder;
73  DlPaint paint;
74  paint.setColor(DlColor::kRed());
75  paint.setStrokeWidth(25);
76  paint.setDrawStyle(DlDrawStyle::kStroke);
77 
78  builder.DrawPath(DlPath::MakeCircle(DlPoint(500, 500), 250), paint);
79 
80  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
81 }
82 
83 TEST_P(AiksTest, CanRenderThickCurvedStrokes) {
84  DisplayListBuilder builder;
85  DlPaint paint;
86  paint.setColor(DlColor::kRed());
87  paint.setStrokeWidth(100);
88  paint.setDrawStyle(DlDrawStyle::kStroke);
89 
90  builder.DrawPath(DlPath::MakeCircle(DlPoint(100, 100), 50), paint);
91 
92  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
93 }
94 
95 TEST_P(AiksTest, CanRenderThinCurvedStrokes) {
96  DisplayListBuilder builder;
97  DlPaint paint;
98  paint.setColor(DlColor::kRed());
99  paint.setStrokeWidth(0.01);
100  paint.setDrawStyle(DlDrawStyle::kStroke);
101 
102  builder.DrawPath(DlPath::MakeCircle(DlPoint(100, 100), 50), paint);
103 
104  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
105 }
106 
107 TEST_P(AiksTest, CanRenderStrokePathThatEndsAtSharpTurn) {
108  DisplayListBuilder builder;
109  DlPaint paint;
110  paint.setColor(DlColor::kRed());
111  paint.setStrokeWidth(200);
112  paint.setDrawStyle(DlDrawStyle::kStroke);
113 
114  DlPath path = DlPath::MakeArc(DlRect::MakeXYWH(100, 100, 200, 200), //
115  DlDegrees(0), DlDegrees(90), false);
116 
117  builder.DrawPath(path, paint);
118 
119  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
120 }
121 
122 TEST_P(AiksTest, CanRenderStrokePathWithCubicLine) {
123  DisplayListBuilder builder;
124 
125  DlPaint paint;
126  paint.setColor(DlColor::kRed());
127  paint.setStrokeWidth(20);
128  paint.setDrawStyle(DlDrawStyle::kStroke);
129 
130  DlPathBuilder path_builder;
131  path_builder.MoveTo(DlPoint(0, 200));
132  path_builder.CubicCurveTo(DlPoint(50, 400), DlPoint(350, 0),
133  DlPoint(400, 200));
134 
135  builder.DrawPath(path_builder.TakePath(), paint);
136  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
137 }
138 
139 TEST_P(AiksTest, CanRenderQuadraticStrokeWithInstantTurn) {
140  DisplayListBuilder builder;
141 
142  DlPaint paint;
143  paint.setColor(DlColor::kRed());
144  paint.setStrokeWidth(50);
145  paint.setDrawStyle(DlDrawStyle::kStroke);
146  paint.setStrokeCap(DlStrokeCap::kRound);
147 
148  // Should draw a diagonal pill shape. If flat on either end, the stroke is
149  // rendering wrong.
150  DlPathBuilder path_builder;
151  path_builder.MoveTo(DlPoint(250, 250));
152  path_builder.QuadraticCurveTo(DlPoint(100, 100), DlPoint(250, 250));
153 
154  builder.DrawPath(path_builder.TakePath(), paint);
155 
156  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
157 }
158 
159 TEST_P(AiksTest, CanRenderFilledConicPaths) {
160  DisplayListBuilder builder;
161  builder.Scale(GetContentScale().x, GetContentScale().y);
162 
163  DlPaint paint;
164  paint.setColor(DlColor::kRed());
165  paint.setDrawStyle(DlDrawStyle::kFill);
166 
167  DlPaint reference_paint;
168  reference_paint.setColor(DlColor::kGreen());
169  reference_paint.setDrawStyle(DlDrawStyle::kFill);
170 
171  DlPathBuilder path_builder;
172  DlPathBuilder reference_builder;
173 
174  // weight of 1.0 is just a quadratic bezier
175  path_builder.MoveTo(DlPoint(100, 100));
176  path_builder.ConicCurveTo(DlPoint(150, 150), DlPoint(200, 100), 1.0f);
177  reference_builder.MoveTo(DlPoint(300, 100));
178  reference_builder.QuadraticCurveTo(DlPoint(350, 150), DlPoint(400, 100));
179 
180  // weight of sqrt(2)/2 is a circular section
181  path_builder.MoveTo(DlPoint(100, 200));
182  path_builder.ConicCurveTo(DlPoint(150, 250), DlPoint(200, 200), kSqrt2Over2);
183  reference_builder.MoveTo(DlPoint(300, 200));
184  auto magic = DlPathBuilder::kArcApproximationMagic;
185  reference_builder.CubicCurveTo(DlPoint(300, 200) + DlPoint(50, 50) * magic,
186  DlPoint(400, 200) + DlPoint(-50, 50) * magic,
187  DlPoint(400, 200));
188 
189  // weight of .01 is nearly a straight line
190  path_builder.MoveTo(DlPoint(100, 300));
191  path_builder.ConicCurveTo(DlPoint(150, 350), DlPoint(200, 300), 0.01f);
192  reference_builder.MoveTo(DlPoint(300, 300));
193  reference_builder.LineTo(DlPoint(350, 300.5));
194  reference_builder.LineTo(DlPoint(400, 300));
195 
196  // weight of 100.0 is nearly a triangle
197  path_builder.MoveTo(DlPoint(100, 400));
198  path_builder.ConicCurveTo(DlPoint(150, 450), DlPoint(200, 400), 100.0f);
199  reference_builder.MoveTo(DlPoint(300, 400));
200  reference_builder.LineTo(DlPoint(350, 450));
201  reference_builder.LineTo(DlPoint(400, 400));
202 
203  builder.DrawPath(path_builder.TakePath(), paint);
204  builder.DrawPath(reference_builder.TakePath(), reference_paint);
205 
206  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
207 }
208 
209 TEST_P(AiksTest, CanRenderStrokedConicPaths) {
210  DisplayListBuilder builder;
211  builder.Scale(GetContentScale().x, GetContentScale().y);
212 
213  DlPaint paint;
214  paint.setColor(DlColor::kRed());
215  paint.setStrokeWidth(10);
216  paint.setDrawStyle(DlDrawStyle::kStroke);
217  paint.setStrokeCap(DlStrokeCap::kRound);
218  paint.setStrokeJoin(DlStrokeJoin::kRound);
219 
220  DlPaint reference_paint;
221  reference_paint.setColor(DlColor::kGreen());
222  reference_paint.setStrokeWidth(10);
223  reference_paint.setDrawStyle(DlDrawStyle::kStroke);
224  reference_paint.setStrokeCap(DlStrokeCap::kRound);
225  reference_paint.setStrokeJoin(DlStrokeJoin::kRound);
226 
227  DlPathBuilder path_builder;
228  DlPathBuilder reference_builder;
229 
230  // weight of 1.0 is just a quadratic bezier
231  path_builder.MoveTo(DlPoint(100, 100));
232  path_builder.ConicCurveTo(DlPoint(150, 150), DlPoint(200, 100), 1.0f);
233  reference_builder.MoveTo(DlPoint(300, 100));
234  reference_builder.QuadraticCurveTo(DlPoint(350, 150), DlPoint(400, 100));
235 
236  // weight of sqrt(2)/2 is a circular section
237  path_builder.MoveTo(DlPoint(100, 200));
238  path_builder.ConicCurveTo(DlPoint(150, 250), DlPoint(200, 200), kSqrt2Over2);
239  reference_builder.MoveTo(DlPoint(300, 200));
240  auto magic = DlPathBuilder::kArcApproximationMagic;
241  reference_builder.CubicCurveTo(DlPoint(300, 200) + DlPoint(50, 50) * magic,
242  DlPoint(400, 200) + DlPoint(-50, 50) * magic,
243  DlPoint(400, 200));
244 
245  // weight of .0 is a straight line
246  path_builder.MoveTo(DlPoint(100, 300));
247  path_builder.ConicCurveTo(DlPoint(150, 350), DlPoint(200, 300), 0.0f);
248  reference_builder.MoveTo(DlPoint(300, 300));
249  reference_builder.LineTo(DlPoint(400, 300));
250 
251  // weight of 100.0 is nearly a triangle
252  path_builder.MoveTo(DlPoint(100, 400));
253  path_builder.ConicCurveTo(DlPoint(150, 450), DlPoint(200, 400), 100.0f);
254  reference_builder.MoveTo(DlPoint(300, 400));
255  reference_builder.LineTo(DlPoint(350, 450));
256  reference_builder.LineTo(DlPoint(400, 400));
257 
258  builder.DrawPath(path_builder.TakePath(), paint);
259  builder.DrawPath(reference_builder.TakePath(), reference_paint);
260 
261  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
262 }
263 
264 TEST_P(AiksTest, HairlinePath) {
265  Scalar scale = 1.f;
266  Scalar rotation = 0.f;
267  Scalar offset = 0.f;
268  auto callback = [&]() -> sk_sp<DisplayList> {
269  if (AiksTest::ImGuiBegin("Controls", nullptr,
270  ImGuiWindowFlags_AlwaysAutoResize)) {
271  ImGui::SliderFloat("Scale", &scale, 0, 6);
272  ImGui::SliderFloat("Rotate", &rotation, 0, 90);
273  ImGui::SliderFloat("Offset", &offset, 0, 2);
274  ImGui::End();
275  }
276 
277  DisplayListBuilder builder;
278  builder.Scale(GetContentScale().x, GetContentScale().y);
279  builder.DrawPaint(DlPaint(DlColor(0xff111111)));
280 
281  DlPaint paint;
282  paint.setStrokeWidth(0.f);
283  paint.setColor(DlColor::kWhite());
284  paint.setStrokeCap(DlStrokeCap::kRound);
285  paint.setStrokeJoin(DlStrokeJoin::kRound);
286  paint.setDrawStyle(DlDrawStyle::kStroke);
287 
288  builder.Translate(512, 384);
289  builder.Scale(scale, scale);
290  builder.Rotate(rotation);
291  builder.Translate(-512, -384 + offset);
292 
293  for (int i = 0; i < 5; ++i) {
294  Scalar yoffset = i * 25.25f + 300.f;
295  DlPathBuilder path_builder;
296 
297  path_builder.MoveTo(DlPoint(100, yoffset));
298  path_builder.LineTo(DlPoint(924, yoffset));
299  builder.DrawPath(path_builder.TakePath(), paint);
300  }
301 
302  return builder.Build();
303  };
304 
305  ASSERT_TRUE(OpenPlaygroundHere(callback));
306 }
307 
308 TEST_P(AiksTest, HairlineDrawLine) {
309  Scalar scale = 1.f;
310  Scalar rotation = 0.f;
311  Scalar offset = 0.f;
312  auto callback = [&]() -> sk_sp<DisplayList> {
313  if (AiksTest::ImGuiBegin("Controls", nullptr,
314  ImGuiWindowFlags_AlwaysAutoResize)) {
315  ImGui::SliderFloat("Scale", &scale, 0, 6);
316  ImGui::SliderFloat("Rotate", &rotation, 0, 90);
317  ImGui::SliderFloat("Offset", &offset, 0, 2);
318  ImGui::End();
319  }
320 
321  DisplayListBuilder builder;
322  builder.Scale(GetContentScale().x, GetContentScale().y);
323  builder.DrawPaint(DlPaint(DlColor(0xff111111)));
324 
325  DlPaint paint;
326  paint.setStrokeWidth(0.f);
327  paint.setColor(DlColor::kWhite());
328 
329  builder.Translate(512, 384);
330  builder.Scale(scale, scale);
331  builder.Rotate(rotation);
332  builder.Translate(-512, -384 + offset);
333 
334  for (int i = 0; i < 5; ++i) {
335  Scalar yoffset = i * 25.25f + 300.f;
336 
337  builder.DrawLine(DlPoint(100, yoffset), DlPoint(924, yoffset), paint);
338  }
339 
340  return builder.Build();
341  };
342 
343  ASSERT_TRUE(OpenPlaygroundHere(callback));
344 }
345 
346 TEST_P(AiksTest, CanRenderTightConicPath) {
347  DisplayListBuilder builder;
348  builder.Scale(GetContentScale().x, GetContentScale().y);
349 
350  DlPaint paint;
351  paint.setColor(DlColor::kRed());
352  paint.setDrawStyle(DlDrawStyle::kFill);
353 
354  DlPaint reference_paint;
355  reference_paint.setColor(DlColor::kGreen());
356  reference_paint.setDrawStyle(DlDrawStyle::kFill);
357 
358  DlPathBuilder path_builder;
359 
360  path_builder.MoveTo(DlPoint(100, 100));
361  path_builder.ConicCurveTo(DlPoint(150, 450), DlPoint(200, 100), 5.0f);
362 
363  DlPathBuilder reference_builder;
364  PathTessellator::Conic component{DlPoint(300, 100), //
365  DlPoint(350, 450), //
366  DlPoint(400, 100), //
367  5.0f};
368  reference_builder.MoveTo(component.p1);
369  constexpr int N = 100;
370  for (int i = 1; i < N; i++) {
371  reference_builder.LineTo(component.Solve(static_cast<Scalar>(i) / N));
372  }
373  reference_builder.LineTo(component.p2);
374 
375  DlPaint line_paint;
376  line_paint.setColor(DlColor::kYellow());
377  line_paint.setDrawStyle(DlDrawStyle::kStroke);
378  line_paint.setStrokeWidth(1.0f);
379 
380  // Draw some lines to provide a spacial reference for the curvature of
381  // the tips of the direct rendering and the manually tessellated versions.
382  builder.DrawLine(DlPoint(145, 100), DlPoint(145, 450), line_paint);
383  builder.DrawLine(DlPoint(155, 100), DlPoint(155, 450), line_paint);
384  builder.DrawLine(DlPoint(345, 100), DlPoint(345, 450), line_paint);
385  builder.DrawLine(DlPoint(355, 100), DlPoint(355, 450), line_paint);
386  builder.DrawLine(DlPoint(100, 392.5f), DlPoint(400, 392.5f), line_paint);
387 
388  // Draw the two paths (direct and manually tessellated) on top of the lines.
389  builder.DrawPath(path_builder.TakePath(), paint);
390  builder.DrawPath(reference_builder.TakePath(), reference_paint);
391 
392  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
393 }
394 
395 TEST_P(AiksTest, CanRenderDifferencePaths) {
396  DisplayListBuilder builder;
397 
398  DlPaint paint;
399  paint.setColor(DlColor::kRed());
400 
401  RoundingRadii radii = {
402  .top_left = {50, 25},
403  .top_right = {25, 50},
404  .bottom_left = {25, 50},
405  .bottom_right = {50, 25},
406  };
407  DlPathBuilder path_builder;
408  DlRoundRect rrect =
409  DlRoundRect::MakeRectRadii(DlRect::MakeXYWH(100, 100, 200, 200), radii);
410  // We use the factory method to convert the rrect and circle to a path so
411  // that they use the legacy conics for legacy golden output.
412  path_builder.AddPath(DlPath::MakeRoundRect(rrect));
413  path_builder.AddPath(DlPath::MakeCircle(DlPoint(200, 200), 50));
414  path_builder.SetFillType(DlPathFillType::kOdd);
415  DlPath path = path_builder.TakePath();
416 
417  builder.DrawImage(
418  DlImageImpeller::Make(CreateTextureForFixture("boston.jpg")),
419  DlPoint{10, 10}, {});
420  builder.DrawPath(path, paint);
421 
422  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
423 }
424 
425 // Regression test for https://github.com/flutter/flutter/issues/134816.
426 //
427 // It should be possible to draw 3 lines, and not have an implicit close path.
428 TEST_P(AiksTest, CanDrawAnOpenPath) {
429  DisplayListBuilder builder;
430 
431  // Starting at (50, 50), draw lines from:
432  // 1. (50, height)
433  // 2. (width, height)
434  // 3. (width, 50)
435  DlPathBuilder path_builder;
436  path_builder.MoveTo(DlPoint(50, 50));
437  path_builder.LineTo(DlPoint(50, 100));
438  path_builder.LineTo(DlPoint(100, 100));
439  path_builder.LineTo(DlPoint(100, 50));
440 
441  DlPaint paint;
442  paint.setColor(DlColor::kRed());
443  paint.setDrawStyle(DlDrawStyle::kStroke);
444  paint.setStrokeWidth(10);
445 
446  builder.DrawPath(path_builder.TakePath(), paint);
447 
448  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
449 }
450 
451 TEST_P(AiksTest, CanDrawAnOpenPathThatIsntARect) {
452  DisplayListBuilder builder;
453 
454  // Draw a stroked path that is explicitly closed to verify
455  // It doesn't become a rectangle.
456  DlPathBuilder path_builder;
457  path_builder.MoveTo(DlPoint(50, 50));
458  path_builder.LineTo(DlPoint(520, 120));
459  path_builder.LineTo(DlPoint(300, 310));
460  path_builder.LineTo(DlPoint(100, 50));
461  path_builder.Close();
462 
463  DlPaint paint;
464  paint.setColor(DlColor::kRed());
465  paint.setDrawStyle(DlDrawStyle::kStroke);
466  paint.setStrokeWidth(10);
467 
468  builder.DrawPath(path_builder.TakePath(), paint);
469 
470  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
471 }
472 
473 TEST_P(AiksTest, SolidStrokesRenderCorrectly) {
474  // Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2
475  auto callback = [&]() -> sk_sp<DisplayList> {
476  static Color color = Color::Black().WithAlpha(0.5);
477  static float scale = 3;
478  static bool add_circle_clip = true;
479 
480  if (AiksTest::ImGuiBegin("Controls", nullptr,
481  ImGuiWindowFlags_AlwaysAutoResize)) {
482  ImGui::ColorEdit4("Color", reinterpret_cast<float*>(&color));
483  ImGui::SliderFloat("Scale", &scale, 0, 6);
484  ImGui::Checkbox("Circle clip", &add_circle_clip);
485  ImGui::End();
486  }
487 
488  DisplayListBuilder builder;
489  builder.Scale(GetContentScale().x, GetContentScale().y);
490  DlPaint paint;
491 
492  paint.setColor(DlColor::kWhite());
493  builder.DrawPaint(paint);
494 
495  paint.setColor(
496  DlColor::ARGB(color.alpha, color.red, color.green, color.blue));
497  paint.setDrawStyle(DlDrawStyle::kStroke);
498  paint.setStrokeWidth(10);
499 
500  DlPathBuilder path_builder;
501  path_builder.MoveTo(DlPoint(20, 20));
502  path_builder.QuadraticCurveTo(DlPoint(60, 20), DlPoint(60, 60));
503  path_builder.Close();
504  path_builder.MoveTo(DlPoint(60, 20));
505  path_builder.QuadraticCurveTo(DlPoint(60, 60), DlPoint(20, 60));
506  DlPath path = path_builder.TakePath();
507 
508  builder.Scale(scale, scale);
509 
510  if (add_circle_clip) {
511  static PlaygroundPoint circle_clip_point_a(Point(60, 300), 20,
512  Color::Red());
513  static PlaygroundPoint circle_clip_point_b(Point(600, 300), 20,
514  Color::Red());
515  auto [handle_a, handle_b] =
516  DrawPlaygroundLine(circle_clip_point_a, circle_clip_point_b);
517 
518  Matrix screen_to_canvas = builder.GetMatrix();
519  if (!screen_to_canvas.IsInvertible()) {
520  return nullptr;
521  }
522  screen_to_canvas = screen_to_canvas.Invert();
523 
524  Point point_a = screen_to_canvas * handle_a;
525  Point point_b = screen_to_canvas * handle_b;
526 
527  Point middle = point_a + point_b;
528  middle *= GetContentScale().x / 2;
529 
530  auto radius = point_a.GetDistance(middle);
531 
532  builder.ClipPath(DlPath::MakeCircle(middle, radius));
533  }
534 
535  for (auto join :
536  {DlStrokeJoin::kBevel, DlStrokeJoin::kRound, DlStrokeJoin::kMiter}) {
537  paint.setStrokeJoin(join);
538  for (auto cap :
539  {DlStrokeCap::kButt, DlStrokeCap::kSquare, DlStrokeCap::kRound}) {
540  paint.setStrokeCap(cap);
541  builder.DrawPath(path, paint);
542  builder.Translate(80, 0);
543  }
544  builder.Translate(-240, 60);
545  }
546 
547  return builder.Build();
548  };
549 
550  ASSERT_TRUE(OpenPlaygroundHere(callback));
551 }
552 
553 TEST_P(AiksTest, DrawLinesRenderCorrectly) {
554  DisplayListBuilder builder;
555  builder.Scale(GetContentScale().x, GetContentScale().y);
556 
557  DlPaint paint;
558  paint.setColor(DlColor::kBlue());
559  paint.setStrokeWidth(10);
560 
561  auto draw = [&builder](DlPaint& paint) {
562  for (auto cap :
563  {DlStrokeCap::kButt, DlStrokeCap::kSquare, DlStrokeCap::kRound}) {
564  paint.setStrokeCap(cap);
565  DlPoint origin = {100, 100};
566  builder.DrawLine(DlPoint(150, 100), DlPoint(250, 100), paint);
567  for (int d = 15; d < 90; d += 15) {
569  Point origin = {100, 100};
570  Point p0 = {50, 0};
571  Point p1 = {150, 0};
572  auto a = origin + m * p0;
573  auto b = origin + m * p1;
574 
575  builder.DrawLine(a, b, paint);
576  }
577  builder.DrawLine(DlPoint(100, 150), DlPoint(100, 250), paint);
578  builder.DrawCircle(origin, 35, paint);
579 
580  builder.DrawLine(DlPoint(250, 250), DlPoint(250, 250), paint);
581 
582  builder.Translate(250, 0);
583  }
584  builder.Translate(-750, 250);
585  };
586 
587  std::vector<DlColor> colors = {
588  DlColor::ARGB(1, 0x1f / 255.0, 0.0, 0x5c / 255.0),
589  DlColor::ARGB(1, 0x5b / 255.0, 0.0, 0x60 / 255.0),
590  DlColor::ARGB(1, 0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0),
591  DlColor::ARGB(1, 0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0),
592  DlColor::ARGB(1, 0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0),
593  DlColor::ARGB(1, 0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0),
594  DlColor::ARGB(1, 0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0)};
595  std::vector<Scalar> stops = {
596  0.0,
597  (1.0 / 6.0) * 1,
598  (1.0 / 6.0) * 2,
599  (1.0 / 6.0) * 3,
600  (1.0 / 6.0) * 4,
601  (1.0 / 6.0) * 5,
602  1.0,
603  };
604 
605  auto texture = DlImageImpeller::Make(
606  CreateTextureForFixture("airplane.jpg",
607  /*enable_mipmapping=*/true));
608 
609  draw(paint);
610 
611  paint.setColorSource(DlColorSource::MakeRadial({100, 100}, 200, stops.size(),
612  colors.data(), stops.data(),
613  DlTileMode::kMirror));
614  draw(paint);
615 
616  DlMatrix matrix = DlMatrix::MakeTranslation({-150, 75});
617  paint.setColorSource(DlColorSource::MakeImage(
618  texture, DlTileMode::kRepeat, DlTileMode::kRepeat,
619  DlImageSampling::kMipmapLinear, &matrix));
620  draw(paint);
621 
622  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
623 }
624 
625 // The goal of this test is to show that scaling the lines doesn't also scale
626 // the antialiasing. The amount of blurring should be the same for both
627 // horizontal lines.
628 TEST_P(AiksTest, ScaleExperimentAntialiasLines) {
629  Scalar scale = 5.0;
630  Scalar line_width = 10.f;
631  auto callback = [&]() -> sk_sp<DisplayList> {
632  if (AiksTest::ImGuiBegin("Controls", nullptr,
633  ImGuiWindowFlags_AlwaysAutoResize)) {
634  ImGui::SliderFloat("Scale", &scale, 0.001, 5);
635  ImGui::SliderFloat("Width", &line_width, 1, 20);
636 
637  ImGui::End();
638  }
639  DisplayListBuilder builder;
640  builder.Scale(GetContentScale().x, GetContentScale().y);
641 
642  builder.DrawPaint(DlPaint(DlColor(0xff111111)));
643 
644  {
645  DlPaint paint;
646  paint.setColor(DlColor::kGreenYellow());
647  paint.setStrokeWidth(line_width);
648 
649  builder.DrawLine(DlPoint(100, 100), DlPoint(350, 100), paint);
650  builder.DrawLine(DlPoint(100, 100), DlPoint(350, 150), paint);
651 
652  builder.Save();
653  builder.Translate(100, 300);
654  builder.Scale(scale, scale);
655  builder.Translate(-100, -300);
656  builder.DrawLine(DlPoint(100, 300), DlPoint(350, 300), paint);
657  builder.DrawLine(DlPoint(100, 300), DlPoint(350, 450), paint);
658  builder.Restore();
659  }
660 
661  {
662  DlPaint paint;
663  paint.setColor(DlColor::kGreenYellow());
664  paint.setStrokeWidth(2.0);
665 
666  builder.Save();
667  builder.Translate(100, 500);
668  builder.Scale(0.2, 0.2);
669  builder.Translate(-100, -500);
670  builder.DrawLine(DlPoint(100, 500), DlPoint(350, 500), paint);
671  builder.DrawLine(DlPoint(100, 500), DlPoint(350, 650), paint);
672  builder.Restore();
673  }
674 
675  return builder.Build();
676  };
677  ASSERT_TRUE(OpenPlaygroundHere(callback));
678 }
679 
680 TEST_P(AiksTest, HexagonExperimentAntialiasLines) {
681  float scale = 5.0f;
682  float line_width = 10.f;
683  float rotation = 0.f;
684 
685  auto callback = [&]() -> sk_sp<DisplayList> {
686  if (AiksTest::ImGuiBegin("Controls", nullptr,
687  ImGuiWindowFlags_AlwaysAutoResize)) {
688  // Use ImGui::SliderFloat for consistency
689  ImGui::SliderFloat("Scale", &scale, 0.001f, 5.0f);
690  ImGui::SliderFloat("Width", &line_width, 1.0f, 20.0f);
691  ImGui::SliderFloat("Rotation", &rotation, 0.0f, 180.0f);
692 
693  ImGui::End();
694  }
695  DisplayListBuilder builder;
696  builder.Scale(static_cast<float>(GetContentScale().x),
697  static_cast<float>(GetContentScale().y));
698 
699  builder.DrawPaint(DlPaint(DlColor(0xff111111))); // Background
700 
701  {
702  DlPaint hex_paint;
703  hex_paint.setColor(
704  DlColor::kGreen()); // Changed color to Red for visibility
705  hex_paint.setStrokeWidth(line_width); // Use the interactive width
706 
707  float cx = 512.0f; // Center X
708  float cy = 384.0f; // Center Y
709  float r = 80.0f; // Radius (distance from center to vertex)
710 
711  float r_sin60 = r * std::sqrt(3.0f) / 2.0f;
712  float r_cos60 = r / 2.0f;
713 
714  DlPoint v0 = DlPoint(cx + r, cy); // Right vertex
715  DlPoint v1 = DlPoint(cx + r_cos60, cy - r_sin60); // Top-right vertex
716  DlPoint v2 = DlPoint(
717  cx - r_cos60,
718  cy - r_sin60); // Top-left vertex (v1-v2 is top horizontal side)
719  DlPoint v3 = DlPoint(cx - r, cy); // Left vertex
720  DlPoint v4 = DlPoint(cx - r_cos60, cy + r_sin60); // Bottom-left vertex
721  DlPoint v5 =
722  DlPoint(cx + r_cos60, cy + r_sin60); // Bottom-right vertex (v4-v5 is
723  // bottom horizontal side)
724 
725  builder.Translate(cx, cy);
726  builder.Scale(scale, scale);
727  builder.Rotate(rotation);
728  builder.Translate(-cx, -cy);
729 
730  builder.DrawLine(v0, v1, hex_paint);
731  builder.DrawLine(v1, v2, hex_paint); // Top side
732  builder.DrawLine(v2, v3, hex_paint);
733  builder.DrawLine(v3, v4, hex_paint);
734  builder.DrawLine(v4, v5, hex_paint); // Bottom side
735  builder.DrawLine(v5, v0, hex_paint); // Close the hexagon
736  }
737 
738  return builder.Build();
739  };
740  ASSERT_TRUE(OpenPlaygroundHere(callback));
741 }
742 
743 TEST_P(AiksTest, SimpleExperimentAntialiasLines) {
744  DisplayListBuilder builder;
745  builder.Scale(GetContentScale().x, GetContentScale().y);
746 
747  builder.DrawPaint(DlPaint(DlColor(0xff111111)));
748 
749  DlPaint paint;
750  paint.setColor(DlColor::kGreenYellow());
751  paint.setStrokeWidth(10);
752 
753  auto draw = [&builder](DlPaint& paint) {
754  for (auto cap :
755  {DlStrokeCap::kButt, DlStrokeCap::kSquare, DlStrokeCap::kRound}) {
756  paint.setStrokeCap(cap);
757  DlPoint origin = {100, 100};
758  builder.DrawLine(DlPoint(150, 100), DlPoint(250, 100), paint);
759  for (int d = 15; d < 90; d += 15) {
761  Point origin = {100, 100};
762  Point p0 = {50, 0};
763  Point p1 = {150, 0};
764  auto a = origin + m * p0;
765  auto b = origin + m * p1;
766 
767  builder.DrawLine(a, b, paint);
768  }
769  builder.DrawLine(DlPoint(100, 150), DlPoint(100, 250), paint);
770  builder.DrawCircle(origin, 35, paint);
771 
772  builder.DrawLine(DlPoint(250, 250), DlPoint(250, 250), paint);
773 
774  builder.Translate(250, 0);
775  }
776  builder.Translate(-750, 250);
777  };
778 
779  draw(paint);
780 
781  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
782 }
783 
784 TEST_P(AiksTest, DrawRectStrokesRenderCorrectly) {
785  DisplayListBuilder builder;
786  DlPaint paint;
787  paint.setColor(DlColor::kRed());
788  paint.setDrawStyle(DlDrawStyle::kStroke);
789  paint.setStrokeWidth(10);
790 
791  builder.Translate(100, 100);
792  builder.DrawPath(DlPath::MakeRect(DlRect::MakeSize(DlSize(100, 100))), paint);
793 
794  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
795 }
796 
797 TEST_P(AiksTest, DrawRectStrokesWithBevelJoinRenderCorrectly) {
798  DisplayListBuilder builder;
799  DlPaint paint;
800  paint.setColor(DlColor::kRed());
801  paint.setDrawStyle(DlDrawStyle::kStroke);
802  paint.setStrokeWidth(10);
803  paint.setStrokeJoin(DlStrokeJoin::kBevel);
804 
805  builder.Translate(100, 100);
806  builder.DrawPath(DlPath::MakeRect(DlRect::MakeSize(DlSize(100, 100))), paint);
807 
808  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
809 }
810 
811 TEST_P(AiksTest, CanDrawMultiContourConvexPath) {
812  DlPathBuilder path_builder;
813  for (auto i = 0; i < 10; i++) {
814  if (i % 2 == 0) {
815  // We use the factory method to convert the circle to a path so that it
816  // uses the legacy conics for legacy golden output.
817  DlPath circle =
818  DlPath::MakeCircle(DlPoint(100 + 50 * i, 100 + 50 * i), 100);
819  path_builder.AddPath(circle);
820  path_builder.Close();
821  } else {
822  path_builder.MoveTo(DlPoint(100.f + 50.f * i - 100, 100.f + 50.f * i));
823  path_builder.LineTo(DlPoint(100.f + 50.f * i, 100.f + 50.f * i - 100));
824  path_builder.LineTo(DlPoint(100.f + 50.f * i - 100, //
825  100.f + 50.f * i - 100));
826  path_builder.Close();
827  }
828  }
829  DlPath path = path_builder.TakePath();
830 
831  DisplayListBuilder builder;
832  DlPaint paint;
833  paint.setColor(DlColor::kRed().withAlpha(102));
834  builder.DrawPath(path, paint);
835 
836  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
837 }
838 
839 TEST_P(AiksTest, ArcWithZeroSweepAndBlur) {
840  DisplayListBuilder builder;
841  builder.Scale(GetContentScale().x, GetContentScale().y);
842 
843  DlPaint paint;
844  paint.setColor(DlColor::kRed());
845 
846  std::vector<DlColor> colors = {DlColor::RGBA(1.0, 0.0, 0.0, 1.0),
847  DlColor::RGBA(0.0, 0.0, 0.0, 1.0)};
848  std::vector<Scalar> stops = {0.0, 1.0};
849 
850  paint.setColorSource(
851  DlColorSource::MakeSweep({100, 100}, 45, 135, stops.size(), colors.data(),
852  stops.data(), DlTileMode::kMirror));
853  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 20));
854 
855  DlPathBuilder path_builder;
856  path_builder.AddArc(DlRect::MakeXYWH(10, 10, 100, 100), //
857  DlDegrees(0), DlDegrees(0));
858  builder.DrawPath(path_builder.TakePath(), paint);
859 
860  // Check that this empty picture can be created without crashing.
861  builder.Build();
862 }
863 
864 TEST_P(AiksTest, CanRenderClips) {
865  DisplayListBuilder builder;
866  DlPaint paint;
867  paint.setColor(DlColor::kFuchsia());
868 
869  builder.ClipPath(DlPath::MakeRect(DlRect::MakeXYWH(0, 0, 500, 500)));
870  builder.DrawPath(DlPath::MakeCircle(DlPoint(500, 500), 250), paint);
871 
872  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
873 }
874 
875 TEST_P(AiksTest, FatStrokeArc) {
876  DlScalar stroke_width = 300;
877  DlScalar aspect = 1.0;
878  DlScalar start_angle = 0;
879  DlScalar end_angle = 90;
880  auto callback = [&]() -> sk_sp<DisplayList> {
881  if (AiksTest::ImGuiBegin("Controls", nullptr,
882  ImGuiWindowFlags_AlwaysAutoResize)) {
883  ImGui::SliderFloat("Stroke Width", &stroke_width, 1, 300);
884  ImGui::SliderFloat("Aspect", &aspect, 0.5, 2.0);
885  ImGui::SliderFloat("Start Angle", &start_angle, 0, 360);
886  ImGui::SliderFloat("End Angle", &end_angle, 0, 360);
887  ImGui::End();
888  }
889 
890  DisplayListBuilder builder;
891  DlPaint grey_paint;
892  grey_paint.setColor(DlColor(0xff111111));
893  builder.DrawPaint(grey_paint);
894 
895  DlPaint white_paint;
896  white_paint.setColor(DlColor::kWhite());
897  white_paint.setStrokeWidth(stroke_width);
898  white_paint.setDrawStyle(DlDrawStyle::kStroke);
899  DlPaint red_paint;
900  red_paint.setColor(DlColor::kRed());
901 
902  Rect rect = Rect::MakeXYWH(100, 100, 100, aspect * 100);
903  builder.DrawRect(rect, red_paint);
904  builder.DrawArc(rect, start_angle, end_angle,
905  /*useCenter=*/false, white_paint);
906  DlScalar frontier = rect.GetRight() + stroke_width / 2.0;
907  builder.DrawLine(Point(frontier, 0), Point(frontier, 150), red_paint);
908 
909  return builder.Build();
910  };
911  ASSERT_TRUE(OpenPlaygroundHere(callback));
912 }
913 
914 TEST_P(AiksTest, CanRenderOverlappingMultiContourPath) {
915  DisplayListBuilder builder;
916 
917  DlPaint paint;
918  paint.setColor(DlColor::kRed());
919 
920  RoundingRadii radii = {
921  .top_left = DlSize(50, 50),
922  .top_right = DlSize(50, 50),
923  .bottom_left = DlSize(50, 50),
924  .bottom_right = DlSize(50, 50),
925  };
926 
927  const Scalar kTriangleHeight = 100;
928  DlRoundRect rrect = DlRoundRect::MakeRectRadii(
929  DlRect::MakeXYWH(-kTriangleHeight / 2.0f, -kTriangleHeight / 2.0f,
930  kTriangleHeight, kTriangleHeight),
931  radii //
932  );
933  // We use the factory method to convert the rrect to a path so that it
934  // uses the legacy conics for legacy golden output.
935  DlPath rrect_path = DlPath::MakeRoundRect(rrect);
936 
937  builder.Translate(200, 200);
938  // Form a path similar to the Material drop slider value indicator. Both
939  // shapes should render identically side-by-side.
940  {
941  DlPathBuilder path_builder;
942  path_builder.MoveTo(DlPoint(0, kTriangleHeight));
943  path_builder.LineTo(DlPoint(-kTriangleHeight / 2.0f, 0));
944  path_builder.LineTo(DlPoint(kTriangleHeight / 2.0f, 0));
945  path_builder.Close();
946  path_builder.AddPath(rrect_path);
947 
948  builder.DrawPath(path_builder.TakePath(), paint);
949  }
950  builder.Translate(100, 0);
951 
952  {
953  DlPathBuilder path_builder;
954  path_builder.MoveTo(DlPoint(0, kTriangleHeight));
955  path_builder.LineTo(DlPoint(-kTriangleHeight / 2.0f, 0));
956  path_builder.LineTo(DlPoint(0, -10));
957  path_builder.LineTo(DlPoint(kTriangleHeight / 2.0f, 0));
958  path_builder.Close();
959  path_builder.AddPath(rrect_path);
960 
961  builder.DrawPath(path_builder.TakePath(), paint);
962  }
963 
964  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
965 }
966 
967 TEST_P(AiksTest, TwoContourPathWithSinglePointContour) {
968  DisplayListBuilder builder;
969 
970  DlPaint paint;
971  paint.setColor(DlColor::kRed());
972  paint.setDrawStyle(DlDrawStyle::kStroke);
973  paint.setStrokeWidth(15.0);
974  paint.setStrokeCap(DlStrokeCap::kRound);
975 
976  DlPathBuilder path_builder;
977  path_builder.MoveTo(DlPoint(100, 100));
978  path_builder.LineTo(DlPoint(150, 150));
979  path_builder.MoveTo(DlPoint(200, 200));
980  path_builder.LineTo(DlPoint(200, 200));
981 
982  builder.DrawPath(path_builder.TakePath(), paint);
983 
984  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
985 }
986 
987 TEST_P(AiksTest, StrokeCapsAndJoins) {
988  DisplayListBuilder builder;
989  builder.Scale(GetContentScale().x, GetContentScale().y);
990 
991  builder.Translate(100, 0);
992 
993  builder.Save();
994  for (auto cap : std::vector<DlStrokeCap>{
995  DlStrokeCap::kButt, DlStrokeCap::kRound, DlStrokeCap::kSquare}) {
996  DlPathBuilder path_builder;
997  path_builder.MoveTo({20, 50});
998  path_builder.LineTo({50, 50});
999  path_builder.MoveTo({120, 50});
1000  path_builder.LineTo({120, 80});
1001  path_builder.MoveTo({180, 50});
1002  path_builder.LineTo({180, 50});
1003  DlPath path = path_builder.TakePath();
1004 
1005  DlPaint paint;
1006  paint.setColor(DlColor::kRed());
1007  paint.setDrawStyle(DlDrawStyle::kStroke);
1008  paint.setStrokeWidth(20.0f);
1009  paint.setStrokeCap(cap);
1010  paint.setStrokeJoin(DlStrokeJoin::kBevel);
1011 
1012  builder.DrawPath(path, paint);
1013 
1014  paint.setColor(DlColor::kYellow());
1015  paint.setStrokeWidth(1.0f);
1016  paint.setStrokeCap(DlStrokeCap::kButt);
1017 
1018  builder.DrawPath(path, paint);
1019 
1020  builder.Translate(250, 0);
1021  }
1022  builder.Restore();
1023 
1024  builder.Translate(0, 100);
1025 
1026  builder.Save();
1027  for (auto join : std::vector<DlStrokeJoin>{
1028  DlStrokeJoin::kBevel, DlStrokeJoin::kRound, DlStrokeJoin::kMiter}) {
1029  DlPathBuilder path_builder;
1030  path_builder.MoveTo({20, 50}); // 0 degree right turn
1031  path_builder.LineTo({50, 50});
1032  path_builder.LineTo({80, 50});
1033  path_builder.MoveTo({20, 150}); // 90 degree right turn
1034  path_builder.LineTo({50, 150});
1035  path_builder.LineTo({50, 180});
1036  path_builder.MoveTo({20, 250}); // 45 degree right turn
1037  path_builder.LineTo({50, 250});
1038  path_builder.LineTo({70, 270});
1039  path_builder.MoveTo({20, 350}); // 135 degree right turn
1040  path_builder.LineTo({50, 350});
1041  path_builder.LineTo({30, 370});
1042  path_builder.MoveTo({20, 450}); // 180 degree right turn
1043  path_builder.LineTo({50, 450});
1044  path_builder.LineTo({20, 450});
1045  path_builder.MoveTo({120, 80}); // 0 degree left turn
1046  path_builder.LineTo({150, 80});
1047  path_builder.LineTo({180, 80});
1048  path_builder.MoveTo({120, 180}); // 90 degree left turn
1049  path_builder.LineTo({150, 180});
1050  path_builder.LineTo({150, 150});
1051  path_builder.MoveTo({120, 280}); // 45 degree left turn
1052  path_builder.LineTo({150, 280});
1053  path_builder.LineTo({170, 260});
1054  path_builder.MoveTo({120, 380}); // 135 degree left turn
1055  path_builder.LineTo({150, 380});
1056  path_builder.LineTo({130, 360});
1057  path_builder.MoveTo({120, 480}); // 180 degree left turn
1058  path_builder.LineTo({150, 480});
1059  path_builder.LineTo({120, 480});
1060  DlPath path = path_builder.TakePath();
1061 
1062  DlPaint paint;
1063 
1064  paint.setColor(DlColor::kRed());
1065  paint.setDrawStyle(DlDrawStyle::kStroke);
1066  paint.setStrokeWidth(20.0f);
1067  paint.setStrokeCap(DlStrokeCap::kSquare);
1068  paint.setStrokeJoin(join);
1069  builder.DrawPath(path, paint);
1070 
1071  paint.setColor(DlColor::kYellow());
1072  paint.setStrokeWidth(1.0f);
1073  paint.setStrokeCap(DlStrokeCap::kButt);
1074  builder.DrawPath(path, paint);
1075 
1076  builder.Translate(250, 0);
1077  }
1078  builder.Restore();
1079 
1080  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1081 }
1082 
1083 TEST_P(AiksTest, BlurredCircleWithStrokeWidth) {
1084  DisplayListBuilder builder;
1085  DlPaint paint;
1086  paint.setColor(DlColor::kGreen());
1087  paint.setDrawStyle(DlDrawStyle::kStroke);
1088  paint.setStrokeWidth(30);
1089  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 5));
1090 
1091  builder.DrawCircle(DlPoint(200, 200), 100, paint);
1092 
1093  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1094 }
1095 
1096 } // namespace testing
1097 } // namespace impeller
static bool ImGuiBegin(const char *name, bool *p_open, ImGuiWindowFlags flags)
static sk_sp< DlImageImpeller > Make(std::shared_ptr< Texture > texture, OwningContext owning_context=OwningContext::kIO)
int32_t x
TEST_P(AiksTest, DrawAtlasNoColor)
float Scalar
Definition: scalar.h:19
std::tuple< Point, Point > DrawPlaygroundLine(PlaygroundPoint &point_a, PlaygroundPoint &point_b)
Definition: widgets.cc:50
constexpr float kSqrt2Over2
Definition: constants.h:51
flutter::DlRoundRect DlRoundRect
Definition: dl_dispatcher.h:27
TPoint< Scalar > Point
Definition: point.h:327
flutter::DlPoint DlPoint
Definition: dl_dispatcher.h:24
flutter::DlPath DlPath
Definition: dl_dispatcher.h:29
flutter::DlScalar DlScalar
Definition: dl_dispatcher.h:23
Scalar blue
Definition: color.h:138
Scalar alpha
Definition: color.h:143
static constexpr Color Black()
Definition: color.h:266
constexpr Color WithAlpha(Scalar new_alpha) const
Definition: color.h:278
Scalar red
Definition: color.h:128
static constexpr Color Red()
Definition: color.h:272
Scalar green
Definition: color.h:133
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
bool IsInvertible() const
Definition: matrix.h:321
Matrix Invert() const
Definition: matrix.cc:97
static Matrix MakeRotationZ(Radians r)
Definition: matrix.h:223
constexpr Type GetDistance(const TPoint &p) const
Definition: point.h:200
constexpr auto GetRight() const
Definition: rect.h:359
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136