Flutter Impeller
dl_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 <array>
6 #include <cmath>
7 #include <memory>
8 #include <vector>
9 
10 #include "flutter/display_list/dl_blend_mode.h"
11 #include "flutter/display_list/dl_builder.h"
12 #include "flutter/display_list/dl_color.h"
13 #include "flutter/display_list/dl_paint.h"
14 #include "flutter/display_list/dl_text_skia.h"
15 #include "flutter/display_list/dl_tile_mode.h"
16 #include "flutter/display_list/effects/dl_color_filter.h"
17 #include "flutter/display_list/effects/dl_color_source.h"
18 #include "flutter/display_list/effects/dl_image_filters.h"
19 #include "flutter/display_list/effects/dl_mask_filter.h"
20 #include "flutter/display_list/geometry/dl_path_builder.h"
21 #include "flutter/testing/testing.h"
22 #include "gtest/gtest.h"
34 #include "third_party/imgui/imgui.h"
35 
36 namespace impeller {
37 namespace testing {
38 
39 flutter::DlColor toColor(const float* components) {
40  return flutter::DlColor(Color::ToIColor(
41  Color(components[0], components[1], components[2], components[3])));
42 }
43 
46 
47 TEST_P(DisplayListTest, CanDrawRect) {
48  flutter::DisplayListBuilder builder;
49  builder.DrawRect(DlRect::MakeXYWH(10, 10, 100, 100),
50  flutter::DlPaint(flutter::DlColor::kBlue()));
51  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
52 }
53 
54 TEST_P(DisplayListTest, CanDrawTextBlob) {
55  flutter::DisplayListBuilder builder;
56  builder.DrawText(flutter::DlTextSkia::Make(
57  SkTextBlob::MakeFromString("Hello", CreateTestFont())),
58  100, 100, flutter::DlPaint(flutter::DlColor::kBlue()));
59  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
60 }
61 
62 TEST_P(DisplayListTest, CanDrawTextBlobWithGradient) {
63  flutter::DisplayListBuilder builder;
64 
65  std::vector<flutter::DlColor> colors = {flutter::DlColor::kBlue(),
66  flutter::DlColor::kRed()};
67  const float stops[2] = {0.0, 1.0};
68 
69  auto linear = flutter::DlColorSource::MakeLinear({0.0, 0.0}, {300.0, 300.0},
70  2, colors.data(), stops,
71  flutter::DlTileMode::kClamp);
72  flutter::DlPaint paint;
73  paint.setColorSource(linear);
74 
75  builder.DrawText(flutter::DlTextSkia::Make(SkTextBlob::MakeFromString(
76  "Hello World", CreateTestFont())),
77  100, 100, paint);
78  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
79 }
80 
81 TEST_P(DisplayListTest, CanDrawTextWithSaveLayer) {
82  flutter::DisplayListBuilder builder;
83  builder.DrawText(flutter::DlTextSkia::Make(
84  SkTextBlob::MakeFromString("Hello", CreateTestFont())),
85  100, 100, flutter::DlPaint(flutter::DlColor::kRed()));
86 
87  flutter::DlPaint save_paint;
88  float alpha = 0.5;
89  save_paint.setAlpha(static_cast<uint8_t>(255 * alpha));
90  builder.SaveLayer(std::nullopt, &save_paint);
91  builder.DrawText(flutter::DlTextSkia::Make(SkTextBlob::MakeFromString(
92  "Hello with half alpha", CreateTestFontOfSize(100))),
93  100, 300, flutter::DlPaint(flutter::DlColor::kRed()));
94  builder.Restore();
95  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
96 }
97 
98 TEST_P(DisplayListTest, CanDrawImage) {
99  auto texture = CreateTextureForFixture("embarcadero.jpg");
100  flutter::DisplayListBuilder builder;
101  builder.DrawImage(DlImageImpeller::Make(texture), DlPoint(100, 100),
102  flutter::DlImageSampling::kNearestNeighbor, nullptr);
103  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
104 }
105 
106 TEST_P(DisplayListTest, CanDrawCapsAndJoins) {
107  flutter::DisplayListBuilder builder;
108  flutter::DlPaint paint;
109 
110  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
111  paint.setStrokeWidth(30);
112  paint.setColor(flutter::DlColor::kRed());
113 
114  flutter::DlPathBuilder path_builder;
115  path_builder.MoveTo(DlPoint(-50, 0));
116  path_builder.LineTo(DlPoint(0, -50));
117  path_builder.LineTo(DlPoint(50, 0));
118  flutter::DlPath path = path_builder.TakePath();
119 
120  builder.Translate(100, 100);
121  {
122  paint.setStrokeCap(flutter::DlStrokeCap::kButt);
123  paint.setStrokeJoin(flutter::DlStrokeJoin::kMiter);
124  paint.setStrokeMiter(4);
125  builder.DrawPath(path, paint);
126  }
127 
128  {
129  builder.Save();
130  builder.Translate(0, 100);
131  // The joint in the path is 45 degrees. A miter length of 1 convert to a
132  // bevel in this case.
133  paint.setStrokeMiter(1);
134  builder.DrawPath(path, paint);
135  builder.Restore();
136  }
137 
138  builder.Translate(150, 0);
139  {
140  paint.setStrokeCap(flutter::DlStrokeCap::kSquare);
141  paint.setStrokeJoin(flutter::DlStrokeJoin::kBevel);
142  builder.DrawPath(path, paint);
143  }
144 
145  builder.Translate(150, 0);
146  {
147  paint.setStrokeCap(flutter::DlStrokeCap::kRound);
148  paint.setStrokeJoin(flutter::DlStrokeJoin::kRound);
149  builder.DrawPath(path, paint);
150  }
151 
152  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
153 }
154 
155 TEST_P(DisplayListTest, CanDrawArc) {
156  auto callback = [&]() {
157  static float start_angle = 45;
158  static float sweep_angle = 270;
159  static float stroke_width = 10;
160  static bool use_center = true;
161 
162  static int selected_cap = 0;
163  const char* cap_names[] = {"Butt", "Round", "Square"};
164  flutter::DlStrokeCap cap;
165 
166  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
167  ImGui::SliderFloat("Start angle", &start_angle, -360, 360);
168  ImGui::SliderFloat("Sweep angle", &sweep_angle, -360, 360);
169  ImGui::SliderFloat("Stroke width", &stroke_width, 0, 300);
170  ImGui::Combo("Cap", &selected_cap, cap_names,
171  sizeof(cap_names) / sizeof(char*));
172  ImGui::Checkbox("Use center", &use_center);
173  ImGui::End();
174 
175  switch (selected_cap) {
176  case 0:
177  cap = flutter::DlStrokeCap::kButt;
178  break;
179  case 1:
180  cap = flutter::DlStrokeCap::kRound;
181  break;
182  case 2:
183  cap = flutter::DlStrokeCap::kSquare;
184  break;
185  default:
186  cap = flutter::DlStrokeCap::kButt;
187  break;
188  }
189 
190  static PlaygroundPoint point_a(Point(200, 200), 20, Color::White());
191  static PlaygroundPoint point_b(Point(400, 400), 20, Color::White());
192  auto [p1, p2] = DrawPlaygroundLine(point_a, point_b);
193 
194  flutter::DisplayListBuilder builder;
195  flutter::DlPaint paint;
196 
197  Vector2 scale = GetContentScale();
198  builder.Scale(scale.x, scale.y);
199  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
200  paint.setStrokeCap(cap);
201  paint.setStrokeJoin(flutter::DlStrokeJoin::kMiter);
202  paint.setStrokeMiter(10);
203  auto rect = DlRect::MakeLTRB(p1.x, p1.y, p2.x, p2.y);
204  paint.setColor(flutter::DlColor::kGreen());
205  paint.setStrokeWidth(2);
206  builder.DrawRect(rect, paint);
207  paint.setColor(flutter::DlColor::kRed());
208  paint.setStrokeWidth(stroke_width);
209  builder.DrawArc(rect, start_angle, sweep_angle, use_center, paint);
210 
211  return builder.Build();
212  };
213  ASSERT_TRUE(OpenPlaygroundHere(callback));
214 }
215 
216 TEST_P(DisplayListTest, StrokedPathsDrawCorrectly) {
217  auto callback = [&]() {
218  flutter::DisplayListBuilder builder;
219  flutter::DlPaint paint;
220 
221  paint.setColor(flutter::DlColor::kRed());
222  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
223 
224  static float stroke_width = 10.0f;
225  static int selected_stroke_type = 0;
226  static int selected_join_type = 0;
227  const char* stroke_types[] = {"Butte", "Round", "Square"};
228  const char* join_type[] = {"kMiter", "Round", "kBevel"};
229 
230  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
231  ImGui::Combo("Cap", &selected_stroke_type, stroke_types,
232  sizeof(stroke_types) / sizeof(char*));
233  ImGui::Combo("Join", &selected_join_type, join_type,
234  sizeof(join_type) / sizeof(char*));
235  ImGui::SliderFloat("Stroke Width", &stroke_width, 10.0f, 50.0f);
236  ImGui::End();
237 
238  flutter::DlStrokeCap cap;
239  flutter::DlStrokeJoin join;
240  switch (selected_stroke_type) {
241  case 0:
242  cap = flutter::DlStrokeCap::kButt;
243  break;
244  case 1:
245  cap = flutter::DlStrokeCap::kRound;
246  break;
247  case 2:
248  cap = flutter::DlStrokeCap::kSquare;
249  break;
250  default:
251  cap = flutter::DlStrokeCap::kButt;
252  break;
253  }
254  switch (selected_join_type) {
255  case 0:
256  join = flutter::DlStrokeJoin::kMiter;
257  break;
258  case 1:
259  join = flutter::DlStrokeJoin::kRound;
260  break;
261  case 2:
262  join = flutter::DlStrokeJoin::kBevel;
263  break;
264  default:
265  join = flutter::DlStrokeJoin::kMiter;
266  break;
267  }
268  paint.setStrokeCap(cap);
269  paint.setStrokeJoin(join);
270  paint.setStrokeWidth(stroke_width);
271 
272  // Make rendering better to watch.
273  builder.Scale(1.5f, 1.5f);
274 
275  // Rectangle
276  builder.Translate(100, 100);
277  builder.DrawRect(DlRect::MakeWH(100, 100), paint);
278 
279  // Rounded rectangle
280  builder.Translate(150, 0);
281  builder.DrawRoundRect(
282  DlRoundRect::MakeRectXY(DlRect::MakeWH(100, 50), 10, 10), paint);
283 
284  // Double rounded rectangle
285  builder.Translate(150, 0);
286  builder.DrawDiffRoundRect(
287  DlRoundRect::MakeRectXY(DlRect::MakeWH(100, 50), 10, 10),
288  DlRoundRect::MakeRectXY(DlRect::MakeXYWH(10, 10, 80, 30), 10, 10),
289  paint);
290 
291  // Contour with duplicate join points
292  {
293  builder.Translate(150, 0);
294  flutter::DlPathBuilder path_builder;
295  path_builder.MoveTo(DlPoint(0, 0));
296  path_builder.LineTo(DlPoint(0, 0));
297  path_builder.LineTo(DlPoint(100, 0));
298  path_builder.LineTo(DlPoint(100, 0));
299  path_builder.LineTo(DlPoint(100, 100));
300  builder.DrawPath(path_builder.TakePath(), paint);
301  }
302 
303  // Contour with duplicate start and end points
304 
305  // Line.
306  builder.Translate(200, 0);
307  {
308  builder.Save();
309 
310  flutter::DlPathBuilder line_path_builder;
311  line_path_builder.MoveTo(DlPoint(0, 0));
312  line_path_builder.MoveTo(DlPoint(0, 0));
313  line_path_builder.LineTo(DlPoint(0, 0));
314  line_path_builder.LineTo(DlPoint(0, 0));
315  line_path_builder.LineTo(DlPoint(50, 50));
316  line_path_builder.LineTo(DlPoint(50, 50));
317  line_path_builder.LineTo(DlPoint(100, 0));
318  line_path_builder.LineTo(DlPoint(100, 0));
319  DlPath line_path = line_path_builder.TakePath();
320  builder.DrawPath(line_path, paint);
321 
322  builder.Translate(0, 100);
323  builder.DrawPath(line_path, paint);
324 
325  builder.Translate(0, 100);
326  flutter::DlPathBuilder line_path_builder2;
327  line_path_builder2.MoveTo(DlPoint(0, 0));
328  line_path_builder2.LineTo(DlPoint(0, 0));
329  line_path_builder2.LineTo(DlPoint(0, 0));
330  builder.DrawPath(line_path_builder2.TakePath(), paint);
331 
332  builder.Restore();
333  }
334 
335  // Cubic.
336  builder.Translate(150, 0);
337  {
338  builder.Save();
339 
340  flutter::DlPathBuilder cubic_path;
341  cubic_path.MoveTo(DlPoint(0, 0));
342  cubic_path.CubicCurveTo(DlPoint(0, 0), //
343  DlPoint(140.0, 100.0), //
344  DlPoint(140, 20));
345  builder.DrawPath(cubic_path.TakePath(), paint);
346 
347  builder.Translate(0, 100);
348  flutter::DlPathBuilder cubic_path2;
349  cubic_path2.MoveTo(DlPoint(0, 0));
350  cubic_path2.CubicCurveTo(DlPoint(0, 0), //
351  DlPoint(0, 0), //
352  DlPoint(150, 150));
353  builder.DrawPath(cubic_path2.TakePath(), paint);
354 
355  builder.Translate(0, 100);
356  flutter::DlPathBuilder cubic_path3;
357  cubic_path3.MoveTo(DlPoint(0, 0));
358  cubic_path3.CubicCurveTo(DlPoint(0, 0), //
359  DlPoint(0, 0), //
360  DlPoint(0, 0));
361  builder.DrawPath(cubic_path3.TakePath(), paint);
362 
363  builder.Restore();
364  }
365 
366  // Quad.
367  builder.Translate(200, 0);
368  {
369  builder.Save();
370 
371  flutter::DlPathBuilder quad_path;
372  quad_path.MoveTo(DlPoint(0, 0));
373  quad_path.MoveTo(DlPoint(0, 0));
374  quad_path.QuadraticCurveTo(DlPoint(100, 40), DlPoint(50, 80));
375  builder.DrawPath(quad_path.TakePath(), paint);
376 
377  builder.Translate(0, 150);
378  flutter::DlPathBuilder quad_path2;
379  quad_path2.MoveTo(DlPoint(0, 0));
380  quad_path2.MoveTo(DlPoint(0, 0));
381  quad_path2.QuadraticCurveTo(DlPoint(0, 0), DlPoint(100, 100));
382  builder.DrawPath(quad_path2.TakePath(), paint);
383 
384  builder.Translate(0, 100);
385  flutter::DlPathBuilder quad_path3;
386  quad_path3.MoveTo(DlPoint(0, 0));
387  quad_path3.QuadraticCurveTo(DlPoint(0, 0), DlPoint(0, 0));
388  builder.DrawPath(quad_path3.TakePath(), paint);
389 
390  builder.Restore();
391  }
392  return builder.Build();
393  };
394  ASSERT_TRUE(OpenPlaygroundHere(callback));
395 }
396 
397 TEST_P(DisplayListTest, CanDrawWithOddPathWinding) {
398  flutter::DisplayListBuilder builder;
399  flutter::DlPaint paint;
400 
401  paint.setColor(flutter::DlColor::kRed());
402  paint.setDrawStyle(flutter::DlDrawStyle::kFill);
403 
404  builder.Translate(300, 300);
405  flutter::DlPathBuilder path_builder;
406  path_builder.AddCircle(DlPoint(0, 0), 100);
407  path_builder.AddCircle(DlPoint(0, 0), 50);
408  path_builder.SetFillType(flutter::DlPathFillType::kOdd);
409  builder.DrawPath(path_builder.TakePath(), paint);
410 
411  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
412 }
413 
414 // Regression test for https://github.com/flutter/flutter/issues/134816.
415 //
416 // It should be possible to draw 3 lines, and not have an implicit close path.
417 TEST_P(DisplayListTest, CanDrawAnOpenPath) {
418  flutter::DisplayListBuilder builder;
419  flutter::DlPaint paint;
420 
421  paint.setColor(flutter::DlColor::kRed());
422  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
423  paint.setStrokeWidth(10);
424 
425  builder.Translate(300, 300);
426 
427  // Move to (50, 50) and draw lines from:
428  // 1. (50, height)
429  // 2. (width, height)
430  // 3. (width, 50)
431  flutter::DlPathBuilder path_builder;
432  path_builder.MoveTo(DlPoint(50, 50));
433  path_builder.LineTo(DlPoint(50, 100));
434  path_builder.LineTo(DlPoint(100, 100));
435  path_builder.LineTo(DlPoint(100, 50));
436  builder.DrawPath(path_builder.TakePath(), paint);
437 
438  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
439 }
440 
441 TEST_P(DisplayListTest, CanDrawWithMaskBlur) {
442  auto texture = CreateTextureForFixture("embarcadero.jpg");
443  flutter::DisplayListBuilder builder;
444  flutter::DlPaint paint;
445 
446  // Mask blurred image.
447  {
448  auto filter =
449  flutter::DlBlurMaskFilter(flutter::DlBlurStyle::kNormal, 10.0f);
450  paint.setMaskFilter(&filter);
451  builder.DrawImage(DlImageImpeller::Make(texture), DlPoint(100, 100),
452  flutter::DlImageSampling::kNearestNeighbor, &paint);
453  }
454 
455  // Mask blurred filled path.
456  {
457  paint.setColor(flutter::DlColor::kYellow());
458  auto filter =
459  flutter::DlBlurMaskFilter(flutter::DlBlurStyle::kOuter, 10.0f);
460  paint.setMaskFilter(&filter);
461  builder.DrawArc(DlRect::MakeXYWH(410, 110, 100, 100), 45, 270, true, paint);
462  }
463 
464  // Mask blurred text.
465  {
466  auto filter =
467  flutter::DlBlurMaskFilter(flutter::DlBlurStyle::kSolid, 10.0f);
468  paint.setMaskFilter(&filter);
469  builder.DrawText(flutter::DlTextSkia::Make(SkTextBlob::MakeFromString(
470  "Testing", CreateTestFont())),
471  220, 170, paint);
472  }
473 
474  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
475 }
476 
477 TEST_P(DisplayListTest, CanDrawStrokedText) {
478  flutter::DisplayListBuilder builder;
479  flutter::DlPaint paint;
480 
481  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
482  paint.setColor(flutter::DlColor::kRed());
483  builder.DrawText(flutter::DlTextSkia::Make(SkTextBlob::MakeFromString(
484  "stoked about stroked text", CreateTestFont())),
485  250, 250, paint);
486 
487  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
488 }
489 
490 // Regression test for https://github.com/flutter/flutter/issues/133157.
491 TEST_P(DisplayListTest, StrokedTextNotOffsetFromNormalText) {
492  flutter::DisplayListBuilder builder;
493  flutter::DlPaint paint;
494  auto const& text_blob = SkTextBlob::MakeFromString("00000", CreateTestFont());
495  auto text = flutter::DlTextSkia::Make(text_blob);
496 
497  // https://api.flutter.dev/flutter/material/Colors/blue-constant.html.
498  auto const& mat_blue = flutter::DlColor(0xFF2196f3);
499 
500  // Draw a blue filled rectangle so the text is easier to see.
501  paint.setDrawStyle(flutter::DlDrawStyle::kFill);
502  paint.setColor(mat_blue);
503  builder.DrawRect(DlRect::MakeXYWH(0, 0, 500, 500), paint);
504 
505  // Draw stacked text, with stroked text on top.
506  paint.setDrawStyle(flutter::DlDrawStyle::kFill);
507  paint.setColor(flutter::DlColor::kWhite());
508  builder.DrawText(text, 250, 250, paint);
509 
510  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
511  paint.setColor(flutter::DlColor::kBlack());
512  builder.DrawText(text, 250, 250, paint);
513 
514  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
515 }
516 
517 TEST_P(DisplayListTest, IgnoreMaskFilterWhenSavingLayer) {
518  auto texture = CreateTextureForFixture("embarcadero.jpg");
519  flutter::DisplayListBuilder builder;
520  auto filter = flutter::DlBlurMaskFilter(flutter::DlBlurStyle::kNormal, 10.0f);
521  flutter::DlPaint paint;
522  paint.setMaskFilter(&filter);
523  builder.SaveLayer(std::nullopt, &paint);
524  builder.DrawImage(DlImageImpeller::Make(texture), DlPoint(100, 100),
525  flutter::DlImageSampling::kNearestNeighbor);
526  builder.Restore();
527  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
528 }
529 
530 TEST_P(DisplayListTest, CanDrawWithBlendColorFilter) {
531  auto texture = CreateTextureForFixture("embarcadero.jpg");
532  flutter::DisplayListBuilder builder;
533  flutter::DlPaint paint;
534 
535  // Pipeline blended image.
536  {
537  auto filter = flutter::DlColorFilter::MakeBlend(
538  flutter::DlColor::kYellow(), flutter::DlBlendMode::kModulate);
539  paint.setColorFilter(filter);
540  builder.DrawImage(DlImageImpeller::Make(texture), DlPoint(100, 100),
541  flutter::DlImageSampling::kNearestNeighbor, &paint);
542  }
543 
544  // Advanced blended image.
545  {
546  auto filter = flutter::DlColorFilter::MakeBlend(
547  flutter::DlColor::kRed(), flutter::DlBlendMode::kScreen);
548  paint.setColorFilter(filter);
549  builder.DrawImage(DlImageImpeller::Make(texture), DlPoint(250, 250),
550  flutter::DlImageSampling::kNearestNeighbor, &paint);
551  }
552 
553  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
554 }
555 
556 TEST_P(DisplayListTest, CanDrawWithColorFilterImageFilter) {
557  const float invert_color_matrix[20] = {
558  -1, 0, 0, 0, 1, //
559  0, -1, 0, 0, 1, //
560  0, 0, -1, 0, 1, //
561  0, 0, 0, 1, 0, //
562  };
563  auto texture = CreateTextureForFixture("boston.jpg");
564  flutter::DisplayListBuilder builder;
565  flutter::DlPaint paint;
566 
567  auto color_filter = flutter::DlColorFilter::MakeMatrix(invert_color_matrix);
568  auto image_filter = flutter::DlImageFilter::MakeColorFilter(color_filter);
569 
570  paint.setImageFilter(image_filter);
571  builder.DrawImage(DlImageImpeller::Make(texture), DlPoint(100, 100),
572  flutter::DlImageSampling::kNearestNeighbor, &paint);
573 
574  builder.Translate(0, 700);
575  paint.setColorFilter(color_filter);
576  builder.DrawImage(DlImageImpeller::Make(texture), DlPoint(100, 100),
577  flutter::DlImageSampling::kNearestNeighbor, &paint);
578  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
579 }
580 
581 TEST_P(DisplayListTest, CanDrawWithImageBlurFilter) {
582  auto texture = CreateTextureForFixture("embarcadero.jpg");
583 
584  auto callback = [&]() {
585  static float sigma[] = {10, 10};
586 
587  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
588  ImGui::SliderFloat2("Sigma", sigma, 0, 100);
589  ImGui::End();
590 
591  flutter::DisplayListBuilder builder;
592  flutter::DlPaint paint;
593 
594  auto filter = flutter::DlBlurImageFilter(sigma[0], sigma[1],
595  flutter::DlTileMode::kClamp);
596  paint.setImageFilter(&filter);
597  builder.DrawImage(DlImageImpeller::Make(texture), DlPoint(200, 200),
598  flutter::DlImageSampling::kNearestNeighbor, &paint);
599 
600  return builder.Build();
601  };
602 
603  ASSERT_TRUE(OpenPlaygroundHere(callback));
604 }
605 
606 TEST_P(DisplayListTest, CanDrawWithComposeImageFilter) {
607  auto texture = CreateTextureForFixture("boston.jpg");
608  flutter::DisplayListBuilder builder;
609  flutter::DlPaint paint;
610 
611  auto dilate = std::make_shared<flutter::DlDilateImageFilter>(10.0, 10.0);
612  auto erode = std::make_shared<flutter::DlErodeImageFilter>(10.0, 10.0);
613  auto open = std::make_shared<flutter::DlComposeImageFilter>(dilate, erode);
614  auto close = std::make_shared<flutter::DlComposeImageFilter>(erode, dilate);
615 
616  paint.setImageFilter(open.get());
617  builder.DrawImage(DlImageImpeller::Make(texture), DlPoint(100, 100),
618  flutter::DlImageSampling::kNearestNeighbor, &paint);
619  builder.Translate(0, 700);
620  paint.setImageFilter(close.get());
621  builder.DrawImage(DlImageImpeller::Make(texture), DlPoint(100, 100),
622  flutter::DlImageSampling::kNearestNeighbor, &paint);
623  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
624 }
625 
626 TEST_P(DisplayListTest, CanClampTheResultingColorOfColorMatrixFilter) {
627  auto texture = CreateTextureForFixture("boston.jpg");
628  const float inner_color_matrix[20] = {
629  1, 0, 0, 0, 0, //
630  0, 1, 0, 0, 0, //
631  0, 0, 1, 0, 0, //
632  0, 0, 0, 2, 0, //
633  };
634  const float outer_color_matrix[20] = {
635  1, 0, 0, 0, 0, //
636  0, 1, 0, 0, 0, //
637  0, 0, 1, 0, 0, //
638  0, 0, 0, 0.5, 0, //
639  };
640  auto inner_color_filter =
641  flutter::DlColorFilter::MakeMatrix(inner_color_matrix);
642  auto outer_color_filter =
643  flutter::DlColorFilter::MakeMatrix(outer_color_matrix);
644  auto inner = flutter::DlImageFilter::MakeColorFilter(inner_color_filter);
645  auto outer = flutter::DlImageFilter::MakeColorFilter(outer_color_filter);
646  auto compose = std::make_shared<flutter::DlComposeImageFilter>(outer, inner);
647 
648  flutter::DisplayListBuilder builder;
649  flutter::DlPaint paint;
650  paint.setImageFilter(compose.get());
651  builder.DrawImage(DlImageImpeller::Make(texture), DlPoint(100, 100),
652  flutter::DlImageSampling::kNearestNeighbor, &paint);
653  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
654 }
655 
656 TEST_P(DisplayListTest, CanDrawBackdropFilter) {
657  auto texture = CreateTextureForFixture("embarcadero.jpg");
658 
659  auto callback = [&]() {
660  static float sigma[] = {10, 10};
661  static float ctm_scale = 1;
662  static bool use_bounds = true;
663  static bool draw_circle = true;
664  static bool add_clip = true;
665 
666  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
667  ImGui::SliderFloat2("Sigma", sigma, 0, 100);
668  ImGui::SliderFloat("Scale", &ctm_scale, 0, 10);
669  ImGui::NewLine();
670  ImGui::TextWrapped(
671  "If everything is working correctly, none of the options below should "
672  "impact the filter's appearance.");
673  ImGui::Checkbox("Use SaveLayer bounds", &use_bounds);
674  ImGui::Checkbox("Draw child element", &draw_circle);
675  ImGui::Checkbox("Add pre-clip", &add_clip);
676  ImGui::End();
677 
678  flutter::DisplayListBuilder builder;
679 
680  Vector2 scale = ctm_scale * GetContentScale();
681  builder.Scale(scale.x, scale.y);
682 
683  auto filter = flutter::DlBlurImageFilter(sigma[0], sigma[1],
684  flutter::DlTileMode::kClamp);
685 
686  std::optional<DlRect> bounds;
687  if (use_bounds) {
688  static PlaygroundPoint point_a(Point(350, 150), 20, Color::White());
689  static PlaygroundPoint point_b(Point(800, 600), 20, Color::White());
690  auto [p1, p2] = DrawPlaygroundLine(point_a, point_b);
691  bounds = DlRect::MakeLTRB(p1.x, p1.y, p2.x, p2.y);
692  }
693 
694  // Insert a clip to test that the backdrop filter handles stencil depths > 0
695  // correctly.
696  if (add_clip) {
697  builder.ClipRect(DlRect::MakeLTRB(0, 0, 99999, 99999),
698  flutter::DlClipOp::kIntersect, true);
699  }
700 
701  builder.DrawImage(DlImageImpeller::Make(texture), DlPoint(200, 200),
702  flutter::DlImageSampling::kNearestNeighbor, nullptr);
703  builder.SaveLayer(bounds, nullptr, &filter);
704 
705  if (draw_circle) {
706  static PlaygroundPoint center_point(Point(500, 400), 20, Color::Red());
707  auto circle_center = DrawPlaygroundPoint(center_point);
708 
709  flutter::DlPaint paint;
710  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
711  paint.setStrokeCap(flutter::DlStrokeCap::kButt);
712  paint.setStrokeJoin(flutter::DlStrokeJoin::kBevel);
713  paint.setStrokeWidth(10);
714  paint.setColor(flutter::DlColor::kRed().withAlpha(100));
715  builder.DrawCircle(DlPoint(circle_center.x, circle_center.y), 100, paint);
716  }
717 
718  return builder.Build();
719  };
720 
721  ASSERT_TRUE(OpenPlaygroundHere(callback));
722 }
723 
724 TEST_P(DisplayListTest, CanDrawNinePatchImage) {
725  // Image is drawn with corners to scale and center pieces stretched to fit.
726  auto texture = CreateTextureForFixture("embarcadero.jpg");
727  flutter::DisplayListBuilder builder;
728  auto size = texture->GetSize();
729  builder.DrawImageNine(
730  DlImageImpeller::Make(texture),
731  DlIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
732  size.height * 3 / 4),
733  DlRect::MakeLTRB(0, 0, size.width * 2, size.height * 2),
734  flutter::DlFilterMode::kNearest, nullptr);
735  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
736 }
737 
738 TEST_P(DisplayListTest, CanDrawNinePatchImageCenterWidthBiggerThanDest) {
739  // Edge case, the width of the corners does not leave any room for the
740  // center slice. The center (across the vertical axis) is folded out of the
741  // resulting image.
742  auto texture = CreateTextureForFixture("embarcadero.jpg");
743  flutter::DisplayListBuilder builder;
744  auto size = texture->GetSize();
745  builder.DrawImageNine(
746  DlImageImpeller::Make(texture),
747  DlIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
748  size.height * 3 / 4),
749  DlRect::MakeLTRB(0, 0, size.width / 2, size.height),
750  flutter::DlFilterMode::kNearest, nullptr);
751  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
752 }
753 
754 TEST_P(DisplayListTest, CanDrawNinePatchImageCenterHeightBiggerThanDest) {
755  // Edge case, the height of the corners does not leave any room for the
756  // center slice. The center (across the horizontal axis) is folded out of the
757  // resulting image.
758  auto texture = CreateTextureForFixture("embarcadero.jpg");
759  flutter::DisplayListBuilder builder;
760  auto size = texture->GetSize();
761  builder.DrawImageNine(
762  DlImageImpeller::Make(texture),
763  DlIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
764  size.height * 3 / 4),
765  DlRect::MakeLTRB(0, 0, size.width, size.height / 2),
766  flutter::DlFilterMode::kNearest, nullptr);
767  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
768 }
769 
770 TEST_P(DisplayListTest, CanDrawNinePatchImageCenterBiggerThanDest) {
771  // Edge case, the width and height of the corners does not leave any
772  // room for the center slices. Only the corners are displayed.
773  auto texture = CreateTextureForFixture("embarcadero.jpg");
774  flutter::DisplayListBuilder builder;
775  auto size = texture->GetSize();
776  builder.DrawImageNine(
777  DlImageImpeller::Make(texture),
778  DlIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
779  size.height * 3 / 4),
780  DlRect::MakeLTRB(0, 0, size.width / 2, size.height / 2),
781  flutter::DlFilterMode::kNearest, nullptr);
782  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
783 }
784 
785 TEST_P(DisplayListTest, CanDrawNinePatchImageCornersScaledDown) {
786  // Edge case, there is not enough room for the corners to be drawn
787  // without scaling them down.
788  auto texture = CreateTextureForFixture("embarcadero.jpg");
789  flutter::DisplayListBuilder builder;
790  auto size = texture->GetSize();
791  builder.DrawImageNine(
792  DlImageImpeller::Make(texture),
793  DlIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
794  size.height * 3 / 4),
795  DlRect::MakeLTRB(0, 0, size.width / 4, size.height / 4),
796  flutter::DlFilterMode::kNearest, nullptr);
797  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
798 }
799 
800 TEST_P(DisplayListTest, NinePatchImagePrecision) {
801  // Draw a nine patch image with colored corners and verify that the corner
802  // color does not leak outside the intended region.
803  auto texture = CreateTextureForFixture("nine_patch_corners.png");
804  flutter::DisplayListBuilder builder;
805  builder.DrawImageNine(DlImageImpeller::Make(texture),
806  DlIRect::MakeXYWH(10, 10, 1, 1),
807  DlRect::MakeXYWH(0, 0, 200, 100),
808  flutter::DlFilterMode::kNearest, nullptr);
809  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
810 }
811 
812 TEST_P(DisplayListTest, CanDrawPoints) {
813  flutter::DisplayListBuilder builder;
814  DlPoint points[7] = {
815  {0, 0}, //
816  {100, 100}, //
817  {100, 0}, //
818  {0, 100}, //
819  {0, 0}, //
820  {48, 48}, //
821  {52, 52}, //
822  };
823  std::vector<flutter::DlStrokeCap> caps = {
824  flutter::DlStrokeCap::kButt,
825  flutter::DlStrokeCap::kRound,
826  flutter::DlStrokeCap::kSquare,
827  };
828  flutter::DlPaint paint =
829  flutter::DlPaint() //
830  .setColor(flutter::DlColor::kYellow().withAlpha(127)) //
831  .setStrokeWidth(20);
832  builder.Translate(50, 50);
833  for (auto cap : caps) {
834  paint.setStrokeCap(cap);
835  builder.Save();
836  builder.DrawPoints(flutter::DlPointMode::kPoints, 7, points, paint);
837  builder.Translate(150, 0);
838  builder.DrawPoints(flutter::DlPointMode::kLines, 5, points, paint);
839  builder.Translate(150, 0);
840  builder.DrawPoints(flutter::DlPointMode::kPolygon, 5, points, paint);
841  builder.Restore();
842  builder.Translate(0, 150);
843  }
844  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
845 }
846 
847 TEST_P(DisplayListTest, CanDrawZeroLengthLine) {
848  flutter::DisplayListBuilder builder;
849  std::vector<flutter::DlStrokeCap> caps = {
850  flutter::DlStrokeCap::kButt,
851  flutter::DlStrokeCap::kRound,
852  flutter::DlStrokeCap::kSquare,
853  };
854  flutter::DlPaint paint =
855  flutter::DlPaint() //
856  .setColor(flutter::DlColor::kYellow().withAlpha(127)) //
857  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
858  .setStrokeCap(flutter::DlStrokeCap::kButt) //
859  .setStrokeWidth(20);
860  DlPath path = DlPath::MakeLine({150, 50}, {150, 50});
861  for (auto cap : caps) {
862  paint.setStrokeCap(cap);
863  builder.DrawLine(DlPoint(50, 50), DlPoint(50, 50), paint);
864  builder.DrawPath(path, paint);
865  builder.Translate(0, 150);
866  }
867  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
868 }
869 
870 TEST_P(DisplayListTest, CanDrawShadow) {
871  flutter::DisplayListBuilder builder;
872  flutter::DlPaint paint;
873 
874  auto content_scale = GetContentScale() * 0.8;
875  builder.Scale(content_scale.x, content_scale.y);
876 
877  constexpr size_t star_spikes = 5;
878  constexpr DlScalar half_spike_rotation = kPi / star_spikes;
879  constexpr DlScalar radius = 40;
880  constexpr DlScalar spike_size = 10;
881  constexpr DlScalar outer_radius = radius + spike_size;
882  constexpr DlScalar inner_radius = radius - spike_size;
883  std::array<DlPoint, star_spikes * 2> star;
884  for (size_t i = 0; i < star_spikes; i++) {
885  const DlScalar rotation = half_spike_rotation * i * 2;
886  star[i * 2] = DlPoint(50 + std::sin(rotation) * outer_radius,
887  50 - std::cos(rotation) * outer_radius);
888  star[i * 2 + 1] =
889  DlPoint(50 + std::sin(rotation + half_spike_rotation) * inner_radius,
890  50 - std::cos(rotation + half_spike_rotation) * inner_radius);
891  }
892 
893  std::array<DlPath, 4> paths = {
894  DlPath::MakeRect(DlRect::MakeXYWH(0, 0, 200, 100)),
895  DlPath::MakeRoundRectXY(DlRect::MakeXYWH(20, 0, 200, 100), 30, 30),
896  DlPath::MakeCircle(DlPoint(100, 50), 50),
897  DlPath::MakePoly(star.data(), star.size(), true),
898  };
899  paint.setColor(flutter::DlColor::kWhite());
900  builder.DrawPaint(paint);
901  paint.setColor(flutter::DlColor::kCyan());
902  builder.Translate(100, 50);
903  for (size_t x = 0; x < paths.size(); x++) {
904  builder.Save();
905  for (size_t y = 0; y < 6; y++) {
906  builder.DrawShadow(paths[x], flutter::DlColor::kBlack(), 3 + y * 8, false,
907  1);
908  builder.DrawPath(paths[x], paint);
909  builder.Translate(0, 150);
910  }
911  builder.Restore();
912  builder.Translate(250, 0);
913  }
914 
915  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
916 }
917 
918 TEST_P(DisplayListTest, CanDrawZeroWidthLine) {
919  flutter::DisplayListBuilder builder;
920  std::vector<flutter::DlStrokeCap> caps = {
921  flutter::DlStrokeCap::kButt,
922  flutter::DlStrokeCap::kRound,
923  flutter::DlStrokeCap::kSquare,
924  };
925  flutter::DlPaint paint = //
926  flutter::DlPaint() //
927  .setColor(flutter::DlColor::kWhite()) //
928  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
929  .setStrokeWidth(0);
930  flutter::DlPaint outline_paint = //
931  flutter::DlPaint() //
932  .setColor(flutter::DlColor::kYellow()) //
933  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
934  .setStrokeCap(flutter::DlStrokeCap::kSquare) //
935  .setStrokeWidth(1);
936  DlPath path = DlPath::MakeLine({150, 50}, {160, 50});
937  for (auto cap : caps) {
938  paint.setStrokeCap(cap);
939  builder.DrawLine(DlPoint(50, 50), DlPoint(60, 50), paint);
940  builder.DrawRect(DlRect::MakeLTRB(45, 45, 65, 55), outline_paint);
941  builder.DrawLine(DlPoint{100, 50}, DlPoint{100, 50}, paint);
942  if (cap != flutter::DlStrokeCap::kButt) {
943  builder.DrawRect(DlRect::MakeLTRB(95, 45, 105, 55), outline_paint);
944  }
945  builder.DrawPath(path, paint);
946  builder.DrawRect(path.GetBounds().Expand(5, 5), outline_paint);
947  builder.Translate(0, 150);
948  }
949  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
950 }
951 
952 TEST_P(DisplayListTest, CanDrawWithMatrixFilter) {
953  auto boston = CreateTextureForFixture("boston.jpg");
954 
955  auto callback = [&]() {
956  static int selected_matrix_type = 0;
957  const char* matrix_type_names[] = {"Matrix", "Local Matrix"};
958 
959  static float ctm_translation[2] = {200, 200};
960  static float ctm_scale[2] = {0.65, 0.65};
961  static float ctm_skew[2] = {0, 0};
962 
963  static bool enable = true;
964  static float translation[2] = {100, 100};
965  static float scale[2] = {0.8, 0.8};
966  static float skew[2] = {0.2, 0.2};
967 
968  static bool enable_savelayer = true;
969 
970  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
971  {
972  ImGui::Combo("Filter type", &selected_matrix_type, matrix_type_names,
973  sizeof(matrix_type_names) / sizeof(char*));
974 
975  ImGui::TextWrapped("Current Transform");
976  ImGui::SliderFloat2("CTM Translation", ctm_translation, 0, 1000);
977  ImGui::SliderFloat2("CTM Scale", ctm_scale, 0, 3);
978  ImGui::SliderFloat2("CTM Skew", ctm_skew, -3, 3);
979 
980  ImGui::TextWrapped(
981  "MatrixFilter and LocalMatrixFilter modify the CTM in the same way. "
982  "The only difference is that MatrixFilter doesn't affect the effect "
983  "transform, whereas LocalMatrixFilter does.");
984  // Note: See this behavior in:
985  // https://fiddle.skia.org/c/6cbb551ab36d06f163db8693972be954
986  ImGui::Checkbox("Enable", &enable);
987  ImGui::SliderFloat2("Filter Translation", translation, 0, 1000);
988  ImGui::SliderFloat2("Filter Scale", scale, 0, 3);
989  ImGui::SliderFloat2("Filter Skew", skew, -3, 3);
990 
991  ImGui::TextWrapped(
992  "Rendering the filtered image within a layer can expose bounds "
993  "issues. If the rendered image gets cut off when this setting is "
994  "enabled, there's a coverage bug in the filter.");
995  ImGui::Checkbox("Render in layer", &enable_savelayer);
996  }
997  ImGui::End();
998 
999  flutter::DisplayListBuilder builder;
1000  flutter::DlPaint paint;
1001 
1002  if (enable_savelayer) {
1003  builder.SaveLayer(std::nullopt, nullptr);
1004  }
1005  {
1006  auto content_scale = GetContentScale();
1007  builder.Scale(content_scale.x, content_scale.y);
1008 
1009  // Set the current transform
1010  auto ctm_matrix = Matrix::MakeRow(
1011  ctm_scale[0], ctm_skew[0], 0.0f, ctm_translation[0], //
1012  ctm_skew[1], ctm_scale[1], 0.0f, ctm_translation[1], //
1013  0, 0, 1, 0, //
1014  0, 0, 0, 1);
1015  builder.Transform(ctm_matrix);
1016 
1017  // Set the matrix filter
1018  auto filter_matrix =
1019  Matrix::MakeRow(scale[0], skew[0], 0.0f, translation[0], //
1020  skew[1], scale[1], 0.0f, translation[1], //
1021  0.0f, 0.0f, 1.0f, 0.0f, //
1022  0.0f, 0.0f, 0.0f, 1.0f);
1023 
1024  if (enable) {
1025  switch (selected_matrix_type) {
1026  case 0: {
1027  auto filter = flutter::DlMatrixImageFilter(
1028  filter_matrix, flutter::DlImageSampling::kLinear);
1029  paint.setImageFilter(&filter);
1030  break;
1031  }
1032  case 1: {
1033  auto internal_filter =
1034  flutter::DlBlurImageFilter(10, 10, flutter::DlTileMode::kDecal)
1035  .shared();
1036  auto filter = flutter::DlLocalMatrixImageFilter(filter_matrix,
1037  internal_filter);
1038  paint.setImageFilter(&filter);
1039  break;
1040  }
1041  }
1042  }
1043 
1044  builder.DrawImage(DlImageImpeller::Make(boston), DlPoint(),
1045  flutter::DlImageSampling::kLinear, &paint);
1046  }
1047  if (enable_savelayer) {
1048  builder.Restore();
1049  }
1050 
1051  return builder.Build();
1052  };
1053 
1054  ASSERT_TRUE(OpenPlaygroundHere(callback));
1055 }
1056 
1057 TEST_P(DisplayListTest, CanDrawWithMatrixFilterWhenSavingLayer) {
1058  auto callback = [&]() {
1059  static float translation[2] = {0, 0};
1060  static bool enable_save_layer = true;
1061 
1062  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1063  ImGui::SliderFloat2("Translation", translation, -130, 130);
1064  ImGui::Checkbox("Enable save layer", &enable_save_layer);
1065  ImGui::End();
1066 
1067  flutter::DisplayListBuilder builder;
1068  builder.Save();
1069  builder.Scale(2.0, 2.0);
1070  flutter::DlPaint paint;
1071  paint.setColor(flutter::DlColor::kYellow());
1072  builder.DrawRect(DlRect::MakeWH(300, 300), paint);
1073  paint.setStrokeWidth(1.0);
1074  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
1075  paint.setColor(flutter::DlColor::kBlack().withAlpha(0x80));
1076  builder.DrawLine(DlPoint(150, 0), DlPoint(150, 300), paint);
1077  builder.DrawLine(DlPoint(0, 150), DlPoint(300, 150), paint);
1078 
1079  flutter::DlPaint save_paint;
1080  DlRect bounds = DlRect::MakeXYWH(100, 100, 100, 100);
1081  Matrix translate_matrix =
1082  Matrix::MakeTranslation({translation[0], translation[1]});
1083  if (enable_save_layer) {
1084  auto filter = flutter::DlMatrixImageFilter(
1085  translate_matrix, flutter::DlImageSampling::kNearestNeighbor);
1086  save_paint.setImageFilter(filter.shared());
1087  builder.SaveLayer(bounds, &save_paint);
1088  } else {
1089  builder.Save();
1090  builder.Transform(translate_matrix);
1091  }
1092 
1093  Matrix filter_matrix;
1094  filter_matrix.Translate({150, 150});
1095  filter_matrix.Scale({0.2f, 0.2f});
1096  filter_matrix.Translate({-150, -150});
1097  auto filter = flutter::DlMatrixImageFilter(
1098  filter_matrix, flutter::DlImageSampling::kNearestNeighbor);
1099 
1100  save_paint.setImageFilter(filter.shared());
1101 
1102  builder.SaveLayer(bounds, &save_paint);
1103  flutter::DlPaint paint2;
1104  paint2.setColor(flutter::DlColor::kBlue());
1105  builder.DrawRect(bounds, paint2);
1106  builder.Restore();
1107  builder.Restore();
1108  return builder.Build();
1109  };
1110 
1111  ASSERT_TRUE(OpenPlaygroundHere(callback));
1112 }
1113 
1114 TEST_P(DisplayListTest, CanDrawRectWithLinearToSrgbColorFilter) {
1115  flutter::DlPaint paint;
1116  paint.setColor(flutter::DlColor(0xFF2196F3).withAlpha(128));
1117  flutter::DisplayListBuilder builder;
1118  paint.setColorFilter(flutter::DlColorFilter::MakeLinearToSrgbGamma());
1119  builder.DrawRect(DlRect::MakeXYWH(0, 0, 200, 200), paint);
1120  builder.Translate(0, 200);
1121 
1122  paint.setColorFilter(flutter::DlColorFilter::MakeSrgbToLinearGamma());
1123  builder.DrawRect(DlRect::MakeXYWH(0, 0, 200, 200), paint);
1124 
1125  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1126 }
1127 
1128 TEST_P(DisplayListTest, CanDrawPaintWithColorSource) {
1129  const flutter::DlColor colors[2] = {
1130  flutter::DlColor(0xFFF44336),
1131  flutter::DlColor(0xFF2196F3),
1132  };
1133  const float stops[2] = {0.0, 1.0};
1134  flutter::DlPaint paint;
1135  flutter::DisplayListBuilder builder;
1136  auto clip_bounds = DlRect::MakeWH(300.0, 300.0);
1137  builder.Save();
1138  builder.Translate(100, 100);
1139  builder.ClipRect(clip_bounds, flutter::DlClipOp::kIntersect, false);
1140  auto linear =
1141  flutter::DlColorSource::MakeLinear({0.0, 0.0}, {100.0, 100.0}, 2, colors,
1142  stops, flutter::DlTileMode::kRepeat);
1143  paint.setColorSource(linear);
1144  builder.DrawPaint(paint);
1145  builder.Restore();
1146 
1147  builder.Save();
1148  builder.Translate(500, 100);
1149  builder.ClipRect(clip_bounds, flutter::DlClipOp::kIntersect, false);
1150  auto radial = flutter::DlColorSource::MakeRadial(
1151  {100.0, 100.0}, 100.0, 2, colors, stops, flutter::DlTileMode::kRepeat);
1152  paint.setColorSource(radial);
1153  builder.DrawPaint(paint);
1154  builder.Restore();
1155 
1156  builder.Save();
1157  builder.Translate(100, 500);
1158  builder.ClipRect(clip_bounds, flutter::DlClipOp::kIntersect, false);
1159  auto sweep =
1160  flutter::DlColorSource::MakeSweep({100.0, 100.0}, 180.0, 270.0, 2, colors,
1161  stops, flutter::DlTileMode::kRepeat);
1162  paint.setColorSource(sweep);
1163  builder.DrawPaint(paint);
1164  builder.Restore();
1165 
1166  builder.Save();
1167  builder.Translate(500, 500);
1168  builder.ClipRect(clip_bounds, flutter::DlClipOp::kIntersect, false);
1169  auto texture = CreateTextureForFixture("table_mountain_nx.png");
1170  auto image = flutter::DlColorSource::MakeImage(DlImageImpeller::Make(texture),
1171  flutter::DlTileMode::kRepeat,
1172  flutter::DlTileMode::kRepeat);
1173  paint.setColorSource(image);
1174  builder.DrawPaint(paint);
1175  builder.Restore();
1176 
1177  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1178 }
1179 
1180 TEST_P(DisplayListTest, CanBlendDstOverAndDstCorrectly) {
1181  flutter::DisplayListBuilder builder;
1182 
1183  {
1184  builder.SaveLayer(std::nullopt, nullptr);
1185  builder.Translate(100, 100);
1186  flutter::DlPaint paint;
1187  paint.setColor(flutter::DlColor::kRed());
1188  builder.DrawRect(DlRect::MakeWH(200, 200), paint);
1189  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1190  paint.setBlendMode(flutter::DlBlendMode::kSrcOver);
1191  builder.DrawRect(DlRect::MakeWH(200, 200), paint);
1192  builder.Restore();
1193  }
1194  {
1195  builder.SaveLayer(std::nullopt, nullptr);
1196  builder.Translate(300, 100);
1197  flutter::DlPaint paint;
1198  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1199  builder.DrawRect(DlRect::MakeWH(200, 200), paint);
1200  paint.setColor(flutter::DlColor::kRed());
1201  paint.setBlendMode(flutter::DlBlendMode::kDstOver);
1202  builder.DrawRect(DlRect::MakeWH(200, 200), paint);
1203  builder.Restore();
1204  }
1205  {
1206  builder.SaveLayer(std::nullopt, nullptr);
1207  builder.Translate(100, 300);
1208  flutter::DlPaint paint;
1209  paint.setColor(flutter::DlColor::kRed());
1210  builder.DrawRect(DlRect::MakeWH(200, 200), paint);
1211  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1212  paint.setBlendMode(flutter::DlBlendMode::kSrc);
1213  builder.DrawRect(DlRect::MakeWH(200, 200), paint);
1214  builder.Restore();
1215  }
1216  {
1217  builder.SaveLayer(std::nullopt, nullptr);
1218  builder.Translate(300, 300);
1219  flutter::DlPaint paint;
1220  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1221  builder.DrawRect(DlRect::MakeWH(200, 200), paint);
1222  paint.setColor(flutter::DlColor::kRed());
1223  paint.setBlendMode(flutter::DlBlendMode::kDst);
1224  builder.DrawRect(DlRect::MakeWH(200, 200), paint);
1225  builder.Restore();
1226  }
1227 
1228  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1229 }
1230 
1231 TEST_P(DisplayListTest, CanDrawCorrectlyWithColorFilterAndImageFilter) {
1232  flutter::DisplayListBuilder builder;
1233  const float green_color_matrix[20] = {
1234  0, 0, 0, 0, 0, //
1235  0, 0, 0, 0, 1, //
1236  0, 0, 0, 0, 0, //
1237  0, 0, 0, 1, 0, //
1238  };
1239  const float blue_color_matrix[20] = {
1240  0, 0, 0, 0, 0, //
1241  0, 0, 0, 0, 0, //
1242  0, 0, 0, 0, 1, //
1243  0, 0, 0, 1, 0, //
1244  };
1245  auto green_color_filter =
1246  flutter::DlColorFilter::MakeMatrix(green_color_matrix);
1247  auto blue_color_filter =
1248  flutter::DlColorFilter::MakeMatrix(blue_color_matrix);
1249  auto blue_image_filter =
1250  flutter::DlImageFilter::MakeColorFilter(blue_color_filter);
1251 
1252  flutter::DlPaint paint;
1253  paint.setColor(flutter::DlColor::kRed());
1254  paint.setColorFilter(green_color_filter);
1255  paint.setImageFilter(blue_image_filter);
1256  builder.DrawRect(DlRect::MakeLTRB(100, 100, 500, 500), paint);
1257  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1258 }
1259 
1260 TEST_P(DisplayListTest, MaskBlursApplyCorrectlyToColorSources) {
1261  auto blur_filter = std::make_shared<flutter::DlBlurMaskFilter>(
1262  flutter::DlBlurStyle::kNormal, 10);
1263 
1264  flutter::DisplayListBuilder builder;
1265 
1266  std::array<flutter::DlColor, 2> colors = {flutter::DlColor::kBlue(),
1267  flutter::DlColor::kGreen()};
1268  std::array<float, 2> stops = {0, 1};
1269  auto texture = CreateTextureForFixture("airplane.jpg");
1270  auto matrix = flutter::DlMatrix::MakeTranslation({-300, -110});
1271  std::array<std::shared_ptr<flutter::DlColorSource>, 2> color_sources = {
1272  flutter::DlColorSource::MakeImage(
1273  DlImageImpeller::Make(texture), flutter::DlTileMode::kRepeat,
1274  flutter::DlTileMode::kRepeat, flutter::DlImageSampling::kLinear,
1275  &matrix),
1276  flutter::DlColorSource::MakeLinear(
1277  flutter::DlPoint(0, 0), flutter::DlPoint(100, 50), 2, colors.data(),
1278  stops.data(), flutter::DlTileMode::kClamp),
1279  };
1280 
1281  builder.Save();
1282  builder.Translate(0, 100);
1283  for (const auto& color_source : color_sources) {
1284  flutter::DlPaint paint;
1285  paint.setColorSource(color_source);
1286  paint.setMaskFilter(blur_filter);
1287 
1288  builder.Save();
1289  builder.Translate(100, 0);
1290  paint.setDrawStyle(flutter::DlDrawStyle::kFill);
1291  builder.DrawRoundRect(
1292  DlRoundRect::MakeRectXY(DlRect::MakeWH(100, 50), 30, 30), paint);
1293 
1294  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
1295  paint.setStrokeWidth(10);
1296  builder.Translate(200, 0);
1297  builder.DrawRoundRect(
1298  DlRoundRect::MakeRectXY(DlRect::MakeWH(100, 50), 30, 30), paint);
1299 
1300  builder.Restore();
1301  builder.Translate(0, 100);
1302  }
1303  builder.Restore();
1304 
1305  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1306 }
1307 
1308 TEST_P(DisplayListTest, DrawShapes) {
1309  flutter::DisplayListBuilder builder;
1310  std::vector<flutter::DlStrokeJoin> joins = {
1311  flutter::DlStrokeJoin::kBevel,
1312  flutter::DlStrokeJoin::kRound,
1313  flutter::DlStrokeJoin::kMiter,
1314  };
1315  flutter::DlPaint paint = //
1316  flutter::DlPaint() //
1317  .setColor(flutter::DlColor::kWhite()) //
1318  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1319  .setStrokeWidth(10);
1320  flutter::DlPaint stroke_paint = //
1321  flutter::DlPaint() //
1322  .setColor(flutter::DlColor::kWhite()) //
1323  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
1324  .setStrokeWidth(10);
1325  DlPath path = DlPath::MakeLine({150, 50}, {160, 50});
1326 
1327  builder.Translate(300, 50);
1328  builder.Scale(0.8, 0.8);
1329  for (auto join : joins) {
1330  paint.setStrokeJoin(join);
1331  stroke_paint.setStrokeJoin(join);
1332  builder.DrawRect(DlRect::MakeXYWH(0, 0, 100, 100), paint);
1333  builder.DrawRect(DlRect::MakeXYWH(0, 150, 100, 100), stroke_paint);
1334  builder.DrawRoundRect(
1335  DlRoundRect::MakeRectXY(DlRect::MakeXYWH(150, 0, 100, 100), 30, 30),
1336  paint);
1337  builder.DrawRoundRect(
1338  DlRoundRect::MakeRectXY(DlRect::MakeXYWH(150, 150, 100, 100), 30, 30),
1339  stroke_paint);
1340  builder.DrawCircle(DlPoint(350, 50), 50, paint);
1341  builder.DrawCircle(DlPoint(350, 200), 50, stroke_paint);
1342  builder.Translate(0, 300);
1343  }
1344  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1345 }
1346 
1347 TEST_P(DisplayListTest, ClipDrawRRectWithNonCircularRadii) {
1348  flutter::DisplayListBuilder builder;
1349 
1350  flutter::DlPaint fill_paint = //
1351  flutter::DlPaint() //
1352  .setColor(flutter::DlColor::kBlue()) //
1353  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1354  .setStrokeWidth(10);
1355  flutter::DlPaint stroke_paint = //
1356  flutter::DlPaint() //
1357  .setColor(flutter::DlColor::kGreen()) //
1358  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
1359  .setStrokeWidth(10);
1360 
1361  builder.DrawRoundRect(
1362  DlRoundRect::MakeRectXY(DlRect::MakeXYWH(500, 100, 300, 300), 120, 40),
1363  fill_paint);
1364  builder.DrawRoundRect(
1365  DlRoundRect::MakeRectXY(DlRect::MakeXYWH(500, 100, 300, 300), 120, 40),
1366  stroke_paint);
1367 
1368  builder.DrawRoundRect(
1369  DlRoundRect::MakeRectXY(DlRect::MakeXYWH(100, 500, 300, 300), 40, 120),
1370  fill_paint);
1371  builder.DrawRoundRect(
1372  DlRoundRect::MakeRectXY(DlRect::MakeXYWH(100, 500, 300, 300), 40, 120),
1373  stroke_paint);
1374 
1375  flutter::DlPaint reference_paint = //
1376  flutter::DlPaint() //
1377  .setColor(flutter::DlColor::kMidGrey()) //
1378  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1379  .setStrokeWidth(10);
1380 
1381  builder.DrawRoundRect(
1382  DlRoundRect::MakeRectXY(DlRect::MakeXYWH(500, 500, 300, 300), 40, 40),
1383  reference_paint);
1384  builder.DrawRoundRect(
1385  DlRoundRect::MakeRectXY(DlRect::MakeXYWH(100, 100, 300, 300), 120, 120),
1386  reference_paint);
1387 
1388  flutter::DlPaint clip_fill_paint = //
1389  flutter::DlPaint() //
1390  .setColor(flutter::DlColor::kCyan()) //
1391  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1392  .setStrokeWidth(10);
1393 
1394  builder.Save();
1395  builder.ClipRoundRect(
1396  DlRoundRect::MakeRectXY(DlRect::MakeXYWH(900, 100, 300, 300), 120, 40));
1397  builder.DrawPaint(clip_fill_paint);
1398  builder.Restore();
1399 
1400  builder.Save();
1401  builder.ClipRoundRect(
1402  DlRoundRect::MakeRectXY(DlRect::MakeXYWH(100, 900, 300, 300), 40, 120));
1403  builder.DrawPaint(clip_fill_paint);
1404  builder.Restore();
1405 
1406  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1407 }
1408 
1409 TEST_P(DisplayListTest, DrawVerticesBlendModes) {
1410  std::vector<const char*> blend_mode_names;
1411  std::vector<flutter::DlBlendMode> blend_mode_values;
1412  {
1413  const std::vector<std::tuple<const char*, flutter::DlBlendMode>> blends = {
1414  // Pipeline blends (Porter-Duff alpha compositing)
1415  {"Clear", flutter::DlBlendMode::kClear},
1416  {"Source", flutter::DlBlendMode::kSrc},
1417  {"Destination", flutter::DlBlendMode::kDst},
1418  {"SourceOver", flutter::DlBlendMode::kSrcOver},
1419  {"DestinationOver", flutter::DlBlendMode::kDstOver},
1420  {"SourceIn", flutter::DlBlendMode::kSrcIn},
1421  {"DestinationIn", flutter::DlBlendMode::kDstIn},
1422  {"SourceOut", flutter::DlBlendMode::kSrcOut},
1423  {"DestinationOut", flutter::DlBlendMode::kDstOut},
1424  {"SourceATop", flutter::DlBlendMode::kSrcATop},
1425  {"DestinationATop", flutter::DlBlendMode::kDstATop},
1426  {"Xor", flutter::DlBlendMode::kXor},
1427  {"Plus", flutter::DlBlendMode::kPlus},
1428  {"Modulate", flutter::DlBlendMode::kModulate},
1429  // Advanced blends (color component blends)
1430  {"Screen", flutter::DlBlendMode::kScreen},
1431  {"Overlay", flutter::DlBlendMode::kOverlay},
1432  {"Darken", flutter::DlBlendMode::kDarken},
1433  {"Lighten", flutter::DlBlendMode::kLighten},
1434  {"ColorDodge", flutter::DlBlendMode::kColorDodge},
1435  {"ColorBurn", flutter::DlBlendMode::kColorBurn},
1436  {"HardLight", flutter::DlBlendMode::kHardLight},
1437  {"SoftLight", flutter::DlBlendMode::kSoftLight},
1438  {"Difference", flutter::DlBlendMode::kDifference},
1439  {"Exclusion", flutter::DlBlendMode::kExclusion},
1440  {"Multiply", flutter::DlBlendMode::kMultiply},
1441  {"Hue", flutter::DlBlendMode::kHue},
1442  {"Saturation", flutter::DlBlendMode::kSaturation},
1443  {"Color", flutter::DlBlendMode::kColor},
1444  {"Luminosity", flutter::DlBlendMode::kLuminosity},
1445  };
1446  assert(blends.size() ==
1447  static_cast<size_t>(flutter::DlBlendMode::kLastMode) + 1);
1448  for (const auto& [name, mode] : blends) {
1449  blend_mode_names.push_back(name);
1450  blend_mode_values.push_back(mode);
1451  }
1452  }
1453 
1454  auto callback = [&]() {
1455  static int current_blend_index = 3;
1456  static float dst_alpha = 1;
1457  static float src_alpha = 1;
1458  static float color0[4] = {1.0f, 0.0f, 0.0f, 1.0f};
1459  static float color1[4] = {0.0f, 1.0f, 0.0f, 1.0f};
1460  static float color2[4] = {0.0f, 0.0f, 1.0f, 1.0f};
1461  static float src_color[4] = {1.0f, 1.0f, 1.0f, 1.0f};
1462 
1463  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1464  {
1465  ImGui::ListBox("Blending mode", &current_blend_index,
1466  blend_mode_names.data(), blend_mode_names.size());
1467  ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1);
1468  ImGui::ColorEdit4("Color A", color0);
1469  ImGui::ColorEdit4("Color B", color1);
1470  ImGui::ColorEdit4("Color C", color2);
1471  ImGui::ColorEdit4("Source Color", src_color);
1472  ImGui::SliderFloat("Destination alpha", &dst_alpha, 0, 1);
1473  }
1474  ImGui::End();
1475 
1476  std::vector<DlPoint> positions = {DlPoint(100, 300), //
1477  DlPoint(200, 100), //
1478  DlPoint(300, 300)};
1479  std::vector<flutter::DlColor> colors = {
1480  toColor(color0).modulateOpacity(dst_alpha),
1481  toColor(color1).modulateOpacity(dst_alpha),
1482  toColor(color2).modulateOpacity(dst_alpha)};
1483 
1484  auto vertices = flutter::DlVertices::Make(
1485  flutter::DlVertexMode::kTriangles, 3, positions.data(),
1486  /*texture_coordinates=*/nullptr, colors.data());
1487 
1488  flutter::DisplayListBuilder builder;
1489  flutter::DlPaint paint;
1490 
1491  paint.setColor(toColor(src_color).modulateOpacity(src_alpha));
1492  builder.DrawVertices(vertices, blend_mode_values[current_blend_index],
1493  paint);
1494  return builder.Build();
1495  };
1496 
1497  ASSERT_TRUE(OpenPlaygroundHere(callback));
1498 }
1499 
1500 TEST_P(DisplayListTest, DrawPaintIgnoresMaskFilter) {
1501  flutter::DisplayListBuilder builder;
1502  builder.DrawPaint(flutter::DlPaint().setColor(flutter::DlColor::kWhite()));
1503 
1504  auto filter = flutter::DlBlurMaskFilter(flutter::DlBlurStyle::kNormal, 10.0f);
1505  builder.DrawCircle(DlPoint(300, 300), 200,
1506  flutter::DlPaint().setMaskFilter(&filter));
1507 
1508  std::vector<flutter::DlColor> colors = {flutter::DlColor::kGreen(),
1509  flutter::DlColor::kGreen()};
1510  const float stops[2] = {0.0, 1.0};
1511  auto linear = flutter::DlColorSource::MakeLinear(
1512  {100.0, 100.0}, {300.0, 300.0}, 2, colors.data(), stops,
1513  flutter::DlTileMode::kRepeat);
1514  flutter::DlPaint blend_paint =
1515  flutter::DlPaint() //
1516  .setColorSource(linear) //
1517  .setBlendMode(flutter::DlBlendMode::kScreen);
1518  builder.DrawPaint(blend_paint);
1519 
1520  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1521 }
1522 
1523 TEST_P(DisplayListTest, DrawMaskBlursThatMightUseSaveLayers) {
1524  flutter::DisplayListBuilder builder;
1525  builder.DrawColor(flutter::DlColor::kWhite(), flutter::DlBlendMode::kSrc);
1526  Vector2 scale = GetContentScale();
1527  builder.Scale(scale.x, scale.y);
1528 
1529  builder.Save();
1530  // We need a small transform op to avoid a deferred save
1531  builder.Translate(1.0f, 1.0f);
1532  auto solid_filter =
1533  flutter::DlBlurMaskFilter::Make(flutter::DlBlurStyle::kSolid, 5.0f);
1534  flutter::DlPaint solid_alpha_paint =
1535  flutter::DlPaint() //
1536  .setMaskFilter(solid_filter) //
1537  .setColor(flutter::DlColor::kBlue()) //
1538  .setAlpha(0x7f);
1539  for (int x = 1; x <= 4; x++) {
1540  for (int y = 1; y <= 4; y++) {
1541  builder.DrawRect(DlRect::MakeXYWH(x * 100, y * 100, 80, 80),
1542  solid_alpha_paint);
1543  }
1544  }
1545  builder.Restore();
1546 
1547  builder.Save();
1548  builder.Translate(500.0f, 0.0f);
1549  auto normal_filter =
1550  flutter::DlBlurMaskFilter::Make(flutter::DlBlurStyle::kNormal, 5.0f);
1551  auto rotate_if = flutter::DlMatrixImageFilter::Make(
1552  Matrix::MakeRotationZ(Degrees(10)), flutter::DlImageSampling::kLinear);
1553  flutter::DlPaint normal_if_paint =
1554  flutter::DlPaint() //
1555  .setMaskFilter(solid_filter) //
1556  .setImageFilter(rotate_if) //
1557  .setColor(flutter::DlColor::kGreen()) //
1558  .setAlpha(0x7f);
1559  for (int x = 1; x <= 4; x++) {
1560  for (int y = 1; y <= 4; y++) {
1561  builder.DrawRect(DlRect::MakeXYWH(x * 100, y * 100, 80, 80),
1562  normal_if_paint);
1563  }
1564  }
1565  builder.Restore();
1566 
1567  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1568 }
1569 
1570 } // namespace testing
1571 } // namespace impeller
bool use_center
static sk_sp< DlImageImpeller > Make(std::shared_ptr< Texture > texture, OwningContext owning_context=OwningContext::kIO)
int32_t x
TEST_P(AiksTest, DrawAtlasNoColor)
flutter::DlColor toColor(const float *components)
Definition: dl_unittests.cc:39
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
flutter::DlRect DlRect
Definition: dl_dispatcher.h:25
constexpr float kPi
Definition: constants.h:26
Point DrawPlaygroundPoint(PlaygroundPoint &point)
Definition: widgets.cc:11
std::tuple< Point, Point > DrawPlaygroundLine(PlaygroundPoint &point_a, PlaygroundPoint &point_b)
Definition: widgets.cc:51
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
static uint32_t ToIColor(Color color)
Convert this color to a 32-bit representation.
Definition: color.h:159
static constexpr Color White()
Definition: color.h:264
static constexpr Color Red()
Definition: color.h:272
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
constexpr Matrix Translate(const Vector3 &t) const
Definition: matrix.h:263
static constexpr Matrix MakeRow(Scalar m0, Scalar m1, Scalar m2, Scalar m3, Scalar m4, Scalar m5, Scalar m6, Scalar m7, Scalar m8, Scalar m9, Scalar m10, Scalar m11, Scalar m12, Scalar m13, Scalar m14, Scalar m15)
Definition: matrix.h:83
constexpr Matrix Scale(const Vector3 &s) const
Definition: matrix.h:275
static Matrix MakeRotationZ(Radians r)
Definition: matrix.h:223
std::vector< Point > points