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