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