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_filter.h"
18 #include "flutter/display_list/effects/dl_mask_filter.h"
19 #include "flutter/testing/testing.h"
20 #include "gtest/gtest.h"
31 #include "impeller/scene/node.h"
32 #include "third_party/imgui/imgui.h"
33 #include "third_party/skia/include/core/SkBlurTypes.h"
34 #include "third_party/skia/include/core/SkClipOp.h"
35 #include "third_party/skia/include/core/SkPathBuilder.h"
36 #include "third_party/skia/include/core/SkRRect.h"
37 
38 namespace impeller {
39 namespace testing {
40 
41 flutter::DlColor toColor(const float* components) {
42  return flutter::DlColor(Color::ToIColor(
43  Color(components[0], components[1], components[2], components[3])));
44 }
45 
48 
49 TEST_P(DisplayListTest, DrawPictureWithAClip) {
50  flutter::DisplayListBuilder sub_builder;
51  sub_builder.ClipRect(SkRect::MakeXYWH(0, 0, 24, 24));
52  sub_builder.DrawPaint(flutter::DlPaint(flutter::DlColor::kBlue()));
53 
54  auto display_list = sub_builder.Build();
55  flutter::DisplayListBuilder builder;
56  builder.DrawDisplayList(display_list);
57  builder.DrawRect(SkRect::MakeXYWH(30, 30, 24, 24),
58  flutter::DlPaint(flutter::DlColor::kRed()));
59  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
60 }
61 
62 TEST_P(DisplayListTest, CanDrawRect) {
63  flutter::DisplayListBuilder builder;
64  builder.DrawRect(SkRect::MakeXYWH(10, 10, 100, 100),
65  flutter::DlPaint(flutter::DlColor::kBlue()));
66  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
67 }
68 
69 TEST_P(DisplayListTest, CanDrawTextBlob) {
70  flutter::DisplayListBuilder builder;
71  builder.DrawTextBlob(SkTextBlob::MakeFromString("Hello", CreateTestFont()),
72  100, 100, flutter::DlPaint(flutter::DlColor::kBlue()));
73  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
74 }
75 
76 TEST_P(DisplayListTest, CanDrawTextBlobWithGradient) {
77  flutter::DisplayListBuilder builder;
78 
79  std::vector<flutter::DlColor> colors = {flutter::DlColor::kBlue(),
80  flutter::DlColor::kRed()};
81  const float stops[2] = {0.0, 1.0};
82 
83  auto linear = flutter::DlColorSource::MakeLinear({0.0, 0.0}, {300.0, 300.0},
84  2, colors.data(), stops,
85  flutter::DlTileMode::kClamp);
86  flutter::DlPaint paint;
87  paint.setColorSource(linear);
88 
89  builder.DrawTextBlob(
90  SkTextBlob::MakeFromString("Hello World", CreateTestFont()), 100, 100,
91  paint);
92  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
93 }
94 
95 TEST_P(DisplayListTest, CanDrawTextWithSaveLayer) {
96  flutter::DisplayListBuilder builder;
97  builder.DrawTextBlob(SkTextBlob::MakeFromString("Hello", CreateTestFont()),
98  100, 100, flutter::DlPaint(flutter::DlColor::kRed()));
99 
100  flutter::DlPaint save_paint;
101  float alpha = 0.5;
102  save_paint.setAlpha(static_cast<uint8_t>(255 * alpha));
103  builder.SaveLayer(nullptr, &save_paint);
104  builder.DrawTextBlob(SkTextBlob::MakeFromString("Hello with half alpha",
105  CreateTestFontOfSize(100)),
106  100, 300, flutter::DlPaint(flutter::DlColor::kRed()));
107  builder.Restore();
108  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
109 }
110 
111 TEST_P(DisplayListTest, CanDrawImage) {
112  auto texture = CreateTextureForFixture("embarcadero.jpg");
113  flutter::DisplayListBuilder builder;
114  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
115  flutter::DlImageSampling::kNearestNeighbor, nullptr);
116  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
117 }
118 
119 TEST_P(DisplayListTest, CanDrawCapsAndJoins) {
120  flutter::DisplayListBuilder builder;
121  flutter::DlPaint paint;
122 
123  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
124  paint.setStrokeWidth(30);
125  paint.setColor(flutter::DlColor::kRed());
126 
127  auto path =
128  SkPathBuilder{}.moveTo(-50, 0).lineTo(0, -50).lineTo(50, 0).snapshot();
129 
130  builder.Translate(100, 100);
131  {
132  paint.setStrokeCap(flutter::DlStrokeCap::kButt);
133  paint.setStrokeJoin(flutter::DlStrokeJoin::kMiter);
134  paint.setStrokeMiter(4);
135  builder.DrawPath(path, paint);
136  }
137 
138  {
139  builder.Save();
140  builder.Translate(0, 100);
141  // The joint in the path is 45 degrees. A miter length of 1 convert to a
142  // bevel in this case.
143  paint.setStrokeMiter(1);
144  builder.DrawPath(path, paint);
145  builder.Restore();
146  }
147 
148  builder.Translate(150, 0);
149  {
150  paint.setStrokeCap(flutter::DlStrokeCap::kSquare);
151  paint.setStrokeJoin(flutter::DlStrokeJoin::kBevel);
152  builder.DrawPath(path, paint);
153  }
154 
155  builder.Translate(150, 0);
156  {
157  paint.setStrokeCap(flutter::DlStrokeCap::kRound);
158  paint.setStrokeJoin(flutter::DlStrokeJoin::kRound);
159  builder.DrawPath(path, paint);
160  }
161 
162  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
163 }
164 
165 TEST_P(DisplayListTest, CanDrawArc) {
166  auto callback = [&]() {
167  static float start_angle = 45;
168  static float sweep_angle = 270;
169  static float stroke_width = 10;
170  static bool use_center = true;
171 
172  static int selected_cap = 0;
173  const char* cap_names[] = {"Butt", "Round", "Square"};
174  flutter::DlStrokeCap cap;
175 
176  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
177  ImGui::SliderFloat("Start angle", &start_angle, -360, 360);
178  ImGui::SliderFloat("Sweep angle", &sweep_angle, -360, 360);
179  ImGui::SliderFloat("Stroke width", &stroke_width, 0, 300);
180  ImGui::Combo("Cap", &selected_cap, cap_names,
181  sizeof(cap_names) / sizeof(char*));
182  ImGui::Checkbox("Use center", &use_center);
183  ImGui::End();
184 
185  switch (selected_cap) {
186  case 0:
187  cap = flutter::DlStrokeCap::kButt;
188  break;
189  case 1:
190  cap = flutter::DlStrokeCap::kRound;
191  break;
192  case 2:
193  cap = flutter::DlStrokeCap::kSquare;
194  break;
195  default:
196  cap = flutter::DlStrokeCap::kButt;
197  break;
198  }
199 
200  auto [p1, p2] = IMPELLER_PLAYGROUND_LINE(
201  Point(200, 200), Point(400, 400), 20, Color::White(), Color::White());
202 
203  flutter::DisplayListBuilder builder;
204  flutter::DlPaint paint;
205 
206  Vector2 scale = GetContentScale();
207  builder.Scale(scale.x, scale.y);
208  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
209  paint.setStrokeCap(cap);
210  paint.setStrokeJoin(flutter::DlStrokeJoin::kMiter);
211  paint.setStrokeMiter(10);
212  auto rect = SkRect::MakeLTRB(p1.x, p1.y, p2.x, p2.y);
213  paint.setColor(flutter::DlColor::kGreen());
214  paint.setStrokeWidth(2);
215  builder.DrawRect(rect, paint);
216  paint.setColor(flutter::DlColor::kRed());
217  paint.setStrokeWidth(stroke_width);
218  builder.DrawArc(rect, start_angle, sweep_angle, use_center, paint);
219 
220  return builder.Build();
221  };
222  ASSERT_TRUE(OpenPlaygroundHere(callback));
223 }
224 
225 TEST_P(DisplayListTest, StrokedPathsDrawCorrectly) {
226  auto callback = [&]() {
227  flutter::DisplayListBuilder builder;
228  flutter::DlPaint paint;
229 
230  paint.setColor(flutter::DlColor::kRed());
231  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
232 
233  static float stroke_width = 10.0f;
234  static int selected_stroke_type = 0;
235  static int selected_join_type = 0;
236  const char* stroke_types[] = {"Butte", "Round", "Square"};
237  const char* join_type[] = {"kMiter", "Round", "kBevel"};
238 
239  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
240  ImGui::Combo("Cap", &selected_stroke_type, stroke_types,
241  sizeof(stroke_types) / sizeof(char*));
242  ImGui::Combo("Join", &selected_join_type, join_type,
243  sizeof(join_type) / sizeof(char*));
244  ImGui::SliderFloat("Stroke Width", &stroke_width, 10.0f, 50.0f);
245  ImGui::End();
246 
247  flutter::DlStrokeCap cap;
248  flutter::DlStrokeJoin join;
249  switch (selected_stroke_type) {
250  case 0:
251  cap = flutter::DlStrokeCap::kButt;
252  break;
253  case 1:
254  cap = flutter::DlStrokeCap::kRound;
255  break;
256  case 2:
257  cap = flutter::DlStrokeCap::kSquare;
258  break;
259  default:
260  cap = flutter::DlStrokeCap::kButt;
261  break;
262  }
263  switch (selected_join_type) {
264  case 0:
265  join = flutter::DlStrokeJoin::kMiter;
266  break;
267  case 1:
268  join = flutter::DlStrokeJoin::kRound;
269  break;
270  case 2:
271  join = flutter::DlStrokeJoin::kBevel;
272  break;
273  default:
274  join = flutter::DlStrokeJoin::kMiter;
275  break;
276  }
277  paint.setStrokeCap(cap);
278  paint.setStrokeJoin(join);
279  paint.setStrokeWidth(stroke_width);
280 
281  // Make rendering better to watch.
282  builder.Scale(1.5f, 1.5f);
283 
284  // Rectangle
285  builder.Translate(100, 100);
286  builder.DrawRect(SkRect::MakeSize({100, 100}), paint);
287 
288  // Rounded rectangle
289  builder.Translate(150, 0);
290  builder.DrawRRect(SkRRect::MakeRectXY(SkRect::MakeSize({100, 50}), 10, 10),
291  paint);
292 
293  // Double rounded rectangle
294  builder.Translate(150, 0);
295  builder.DrawDRRect(
296  SkRRect::MakeRectXY(SkRect::MakeSize({100, 50}), 10, 10),
297  SkRRect::MakeRectXY(SkRect::MakeXYWH(10, 10, 80, 30), 10, 10), paint);
298 
299  // Contour with duplicate join points
300  {
301  builder.Translate(150, 0);
302  SkPath path;
303  path.moveTo(0, 0);
304  path.lineTo(0, 0);
305  path.lineTo({100, 0});
306  path.lineTo({100, 0});
307  path.lineTo({100, 100});
308  builder.DrawPath(path, paint);
309  }
310 
311  // Contour with duplicate start and end points
312 
313  // Line.
314  builder.Translate(200, 0);
315  {
316  builder.Save();
317 
318  SkPath line_path;
319  line_path.moveTo(0, 0);
320  line_path.moveTo(0, 0);
321  line_path.lineTo({0, 0});
322  line_path.lineTo({0, 0});
323  line_path.lineTo({50, 50});
324  line_path.lineTo({50, 50});
325  line_path.lineTo({100, 0});
326  line_path.lineTo({100, 0});
327  builder.DrawPath(line_path, paint);
328 
329  builder.Translate(0, 100);
330  builder.DrawPath(line_path, paint);
331 
332  builder.Translate(0, 100);
333  SkPath line_path2;
334  line_path2.moveTo(0, 0);
335  line_path2.lineTo(0, 0);
336  line_path2.lineTo(0, 0);
337  builder.DrawPath(line_path2, paint);
338 
339  builder.Restore();
340  }
341 
342  // Cubic.
343  builder.Translate(150, 0);
344  {
345  builder.Save();
346 
347  SkPath cubic_path;
348  cubic_path.moveTo({0, 0});
349  cubic_path.cubicTo(0, 0, 140.0, 100.0, 140, 20);
350  builder.DrawPath(cubic_path, paint);
351 
352  builder.Translate(0, 100);
353  SkPath cubic_path2;
354  cubic_path2.moveTo({0, 0});
355  cubic_path2.cubicTo(0, 0, 0, 0, 150, 150);
356  builder.DrawPath(cubic_path2, paint);
357 
358  builder.Translate(0, 100);
359  SkPath cubic_path3;
360  cubic_path3.moveTo({0, 0});
361  cubic_path3.cubicTo(0, 0, 0, 0, 0, 0);
362  builder.DrawPath(cubic_path3, paint);
363 
364  builder.Restore();
365  }
366 
367  // Quad.
368  builder.Translate(200, 0);
369  {
370  builder.Save();
371 
372  SkPath quad_path;
373  quad_path.moveTo(0, 0);
374  quad_path.moveTo(0, 0);
375  quad_path.quadTo({100, 40}, {50, 80});
376  builder.DrawPath(quad_path, paint);
377 
378  builder.Translate(0, 150);
379  SkPath quad_path2;
380  quad_path2.moveTo(0, 0);
381  quad_path2.moveTo(0, 0);
382  quad_path2.quadTo({0, 0}, {100, 100});
383  builder.DrawPath(quad_path2, paint);
384 
385  builder.Translate(0, 100);
386  SkPath quad_path3;
387  quad_path3.moveTo(0, 0);
388  quad_path3.quadTo({0, 0}, {0, 0});
389  builder.DrawPath(quad_path3, paint);
390 
391  builder.Restore();
392  }
393  return builder.Build();
394  };
395  ASSERT_TRUE(OpenPlaygroundHere(callback));
396 }
397 
398 TEST_P(DisplayListTest, CanDrawWithOddPathWinding) {
399  flutter::DisplayListBuilder builder;
400  flutter::DlPaint paint;
401 
402  paint.setColor(flutter::DlColor::kRed());
403  paint.setDrawStyle(flutter::DlDrawStyle::kFill);
404 
405  builder.Translate(300, 300);
406  SkPath path;
407  path.setFillType(SkPathFillType::kEvenOdd);
408  path.addCircle(0, 0, 100);
409  path.addCircle(0, 0, 50);
410  builder.DrawPath(path, paint);
411 
412  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
413 }
414 
415 // Regression test for https://github.com/flutter/flutter/issues/134816.
416 //
417 // It should be possible to draw 3 lines, and not have an implicit close path.
418 TEST_P(DisplayListTest, CanDrawAnOpenPath) {
419  flutter::DisplayListBuilder builder;
420  flutter::DlPaint paint;
421 
422  paint.setColor(flutter::DlColor::kRed());
423  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
424  paint.setStrokeWidth(10);
425 
426  builder.Translate(300, 300);
427 
428  // Move to (50, 50) and draw lines from:
429  // 1. (50, height)
430  // 2. (width, height)
431  // 3. (width, 50)
432  SkPath path;
433  path.moveTo(50, 50);
434  path.lineTo(50, 100);
435  path.lineTo(100, 100);
436  path.lineTo(100, 50);
437  builder.DrawPath(path, paint);
438 
439  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
440 }
441 
442 TEST_P(DisplayListTest, CanDrawWithMaskBlur) {
443  auto texture = CreateTextureForFixture("embarcadero.jpg");
444  flutter::DisplayListBuilder builder;
445  flutter::DlPaint paint;
446 
447  // Mask blurred image.
448  {
449  auto filter =
450  flutter::DlBlurMaskFilter(flutter::DlBlurStyle::kNormal, 10.0f);
451  paint.setMaskFilter(&filter);
452  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
453  flutter::DlImageSampling::kNearestNeighbor, &paint);
454  }
455 
456  // Mask blurred filled path.
457  {
458  paint.setColor(flutter::DlColor::kYellow());
459  auto filter =
460  flutter::DlBlurMaskFilter(flutter::DlBlurStyle::kOuter, 10.0f);
461  paint.setMaskFilter(&filter);
462  builder.DrawArc(SkRect::MakeXYWH(410, 110, 100, 100), 45, 270, true, paint);
463  }
464 
465  // Mask blurred text.
466  {
467  auto filter =
468  flutter::DlBlurMaskFilter(flutter::DlBlurStyle::kSolid, 10.0f);
469  paint.setMaskFilter(&filter);
470  builder.DrawTextBlob(
471  SkTextBlob::MakeFromString("Testing", CreateTestFont()), 220, 170,
472  paint);
473  }
474 
475  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
476 }
477 
478 TEST_P(DisplayListTest, CanDrawStrokedText) {
479  flutter::DisplayListBuilder builder;
480  flutter::DlPaint paint;
481 
482  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
483  paint.setColor(flutter::DlColor::kRed());
484  builder.DrawTextBlob(
485  SkTextBlob::MakeFromString("stoked about stroked text", CreateTestFont()),
486  250, 250, paint);
487 
488  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
489 }
490 
491 // Regression test for https://github.com/flutter/flutter/issues/133157.
492 TEST_P(DisplayListTest, StrokedTextNotOffsetFromNormalText) {
493  flutter::DisplayListBuilder builder;
494  flutter::DlPaint paint;
495  auto const& text_blob = SkTextBlob::MakeFromString("00000", CreateTestFont());
496 
497  // https://api.flutter.dev/flutter/material/Colors/blue-constant.html.
498  auto const& mat_blue = flutter::DlColor(0xFF2196f3);
499 
500  // Draw a blue filled rectangle so the text is easier to see.
501  paint.setDrawStyle(flutter::DlDrawStyle::kFill);
502  paint.setColor(mat_blue);
503  builder.DrawRect(SkRect::MakeXYWH(0, 0, 500, 500), paint);
504 
505  // Draw stacked text, with stroked text on top.
506  paint.setDrawStyle(flutter::DlDrawStyle::kFill);
507  paint.setColor(flutter::DlColor::kWhite());
508  builder.DrawTextBlob(text_blob, 250, 250, paint);
509 
510  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
511  paint.setColor(flutter::DlColor::kBlack());
512  builder.DrawTextBlob(text_blob, 250, 250, paint);
513 
514  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
515 }
516 
517 TEST_P(DisplayListTest, IgnoreMaskFilterWhenSavingLayer) {
518  auto texture = CreateTextureForFixture("embarcadero.jpg");
519  flutter::DisplayListBuilder builder;
520  auto filter = flutter::DlBlurMaskFilter(flutter::DlBlurStyle::kNormal, 10.0f);
521  flutter::DlPaint paint;
522  paint.setMaskFilter(&filter);
523  builder.SaveLayer(nullptr, &paint);
524  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
525  flutter::DlImageSampling::kNearestNeighbor);
526  builder.Restore();
527  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
528 }
529 
530 TEST_P(DisplayListTest, CanDrawWithBlendColorFilter) {
531  auto texture = CreateTextureForFixture("embarcadero.jpg");
532  flutter::DisplayListBuilder builder;
533  flutter::DlPaint paint;
534 
535  // Pipeline blended image.
536  {
537  auto filter = flutter::DlBlendColorFilter(flutter::DlColor::kYellow(),
538  flutter::DlBlendMode::kModulate);
539  paint.setColorFilter(&filter);
540  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
541  flutter::DlImageSampling::kNearestNeighbor, &paint);
542  }
543 
544  // Advanced blended image.
545  {
546  auto filter = flutter::DlBlendColorFilter(flutter::DlColor::kRed(),
547  flutter::DlBlendMode::kScreen);
548  paint.setColorFilter(&filter);
549  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(250, 250),
550  flutter::DlImageSampling::kNearestNeighbor, &paint);
551  }
552 
553  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
554 }
555 
556 TEST_P(DisplayListTest, CanDrawWithColorFilterImageFilter) {
557  const float invert_color_matrix[20] = {
558  -1, 0, 0, 0, 1, //
559  0, -1, 0, 0, 1, //
560  0, 0, -1, 0, 1, //
561  0, 0, 0, 1, 0, //
562  };
563  auto texture = CreateTextureForFixture("boston.jpg");
564  flutter::DisplayListBuilder builder;
565  flutter::DlPaint paint;
566 
567  auto color_filter =
568  std::make_shared<flutter::DlMatrixColorFilter>(invert_color_matrix);
569  auto image_filter =
570  std::make_shared<flutter::DlColorFilterImageFilter>(color_filter);
571 
572  paint.setImageFilter(image_filter.get());
573  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
574  flutter::DlImageSampling::kNearestNeighbor, &paint);
575 
576  builder.Translate(0, 700);
577  paint.setColorFilter(color_filter.get());
578  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
579  flutter::DlImageSampling::kNearestNeighbor, &paint);
580  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
581 }
582 
583 TEST_P(DisplayListTest, CanDrawWithImageBlurFilter) {
584  auto texture = CreateTextureForFixture("embarcadero.jpg");
585 
586  auto callback = [&]() {
587  static float sigma[] = {10, 10};
588 
589  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
590  ImGui::SliderFloat2("Sigma", sigma, 0, 100);
591  ImGui::End();
592 
593  flutter::DisplayListBuilder builder;
594  flutter::DlPaint paint;
595 
596  auto filter = flutter::DlBlurImageFilter(sigma[0], sigma[1],
597  flutter::DlTileMode::kClamp);
598  paint.setImageFilter(&filter);
599  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(200, 200),
600  flutter::DlImageSampling::kNearestNeighbor, &paint);
601 
602  return builder.Build();
603  };
604 
605  ASSERT_TRUE(OpenPlaygroundHere(callback));
606 }
607 
608 TEST_P(DisplayListTest, CanDrawWithComposeImageFilter) {
609  auto texture = CreateTextureForFixture("boston.jpg");
610  flutter::DisplayListBuilder builder;
611  flutter::DlPaint paint;
612 
613  auto dilate = std::make_shared<flutter::DlDilateImageFilter>(10.0, 10.0);
614  auto erode = std::make_shared<flutter::DlErodeImageFilter>(10.0, 10.0);
615  auto open = std::make_shared<flutter::DlComposeImageFilter>(dilate, erode);
616  auto close = std::make_shared<flutter::DlComposeImageFilter>(erode, dilate);
617 
618  paint.setImageFilter(open.get());
619  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
620  flutter::DlImageSampling::kNearestNeighbor, &paint);
621  builder.Translate(0, 700);
622  paint.setImageFilter(close.get());
623  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
624  flutter::DlImageSampling::kNearestNeighbor, &paint);
625  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
626 }
627 
628 TEST_P(DisplayListTest, CanClampTheResultingColorOfColorMatrixFilter) {
629  auto texture = CreateTextureForFixture("boston.jpg");
630  const float inner_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, 2, 0, //
635  };
636  const float outer_color_matrix[20] = {
637  1, 0, 0, 0, 0, //
638  0, 1, 0, 0, 0, //
639  0, 0, 1, 0, 0, //
640  0, 0, 0, 0.5, 0, //
641  };
642  auto inner_color_filter =
643  std::make_shared<flutter::DlMatrixColorFilter>(inner_color_matrix);
644  auto outer_color_filter =
645  std::make_shared<flutter::DlMatrixColorFilter>(outer_color_matrix);
646  auto inner =
647  std::make_shared<flutter::DlColorFilterImageFilter>(inner_color_filter);
648  auto outer =
649  std::make_shared<flutter::DlColorFilterImageFilter>(outer_color_filter);
650  auto compose = std::make_shared<flutter::DlComposeImageFilter>(outer, inner);
651 
652  flutter::DisplayListBuilder builder;
653  flutter::DlPaint paint;
654  paint.setImageFilter(compose.get());
655  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
656  flutter::DlImageSampling::kNearestNeighbor, &paint);
657  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
658 }
659 
660 TEST_P(DisplayListTest, CanDrawBackdropFilter) {
661  auto texture = CreateTextureForFixture("embarcadero.jpg");
662 
663  auto callback = [&]() {
664  static float sigma[] = {10, 10};
665  static float ctm_scale = 1;
666  static bool use_bounds = true;
667  static bool draw_circle = true;
668  static bool add_clip = true;
669 
670  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
671  ImGui::SliderFloat2("Sigma", sigma, 0, 100);
672  ImGui::SliderFloat("Scale", &ctm_scale, 0, 10);
673  ImGui::NewLine();
674  ImGui::TextWrapped(
675  "If everything is working correctly, none of the options below should "
676  "impact the filter's appearance.");
677  ImGui::Checkbox("Use SaveLayer bounds", &use_bounds);
678  ImGui::Checkbox("Draw child element", &draw_circle);
679  ImGui::Checkbox("Add pre-clip", &add_clip);
680  ImGui::End();
681 
682  flutter::DisplayListBuilder builder;
683 
684  Vector2 scale = ctm_scale * GetContentScale();
685  builder.Scale(scale.x, scale.y);
686 
687  auto filter = flutter::DlBlurImageFilter(sigma[0], sigma[1],
688  flutter::DlTileMode::kClamp);
689 
690  std::optional<SkRect> bounds;
691  if (use_bounds) {
692  auto [p1, p2] = IMPELLER_PLAYGROUND_LINE(
693  Point(350, 150), Point(800, 600), 20, Color::White(), Color::White());
694  bounds = SkRect::MakeLTRB(p1.x, p1.y, p2.x, p2.y);
695  }
696 
697  // Insert a clip to test that the backdrop filter handles stencil depths > 0
698  // correctly.
699  if (add_clip) {
700  builder.ClipRect(SkRect::MakeLTRB(0, 0, 99999, 99999),
701  flutter::DlCanvas::ClipOp::kIntersect, true);
702  }
703 
704  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(200, 200),
705  flutter::DlImageSampling::kNearestNeighbor, nullptr);
706  builder.SaveLayer(bounds.has_value() ? &bounds.value() : nullptr, nullptr,
707  &filter);
708 
709  if (draw_circle) {
710  auto circle_center =
711  IMPELLER_PLAYGROUND_POINT(Point(500, 400), 20, Color::Red());
712 
713  flutter::DlPaint paint;
714  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
715  paint.setStrokeCap(flutter::DlStrokeCap::kButt);
716  paint.setStrokeJoin(flutter::DlStrokeJoin::kBevel);
717  paint.setStrokeWidth(10);
718  paint.setColor(flutter::DlColor::kRed().withAlpha(100));
719  builder.DrawCircle({circle_center.x, circle_center.y}, 100, paint);
720  }
721 
722  return builder.Build();
723  };
724 
725  ASSERT_TRUE(OpenPlaygroundHere(callback));
726 }
727 
728 TEST_P(DisplayListTest, CanDrawNinePatchImage) {
729  // Image is drawn with corners to scale and center pieces stretched to fit.
730  auto texture = CreateTextureForFixture("embarcadero.jpg");
731  flutter::DisplayListBuilder builder;
732  auto size = texture->GetSize();
733  builder.DrawImageNine(
734  DlImageImpeller::Make(texture),
735  SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
736  size.height * 3 / 4),
737  SkRect::MakeLTRB(0, 0, size.width * 2, size.height * 2),
738  flutter::DlFilterMode::kNearest, nullptr);
739  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
740 }
741 
742 TEST_P(DisplayListTest, CanDrawNinePatchImageCenterWidthBiggerThanDest) {
743  // Edge case, the width of the corners does not leave any room for the
744  // center slice. The center (across the vertical axis) is folded out of the
745  // resulting image.
746  auto texture = CreateTextureForFixture("embarcadero.jpg");
747  flutter::DisplayListBuilder builder;
748  auto size = texture->GetSize();
749  builder.DrawImageNine(
750  DlImageImpeller::Make(texture),
751  SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
752  size.height * 3 / 4),
753  SkRect::MakeLTRB(0, 0, size.width / 2, size.height),
754  flutter::DlFilterMode::kNearest, nullptr);
755  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
756 }
757 
758 TEST_P(DisplayListTest, CanDrawNinePatchImageCenterHeightBiggerThanDest) {
759  // Edge case, the height of the corners does not leave any room for the
760  // center slice. The center (across the horizontal axis) is folded out of the
761  // resulting image.
762  auto texture = CreateTextureForFixture("embarcadero.jpg");
763  flutter::DisplayListBuilder builder;
764  auto size = texture->GetSize();
765  builder.DrawImageNine(
766  DlImageImpeller::Make(texture),
767  SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
768  size.height * 3 / 4),
769  SkRect::MakeLTRB(0, 0, size.width, size.height / 2),
770  flutter::DlFilterMode::kNearest, nullptr);
771  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
772 }
773 
774 TEST_P(DisplayListTest, CanDrawNinePatchImageCenterBiggerThanDest) {
775  // Edge case, the width and height of the corners does not leave any
776  // room for the center slices. Only the corners are displayed.
777  auto texture = CreateTextureForFixture("embarcadero.jpg");
778  flutter::DisplayListBuilder builder;
779  auto size = texture->GetSize();
780  builder.DrawImageNine(
781  DlImageImpeller::Make(texture),
782  SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
783  size.height * 3 / 4),
784  SkRect::MakeLTRB(0, 0, size.width / 2, size.height / 2),
785  flutter::DlFilterMode::kNearest, nullptr);
786  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
787 }
788 
789 TEST_P(DisplayListTest, CanDrawNinePatchImageCornersScaledDown) {
790  // Edge case, there is not enough room for the corners to be drawn
791  // without scaling them down.
792  auto texture = CreateTextureForFixture("embarcadero.jpg");
793  flutter::DisplayListBuilder builder;
794  auto size = texture->GetSize();
795  builder.DrawImageNine(
796  DlImageImpeller::Make(texture),
797  SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
798  size.height * 3 / 4),
799  SkRect::MakeLTRB(0, 0, size.width / 4, size.height / 4),
800  flutter::DlFilterMode::kNearest, nullptr);
801  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
802 }
803 
804 TEST_P(DisplayListTest, CanDrawPoints) {
805  flutter::DisplayListBuilder builder;
806  SkPoint points[7] = {
807  {0, 0}, //
808  {100, 100}, //
809  {100, 0}, //
810  {0, 100}, //
811  {0, 0}, //
812  {48, 48}, //
813  {52, 52}, //
814  };
815  std::vector<flutter::DlStrokeCap> caps = {
816  flutter::DlStrokeCap::kButt,
817  flutter::DlStrokeCap::kRound,
818  flutter::DlStrokeCap::kSquare,
819  };
820  flutter::DlPaint paint =
821  flutter::DlPaint() //
822  .setColor(flutter::DlColor::kYellow().withAlpha(127)) //
823  .setStrokeWidth(20);
824  builder.Translate(50, 50);
825  for (auto cap : caps) {
826  paint.setStrokeCap(cap);
827  builder.Save();
828  builder.DrawPoints(flutter::DlCanvas::PointMode::kPoints, 7, points, paint);
829  builder.Translate(150, 0);
830  builder.DrawPoints(flutter::DlCanvas::PointMode::kLines, 5, points, paint);
831  builder.Translate(150, 0);
832  builder.DrawPoints(flutter::DlCanvas::PointMode::kPolygon, 5, points,
833  paint);
834  builder.Restore();
835  builder.Translate(0, 150);
836  }
837  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
838 }
839 
840 TEST_P(DisplayListTest, CanDrawZeroLengthLine) {
841  flutter::DisplayListBuilder builder;
842  std::vector<flutter::DlStrokeCap> caps = {
843  flutter::DlStrokeCap::kButt,
844  flutter::DlStrokeCap::kRound,
845  flutter::DlStrokeCap::kSquare,
846  };
847  flutter::DlPaint paint =
848  flutter::DlPaint() //
849  .setColor(flutter::DlColor::kYellow().withAlpha(127)) //
850  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
851  .setStrokeCap(flutter::DlStrokeCap::kButt) //
852  .setStrokeWidth(20);
853  SkPath path = SkPath().addPoly({{150, 50}, {150, 50}}, false);
854  for (auto cap : caps) {
855  paint.setStrokeCap(cap);
856  builder.DrawLine({50, 50}, {50, 50}, paint);
857  builder.DrawPath(path, paint);
858  builder.Translate(0, 150);
859  }
860  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
861 }
862 
863 TEST_P(DisplayListTest, CanDrawShadow) {
864  flutter::DisplayListBuilder builder;
865  flutter::DlPaint paint;
866 
867  auto content_scale = GetContentScale() * 0.8;
868  builder.Scale(content_scale.x, content_scale.y);
869 
870  constexpr size_t star_spikes = 5;
871  constexpr SkScalar half_spike_rotation = kPi / star_spikes;
872  constexpr SkScalar radius = 40;
873  constexpr SkScalar spike_size = 10;
874  constexpr SkScalar outer_radius = radius + spike_size;
875  constexpr SkScalar inner_radius = radius - spike_size;
876  std::array<SkPoint, star_spikes * 2> star;
877  for (size_t i = 0; i < star_spikes; i++) {
878  const SkScalar rotation = half_spike_rotation * i * 2;
879  star[i * 2] = SkPoint::Make(50 + std::sin(rotation) * outer_radius,
880  50 - std::cos(rotation) * outer_radius);
881  star[i * 2 + 1] = SkPoint::Make(
882  50 + std::sin(rotation + half_spike_rotation) * inner_radius,
883  50 - std::cos(rotation + half_spike_rotation) * inner_radius);
884  }
885 
886  std::array<SkPath, 4> paths = {
887  SkPath{}.addRect(SkRect::MakeXYWH(0, 0, 200, 100)),
888  SkPath{}.addRRect(
889  SkRRect::MakeRectXY(SkRect::MakeXYWH(20, 0, 200, 100), 30, 30)),
890  SkPath{}.addCircle(100, 50, 50),
891  SkPath{}.addPoly(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 
913  DispatcherDoesNotCullPerspectiveTransformedChildDisplayLists) {
914  // Regression test for https://github.com/flutter/flutter/issues/130613
915  flutter::DisplayListBuilder sub_builder(true);
916  sub_builder.DrawRect(SkRect::MakeXYWH(0, 0, 50, 50),
917  flutter::DlPaint(flutter::DlColor::kRed()));
918  auto display_list = sub_builder.Build();
919 
920  DlDispatcher dispatcher(Rect::MakeLTRB(0, 0, 2400, 1800));
921  dispatcher.scale(2.0, 2.0);
922  dispatcher.translate(-93.0, 0.0);
923  // clang-format off
924  dispatcher.transformFullPerspective(
925  0.8, -0.2, -0.1, -0.0,
926  0.0, 1.0, 0.0, 0.0,
927  1.4, 1.3, 1.0, 0.0,
928  63.2, 65.3, 48.6, 1.1
929  );
930  // clang-format on
931  dispatcher.translate(35.0, 75.0);
932  dispatcher.drawDisplayList(display_list, 1.0f);
933  auto picture = dispatcher.EndRecordingAsPicture();
934 
935  bool found = false;
936  picture.pass->IterateAllEntities([&found](Entity& entity) {
937  if (std::static_pointer_cast<SolidColorContents>(entity.GetContents())
938  ->GetColor() == Color::Red()) {
939  found = true;
940  return false;
941  }
942 
943  return true;
944  });
945  EXPECT_TRUE(found);
946 }
947 
948 TEST_P(DisplayListTest, TransparentShadowProducesCorrectColor) {
949  DlDispatcher dispatcher;
950  dispatcher.save();
951  dispatcher.scale(1.618, 1.618);
952  dispatcher.drawShadow(SkPath{}.addRect(SkRect::MakeXYWH(0, 0, 200, 100)),
953  flutter::DlColor::kTransparent(), 15, false, 1);
954  dispatcher.restore();
955  auto picture = dispatcher.EndRecordingAsPicture();
956 
957  std::shared_ptr<SolidRRectBlurContents> rrect_blur;
958  picture.pass->IterateAllEntities([&rrect_blur](Entity& entity) {
959  if (ScalarNearlyEqual(entity.GetTransform().GetScale().x, 1.618f)) {
960  rrect_blur = std::static_pointer_cast<SolidRRectBlurContents>(
961  entity.GetContents());
962  return false;
963  }
964  return true;
965  });
966 
967  ASSERT_NE(rrect_blur, nullptr);
968  ASSERT_EQ(rrect_blur->GetColor().red, 0);
969  ASSERT_EQ(rrect_blur->GetColor().green, 0);
970  ASSERT_EQ(rrect_blur->GetColor().blue, 0);
971  ASSERT_EQ(rrect_blur->GetColor().alpha, 0);
972 }
973 
974 // Draw a hexagon using triangle fan
975 TEST_P(DisplayListTest, CanConvertTriangleFanToTriangles) {
976  constexpr Scalar hexagon_radius = 125;
977  auto hex_start = Point(200.0, -hexagon_radius + 200.0);
978  auto center_to_flat = 1.73 / 2 * hexagon_radius;
979 
980  // clang-format off
981  std::vector<SkPoint> vertices = {
982  SkPoint::Make(hex_start.x, hex_start.y),
983  SkPoint::Make(hex_start.x + center_to_flat, hex_start.y + 0.5 * hexagon_radius),
984  SkPoint::Make(hex_start.x + center_to_flat, hex_start.y + 1.5 * hexagon_radius),
985  SkPoint::Make(hex_start.x + center_to_flat, hex_start.y + 1.5 * hexagon_radius),
986  SkPoint::Make(hex_start.x, hex_start.y + 2 * hexagon_radius),
987  SkPoint::Make(hex_start.x, hex_start.y + 2 * hexagon_radius),
988  SkPoint::Make(hex_start.x - center_to_flat, hex_start.y + 1.5 * hexagon_radius),
989  SkPoint::Make(hex_start.x - center_to_flat, hex_start.y + 1.5 * hexagon_radius),
990  SkPoint::Make(hex_start.x - center_to_flat, hex_start.y + 0.5 * hexagon_radius)
991  };
992  // clang-format on
993  auto paint = flutter::DlPaint(flutter::DlColor::kDarkGrey());
994  auto dl_vertices = flutter::DlVertices::Make(
995  flutter::DlVertexMode::kTriangleFan, vertices.size(), vertices.data(),
996  nullptr, nullptr);
997  flutter::DisplayListBuilder builder;
998  builder.DrawVertices(dl_vertices, flutter::DlBlendMode::kSrcOver, paint);
999  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1000 }
1001 
1002 TEST_P(DisplayListTest, CanDrawZeroWidthLine) {
1003  flutter::DisplayListBuilder builder;
1004  std::vector<flutter::DlStrokeCap> caps = {
1005  flutter::DlStrokeCap::kButt,
1006  flutter::DlStrokeCap::kRound,
1007  flutter::DlStrokeCap::kSquare,
1008  };
1009  flutter::DlPaint paint = //
1010  flutter::DlPaint() //
1011  .setColor(flutter::DlColor::kWhite()) //
1012  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
1013  .setStrokeWidth(0);
1014  flutter::DlPaint outline_paint = //
1015  flutter::DlPaint() //
1016  .setColor(flutter::DlColor::kYellow()) //
1017  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
1018  .setStrokeCap(flutter::DlStrokeCap::kSquare) //
1019  .setStrokeWidth(1);
1020  SkPath path = SkPath().addPoly({{150, 50}, {160, 50}}, false);
1021  for (auto cap : caps) {
1022  paint.setStrokeCap(cap);
1023  builder.DrawLine({50, 50}, {60, 50}, paint);
1024  builder.DrawRect({45, 45, 65, 55}, outline_paint);
1025  builder.DrawLine({100, 50}, {100, 50}, paint);
1026  if (cap != flutter::DlStrokeCap::kButt) {
1027  builder.DrawRect({95, 45, 105, 55}, outline_paint);
1028  }
1029  builder.DrawPath(path, paint);
1030  builder.DrawRect(path.getBounds().makeOutset(5, 5), outline_paint);
1031  builder.Translate(0, 150);
1032  }
1033  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1034 }
1035 
1036 TEST_P(DisplayListTest, CanDrawWithMatrixFilter) {
1037  auto boston = CreateTextureForFixture("boston.jpg");
1038 
1039  auto callback = [&]() {
1040  static int selected_matrix_type = 0;
1041  const char* matrix_type_names[] = {"Matrix", "Local Matrix"};
1042 
1043  static float ctm_translation[2] = {200, 200};
1044  static float ctm_scale[2] = {0.65, 0.65};
1045  static float ctm_skew[2] = {0, 0};
1046 
1047  static bool enable = true;
1048  static float translation[2] = {100, 100};
1049  static float scale[2] = {0.8, 0.8};
1050  static float skew[2] = {0.2, 0.2};
1051 
1052  static bool enable_savelayer = true;
1053 
1054  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1055  {
1056  ImGui::Combo("Filter type", &selected_matrix_type, matrix_type_names,
1057  sizeof(matrix_type_names) / sizeof(char*));
1058 
1059  ImGui::TextWrapped("Current Transform");
1060  ImGui::SliderFloat2("CTM Translation", ctm_translation, 0, 1000);
1061  ImGui::SliderFloat2("CTM Scale", ctm_scale, 0, 3);
1062  ImGui::SliderFloat2("CTM Skew", ctm_skew, -3, 3);
1063 
1064  ImGui::TextWrapped(
1065  "MatrixFilter and LocalMatrixFilter modify the CTM in the same way. "
1066  "The only difference is that MatrixFilter doesn't affect the effect "
1067  "transform, whereas LocalMatrixFilter does.");
1068  // Note: See this behavior in:
1069  // https://fiddle.skia.org/c/6cbb551ab36d06f163db8693972be954
1070  ImGui::Checkbox("Enable", &enable);
1071  ImGui::SliderFloat2("Filter Translation", translation, 0, 1000);
1072  ImGui::SliderFloat2("Filter Scale", scale, 0, 3);
1073  ImGui::SliderFloat2("Filter Skew", skew, -3, 3);
1074 
1075  ImGui::TextWrapped(
1076  "Rendering the filtered image within a layer can expose bounds "
1077  "issues. If the rendered image gets cut off when this setting is "
1078  "enabled, there's a coverage bug in the filter.");
1079  ImGui::Checkbox("Render in layer", &enable_savelayer);
1080  }
1081  ImGui::End();
1082 
1083  flutter::DisplayListBuilder builder;
1084  flutter::DlPaint paint;
1085 
1086  if (enable_savelayer) {
1087  builder.SaveLayer(nullptr, nullptr);
1088  }
1089  {
1090  auto content_scale = GetContentScale();
1091  builder.Scale(content_scale.x, content_scale.y);
1092 
1093  // Set the current transform
1094  auto ctm_matrix =
1095  SkMatrix::MakeAll(ctm_scale[0], ctm_skew[0], ctm_translation[0], //
1096  ctm_skew[1], ctm_scale[1], ctm_translation[1], //
1097  0, 0, 1);
1098  builder.Transform(ctm_matrix);
1099 
1100  // Set the matrix filter
1101  auto filter_matrix =
1102  SkMatrix::MakeAll(scale[0], skew[0], translation[0], //
1103  skew[1], scale[1], translation[1], //
1104  0, 0, 1);
1105 
1106  if (enable) {
1107  switch (selected_matrix_type) {
1108  case 0: {
1109  auto filter = flutter::DlMatrixImageFilter(
1110  filter_matrix, flutter::DlImageSampling::kLinear);
1111  paint.setImageFilter(&filter);
1112  break;
1113  }
1114  case 1: {
1115  auto internal_filter =
1116  flutter::DlBlurImageFilter(10, 10, flutter::DlTileMode::kDecal)
1117  .shared();
1118  auto filter = flutter::DlLocalMatrixImageFilter(filter_matrix,
1119  internal_filter);
1120  paint.setImageFilter(&filter);
1121  break;
1122  }
1123  }
1124  }
1125 
1126  builder.DrawImage(DlImageImpeller::Make(boston), {},
1127  flutter::DlImageSampling::kLinear, &paint);
1128  }
1129  if (enable_savelayer) {
1130  builder.Restore();
1131  }
1132 
1133  return builder.Build();
1134  };
1135 
1136  ASSERT_TRUE(OpenPlaygroundHere(callback));
1137 }
1138 
1139 TEST_P(DisplayListTest, CanDrawWithMatrixFilterWhenSavingLayer) {
1140  auto callback = [&]() {
1141  static float translation[2] = {0, 0};
1142  static bool enable_save_layer = true;
1143 
1144  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1145  ImGui::SliderFloat2("Translation", translation, -130, 130);
1146  ImGui::Checkbox("Enable save layer", &enable_save_layer);
1147  ImGui::End();
1148 
1149  flutter::DisplayListBuilder builder;
1150  builder.Save();
1151  builder.Scale(2.0, 2.0);
1152  flutter::DlPaint paint;
1153  paint.setColor(flutter::DlColor::kYellow());
1154  builder.DrawRect(SkRect::MakeWH(300, 300), paint);
1155  paint.setStrokeWidth(1.0);
1156  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
1157  paint.setColor(flutter::DlColor::kBlack().withAlpha(0x80));
1158  builder.DrawLine(SkPoint::Make(150, 0), SkPoint::Make(150, 300), paint);
1159  builder.DrawLine(SkPoint::Make(0, 150), SkPoint::Make(300, 150), paint);
1160 
1161  flutter::DlPaint save_paint;
1162  SkRect bounds = SkRect::MakeXYWH(100, 100, 100, 100);
1163  SkMatrix translate_matrix =
1164  SkMatrix::Translate(translation[0], translation[1]);
1165  if (enable_save_layer) {
1166  auto filter = flutter::DlMatrixImageFilter(
1167  translate_matrix, flutter::DlImageSampling::kNearestNeighbor);
1168  save_paint.setImageFilter(filter.shared());
1169  builder.SaveLayer(&bounds, &save_paint);
1170  } else {
1171  builder.Save();
1172  builder.Transform(translate_matrix);
1173  }
1174 
1175  SkMatrix filter_matrix = SkMatrix::I();
1176  filter_matrix.postTranslate(-150, -150);
1177  filter_matrix.postScale(0.2f, 0.2f);
1178  filter_matrix.postTranslate(150, 150);
1179  auto filter = flutter::DlMatrixImageFilter(
1180  filter_matrix, flutter::DlImageSampling::kNearestNeighbor);
1181 
1182  save_paint.setImageFilter(filter.shared());
1183 
1184  builder.SaveLayer(&bounds, &save_paint);
1185  flutter::DlPaint paint2;
1186  paint2.setColor(flutter::DlColor::kBlue());
1187  builder.DrawRect(bounds, paint2);
1188  builder.Restore();
1189  builder.Restore();
1190  return builder.Build();
1191  };
1192 
1193  ASSERT_TRUE(OpenPlaygroundHere(callback));
1194 }
1195 
1196 TEST_P(DisplayListTest, CanDrawRectWithLinearToSrgbColorFilter) {
1197  flutter::DlPaint paint;
1198  paint.setColor(flutter::DlColor(0xFF2196F3).withAlpha(128));
1199  flutter::DisplayListBuilder builder;
1200  paint.setColorFilter(
1201  flutter::DlLinearToSrgbGammaColorFilter::kInstance.get());
1202  builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), paint);
1203  builder.Translate(0, 200);
1204 
1205  paint.setColorFilter(
1206  flutter::DlSrgbToLinearGammaColorFilter::kInstance.get());
1207  builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), paint);
1208 
1209  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1210 }
1211 
1212 TEST_P(DisplayListTest, CanDrawPaintWithColorSource) {
1213  const flutter::DlColor colors[2] = {
1214  flutter::DlColor(0xFFF44336),
1215  flutter::DlColor(0xFF2196F3),
1216  };
1217  const float stops[2] = {0.0, 1.0};
1218  flutter::DlPaint paint;
1219  flutter::DisplayListBuilder builder;
1220  auto clip_bounds = SkRect::MakeWH(300.0, 300.0);
1221  builder.Save();
1222  builder.Translate(100, 100);
1223  builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
1224  auto linear =
1225  flutter::DlColorSource::MakeLinear({0.0, 0.0}, {100.0, 100.0}, 2, colors,
1226  stops, flutter::DlTileMode::kRepeat);
1227  paint.setColorSource(linear);
1228  builder.DrawPaint(paint);
1229  builder.Restore();
1230 
1231  builder.Save();
1232  builder.Translate(500, 100);
1233  builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
1234  auto radial = flutter::DlColorSource::MakeRadial(
1235  {100.0, 100.0}, 100.0, 2, colors, stops, flutter::DlTileMode::kRepeat);
1236  paint.setColorSource(radial);
1237  builder.DrawPaint(paint);
1238  builder.Restore();
1239 
1240  builder.Save();
1241  builder.Translate(100, 500);
1242  builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
1243  auto sweep =
1244  flutter::DlColorSource::MakeSweep({100.0, 100.0}, 180.0, 270.0, 2, colors,
1245  stops, flutter::DlTileMode::kRepeat);
1246  paint.setColorSource(sweep);
1247  builder.DrawPaint(paint);
1248  builder.Restore();
1249 
1250  builder.Save();
1251  builder.Translate(500, 500);
1252  builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
1253  auto texture = CreateTextureForFixture("table_mountain_nx.png");
1254  auto image = std::make_shared<flutter::DlImageColorSource>(
1255  DlImageImpeller::Make(texture), flutter::DlTileMode::kRepeat,
1256  flutter::DlTileMode::kRepeat);
1257  paint.setColorSource(image);
1258  builder.DrawPaint(paint);
1259  builder.Restore();
1260 
1261  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1262 }
1263 
1264 TEST_P(DisplayListTest, CanBlendDstOverAndDstCorrectly) {
1265  flutter::DisplayListBuilder builder;
1266 
1267  {
1268  builder.SaveLayer(nullptr, nullptr);
1269  builder.Translate(100, 100);
1270  flutter::DlPaint paint;
1271  paint.setColor(flutter::DlColor::kRed());
1272  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1273  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1274  paint.setBlendMode(flutter::DlBlendMode::kSrcOver);
1275  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1276  builder.Restore();
1277  }
1278  {
1279  builder.SaveLayer(nullptr, nullptr);
1280  builder.Translate(300, 100);
1281  flutter::DlPaint paint;
1282  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1283  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1284  paint.setColor(flutter::DlColor::kRed());
1285  paint.setBlendMode(flutter::DlBlendMode::kDstOver);
1286  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1287  builder.Restore();
1288  }
1289  {
1290  builder.SaveLayer(nullptr, nullptr);
1291  builder.Translate(100, 300);
1292  flutter::DlPaint paint;
1293  paint.setColor(flutter::DlColor::kRed());
1294  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1295  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1296  paint.setBlendMode(flutter::DlBlendMode::kSrc);
1297  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1298  builder.Restore();
1299  }
1300  {
1301  builder.SaveLayer(nullptr, nullptr);
1302  builder.Translate(300, 300);
1303  flutter::DlPaint paint;
1304  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1305  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1306  paint.setColor(flutter::DlColor::kRed());
1307  paint.setBlendMode(flutter::DlBlendMode::kDst);
1308  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1309  builder.Restore();
1310  }
1311 
1312  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1313 }
1314 
1315 TEST_P(DisplayListTest, CanDrawCorrectlyWithColorFilterAndImageFilter) {
1316  flutter::DisplayListBuilder builder;
1317  const float green_color_matrix[20] = {
1318  0, 0, 0, 0, 0, //
1319  0, 0, 0, 0, 1, //
1320  0, 0, 0, 0, 0, //
1321  0, 0, 0, 1, 0, //
1322  };
1323  const float blue_color_matrix[20] = {
1324  0, 0, 0, 0, 0, //
1325  0, 0, 0, 0, 0, //
1326  0, 0, 0, 0, 1, //
1327  0, 0, 0, 1, 0, //
1328  };
1329  auto green_color_filter =
1330  std::make_shared<flutter::DlMatrixColorFilter>(green_color_matrix);
1331  auto blue_color_filter =
1332  std::make_shared<flutter::DlMatrixColorFilter>(blue_color_matrix);
1333  auto blue_image_filter =
1334  std::make_shared<flutter::DlColorFilterImageFilter>(blue_color_filter);
1335 
1336  flutter::DlPaint paint;
1337  paint.setColor(flutter::DlColor::kRed());
1338  paint.setColorFilter(green_color_filter);
1339  paint.setImageFilter(blue_image_filter);
1340  builder.DrawRect(SkRect::MakeLTRB(100, 100, 500, 500), paint);
1341  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1342 }
1343 
1344 TEST_P(DisplayListTest, MaskBlursApplyCorrectlyToColorSources) {
1345  auto blur_filter = std::make_shared<flutter::DlBlurMaskFilter>(
1346  flutter::DlBlurStyle::kNormal, 10);
1347 
1348  flutter::DisplayListBuilder builder;
1349 
1350  std::array<flutter::DlColor, 2> colors = {flutter::DlColor::kBlue(),
1351  flutter::DlColor::kGreen()};
1352  std::array<float, 2> stops = {0, 1};
1353  std::array<std::shared_ptr<flutter::DlColorSource>, 2> color_sources = {
1354  std::make_shared<flutter::DlColorColorSource>(flutter::DlColor::kWhite()),
1355  flutter::DlColorSource::MakeLinear(
1356  SkPoint::Make(0, 0), SkPoint::Make(100, 50), 2, colors.data(),
1357  stops.data(), flutter::DlTileMode::kClamp)};
1358 
1359  int offset = 100;
1360  for (const auto& color_source : color_sources) {
1361  flutter::DlPaint paint;
1362  paint.setColorSource(color_source);
1363  paint.setMaskFilter(blur_filter);
1364 
1365  paint.setDrawStyle(flutter::DlDrawStyle::kFill);
1366  builder.DrawRRect(
1367  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, offset, 100, 50), 30, 30),
1368  paint);
1369  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
1370  paint.setStrokeWidth(10);
1371  builder.DrawRRect(
1372  SkRRect::MakeRectXY(SkRect::MakeXYWH(300, offset, 100, 50), 30, 30),
1373  paint);
1374 
1375  offset += 100;
1376  }
1377 
1378  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1379 }
1380 
1381 TEST_P(DisplayListTest, DrawVerticesSolidColorTrianglesWithoutIndices) {
1382  // Use negative coordinates and then scale the transform by -1, -1 to make
1383  // sure coverage is taking the transform into account.
1384  std::vector<SkPoint> positions = {SkPoint::Make(-100, -300),
1385  SkPoint::Make(-200, -100),
1386  SkPoint::Make(-300, -300)};
1387  std::vector<flutter::DlColor> colors = {flutter::DlColor::kWhite(),
1388  flutter::DlColor::kGreen(),
1389  flutter::DlColor::kWhite()};
1390 
1391  auto vertices = flutter::DlVertices::Make(
1392  flutter::DlVertexMode::kTriangles, 3, positions.data(),
1393  /*texture_coordinates=*/nullptr, colors.data());
1394 
1395  flutter::DisplayListBuilder builder;
1396  flutter::DlPaint paint;
1397 
1398  paint.setColor(flutter::DlColor::kRed().modulateOpacity(0.5));
1399  builder.Scale(-1, -1);
1400  builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
1401 
1402  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1403 }
1404 
1405 TEST_P(DisplayListTest, DrawVerticesLinearGradientWithoutIndices) {
1406  std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
1407  SkPoint::Make(200, 100),
1408  SkPoint::Make(300, 300)};
1409 
1410  auto vertices = flutter::DlVertices::Make(
1411  flutter::DlVertexMode::kTriangles, 3, positions.data(),
1412  /*texture_coordinates=*/nullptr, /*colors=*/nullptr);
1413 
1414  std::vector<flutter::DlColor> colors = {flutter::DlColor::kBlue(),
1415  flutter::DlColor::kRed()};
1416  const float stops[2] = {0.0, 1.0};
1417 
1418  auto linear = flutter::DlColorSource::MakeLinear(
1419  {100.0, 100.0}, {300.0, 300.0}, 2, colors.data(), stops,
1420  flutter::DlTileMode::kRepeat);
1421 
1422  flutter::DisplayListBuilder builder;
1423  flutter::DlPaint paint;
1424 
1425  paint.setColorSource(linear);
1426  builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
1427 
1428  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1429 }
1430 
1431 TEST_P(DisplayListTest, DrawVerticesLinearGradientWithTextureCoordinates) {
1432  std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
1433  SkPoint::Make(200, 100),
1434  SkPoint::Make(300, 300)};
1435  std::vector<SkPoint> texture_coordinates = {SkPoint::Make(300, 100),
1436  SkPoint::Make(100, 200),
1437  SkPoint::Make(300, 300)};
1438 
1439  auto vertices = flutter::DlVertices::Make(
1440  flutter::DlVertexMode::kTriangles, 3, positions.data(),
1441  texture_coordinates.data(), /*colors=*/nullptr);
1442 
1443  std::vector<flutter::DlColor> colors = {flutter::DlColor::kBlue(),
1444  flutter::DlColor::kRed()};
1445  const float stops[2] = {0.0, 1.0};
1446 
1447  auto linear = flutter::DlColorSource::MakeLinear(
1448  {100.0, 100.0}, {300.0, 300.0}, 2, colors.data(), stops,
1449  flutter::DlTileMode::kRepeat);
1450 
1451  flutter::DisplayListBuilder builder;
1452  flutter::DlPaint paint;
1453 
1454  paint.setColorSource(linear);
1455  builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
1456 
1457  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1458 }
1459 
1460 TEST_P(DisplayListTest, DrawVerticesImageSourceWithTextureCoordinates) {
1461  auto texture = CreateTextureForFixture("embarcadero.jpg");
1462  auto dl_image = DlImageImpeller::Make(texture);
1463  std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
1464  SkPoint::Make(200, 100),
1465  SkPoint::Make(300, 300)};
1466  std::vector<SkPoint> texture_coordinates = {
1467  SkPoint::Make(0, 0), SkPoint::Make(100, 200), SkPoint::Make(200, 100)};
1468 
1469  auto vertices = flutter::DlVertices::Make(
1470  flutter::DlVertexMode::kTriangles, 3, positions.data(),
1471  texture_coordinates.data(), /*colors=*/nullptr);
1472 
1473  flutter::DisplayListBuilder builder;
1474  flutter::DlPaint paint;
1475 
1476  auto image_source = flutter::DlImageColorSource(
1477  dl_image, flutter::DlTileMode::kRepeat, flutter::DlTileMode::kRepeat);
1478 
1479  paint.setColorSource(&image_source);
1480  builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
1481 
1482  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1483 }
1484 
1486  DrawVerticesImageSourceWithTextureCoordinatesAndColorBlending) {
1487  auto texture = CreateTextureForFixture("embarcadero.jpg");
1488  auto dl_image = DlImageImpeller::Make(texture);
1489  std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
1490  SkPoint::Make(200, 100),
1491  SkPoint::Make(300, 300)};
1492  std::vector<flutter::DlColor> colors = {flutter::DlColor::kWhite(),
1493  flutter::DlColor::kGreen(),
1494  flutter::DlColor::kWhite()};
1495  std::vector<SkPoint> texture_coordinates = {
1496  SkPoint::Make(0, 0), SkPoint::Make(100, 200), SkPoint::Make(200, 100)};
1497 
1498  auto vertices = flutter::DlVertices::Make(
1499  flutter::DlVertexMode::kTriangles, 3, positions.data(),
1500  texture_coordinates.data(), colors.data());
1501 
1502  flutter::DisplayListBuilder builder;
1503  flutter::DlPaint paint;
1504 
1505  auto image_source = flutter::DlImageColorSource(
1506  dl_image, flutter::DlTileMode::kRepeat, flutter::DlTileMode::kRepeat);
1507 
1508  paint.setColorSource(&image_source);
1509  builder.DrawVertices(vertices, flutter::DlBlendMode::kModulate, paint);
1510 
1511  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1512 }
1513 
1514 TEST_P(DisplayListTest, DrawVerticesSolidColorTrianglesWithIndices) {
1515  std::vector<SkPoint> positions = {
1516  SkPoint::Make(100, 300), SkPoint::Make(200, 100), SkPoint::Make(300, 300),
1517  SkPoint::Make(200, 500)};
1518  std::vector<uint16_t> indices = {0, 1, 2, 0, 2, 3};
1519 
1520  auto vertices = flutter::DlVertices::Make(
1521  flutter::DlVertexMode::kTriangles, 6, positions.data(),
1522  /*texture_coordinates=*/nullptr, /*colors=*/nullptr, 6, indices.data());
1523 
1524  flutter::DisplayListBuilder builder;
1525  flutter::DlPaint paint;
1526 
1527  paint.setColor(flutter::DlColor::kWhite());
1528  builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
1529 
1530  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1531 }
1532 
1533 TEST_P(DisplayListTest, DrawVerticesPremultipliesColors) {
1534  std::vector<SkPoint> positions = {
1535  SkPoint::Make(100, 300), SkPoint::Make(200, 100), SkPoint::Make(300, 300),
1536  SkPoint::Make(200, 500)};
1537  auto color = flutter::DlColor::kBlue().withAlpha(0x99);
1538  std::vector<uint16_t> indices = {0, 1, 2, 0, 2, 3};
1539  std::vector<flutter::DlColor> colors = {color, color, color, color};
1540 
1541  auto vertices = flutter::DlVertices::Make(
1542  flutter::DlVertexMode::kTriangles, 6, positions.data(),
1543  /*texture_coordinates=*/nullptr, colors.data(), 6, indices.data());
1544 
1545  flutter::DisplayListBuilder builder;
1546  flutter::DlPaint paint;
1547  paint.setBlendMode(flutter::DlBlendMode::kSrcOver);
1548  paint.setColor(flutter::DlColor::kRed());
1549 
1550  builder.DrawRect(SkRect::MakeLTRB(0, 0, 400, 400), paint);
1551  builder.DrawVertices(vertices, flutter::DlBlendMode::kDst, paint);
1552 
1553  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1554 }
1555 
1556 TEST_P(DisplayListTest, DrawShapes) {
1557  flutter::DisplayListBuilder builder;
1558  std::vector<flutter::DlStrokeJoin> joins = {
1559  flutter::DlStrokeJoin::kBevel,
1560  flutter::DlStrokeJoin::kRound,
1561  flutter::DlStrokeJoin::kMiter,
1562  };
1563  flutter::DlPaint paint = //
1564  flutter::DlPaint() //
1565  .setColor(flutter::DlColor::kWhite()) //
1566  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1567  .setStrokeWidth(10);
1568  flutter::DlPaint stroke_paint = //
1569  flutter::DlPaint() //
1570  .setColor(flutter::DlColor::kWhite()) //
1571  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
1572  .setStrokeWidth(10);
1573  SkPath path = SkPath().addPoly({{150, 50}, {160, 50}}, false);
1574 
1575  builder.Translate(300, 50);
1576  builder.Scale(0.8, 0.8);
1577  for (auto join : joins) {
1578  paint.setStrokeJoin(join);
1579  stroke_paint.setStrokeJoin(join);
1580  builder.DrawRect(SkRect::MakeXYWH(0, 0, 100, 100), paint);
1581  builder.DrawRect(SkRect::MakeXYWH(0, 150, 100, 100), stroke_paint);
1582  builder.DrawRRect(
1583  SkRRect::MakeRectXY(SkRect::MakeXYWH(150, 0, 100, 100), 30, 30), paint);
1584  builder.DrawRRect(
1585  SkRRect::MakeRectXY(SkRect::MakeXYWH(150, 150, 100, 100), 30, 30),
1586  stroke_paint);
1587  builder.DrawCircle({350, 50}, 50, paint);
1588  builder.DrawCircle({350, 200}, 50, stroke_paint);
1589  builder.Translate(0, 300);
1590  }
1591  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1592 }
1593 
1594 TEST_P(DisplayListTest, ClipDrawRRectWithNonCircularRadii) {
1595  flutter::DisplayListBuilder builder;
1596 
1597  flutter::DlPaint fill_paint = //
1598  flutter::DlPaint() //
1599  .setColor(flutter::DlColor::kBlue()) //
1600  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1601  .setStrokeWidth(10);
1602  flutter::DlPaint stroke_paint = //
1603  flutter::DlPaint() //
1604  .setColor(flutter::DlColor::kGreen()) //
1605  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
1606  .setStrokeWidth(10);
1607 
1608  builder.DrawRRect(
1609  SkRRect::MakeRectXY(SkRect::MakeXYWH(500, 100, 300, 300), 120, 40),
1610  fill_paint);
1611  builder.DrawRRect(
1612  SkRRect::MakeRectXY(SkRect::MakeXYWH(500, 100, 300, 300), 120, 40),
1613  stroke_paint);
1614 
1615  builder.DrawRRect(
1616  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 500, 300, 300), 40, 120),
1617  fill_paint);
1618  builder.DrawRRect(
1619  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 500, 300, 300), 40, 120),
1620  stroke_paint);
1621 
1622  flutter::DlPaint reference_paint = //
1623  flutter::DlPaint() //
1624  .setColor(flutter::DlColor::kMidGrey()) //
1625  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1626  .setStrokeWidth(10);
1627 
1628  builder.DrawRRect(
1629  SkRRect::MakeRectXY(SkRect::MakeXYWH(500, 500, 300, 300), 40, 40),
1630  reference_paint);
1631  builder.DrawRRect(
1632  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 100, 300, 300), 120, 120),
1633  reference_paint);
1634 
1635  flutter::DlPaint clip_fill_paint = //
1636  flutter::DlPaint() //
1637  .setColor(flutter::DlColor::kCyan()) //
1638  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1639  .setStrokeWidth(10);
1640 
1641  builder.Save();
1642  builder.ClipRRect(
1643  SkRRect::MakeRectXY(SkRect::MakeXYWH(900, 100, 300, 300), 120, 40));
1644  builder.DrawPaint(clip_fill_paint);
1645  builder.Restore();
1646 
1647  builder.Save();
1648  builder.ClipRRect(
1649  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 900, 300, 300), 40, 120));
1650  builder.DrawPaint(clip_fill_paint);
1651  builder.Restore();
1652 
1653  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1654 }
1655 
1656 TEST_P(DisplayListTest, DrawVerticesBlendModes) {
1657  std::vector<const char*> blend_mode_names;
1658  std::vector<flutter::DlBlendMode> blend_mode_values;
1659  {
1660  const std::vector<std::tuple<const char*, flutter::DlBlendMode>> blends = {
1661  // Pipeline blends (Porter-Duff alpha compositing)
1662  {"Clear", flutter::DlBlendMode::kClear},
1663  {"Source", flutter::DlBlendMode::kSrc},
1664  {"Destination", flutter::DlBlendMode::kDst},
1665  {"SourceOver", flutter::DlBlendMode::kSrcOver},
1666  {"DestinationOver", flutter::DlBlendMode::kDstOver},
1667  {"SourceIn", flutter::DlBlendMode::kSrcIn},
1668  {"DestinationIn", flutter::DlBlendMode::kDstIn},
1669  {"SourceOut", flutter::DlBlendMode::kSrcOut},
1670  {"DestinationOut", flutter::DlBlendMode::kDstOut},
1671  {"SourceATop", flutter::DlBlendMode::kSrcATop},
1672  {"DestinationATop", flutter::DlBlendMode::kDstATop},
1673  {"Xor", flutter::DlBlendMode::kXor},
1674  {"Plus", flutter::DlBlendMode::kPlus},
1675  {"Modulate", flutter::DlBlendMode::kModulate},
1676  // Advanced blends (color component blends)
1677  {"Screen", flutter::DlBlendMode::kScreen},
1678  {"Overlay", flutter::DlBlendMode::kOverlay},
1679  {"Darken", flutter::DlBlendMode::kDarken},
1680  {"Lighten", flutter::DlBlendMode::kLighten},
1681  {"ColorDodge", flutter::DlBlendMode::kColorDodge},
1682  {"ColorBurn", flutter::DlBlendMode::kColorBurn},
1683  {"HardLight", flutter::DlBlendMode::kHardLight},
1684  {"SoftLight", flutter::DlBlendMode::kSoftLight},
1685  {"Difference", flutter::DlBlendMode::kDifference},
1686  {"Exclusion", flutter::DlBlendMode::kExclusion},
1687  {"Multiply", flutter::DlBlendMode::kMultiply},
1688  {"Hue", flutter::DlBlendMode::kHue},
1689  {"Saturation", flutter::DlBlendMode::kSaturation},
1690  {"Color", flutter::DlBlendMode::kColor},
1691  {"Luminosity", flutter::DlBlendMode::kLuminosity},
1692  };
1693  assert(blends.size() ==
1694  static_cast<size_t>(flutter::DlBlendMode::kLastMode) + 1);
1695  for (const auto& [name, mode] : blends) {
1696  blend_mode_names.push_back(name);
1697  blend_mode_values.push_back(mode);
1698  }
1699  }
1700 
1701  auto callback = [&]() {
1702  static int current_blend_index = 3;
1703  static float dst_alpha = 1;
1704  static float src_alpha = 1;
1705  static float color0[4] = {1.0f, 0.0f, 0.0f, 1.0f};
1706  static float color1[4] = {0.0f, 1.0f, 0.0f, 1.0f};
1707  static float color2[4] = {0.0f, 0.0f, 1.0f, 1.0f};
1708  static float src_color[4] = {1.0f, 1.0f, 1.0f, 1.0f};
1709 
1710  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1711  {
1712  ImGui::ListBox("Blending mode", &current_blend_index,
1713  blend_mode_names.data(), blend_mode_names.size());
1714  ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1);
1715  ImGui::ColorEdit4("Color A", color0);
1716  ImGui::ColorEdit4("Color B", color1);
1717  ImGui::ColorEdit4("Color C", color2);
1718  ImGui::ColorEdit4("Source Color", src_color);
1719  ImGui::SliderFloat("Destination alpha", &dst_alpha, 0, 1);
1720  }
1721  ImGui::End();
1722 
1723  std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
1724  SkPoint::Make(200, 100),
1725  SkPoint::Make(300, 300)};
1726  std::vector<flutter::DlColor> colors = {
1727  toColor(color0).modulateOpacity(dst_alpha),
1728  toColor(color1).modulateOpacity(dst_alpha),
1729  toColor(color2).modulateOpacity(dst_alpha)};
1730 
1731  auto vertices = flutter::DlVertices::Make(
1732  flutter::DlVertexMode::kTriangles, 3, positions.data(),
1733  /*texture_coordinates=*/nullptr, colors.data());
1734 
1735  flutter::DisplayListBuilder builder;
1736  flutter::DlPaint paint;
1737 
1738  paint.setColor(toColor(src_color).modulateOpacity(src_alpha));
1739  builder.DrawVertices(vertices, blend_mode_values[current_blend_index],
1740  paint);
1741  return builder.Build();
1742  };
1743 
1744  ASSERT_TRUE(OpenPlaygroundHere(callback));
1745 }
1746 
1747 template <typename Contents>
1748 static std::optional<Rect> GetCoverageOfFirstEntity(const Picture& picture) {
1749  std::optional<Rect> coverage;
1750  picture.pass->IterateAllEntities([&coverage](Entity& entity) {
1751  if (std::static_pointer_cast<Contents>(entity.GetContents())) {
1752  auto contents = std::static_pointer_cast<Contents>(entity.GetContents());
1753  Entity entity;
1754  coverage = contents->GetCoverage(entity);
1755  return false;
1756  }
1757  return true;
1758  });
1759  return coverage;
1760 }
1761 
1762 TEST(DisplayListTest, RRectBoundsComputation) {
1763  SkRRect rrect = SkRRect::MakeRectXY(SkRect::MakeLTRB(0, 0, 100, 100), 4, 4);
1764  SkPath path = SkPath().addRRect(rrect);
1765 
1766  flutter::DlPaint paint;
1767  flutter::DisplayListBuilder builder;
1768 
1769  builder.DrawPath(path, paint);
1770  auto display_list = builder.Build();
1771 
1772  DlDispatcher dispatcher;
1773  display_list->Dispatch(dispatcher);
1774  auto picture = dispatcher.EndRecordingAsPicture();
1775 
1776  std::optional<Rect> coverage =
1777  GetCoverageOfFirstEntity<SolidColorContents>(picture);
1778 
1779  // Validate that the RRect coverage is _exactly_ the same as the input rect.
1780  ASSERT_TRUE(coverage.has_value());
1781  ASSERT_EQ(coverage.value_or(Rect::MakeMaximum()),
1782  Rect::MakeLTRB(0, 0, 100, 100));
1783 }
1784 
1785 TEST(DisplayListTest, CircleBoundsComputation) {
1786  SkPath path = SkPath().addCircle(0, 0, 5);
1787 
1788  flutter::DlPaint paint;
1789  flutter::DisplayListBuilder builder;
1790 
1791  builder.DrawPath(path, paint);
1792  auto display_list = builder.Build();
1793 
1794  DlDispatcher dispatcher;
1795  display_list->Dispatch(dispatcher);
1796  auto picture = dispatcher.EndRecordingAsPicture();
1797 
1798  std::optional<Rect> coverage =
1799  GetCoverageOfFirstEntity<SolidColorContents>(picture);
1800 
1801  ASSERT_TRUE(coverage.has_value());
1802  ASSERT_EQ(coverage.value_or(Rect::MakeMaximum()),
1803  Rect::MakeLTRB(-5, -5, 5, 5));
1804 }
1805 
1806 #ifdef IMPELLER_ENABLE_3D
1807 TEST_P(DisplayListTest, SceneColorSource) {
1808  // Load up the scene.
1809  auto mapping =
1810  flutter::testing::OpenFixtureAsMapping("flutter_logo_baked.glb.ipscene");
1811  ASSERT_NE(mapping, nullptr);
1812 
1813  std::shared_ptr<scene::Node> gltf_scene =
1815  *mapping, *GetContext()->GetResourceAllocator());
1816  ASSERT_NE(gltf_scene, nullptr);
1817 
1818  flutter::DisplayListBuilder builder;
1819 
1820  auto color_source = std::make_shared<flutter::DlSceneColorSource>(
1821  gltf_scene,
1822  Matrix::MakePerspective(Degrees(45), GetWindowSize(), 0.1, 1000) *
1823  Matrix::MakeLookAt({3, 2, -5}, {0, 0, 0}, {0, 1, 0}));
1824 
1825  flutter::DlPaint paint = flutter::DlPaint().setColorSource(color_source);
1826 
1827  builder.DrawPaint(paint);
1828 
1829  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1830 }
1831 #endif
1832 
1833 } // namespace testing
1834 } // namespace impeller
impeller::Picture::pass
std::unique_ptr< EntityPass > pass
Definition: picture.h:21
impeller::DlDispatcher::save
void save() override
Definition: dl_dispatcher.cc:621
point.h
impeller::DlDispatcher::scale
void scale(SkScalar sx, SkScalar sy) override
Definition: dl_dispatcher.cc:645
impeller::TPoint::y
Type y
Definition: point.h:26
impeller::Scalar
float Scalar
Definition: scalar.h:18
impeller::DlDispatcher
Definition: dl_dispatcher.h:14
impeller::Color::Red
static constexpr Color Red()
Definition: color.h:264
impeller::Entity::GetTransform
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition: entity.cc:49
solid_color_contents.h
impeller::Color
Definition: color.h:124
impeller::DlDispatcher::EndRecordingAsPicture
Picture EndRecordingAsPicture()
Definition: dl_dispatcher.cc:1118
dl_dispatcher.h
impeller::testing::DisplayListTest
DlPlayground DisplayListTest
Definition: dl_unittests.cc:46
impeller::DlDispatcher::transformFullPerspective
void transformFullPerspective(SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt, SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt, SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt, SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) override
Definition: dl_dispatcher.cc:677
impeller::kPi
constexpr float kPi
Definition: constants.h:26
IMPELLER_PLAYGROUND_POINT
#define IMPELLER_PLAYGROUND_POINT(default_position, radius, color)
Definition: widgets.h:15
impeller::Vector3::x
Scalar x
Definition: vector.h:23
impeller::DlDispatcher::restore
void restore() override
Definition: dl_dispatcher.cc:635
impeller::DlImageImpeller::Make
static sk_sp< DlImageImpeller > Make(std::shared_ptr< Texture > texture, OwningContext owning_context=OwningContext::kIO)
Definition: dl_image_impeller.cc:12
impeller::DlDispatcher::drawDisplayList
void drawDisplayList(const sk_sp< flutter::DisplayList > display_list, SkScalar opacity) override
Definition: dl_dispatcher.cc:983
impeller::Matrix::MakeLookAt
static constexpr Matrix MakeLookAt(Vector3 position, Vector3 target, Vector3 up)
Definition: matrix.h:494
impeller::Matrix::MakePerspective
static constexpr Matrix MakePerspective(Radians fov_y, Scalar aspect_ratio, Scalar z_near, Scalar z_far)
Definition: matrix.h:468
dl_playground.h
impeller::testing::INSTANTIATE_PLAYGROUND_SUITE
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
impeller::kColor
@ kColor
Definition: geometry.h:36
impeller::Entity
Definition: entity.h:21
impeller::testing::toColor
flutter::DlColor toColor(const float *components)
Definition: dl_unittests.cc:41
impeller::Picture
Definition: picture.h:20
impeller::Point
TPoint< Scalar > Point
Definition: point.h:308
node.h
impeller::Color::ToIColor
static constexpr uint32_t ToIColor(Color color)
Convert this color to a 32-bit representation.
Definition: color.h:161
impeller::Matrix::GetScale
constexpr Vector3 GetScale() const
Definition: matrix.h:303
widgets.h
impeller::testing::TEST
TEST(CanvasRecorder, Save)
Definition: canvas_recorder_unittests.cc:61
impeller::Entity::GetContents
const std::shared_ptr< Contents > & GetContents() const
Definition: entity.cc:85
impeller::Color::White
static constexpr Color White()
Definition: color.h:256
clip_contents.h
impeller::DlDispatcher::drawShadow
void drawShadow(const SkPath &path, const flutter::DlColor color, const SkScalar elevation, bool transparent_occluder, SkScalar dpr) override
Definition: dl_dispatcher.cc:1060
impeller::TPoint::x
Type x
Definition: point.h:25
scalar.h
constants.h
impeller::TPoint< Scalar >
impeller::TRect< Scalar >::MakeMaximum
constexpr static TRect MakeMaximum()
Definition: rect.h:72
impeller::ScalarNearlyEqual
constexpr bool ScalarNearlyEqual(Scalar x, Scalar y, Scalar tolerance=kEhCloseEnough)
Definition: scalar.h:30
impeller::Degrees
Definition: scalar.h:46
impeller::TRect< Scalar >::MakeLTRB
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:27
impeller::testing::TEST_P
TEST_P(AiksTest, CanRenderLinearGradientClamp)
Definition: aiks_gradient_unittests.cc:48
impeller::scene::Node::MakeFromFlatbuffer
static std::shared_ptr< Node > MakeFromFlatbuffer(const fml::Mapping &ipscene_mapping, Allocator &allocator)
Definition: node.cc:48
IMPELLER_PLAYGROUND_LINE
#define IMPELLER_PLAYGROUND_LINE(default_position_a, default_position_b, radius, color_a, color_b)
Definition: widgets.h:56
impeller
Definition: aiks_context.cc:10
impeller::testing::GetCoverageOfFirstEntity
static std::optional< Rect > GetCoverageOfFirstEntity(const Picture &picture)
Definition: dl_unittests.cc:1748
solid_rrect_blur_contents.h
impeller::DlDispatcher::translate
void translate(SkScalar tx, SkScalar ty) override
Definition: dl_dispatcher.cc:640
dl_image_impeller.h
impeller::DlPlayground
Definition: dl_playground.h:15