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