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 // Draw a hexagon using triangle fan
977 TEST_P(DisplayListTest, CanConvertTriangleFanToTriangles) {
978  constexpr Scalar hexagon_radius = 125;
979  auto hex_start = Point(200.0, -hexagon_radius + 200.0);
980  auto center_to_flat = 1.73 / 2 * hexagon_radius;
981 
982  // clang-format off
983  std::vector<SkPoint> vertices = {
984  SkPoint::Make(hex_start.x, hex_start.y),
985  SkPoint::Make(hex_start.x + center_to_flat, hex_start.y + 0.5 * hexagon_radius),
986  SkPoint::Make(hex_start.x + center_to_flat, hex_start.y + 1.5 * hexagon_radius),
987  SkPoint::Make(hex_start.x + center_to_flat, hex_start.y + 1.5 * hexagon_radius),
988  SkPoint::Make(hex_start.x, hex_start.y + 2 * hexagon_radius),
989  SkPoint::Make(hex_start.x, hex_start.y + 2 * hexagon_radius),
990  SkPoint::Make(hex_start.x - center_to_flat, hex_start.y + 1.5 * hexagon_radius),
991  SkPoint::Make(hex_start.x - center_to_flat, hex_start.y + 1.5 * hexagon_radius),
992  SkPoint::Make(hex_start.x - center_to_flat, hex_start.y + 0.5 * hexagon_radius)
993  };
994  // clang-format on
995  auto paint = flutter::DlPaint(flutter::DlColor::kDarkGrey());
996  auto dl_vertices = flutter::DlVertices::Make(
997  flutter::DlVertexMode::kTriangleFan, vertices.size(), vertices.data(),
998  nullptr, nullptr);
999  flutter::DisplayListBuilder builder;
1000  builder.DrawVertices(dl_vertices, flutter::DlBlendMode::kSrcOver, paint);
1001  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1002 }
1003 
1004 TEST_P(DisplayListTest, CanDrawZeroWidthLine) {
1005  flutter::DisplayListBuilder builder;
1006  std::vector<flutter::DlStrokeCap> caps = {
1007  flutter::DlStrokeCap::kButt,
1008  flutter::DlStrokeCap::kRound,
1009  flutter::DlStrokeCap::kSquare,
1010  };
1011  flutter::DlPaint paint = //
1012  flutter::DlPaint() //
1013  .setColor(flutter::DlColor::kWhite()) //
1014  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
1015  .setStrokeWidth(0);
1016  flutter::DlPaint outline_paint = //
1017  flutter::DlPaint() //
1018  .setColor(flutter::DlColor::kYellow()) //
1019  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
1020  .setStrokeCap(flutter::DlStrokeCap::kSquare) //
1021  .setStrokeWidth(1);
1022  SkPath path = SkPath().addPoly({{150, 50}, {160, 50}}, false);
1023  for (auto cap : caps) {
1024  paint.setStrokeCap(cap);
1025  builder.DrawLine({50, 50}, {60, 50}, paint);
1026  builder.DrawRect({45, 45, 65, 55}, outline_paint);
1027  builder.DrawLine({100, 50}, {100, 50}, paint);
1028  if (cap != flutter::DlStrokeCap::kButt) {
1029  builder.DrawRect({95, 45, 105, 55}, outline_paint);
1030  }
1031  builder.DrawPath(path, paint);
1032  builder.DrawRect(path.getBounds().makeOutset(5, 5), outline_paint);
1033  builder.Translate(0, 150);
1034  }
1035  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1036 }
1037 
1038 TEST_P(DisplayListTest, CanDrawWithMatrixFilter) {
1039  auto boston = CreateTextureForFixture("boston.jpg");
1040 
1041  auto callback = [&]() {
1042  static int selected_matrix_type = 0;
1043  const char* matrix_type_names[] = {"Matrix", "Local Matrix"};
1044 
1045  static float ctm_translation[2] = {200, 200};
1046  static float ctm_scale[2] = {0.65, 0.65};
1047  static float ctm_skew[2] = {0, 0};
1048 
1049  static bool enable = true;
1050  static float translation[2] = {100, 100};
1051  static float scale[2] = {0.8, 0.8};
1052  static float skew[2] = {0.2, 0.2};
1053 
1054  static bool enable_savelayer = true;
1055 
1056  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1057  {
1058  ImGui::Combo("Filter type", &selected_matrix_type, matrix_type_names,
1059  sizeof(matrix_type_names) / sizeof(char*));
1060 
1061  ImGui::TextWrapped("Current Transform");
1062  ImGui::SliderFloat2("CTM Translation", ctm_translation, 0, 1000);
1063  ImGui::SliderFloat2("CTM Scale", ctm_scale, 0, 3);
1064  ImGui::SliderFloat2("CTM Skew", ctm_skew, -3, 3);
1065 
1066  ImGui::TextWrapped(
1067  "MatrixFilter and LocalMatrixFilter modify the CTM in the same way. "
1068  "The only difference is that MatrixFilter doesn't affect the effect "
1069  "transform, whereas LocalMatrixFilter does.");
1070  // Note: See this behavior in:
1071  // https://fiddle.skia.org/c/6cbb551ab36d06f163db8693972be954
1072  ImGui::Checkbox("Enable", &enable);
1073  ImGui::SliderFloat2("Filter Translation", translation, 0, 1000);
1074  ImGui::SliderFloat2("Filter Scale", scale, 0, 3);
1075  ImGui::SliderFloat2("Filter Skew", skew, -3, 3);
1076 
1077  ImGui::TextWrapped(
1078  "Rendering the filtered image within a layer can expose bounds "
1079  "issues. If the rendered image gets cut off when this setting is "
1080  "enabled, there's a coverage bug in the filter.");
1081  ImGui::Checkbox("Render in layer", &enable_savelayer);
1082  }
1083  ImGui::End();
1084 
1085  flutter::DisplayListBuilder builder;
1086  flutter::DlPaint paint;
1087 
1088  if (enable_savelayer) {
1089  builder.SaveLayer(nullptr, nullptr);
1090  }
1091  {
1092  auto content_scale = GetContentScale();
1093  builder.Scale(content_scale.x, content_scale.y);
1094 
1095  // Set the current transform
1096  auto ctm_matrix =
1097  SkMatrix::MakeAll(ctm_scale[0], ctm_skew[0], ctm_translation[0], //
1098  ctm_skew[1], ctm_scale[1], ctm_translation[1], //
1099  0, 0, 1);
1100  builder.Transform(ctm_matrix);
1101 
1102  // Set the matrix filter
1103  auto filter_matrix =
1104  SkMatrix::MakeAll(scale[0], skew[0], translation[0], //
1105  skew[1], scale[1], translation[1], //
1106  0, 0, 1);
1107 
1108  if (enable) {
1109  switch (selected_matrix_type) {
1110  case 0: {
1111  auto filter = flutter::DlMatrixImageFilter(
1112  filter_matrix, flutter::DlImageSampling::kLinear);
1113  paint.setImageFilter(&filter);
1114  break;
1115  }
1116  case 1: {
1117  auto internal_filter =
1118  flutter::DlBlurImageFilter(10, 10, flutter::DlTileMode::kDecal)
1119  .shared();
1120  auto filter = flutter::DlLocalMatrixImageFilter(filter_matrix,
1121  internal_filter);
1122  paint.setImageFilter(&filter);
1123  break;
1124  }
1125  }
1126  }
1127 
1128  builder.DrawImage(DlImageImpeller::Make(boston), {},
1129  flutter::DlImageSampling::kLinear, &paint);
1130  }
1131  if (enable_savelayer) {
1132  builder.Restore();
1133  }
1134 
1135  return builder.Build();
1136  };
1137 
1138  ASSERT_TRUE(OpenPlaygroundHere(callback));
1139 }
1140 
1141 TEST_P(DisplayListTest, CanDrawWithMatrixFilterWhenSavingLayer) {
1142  auto callback = [&]() {
1143  static float translation[2] = {0, 0};
1144  static bool enable_save_layer = true;
1145 
1146  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1147  ImGui::SliderFloat2("Translation", translation, -130, 130);
1148  ImGui::Checkbox("Enable save layer", &enable_save_layer);
1149  ImGui::End();
1150 
1151  flutter::DisplayListBuilder builder;
1152  builder.Save();
1153  builder.Scale(2.0, 2.0);
1154  flutter::DlPaint paint;
1155  paint.setColor(flutter::DlColor::kYellow());
1156  builder.DrawRect(SkRect::MakeWH(300, 300), paint);
1157  paint.setStrokeWidth(1.0);
1158  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
1159  paint.setColor(flutter::DlColor::kBlack().withAlpha(0x80));
1160  builder.DrawLine(SkPoint::Make(150, 0), SkPoint::Make(150, 300), paint);
1161  builder.DrawLine(SkPoint::Make(0, 150), SkPoint::Make(300, 150), paint);
1162 
1163  flutter::DlPaint save_paint;
1164  SkRect bounds = SkRect::MakeXYWH(100, 100, 100, 100);
1165  SkMatrix translate_matrix =
1166  SkMatrix::Translate(translation[0], translation[1]);
1167  if (enable_save_layer) {
1168  auto filter = flutter::DlMatrixImageFilter(
1169  translate_matrix, flutter::DlImageSampling::kNearestNeighbor);
1170  save_paint.setImageFilter(filter.shared());
1171  builder.SaveLayer(&bounds, &save_paint);
1172  } else {
1173  builder.Save();
1174  builder.Transform(translate_matrix);
1175  }
1176 
1177  SkMatrix filter_matrix = SkMatrix::I();
1178  filter_matrix.postTranslate(-150, -150);
1179  filter_matrix.postScale(0.2f, 0.2f);
1180  filter_matrix.postTranslate(150, 150);
1181  auto filter = flutter::DlMatrixImageFilter(
1182  filter_matrix, flutter::DlImageSampling::kNearestNeighbor);
1183 
1184  save_paint.setImageFilter(filter.shared());
1185 
1186  builder.SaveLayer(&bounds, &save_paint);
1187  flutter::DlPaint paint2;
1188  paint2.setColor(flutter::DlColor::kBlue());
1189  builder.DrawRect(bounds, paint2);
1190  builder.Restore();
1191  builder.Restore();
1192  return builder.Build();
1193  };
1194 
1195  ASSERT_TRUE(OpenPlaygroundHere(callback));
1196 }
1197 
1198 TEST_P(DisplayListTest, CanDrawRectWithLinearToSrgbColorFilter) {
1199  flutter::DlPaint paint;
1200  paint.setColor(flutter::DlColor(0xFF2196F3).withAlpha(128));
1201  flutter::DisplayListBuilder builder;
1202  paint.setColorFilter(
1203  flutter::DlLinearToSrgbGammaColorFilter::kInstance.get());
1204  builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), paint);
1205  builder.Translate(0, 200);
1206 
1207  paint.setColorFilter(
1208  flutter::DlSrgbToLinearGammaColorFilter::kInstance.get());
1209  builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), paint);
1210 
1211  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1212 }
1213 
1214 TEST_P(DisplayListTest, CanDrawPaintWithColorSource) {
1215  const flutter::DlColor colors[2] = {
1216  flutter::DlColor(0xFFF44336),
1217  flutter::DlColor(0xFF2196F3),
1218  };
1219  const float stops[2] = {0.0, 1.0};
1220  flutter::DlPaint paint;
1221  flutter::DisplayListBuilder builder;
1222  auto clip_bounds = SkRect::MakeWH(300.0, 300.0);
1223  builder.Save();
1224  builder.Translate(100, 100);
1225  builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
1226  auto linear =
1227  flutter::DlColorSource::MakeLinear({0.0, 0.0}, {100.0, 100.0}, 2, colors,
1228  stops, flutter::DlTileMode::kRepeat);
1229  paint.setColorSource(linear);
1230  builder.DrawPaint(paint);
1231  builder.Restore();
1232 
1233  builder.Save();
1234  builder.Translate(500, 100);
1235  builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
1236  auto radial = flutter::DlColorSource::MakeRadial(
1237  {100.0, 100.0}, 100.0, 2, colors, stops, flutter::DlTileMode::kRepeat);
1238  paint.setColorSource(radial);
1239  builder.DrawPaint(paint);
1240  builder.Restore();
1241 
1242  builder.Save();
1243  builder.Translate(100, 500);
1244  builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
1245  auto sweep =
1246  flutter::DlColorSource::MakeSweep({100.0, 100.0}, 180.0, 270.0, 2, colors,
1247  stops, flutter::DlTileMode::kRepeat);
1248  paint.setColorSource(sweep);
1249  builder.DrawPaint(paint);
1250  builder.Restore();
1251 
1252  builder.Save();
1253  builder.Translate(500, 500);
1254  builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
1255  auto texture = CreateTextureForFixture("table_mountain_nx.png");
1256  auto image = std::make_shared<flutter::DlImageColorSource>(
1257  DlImageImpeller::Make(texture), flutter::DlTileMode::kRepeat,
1258  flutter::DlTileMode::kRepeat);
1259  paint.setColorSource(image);
1260  builder.DrawPaint(paint);
1261  builder.Restore();
1262 
1263  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1264 }
1265 
1266 TEST_P(DisplayListTest, CanBlendDstOverAndDstCorrectly) {
1267  flutter::DisplayListBuilder builder;
1268 
1269  {
1270  builder.SaveLayer(nullptr, nullptr);
1271  builder.Translate(100, 100);
1272  flutter::DlPaint paint;
1273  paint.setColor(flutter::DlColor::kRed());
1274  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1275  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1276  paint.setBlendMode(flutter::DlBlendMode::kSrcOver);
1277  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1278  builder.Restore();
1279  }
1280  {
1281  builder.SaveLayer(nullptr, nullptr);
1282  builder.Translate(300, 100);
1283  flutter::DlPaint paint;
1284  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1285  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1286  paint.setColor(flutter::DlColor::kRed());
1287  paint.setBlendMode(flutter::DlBlendMode::kDstOver);
1288  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1289  builder.Restore();
1290  }
1291  {
1292  builder.SaveLayer(nullptr, nullptr);
1293  builder.Translate(100, 300);
1294  flutter::DlPaint paint;
1295  paint.setColor(flutter::DlColor::kRed());
1296  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1297  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1298  paint.setBlendMode(flutter::DlBlendMode::kSrc);
1299  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1300  builder.Restore();
1301  }
1302  {
1303  builder.SaveLayer(nullptr, nullptr);
1304  builder.Translate(300, 300);
1305  flutter::DlPaint paint;
1306  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1307  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1308  paint.setColor(flutter::DlColor::kRed());
1309  paint.setBlendMode(flutter::DlBlendMode::kDst);
1310  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1311  builder.Restore();
1312  }
1313 
1314  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1315 }
1316 
1317 TEST_P(DisplayListTest, CanDrawCorrectlyWithColorFilterAndImageFilter) {
1318  flutter::DisplayListBuilder builder;
1319  const float green_color_matrix[20] = {
1320  0, 0, 0, 0, 0, //
1321  0, 0, 0, 0, 1, //
1322  0, 0, 0, 0, 0, //
1323  0, 0, 0, 1, 0, //
1324  };
1325  const float blue_color_matrix[20] = {
1326  0, 0, 0, 0, 0, //
1327  0, 0, 0, 0, 0, //
1328  0, 0, 0, 0, 1, //
1329  0, 0, 0, 1, 0, //
1330  };
1331  auto green_color_filter =
1332  std::make_shared<flutter::DlMatrixColorFilter>(green_color_matrix);
1333  auto blue_color_filter =
1334  std::make_shared<flutter::DlMatrixColorFilter>(blue_color_matrix);
1335  auto blue_image_filter =
1336  std::make_shared<flutter::DlColorFilterImageFilter>(blue_color_filter);
1337 
1338  flutter::DlPaint paint;
1339  paint.setColor(flutter::DlColor::kRed());
1340  paint.setColorFilter(green_color_filter);
1341  paint.setImageFilter(blue_image_filter);
1342  builder.DrawRect(SkRect::MakeLTRB(100, 100, 500, 500), paint);
1343  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1344 }
1345 
1346 TEST_P(DisplayListTest, MaskBlursApplyCorrectlyToColorSources) {
1347  auto blur_filter = std::make_shared<flutter::DlBlurMaskFilter>(
1348  flutter::DlBlurStyle::kNormal, 10);
1349 
1350  flutter::DisplayListBuilder builder;
1351 
1352  std::array<flutter::DlColor, 2> colors = {flutter::DlColor::kBlue(),
1353  flutter::DlColor::kGreen()};
1354  std::array<float, 2> stops = {0, 1};
1355  std::array<std::shared_ptr<flutter::DlColorSource>, 2> color_sources = {
1356  std::make_shared<flutter::DlColorColorSource>(flutter::DlColor::kWhite()),
1357  flutter::DlColorSource::MakeLinear(
1358  SkPoint::Make(0, 0), SkPoint::Make(100, 50), 2, colors.data(),
1359  stops.data(), flutter::DlTileMode::kClamp)};
1360 
1361  int offset = 100;
1362  for (const auto& color_source : color_sources) {
1363  flutter::DlPaint paint;
1364  paint.setColorSource(color_source);
1365  paint.setMaskFilter(blur_filter);
1366 
1367  paint.setDrawStyle(flutter::DlDrawStyle::kFill);
1368  builder.DrawRRect(
1369  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, offset, 100, 50), 30, 30),
1370  paint);
1371  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
1372  paint.setStrokeWidth(10);
1373  builder.DrawRRect(
1374  SkRRect::MakeRectXY(SkRect::MakeXYWH(300, offset, 100, 50), 30, 30),
1375  paint);
1376 
1377  offset += 100;
1378  }
1379 
1380  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1381 }
1382 
1383 TEST_P(DisplayListTest, DrawVerticesSolidColorTrianglesWithoutIndices) {
1384  // Use negative coordinates and then scale the transform by -1, -1 to make
1385  // sure coverage is taking the transform into account.
1386  std::vector<SkPoint> positions = {SkPoint::Make(-100, -300),
1387  SkPoint::Make(-200, -100),
1388  SkPoint::Make(-300, -300)};
1389  std::vector<flutter::DlColor> colors = {flutter::DlColor::kWhite(),
1390  flutter::DlColor::kGreen(),
1391  flutter::DlColor::kWhite()};
1392 
1393  auto vertices = flutter::DlVertices::Make(
1394  flutter::DlVertexMode::kTriangles, 3, positions.data(),
1395  /*texture_coordinates=*/nullptr, colors.data());
1396 
1397  flutter::DisplayListBuilder builder;
1398  flutter::DlPaint paint;
1399 
1400  paint.setColor(flutter::DlColor::kRed().modulateOpacity(0.5));
1401  builder.Scale(-1, -1);
1402  builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
1403 
1404  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1405 }
1406 
1407 TEST_P(DisplayListTest, DrawVerticesLinearGradientWithoutIndices) {
1408  std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
1409  SkPoint::Make(200, 100),
1410  SkPoint::Make(300, 300)};
1411 
1412  auto vertices = flutter::DlVertices::Make(
1413  flutter::DlVertexMode::kTriangles, 3, positions.data(),
1414  /*texture_coordinates=*/nullptr, /*colors=*/nullptr);
1415 
1416  std::vector<flutter::DlColor> colors = {flutter::DlColor::kBlue(),
1417  flutter::DlColor::kRed()};
1418  const float stops[2] = {0.0, 1.0};
1419 
1420  auto linear = flutter::DlColorSource::MakeLinear(
1421  {100.0, 100.0}, {300.0, 300.0}, 2, colors.data(), stops,
1422  flutter::DlTileMode::kRepeat);
1423 
1424  flutter::DisplayListBuilder builder;
1425  flutter::DlPaint paint;
1426 
1427  paint.setColorSource(linear);
1428  builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
1429 
1430  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1431 }
1432 
1433 TEST_P(DisplayListTest, DrawVerticesLinearGradientWithTextureCoordinates) {
1434  std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
1435  SkPoint::Make(200, 100),
1436  SkPoint::Make(300, 300)};
1437  std::vector<SkPoint> texture_coordinates = {SkPoint::Make(300, 100),
1438  SkPoint::Make(100, 200),
1439  SkPoint::Make(300, 300)};
1440 
1441  auto vertices = flutter::DlVertices::Make(
1442  flutter::DlVertexMode::kTriangles, 3, positions.data(),
1443  texture_coordinates.data(), /*colors=*/nullptr);
1444 
1445  std::vector<flutter::DlColor> colors = {flutter::DlColor::kBlue(),
1446  flutter::DlColor::kRed()};
1447  const float stops[2] = {0.0, 1.0};
1448 
1449  auto linear = flutter::DlColorSource::MakeLinear(
1450  {100.0, 100.0}, {300.0, 300.0}, 2, colors.data(), stops,
1451  flutter::DlTileMode::kRepeat);
1452 
1453  flutter::DisplayListBuilder builder;
1454  flutter::DlPaint paint;
1455 
1456  paint.setColorSource(linear);
1457  builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
1458 
1459  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1460 }
1461 
1462 TEST_P(DisplayListTest, DrawVerticesImageSourceWithTextureCoordinates) {
1463  auto texture = CreateTextureForFixture("embarcadero.jpg");
1464  auto dl_image = DlImageImpeller::Make(texture);
1465  std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
1466  SkPoint::Make(200, 100),
1467  SkPoint::Make(300, 300)};
1468  std::vector<SkPoint> texture_coordinates = {
1469  SkPoint::Make(0, 0), SkPoint::Make(100, 200), SkPoint::Make(200, 100)};
1470 
1471  auto vertices = flutter::DlVertices::Make(
1472  flutter::DlVertexMode::kTriangles, 3, positions.data(),
1473  texture_coordinates.data(), /*colors=*/nullptr);
1474 
1475  flutter::DisplayListBuilder builder;
1476  flutter::DlPaint paint;
1477 
1478  auto image_source = flutter::DlImageColorSource(
1479  dl_image, flutter::DlTileMode::kRepeat, flutter::DlTileMode::kRepeat);
1480 
1481  paint.setColorSource(&image_source);
1482  builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
1483 
1484  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1485 }
1486 
1488  DrawVerticesImageSourceWithTextureCoordinatesAndColorBlending) {
1489  auto texture = CreateTextureForFixture("embarcadero.jpg");
1490  auto dl_image = DlImageImpeller::Make(texture);
1491  std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
1492  SkPoint::Make(200, 100),
1493  SkPoint::Make(300, 300)};
1494  std::vector<flutter::DlColor> colors = {flutter::DlColor::kWhite(),
1495  flutter::DlColor::kGreen(),
1496  flutter::DlColor::kWhite()};
1497  std::vector<SkPoint> texture_coordinates = {
1498  SkPoint::Make(0, 0), SkPoint::Make(100, 200), SkPoint::Make(200, 100)};
1499 
1500  auto vertices = flutter::DlVertices::Make(
1501  flutter::DlVertexMode::kTriangles, 3, positions.data(),
1502  texture_coordinates.data(), colors.data());
1503 
1504  flutter::DisplayListBuilder builder;
1505  flutter::DlPaint paint;
1506 
1507  auto image_source = flutter::DlImageColorSource(
1508  dl_image, flutter::DlTileMode::kRepeat, flutter::DlTileMode::kRepeat);
1509 
1510  paint.setColorSource(&image_source);
1511  builder.DrawVertices(vertices, flutter::DlBlendMode::kModulate, paint);
1512 
1513  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1514 }
1515 
1516 TEST_P(DisplayListTest, DrawVerticesSolidColorTrianglesWithIndices) {
1517  std::vector<SkPoint> positions = {
1518  SkPoint::Make(100, 300), SkPoint::Make(200, 100), SkPoint::Make(300, 300),
1519  SkPoint::Make(200, 500)};
1520  std::vector<uint16_t> indices = {0, 1, 2, 0, 2, 3};
1521 
1522  auto vertices = flutter::DlVertices::Make(
1523  flutter::DlVertexMode::kTriangles, positions.size(), positions.data(),
1524  /*texture_coordinates=*/nullptr, /*colors=*/nullptr, indices.size(),
1525  indices.data());
1526 
1527  flutter::DisplayListBuilder builder;
1528  flutter::DlPaint paint;
1529 
1530  paint.setColor(flutter::DlColor::kWhite());
1531  builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
1532 
1533  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1534 }
1535 
1536 TEST_P(DisplayListTest, DrawVerticesPremultipliesColors) {
1537  std::vector<SkPoint> positions = {
1538  SkPoint::Make(100, 300), SkPoint::Make(200, 100), SkPoint::Make(300, 300),
1539  SkPoint::Make(200, 500)};
1540  auto color = flutter::DlColor::kBlue().withAlpha(0x99);
1541  std::vector<uint16_t> indices = {0, 1, 2, 0, 2, 3};
1542  std::vector<flutter::DlColor> colors = {color, color, color, color};
1543 
1544  auto vertices = flutter::DlVertices::Make(
1545  flutter::DlVertexMode::kTriangles, positions.size(), positions.data(),
1546  /*texture_coordinates=*/nullptr, colors.data(), indices.size(),
1547  indices.data());
1548 
1549  flutter::DisplayListBuilder builder;
1550  flutter::DlPaint paint;
1551  paint.setBlendMode(flutter::DlBlendMode::kSrcOver);
1552  paint.setColor(flutter::DlColor::kRed());
1553 
1554  builder.DrawRect(SkRect::MakeLTRB(0, 0, 400, 400), paint);
1555  builder.DrawVertices(vertices, flutter::DlBlendMode::kDst, paint);
1556 
1557  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1558 }
1559 
1560 TEST_P(DisplayListTest, DrawShapes) {
1561  flutter::DisplayListBuilder builder;
1562  std::vector<flutter::DlStrokeJoin> joins = {
1563  flutter::DlStrokeJoin::kBevel,
1564  flutter::DlStrokeJoin::kRound,
1565  flutter::DlStrokeJoin::kMiter,
1566  };
1567  flutter::DlPaint paint = //
1568  flutter::DlPaint() //
1569  .setColor(flutter::DlColor::kWhite()) //
1570  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1571  .setStrokeWidth(10);
1572  flutter::DlPaint stroke_paint = //
1573  flutter::DlPaint() //
1574  .setColor(flutter::DlColor::kWhite()) //
1575  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
1576  .setStrokeWidth(10);
1577  SkPath path = SkPath().addPoly({{150, 50}, {160, 50}}, false);
1578 
1579  builder.Translate(300, 50);
1580  builder.Scale(0.8, 0.8);
1581  for (auto join : joins) {
1582  paint.setStrokeJoin(join);
1583  stroke_paint.setStrokeJoin(join);
1584  builder.DrawRect(SkRect::MakeXYWH(0, 0, 100, 100), paint);
1585  builder.DrawRect(SkRect::MakeXYWH(0, 150, 100, 100), stroke_paint);
1586  builder.DrawRRect(
1587  SkRRect::MakeRectXY(SkRect::MakeXYWH(150, 0, 100, 100), 30, 30), paint);
1588  builder.DrawRRect(
1589  SkRRect::MakeRectXY(SkRect::MakeXYWH(150, 150, 100, 100), 30, 30),
1590  stroke_paint);
1591  builder.DrawCircle({350, 50}, 50, paint);
1592  builder.DrawCircle({350, 200}, 50, stroke_paint);
1593  builder.Translate(0, 300);
1594  }
1595  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1596 }
1597 
1598 TEST_P(DisplayListTest, ClipDrawRRectWithNonCircularRadii) {
1599  flutter::DisplayListBuilder builder;
1600 
1601  flutter::DlPaint fill_paint = //
1602  flutter::DlPaint() //
1603  .setColor(flutter::DlColor::kBlue()) //
1604  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1605  .setStrokeWidth(10);
1606  flutter::DlPaint stroke_paint = //
1607  flutter::DlPaint() //
1608  .setColor(flutter::DlColor::kGreen()) //
1609  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
1610  .setStrokeWidth(10);
1611 
1612  builder.DrawRRect(
1613  SkRRect::MakeRectXY(SkRect::MakeXYWH(500, 100, 300, 300), 120, 40),
1614  fill_paint);
1615  builder.DrawRRect(
1616  SkRRect::MakeRectXY(SkRect::MakeXYWH(500, 100, 300, 300), 120, 40),
1617  stroke_paint);
1618 
1619  builder.DrawRRect(
1620  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 500, 300, 300), 40, 120),
1621  fill_paint);
1622  builder.DrawRRect(
1623  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 500, 300, 300), 40, 120),
1624  stroke_paint);
1625 
1626  flutter::DlPaint reference_paint = //
1627  flutter::DlPaint() //
1628  .setColor(flutter::DlColor::kMidGrey()) //
1629  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1630  .setStrokeWidth(10);
1631 
1632  builder.DrawRRect(
1633  SkRRect::MakeRectXY(SkRect::MakeXYWH(500, 500, 300, 300), 40, 40),
1634  reference_paint);
1635  builder.DrawRRect(
1636  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 100, 300, 300), 120, 120),
1637  reference_paint);
1638 
1639  flutter::DlPaint clip_fill_paint = //
1640  flutter::DlPaint() //
1641  .setColor(flutter::DlColor::kCyan()) //
1642  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1643  .setStrokeWidth(10);
1644 
1645  builder.Save();
1646  builder.ClipRRect(
1647  SkRRect::MakeRectXY(SkRect::MakeXYWH(900, 100, 300, 300), 120, 40));
1648  builder.DrawPaint(clip_fill_paint);
1649  builder.Restore();
1650 
1651  builder.Save();
1652  builder.ClipRRect(
1653  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 900, 300, 300), 40, 120));
1654  builder.DrawPaint(clip_fill_paint);
1655  builder.Restore();
1656 
1657  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1658 }
1659 
1660 TEST_P(DisplayListTest, DrawVerticesBlendModes) {
1661  std::vector<const char*> blend_mode_names;
1662  std::vector<flutter::DlBlendMode> blend_mode_values;
1663  {
1664  const std::vector<std::tuple<const char*, flutter::DlBlendMode>> blends = {
1665  // Pipeline blends (Porter-Duff alpha compositing)
1666  {"Clear", flutter::DlBlendMode::kClear},
1667  {"Source", flutter::DlBlendMode::kSrc},
1668  {"Destination", flutter::DlBlendMode::kDst},
1669  {"SourceOver", flutter::DlBlendMode::kSrcOver},
1670  {"DestinationOver", flutter::DlBlendMode::kDstOver},
1671  {"SourceIn", flutter::DlBlendMode::kSrcIn},
1672  {"DestinationIn", flutter::DlBlendMode::kDstIn},
1673  {"SourceOut", flutter::DlBlendMode::kSrcOut},
1674  {"DestinationOut", flutter::DlBlendMode::kDstOut},
1675  {"SourceATop", flutter::DlBlendMode::kSrcATop},
1676  {"DestinationATop", flutter::DlBlendMode::kDstATop},
1677  {"Xor", flutter::DlBlendMode::kXor},
1678  {"Plus", flutter::DlBlendMode::kPlus},
1679  {"Modulate", flutter::DlBlendMode::kModulate},
1680  // Advanced blends (color component blends)
1681  {"Screen", flutter::DlBlendMode::kScreen},
1682  {"Overlay", flutter::DlBlendMode::kOverlay},
1683  {"Darken", flutter::DlBlendMode::kDarken},
1684  {"Lighten", flutter::DlBlendMode::kLighten},
1685  {"ColorDodge", flutter::DlBlendMode::kColorDodge},
1686  {"ColorBurn", flutter::DlBlendMode::kColorBurn},
1687  {"HardLight", flutter::DlBlendMode::kHardLight},
1688  {"SoftLight", flutter::DlBlendMode::kSoftLight},
1689  {"Difference", flutter::DlBlendMode::kDifference},
1690  {"Exclusion", flutter::DlBlendMode::kExclusion},
1691  {"Multiply", flutter::DlBlendMode::kMultiply},
1692  {"Hue", flutter::DlBlendMode::kHue},
1693  {"Saturation", flutter::DlBlendMode::kSaturation},
1694  {"Color", flutter::DlBlendMode::kColor},
1695  {"Luminosity", flutter::DlBlendMode::kLuminosity},
1696  };
1697  assert(blends.size() ==
1698  static_cast<size_t>(flutter::DlBlendMode::kLastMode) + 1);
1699  for (const auto& [name, mode] : blends) {
1700  blend_mode_names.push_back(name);
1701  blend_mode_values.push_back(mode);
1702  }
1703  }
1704 
1705  auto callback = [&]() {
1706  static int current_blend_index = 3;
1707  static float dst_alpha = 1;
1708  static float src_alpha = 1;
1709  static float color0[4] = {1.0f, 0.0f, 0.0f, 1.0f};
1710  static float color1[4] = {0.0f, 1.0f, 0.0f, 1.0f};
1711  static float color2[4] = {0.0f, 0.0f, 1.0f, 1.0f};
1712  static float src_color[4] = {1.0f, 1.0f, 1.0f, 1.0f};
1713 
1714  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1715  {
1716  ImGui::ListBox("Blending mode", &current_blend_index,
1717  blend_mode_names.data(), blend_mode_names.size());
1718  ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1);
1719  ImGui::ColorEdit4("Color A", color0);
1720  ImGui::ColorEdit4("Color B", color1);
1721  ImGui::ColorEdit4("Color C", color2);
1722  ImGui::ColorEdit4("Source Color", src_color);
1723  ImGui::SliderFloat("Destination alpha", &dst_alpha, 0, 1);
1724  }
1725  ImGui::End();
1726 
1727  std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
1728  SkPoint::Make(200, 100),
1729  SkPoint::Make(300, 300)};
1730  std::vector<flutter::DlColor> colors = {
1731  toColor(color0).modulateOpacity(dst_alpha),
1732  toColor(color1).modulateOpacity(dst_alpha),
1733  toColor(color2).modulateOpacity(dst_alpha)};
1734 
1735  auto vertices = flutter::DlVertices::Make(
1736  flutter::DlVertexMode::kTriangles, 3, positions.data(),
1737  /*texture_coordinates=*/nullptr, colors.data());
1738 
1739  flutter::DisplayListBuilder builder;
1740  flutter::DlPaint paint;
1741 
1742  paint.setColor(toColor(src_color).modulateOpacity(src_alpha));
1743  builder.DrawVertices(vertices, blend_mode_values[current_blend_index],
1744  paint);
1745  return builder.Build();
1746  };
1747 
1748  ASSERT_TRUE(OpenPlaygroundHere(callback));
1749 }
1750 
1751 template <typename Contents>
1752 static std::optional<Rect> GetCoverageOfFirstEntity(const Picture& picture) {
1753  std::optional<Rect> coverage;
1754  picture.pass->IterateAllEntities([&coverage](Entity& entity) {
1755  if (std::static_pointer_cast<Contents>(entity.GetContents())) {
1756  auto contents = std::static_pointer_cast<Contents>(entity.GetContents());
1757  Entity entity;
1758  coverage = contents->GetCoverage(entity);
1759  return false;
1760  }
1761  return true;
1762  });
1763  return coverage;
1764 }
1765 
1766 TEST(DisplayListTest, RRectBoundsComputation) {
1767  SkRRect rrect = SkRRect::MakeRectXY(SkRect::MakeLTRB(0, 0, 100, 100), 4, 4);
1768  SkPath path = SkPath().addRRect(rrect);
1769 
1770  flutter::DlPaint paint;
1771  flutter::DisplayListBuilder builder;
1772 
1773  builder.DrawPath(path, paint);
1774  auto display_list = builder.Build();
1775 
1776  DlDispatcher dispatcher;
1777  display_list->Dispatch(dispatcher);
1778  auto picture = dispatcher.EndRecordingAsPicture();
1779 
1780  std::optional<Rect> coverage =
1781  GetCoverageOfFirstEntity<SolidColorContents>(picture);
1782 
1783  // Validate that the RRect coverage is _exactly_ the same as the input rect.
1784  ASSERT_TRUE(coverage.has_value());
1785  ASSERT_EQ(coverage.value_or(Rect::MakeMaximum()),
1786  Rect::MakeLTRB(0, 0, 100, 100));
1787 }
1788 
1789 TEST(DisplayListTest, CircleBoundsComputation) {
1790  SkPath path = SkPath().addCircle(0, 0, 5);
1791 
1792  flutter::DlPaint paint;
1793  flutter::DisplayListBuilder builder;
1794 
1795  builder.DrawPath(path, paint);
1796  auto display_list = builder.Build();
1797 
1798  DlDispatcher dispatcher;
1799  display_list->Dispatch(dispatcher);
1800  auto picture = dispatcher.EndRecordingAsPicture();
1801 
1802  std::optional<Rect> coverage =
1803  GetCoverageOfFirstEntity<SolidColorContents>(picture);
1804 
1805  ASSERT_TRUE(coverage.has_value());
1806  ASSERT_EQ(coverage.value_or(Rect::MakeMaximum()),
1807  Rect::MakeLTRB(-5, -5, 5, 5));
1808 }
1809 
1810 #ifdef IMPELLER_ENABLE_3D
1811 TEST_P(DisplayListTest, SceneColorSource) {
1812  // Load up the scene.
1813  auto mapping =
1814  flutter::testing::OpenFixtureAsMapping("flutter_logo_baked.glb.ipscene");
1815  ASSERT_NE(mapping, nullptr);
1816 
1817  std::shared_ptr<scene::Node> gltf_scene =
1819  *mapping, *GetContext()->GetResourceAllocator());
1820  ASSERT_NE(gltf_scene, nullptr);
1821 
1822  flutter::DisplayListBuilder builder;
1823 
1824  auto color_source = std::make_shared<flutter::DlSceneColorSource>(
1825  gltf_scene,
1826  Matrix::MakePerspective(Degrees(45), GetWindowSize(), 0.1, 1000) *
1827  Matrix::MakeLookAt({3, 2, -5}, {0, 0, 0}, {0, 1, 0}));
1828 
1829  flutter::DlPaint paint = flutter::DlPaint().setColorSource(color_source);
1830 
1831  builder.DrawPaint(paint);
1832 
1833  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1834 }
1835 #endif
1836 
1837 TEST_P(DisplayListTest, DrawPaintIgnoresMaskFilter) {
1838  flutter::DisplayListBuilder builder;
1839  builder.DrawPaint(flutter::DlPaint().setColor(flutter::DlColor::kWhite()));
1840 
1841  auto filter = flutter::DlBlurMaskFilter(flutter::DlBlurStyle::kNormal, 10.0f);
1842  builder.DrawCircle({300, 300}, 200,
1843  flutter::DlPaint().setMaskFilter(&filter));
1844 
1845  std::vector<flutter::DlColor> colors = {flutter::DlColor::kGreen(),
1846  flutter::DlColor::kGreen()};
1847  const float stops[2] = {0.0, 1.0};
1848  auto linear = flutter::DlColorSource::MakeLinear(
1849  {100.0, 100.0}, {300.0, 300.0}, 2, colors.data(), stops,
1850  flutter::DlTileMode::kRepeat);
1851  flutter::DlPaint blend_paint =
1852  flutter::DlPaint() //
1853  .setColorSource(linear) //
1854  .setBlendMode(flutter::DlBlendMode::kScreen);
1855  builder.DrawPaint(blend_paint);
1856 
1857  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1858 }
1859 
1860 } // namespace testing
1861 } // 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:648
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:1149
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:680
impeller::kPi
constexpr float kPi
Definition: constants.h:26
impeller::Vector3::x
Scalar x
Definition: vector.h:23
stroke_width
const Scalar stroke_width
Definition: stroke_path_geometry.cc:293
impeller::DlDispatcher::restore
void restore() override
Definition: dl_dispatcher.cc:638
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:1005
impeller::Matrix::MakeLookAt
static constexpr Matrix MakeLookAt(Vector3 position, Vector3 target, Vector3 up)
Definition: matrix.h:497
impeller::Matrix::MakePerspective
static constexpr Matrix MakePerspective(Radians fov_y, Scalar aspect_ratio, Scalar z_near, Scalar z_far)
Definition: matrix.h:471
dl_playground.h
impeller::testing::INSTANTIATE_PLAYGROUND_SUITE
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
impeller::kColor
@ kColor
Definition: geometry.h:51
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:316
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:306
widgets.h
impeller::testing::TEST
TEST(CanvasRecorder, Save)
Definition: canvas_recorder_unittests.cc:65
impeller::Entity::GetContents
const std::shared_ptr< Contents > & GetContents() const
Definition: entity.cc:97
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:1082
scalar.h
impeller::DrawPlaygroundPoint
Point DrawPlaygroundPoint(PlaygroundPoint &point)
Definition: widgets.cc:9
impeller::PlaygroundPoint
Definition: widgets.h:17
constants.h
impeller::testing::TEST_P
TEST_P(AiksTest, CanRenderMaskBlurHugeSigma)
Definition: aiks_blur_unittests.cc:23
impeller::TPoint< Scalar >
impeller::TRect< Scalar >::MakeMaximum
constexpr static TRect MakeMaximum()
Definition: rect.h:174
scale
const Scalar scale
Definition: stroke_path_geometry.cc:297
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:129
offset
Point offset
Definition: stroke_path_geometry.cc:300
impeller::scene::Node::MakeFromFlatbuffer
static std::shared_ptr< Node > MakeFromFlatbuffer(const fml::Mapping &ipscene_mapping, Allocator &allocator)
Definition: node.cc:47
impeller
Definition: aiks_blur_unittests.cc:20
impeller::testing::GetCoverageOfFirstEntity
static std::optional< Rect > GetCoverageOfFirstEntity(const Picture &picture)
Definition: dl_unittests.cc:1752
solid_rrect_blur_contents.h
impeller::DlDispatcher::translate
void translate(SkScalar tx, SkScalar ty) override
Definition: dl_dispatcher.cc:643
impeller::DrawPlaygroundLine
std::tuple< Point, Point > DrawPlaygroundLine(PlaygroundPoint &point_a, PlaygroundPoint &point_b)
Definition: widgets.cc:50
dl_image_impeller.h
impeller::DlPlayground
Definition: dl_playground.h:15