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"
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 
913 TEST_P(DisplayListTest, CanDrawZeroWidthLine) {
914  flutter::DisplayListBuilder builder;
915  std::vector<flutter::DlStrokeCap> caps = {
916  flutter::DlStrokeCap::kButt,
917  flutter::DlStrokeCap::kRound,
918  flutter::DlStrokeCap::kSquare,
919  };
920  flutter::DlPaint paint = //
921  flutter::DlPaint() //
922  .setColor(flutter::DlColor::kWhite()) //
923  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
924  .setStrokeWidth(0);
925  flutter::DlPaint outline_paint = //
926  flutter::DlPaint() //
927  .setColor(flutter::DlColor::kYellow()) //
928  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
929  .setStrokeCap(flutter::DlStrokeCap::kSquare) //
930  .setStrokeWidth(1);
931  SkPath path = SkPath().addPoly({{150, 50}, {160, 50}}, false);
932  for (auto cap : caps) {
933  paint.setStrokeCap(cap);
934  builder.DrawLine({50, 50}, {60, 50}, paint);
935  builder.DrawRect({45, 45, 65, 55}, outline_paint);
936  builder.DrawLine({100, 50}, {100, 50}, paint);
937  if (cap != flutter::DlStrokeCap::kButt) {
938  builder.DrawRect({95, 45, 105, 55}, outline_paint);
939  }
940  builder.DrawPath(path, paint);
941  builder.DrawRect(path.getBounds().makeOutset(5, 5), outline_paint);
942  builder.Translate(0, 150);
943  }
944  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
945 }
946 
947 TEST_P(DisplayListTest, CanDrawWithMatrixFilter) {
948  auto boston = CreateTextureForFixture("boston.jpg");
949 
950  auto callback = [&]() {
951  static int selected_matrix_type = 0;
952  const char* matrix_type_names[] = {"Matrix", "Local Matrix"};
953 
954  static float ctm_translation[2] = {200, 200};
955  static float ctm_scale[2] = {0.65, 0.65};
956  static float ctm_skew[2] = {0, 0};
957 
958  static bool enable = true;
959  static float translation[2] = {100, 100};
960  static float scale[2] = {0.8, 0.8};
961  static float skew[2] = {0.2, 0.2};
962 
963  static bool enable_savelayer = true;
964 
965  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
966  {
967  ImGui::Combo("Filter type", &selected_matrix_type, matrix_type_names,
968  sizeof(matrix_type_names) / sizeof(char*));
969 
970  ImGui::TextWrapped("Current Transform");
971  ImGui::SliderFloat2("CTM Translation", ctm_translation, 0, 1000);
972  ImGui::SliderFloat2("CTM Scale", ctm_scale, 0, 3);
973  ImGui::SliderFloat2("CTM Skew", ctm_skew, -3, 3);
974 
975  ImGui::TextWrapped(
976  "MatrixFilter and LocalMatrixFilter modify the CTM in the same way. "
977  "The only difference is that MatrixFilter doesn't affect the effect "
978  "transform, whereas LocalMatrixFilter does.");
979  // Note: See this behavior in:
980  // https://fiddle.skia.org/c/6cbb551ab36d06f163db8693972be954
981  ImGui::Checkbox("Enable", &enable);
982  ImGui::SliderFloat2("Filter Translation", translation, 0, 1000);
983  ImGui::SliderFloat2("Filter Scale", scale, 0, 3);
984  ImGui::SliderFloat2("Filter Skew", skew, -3, 3);
985 
986  ImGui::TextWrapped(
987  "Rendering the filtered image within a layer can expose bounds "
988  "issues. If the rendered image gets cut off when this setting is "
989  "enabled, there's a coverage bug in the filter.");
990  ImGui::Checkbox("Render in layer", &enable_savelayer);
991  }
992  ImGui::End();
993 
994  flutter::DisplayListBuilder builder;
995  flutter::DlPaint paint;
996 
997  if (enable_savelayer) {
998  builder.SaveLayer(nullptr, nullptr);
999  }
1000  {
1001  auto content_scale = GetContentScale();
1002  builder.Scale(content_scale.x, content_scale.y);
1003 
1004  // Set the current transform
1005  auto ctm_matrix =
1006  SkMatrix::MakeAll(ctm_scale[0], ctm_skew[0], ctm_translation[0], //
1007  ctm_skew[1], ctm_scale[1], ctm_translation[1], //
1008  0, 0, 1);
1009  builder.Transform(ctm_matrix);
1010 
1011  // Set the matrix filter
1012  auto filter_matrix =
1013  SkMatrix::MakeAll(scale[0], skew[0], translation[0], //
1014  skew[1], scale[1], translation[1], //
1015  0, 0, 1);
1016 
1017  if (enable) {
1018  switch (selected_matrix_type) {
1019  case 0: {
1020  auto filter = flutter::DlMatrixImageFilter(
1021  filter_matrix, flutter::DlImageSampling::kLinear);
1022  paint.setImageFilter(&filter);
1023  break;
1024  }
1025  case 1: {
1026  auto internal_filter =
1027  flutter::DlBlurImageFilter(10, 10, flutter::DlTileMode::kDecal)
1028  .shared();
1029  auto filter = flutter::DlLocalMatrixImageFilter(filter_matrix,
1030  internal_filter);
1031  paint.setImageFilter(&filter);
1032  break;
1033  }
1034  }
1035  }
1036 
1037  builder.DrawImage(DlImageImpeller::Make(boston), {},
1038  flutter::DlImageSampling::kLinear, &paint);
1039  }
1040  if (enable_savelayer) {
1041  builder.Restore();
1042  }
1043 
1044  return builder.Build();
1045  };
1046 
1047  ASSERT_TRUE(OpenPlaygroundHere(callback));
1048 }
1049 
1050 TEST_P(DisplayListTest, CanDrawWithMatrixFilterWhenSavingLayer) {
1051  auto callback = [&]() {
1052  static float translation[2] = {0, 0};
1053  static bool enable_save_layer = true;
1054 
1055  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1056  ImGui::SliderFloat2("Translation", translation, -130, 130);
1057  ImGui::Checkbox("Enable save layer", &enable_save_layer);
1058  ImGui::End();
1059 
1060  flutter::DisplayListBuilder builder;
1061  builder.Save();
1062  builder.Scale(2.0, 2.0);
1063  flutter::DlPaint paint;
1064  paint.setColor(flutter::DlColor::kYellow());
1065  builder.DrawRect(SkRect::MakeWH(300, 300), paint);
1066  paint.setStrokeWidth(1.0);
1067  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
1068  paint.setColor(flutter::DlColor::kBlack().withAlpha(0x80));
1069  builder.DrawLine(SkPoint::Make(150, 0), SkPoint::Make(150, 300), paint);
1070  builder.DrawLine(SkPoint::Make(0, 150), SkPoint::Make(300, 150), paint);
1071 
1072  flutter::DlPaint save_paint;
1073  SkRect bounds = SkRect::MakeXYWH(100, 100, 100, 100);
1074  SkMatrix translate_matrix =
1075  SkMatrix::Translate(translation[0], translation[1]);
1076  if (enable_save_layer) {
1077  auto filter = flutter::DlMatrixImageFilter(
1078  translate_matrix, flutter::DlImageSampling::kNearestNeighbor);
1079  save_paint.setImageFilter(filter.shared());
1080  builder.SaveLayer(&bounds, &save_paint);
1081  } else {
1082  builder.Save();
1083  builder.Transform(translate_matrix);
1084  }
1085 
1086  SkMatrix filter_matrix = SkMatrix::I();
1087  filter_matrix.postTranslate(-150, -150);
1088  filter_matrix.postScale(0.2f, 0.2f);
1089  filter_matrix.postTranslate(150, 150);
1090  auto filter = flutter::DlMatrixImageFilter(
1091  filter_matrix, flutter::DlImageSampling::kNearestNeighbor);
1092 
1093  save_paint.setImageFilter(filter.shared());
1094 
1095  builder.SaveLayer(&bounds, &save_paint);
1096  flutter::DlPaint paint2;
1097  paint2.setColor(flutter::DlColor::kBlue());
1098  builder.DrawRect(bounds, paint2);
1099  builder.Restore();
1100  builder.Restore();
1101  return builder.Build();
1102  };
1103 
1104  ASSERT_TRUE(OpenPlaygroundHere(callback));
1105 }
1106 
1107 TEST_P(DisplayListTest, CanDrawRectWithLinearToSrgbColorFilter) {
1108  flutter::DlPaint paint;
1109  paint.setColor(flutter::DlColor(0xFF2196F3).withAlpha(128));
1110  flutter::DisplayListBuilder builder;
1111  paint.setColorFilter(
1112  flutter::DlLinearToSrgbGammaColorFilter::kInstance.get());
1113  builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), paint);
1114  builder.Translate(0, 200);
1115 
1116  paint.setColorFilter(
1117  flutter::DlSrgbToLinearGammaColorFilter::kInstance.get());
1118  builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), paint);
1119 
1120  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1121 }
1122 
1123 TEST_P(DisplayListTest, CanDrawPaintWithColorSource) {
1124  const flutter::DlColor colors[2] = {
1125  flutter::DlColor(0xFFF44336),
1126  flutter::DlColor(0xFF2196F3),
1127  };
1128  const float stops[2] = {0.0, 1.0};
1129  flutter::DlPaint paint;
1130  flutter::DisplayListBuilder builder;
1131  auto clip_bounds = SkRect::MakeWH(300.0, 300.0);
1132  builder.Save();
1133  builder.Translate(100, 100);
1134  builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
1135  auto linear =
1136  flutter::DlColorSource::MakeLinear({0.0, 0.0}, {100.0, 100.0}, 2, colors,
1137  stops, flutter::DlTileMode::kRepeat);
1138  paint.setColorSource(linear);
1139  builder.DrawPaint(paint);
1140  builder.Restore();
1141 
1142  builder.Save();
1143  builder.Translate(500, 100);
1144  builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
1145  auto radial = flutter::DlColorSource::MakeRadial(
1146  {100.0, 100.0}, 100.0, 2, colors, stops, flutter::DlTileMode::kRepeat);
1147  paint.setColorSource(radial);
1148  builder.DrawPaint(paint);
1149  builder.Restore();
1150 
1151  builder.Save();
1152  builder.Translate(100, 500);
1153  builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
1154  auto sweep =
1155  flutter::DlColorSource::MakeSweep({100.0, 100.0}, 180.0, 270.0, 2, colors,
1156  stops, flutter::DlTileMode::kRepeat);
1157  paint.setColorSource(sweep);
1158  builder.DrawPaint(paint);
1159  builder.Restore();
1160 
1161  builder.Save();
1162  builder.Translate(500, 500);
1163  builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
1164  auto texture = CreateTextureForFixture("table_mountain_nx.png");
1165  auto image = std::make_shared<flutter::DlImageColorSource>(
1166  DlImageImpeller::Make(texture), flutter::DlTileMode::kRepeat,
1167  flutter::DlTileMode::kRepeat);
1168  paint.setColorSource(image);
1169  builder.DrawPaint(paint);
1170  builder.Restore();
1171 
1172  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1173 }
1174 
1175 TEST_P(DisplayListTest, CanBlendDstOverAndDstCorrectly) {
1176  flutter::DisplayListBuilder builder;
1177 
1178  {
1179  builder.SaveLayer(nullptr, nullptr);
1180  builder.Translate(100, 100);
1181  flutter::DlPaint paint;
1182  paint.setColor(flutter::DlColor::kRed());
1183  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1184  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1185  paint.setBlendMode(flutter::DlBlendMode::kSrcOver);
1186  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1187  builder.Restore();
1188  }
1189  {
1190  builder.SaveLayer(nullptr, nullptr);
1191  builder.Translate(300, 100);
1192  flutter::DlPaint paint;
1193  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1194  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1195  paint.setColor(flutter::DlColor::kRed());
1196  paint.setBlendMode(flutter::DlBlendMode::kDstOver);
1197  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1198  builder.Restore();
1199  }
1200  {
1201  builder.SaveLayer(nullptr, nullptr);
1202  builder.Translate(100, 300);
1203  flutter::DlPaint paint;
1204  paint.setColor(flutter::DlColor::kRed());
1205  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1206  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1207  paint.setBlendMode(flutter::DlBlendMode::kSrc);
1208  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1209  builder.Restore();
1210  }
1211  {
1212  builder.SaveLayer(nullptr, nullptr);
1213  builder.Translate(300, 300);
1214  flutter::DlPaint paint;
1215  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1216  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1217  paint.setColor(flutter::DlColor::kRed());
1218  paint.setBlendMode(flutter::DlBlendMode::kDst);
1219  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1220  builder.Restore();
1221  }
1222 
1223  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1224 }
1225 
1226 TEST_P(DisplayListTest, CanDrawCorrectlyWithColorFilterAndImageFilter) {
1227  flutter::DisplayListBuilder builder;
1228  const float green_color_matrix[20] = {
1229  0, 0, 0, 0, 0, //
1230  0, 0, 0, 0, 1, //
1231  0, 0, 0, 0, 0, //
1232  0, 0, 0, 1, 0, //
1233  };
1234  const float blue_color_matrix[20] = {
1235  0, 0, 0, 0, 0, //
1236  0, 0, 0, 0, 0, //
1237  0, 0, 0, 0, 1, //
1238  0, 0, 0, 1, 0, //
1239  };
1240  auto green_color_filter =
1241  std::make_shared<flutter::DlMatrixColorFilter>(green_color_matrix);
1242  auto blue_color_filter =
1243  std::make_shared<flutter::DlMatrixColorFilter>(blue_color_matrix);
1244  auto blue_image_filter =
1245  std::make_shared<flutter::DlColorFilterImageFilter>(blue_color_filter);
1246 
1247  flutter::DlPaint paint;
1248  paint.setColor(flutter::DlColor::kRed());
1249  paint.setColorFilter(green_color_filter);
1250  paint.setImageFilter(blue_image_filter);
1251  builder.DrawRect(SkRect::MakeLTRB(100, 100, 500, 500), paint);
1252  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1253 }
1254 
1255 TEST_P(DisplayListTest, MaskBlursApplyCorrectlyToColorSources) {
1256  auto blur_filter = std::make_shared<flutter::DlBlurMaskFilter>(
1257  flutter::DlBlurStyle::kNormal, 10);
1258 
1259  flutter::DisplayListBuilder builder;
1260 
1261  std::array<flutter::DlColor, 2> colors = {flutter::DlColor::kBlue(),
1262  flutter::DlColor::kGreen()};
1263  std::array<float, 2> stops = {0, 1};
1264  std::array<std::shared_ptr<flutter::DlColorSource>, 2> color_sources = {
1265  std::make_shared<flutter::DlColorColorSource>(flutter::DlColor::kWhite()),
1266  flutter::DlColorSource::MakeLinear(
1267  SkPoint::Make(0, 0), SkPoint::Make(100, 50), 2, colors.data(),
1268  stops.data(), flutter::DlTileMode::kClamp)};
1269 
1270  int offset = 100;
1271  for (const auto& color_source : color_sources) {
1272  flutter::DlPaint paint;
1273  paint.setColorSource(color_source);
1274  paint.setMaskFilter(blur_filter);
1275 
1276  paint.setDrawStyle(flutter::DlDrawStyle::kFill);
1277  builder.DrawRRect(
1278  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, offset, 100, 50), 30, 30),
1279  paint);
1280  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
1281  paint.setStrokeWidth(10);
1282  builder.DrawRRect(
1283  SkRRect::MakeRectXY(SkRect::MakeXYWH(300, offset, 100, 50), 30, 30),
1284  paint);
1285 
1286  offset += 100;
1287  }
1288 
1289  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1290 }
1291 
1292 TEST_P(DisplayListTest, DrawShapes) {
1293  flutter::DisplayListBuilder builder;
1294  std::vector<flutter::DlStrokeJoin> joins = {
1295  flutter::DlStrokeJoin::kBevel,
1296  flutter::DlStrokeJoin::kRound,
1297  flutter::DlStrokeJoin::kMiter,
1298  };
1299  flutter::DlPaint paint = //
1300  flutter::DlPaint() //
1301  .setColor(flutter::DlColor::kWhite()) //
1302  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1303  .setStrokeWidth(10);
1304  flutter::DlPaint stroke_paint = //
1305  flutter::DlPaint() //
1306  .setColor(flutter::DlColor::kWhite()) //
1307  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
1308  .setStrokeWidth(10);
1309  SkPath path = SkPath().addPoly({{150, 50}, {160, 50}}, false);
1310 
1311  builder.Translate(300, 50);
1312  builder.Scale(0.8, 0.8);
1313  for (auto join : joins) {
1314  paint.setStrokeJoin(join);
1315  stroke_paint.setStrokeJoin(join);
1316  builder.DrawRect(SkRect::MakeXYWH(0, 0, 100, 100), paint);
1317  builder.DrawRect(SkRect::MakeXYWH(0, 150, 100, 100), stroke_paint);
1318  builder.DrawRRect(
1319  SkRRect::MakeRectXY(SkRect::MakeXYWH(150, 0, 100, 100), 30, 30), paint);
1320  builder.DrawRRect(
1321  SkRRect::MakeRectXY(SkRect::MakeXYWH(150, 150, 100, 100), 30, 30),
1322  stroke_paint);
1323  builder.DrawCircle({350, 50}, 50, paint);
1324  builder.DrawCircle({350, 200}, 50, stroke_paint);
1325  builder.Translate(0, 300);
1326  }
1327  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1328 }
1329 
1330 TEST_P(DisplayListTest, ClipDrawRRectWithNonCircularRadii) {
1331  flutter::DisplayListBuilder builder;
1332 
1333  flutter::DlPaint fill_paint = //
1334  flutter::DlPaint() //
1335  .setColor(flutter::DlColor::kBlue()) //
1336  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1337  .setStrokeWidth(10);
1338  flutter::DlPaint stroke_paint = //
1339  flutter::DlPaint() //
1340  .setColor(flutter::DlColor::kGreen()) //
1341  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
1342  .setStrokeWidth(10);
1343 
1344  builder.DrawRRect(
1345  SkRRect::MakeRectXY(SkRect::MakeXYWH(500, 100, 300, 300), 120, 40),
1346  fill_paint);
1347  builder.DrawRRect(
1348  SkRRect::MakeRectXY(SkRect::MakeXYWH(500, 100, 300, 300), 120, 40),
1349  stroke_paint);
1350 
1351  builder.DrawRRect(
1352  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 500, 300, 300), 40, 120),
1353  fill_paint);
1354  builder.DrawRRect(
1355  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 500, 300, 300), 40, 120),
1356  stroke_paint);
1357 
1358  flutter::DlPaint reference_paint = //
1359  flutter::DlPaint() //
1360  .setColor(flutter::DlColor::kMidGrey()) //
1361  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1362  .setStrokeWidth(10);
1363 
1364  builder.DrawRRect(
1365  SkRRect::MakeRectXY(SkRect::MakeXYWH(500, 500, 300, 300), 40, 40),
1366  reference_paint);
1367  builder.DrawRRect(
1368  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 100, 300, 300), 120, 120),
1369  reference_paint);
1370 
1371  flutter::DlPaint clip_fill_paint = //
1372  flutter::DlPaint() //
1373  .setColor(flutter::DlColor::kCyan()) //
1374  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1375  .setStrokeWidth(10);
1376 
1377  builder.Save();
1378  builder.ClipRRect(
1379  SkRRect::MakeRectXY(SkRect::MakeXYWH(900, 100, 300, 300), 120, 40));
1380  builder.DrawPaint(clip_fill_paint);
1381  builder.Restore();
1382 
1383  builder.Save();
1384  builder.ClipRRect(
1385  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 900, 300, 300), 40, 120));
1386  builder.DrawPaint(clip_fill_paint);
1387  builder.Restore();
1388 
1389  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1390 }
1391 
1392 TEST_P(DisplayListTest, DrawVerticesBlendModes) {
1393  std::vector<const char*> blend_mode_names;
1394  std::vector<flutter::DlBlendMode> blend_mode_values;
1395  {
1396  const std::vector<std::tuple<const char*, flutter::DlBlendMode>> blends = {
1397  // Pipeline blends (Porter-Duff alpha compositing)
1398  {"Clear", flutter::DlBlendMode::kClear},
1399  {"Source", flutter::DlBlendMode::kSrc},
1400  {"Destination", flutter::DlBlendMode::kDst},
1401  {"SourceOver", flutter::DlBlendMode::kSrcOver},
1402  {"DestinationOver", flutter::DlBlendMode::kDstOver},
1403  {"SourceIn", flutter::DlBlendMode::kSrcIn},
1404  {"DestinationIn", flutter::DlBlendMode::kDstIn},
1405  {"SourceOut", flutter::DlBlendMode::kSrcOut},
1406  {"DestinationOut", flutter::DlBlendMode::kDstOut},
1407  {"SourceATop", flutter::DlBlendMode::kSrcATop},
1408  {"DestinationATop", flutter::DlBlendMode::kDstATop},
1409  {"Xor", flutter::DlBlendMode::kXor},
1410  {"Plus", flutter::DlBlendMode::kPlus},
1411  {"Modulate", flutter::DlBlendMode::kModulate},
1412  // Advanced blends (color component blends)
1413  {"Screen", flutter::DlBlendMode::kScreen},
1414  {"Overlay", flutter::DlBlendMode::kOverlay},
1415  {"Darken", flutter::DlBlendMode::kDarken},
1416  {"Lighten", flutter::DlBlendMode::kLighten},
1417  {"ColorDodge", flutter::DlBlendMode::kColorDodge},
1418  {"ColorBurn", flutter::DlBlendMode::kColorBurn},
1419  {"HardLight", flutter::DlBlendMode::kHardLight},
1420  {"SoftLight", flutter::DlBlendMode::kSoftLight},
1421  {"Difference", flutter::DlBlendMode::kDifference},
1422  {"Exclusion", flutter::DlBlendMode::kExclusion},
1423  {"Multiply", flutter::DlBlendMode::kMultiply},
1424  {"Hue", flutter::DlBlendMode::kHue},
1425  {"Saturation", flutter::DlBlendMode::kSaturation},
1426  {"Color", flutter::DlBlendMode::kColor},
1427  {"Luminosity", flutter::DlBlendMode::kLuminosity},
1428  };
1429  assert(blends.size() ==
1430  static_cast<size_t>(flutter::DlBlendMode::kLastMode) + 1);
1431  for (const auto& [name, mode] : blends) {
1432  blend_mode_names.push_back(name);
1433  blend_mode_values.push_back(mode);
1434  }
1435  }
1436 
1437  auto callback = [&]() {
1438  static int current_blend_index = 3;
1439  static float dst_alpha = 1;
1440  static float src_alpha = 1;
1441  static float color0[4] = {1.0f, 0.0f, 0.0f, 1.0f};
1442  static float color1[4] = {0.0f, 1.0f, 0.0f, 1.0f};
1443  static float color2[4] = {0.0f, 0.0f, 1.0f, 1.0f};
1444  static float src_color[4] = {1.0f, 1.0f, 1.0f, 1.0f};
1445 
1446  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1447  {
1448  ImGui::ListBox("Blending mode", &current_blend_index,
1449  blend_mode_names.data(), blend_mode_names.size());
1450  ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1);
1451  ImGui::ColorEdit4("Color A", color0);
1452  ImGui::ColorEdit4("Color B", color1);
1453  ImGui::ColorEdit4("Color C", color2);
1454  ImGui::ColorEdit4("Source Color", src_color);
1455  ImGui::SliderFloat("Destination alpha", &dst_alpha, 0, 1);
1456  }
1457  ImGui::End();
1458 
1459  std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
1460  SkPoint::Make(200, 100),
1461  SkPoint::Make(300, 300)};
1462  std::vector<flutter::DlColor> colors = {
1463  toColor(color0).modulateOpacity(dst_alpha),
1464  toColor(color1).modulateOpacity(dst_alpha),
1465  toColor(color2).modulateOpacity(dst_alpha)};
1466 
1467  auto vertices = flutter::DlVertices::Make(
1468  flutter::DlVertexMode::kTriangles, 3, positions.data(),
1469  /*texture_coordinates=*/nullptr, colors.data());
1470 
1471  flutter::DisplayListBuilder builder;
1472  flutter::DlPaint paint;
1473 
1474  paint.setColor(toColor(src_color).modulateOpacity(src_alpha));
1475  builder.DrawVertices(vertices, blend_mode_values[current_blend_index],
1476  paint);
1477  return builder.Build();
1478  };
1479 
1480  ASSERT_TRUE(OpenPlaygroundHere(callback));
1481 }
1482 
1483 TEST_P(DisplayListTest, DrawPaintIgnoresMaskFilter) {
1484  flutter::DisplayListBuilder builder;
1485  builder.DrawPaint(flutter::DlPaint().setColor(flutter::DlColor::kWhite()));
1486 
1487  auto filter = flutter::DlBlurMaskFilter(flutter::DlBlurStyle::kNormal, 10.0f);
1488  builder.DrawCircle({300, 300}, 200,
1489  flutter::DlPaint().setMaskFilter(&filter));
1490 
1491  std::vector<flutter::DlColor> colors = {flutter::DlColor::kGreen(),
1492  flutter::DlColor::kGreen()};
1493  const float stops[2] = {0.0, 1.0};
1494  auto linear = flutter::DlColorSource::MakeLinear(
1495  {100.0, 100.0}, {300.0, 300.0}, 2, colors.data(), stops,
1496  flutter::DlTileMode::kRepeat);
1497  flutter::DlPaint blend_paint =
1498  flutter::DlPaint() //
1499  .setColorSource(linear) //
1500  .setBlendMode(flutter::DlBlendMode::kScreen);
1501  builder.DrawPaint(blend_paint);
1502 
1503  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1504 }
1505 
1506 TEST_P(DisplayListTest, DrawMaskBlursThatMightUseSaveLayers) {
1507  flutter::DisplayListBuilder builder;
1508  builder.DrawColor(flutter::DlColor::kWhite(), flutter::DlBlendMode::kSrc);
1509  Vector2 scale = GetContentScale();
1510  builder.Scale(scale.x, scale.y);
1511 
1512  builder.Save();
1513  // We need a small transform op to avoid a deferred save
1514  builder.Translate(1.0f, 1.0f);
1515  auto solid_filter =
1516  flutter::DlBlurMaskFilter::Make(flutter::DlBlurStyle::kSolid, 5.0f);
1517  flutter::DlPaint solid_alpha_paint =
1518  flutter::DlPaint() //
1519  .setMaskFilter(solid_filter) //
1520  .setColor(flutter::DlColor::kBlue()) //
1521  .setAlpha(0x7f);
1522  for (int x = 1; x <= 4; x++) {
1523  for (int y = 1; y <= 4; y++) {
1524  builder.DrawRect(SkRect::MakeXYWH(x * 100, y * 100, 80, 80),
1525  solid_alpha_paint);
1526  }
1527  }
1528  builder.Restore();
1529 
1530  builder.Save();
1531  builder.Translate(500.0f, 0.0f);
1532  auto normal_filter =
1533  flutter::DlBlurMaskFilter::Make(flutter::DlBlurStyle::kNormal, 5.0f);
1534  auto rotate_if = flutter::DlMatrixImageFilter::Make(
1535  SkMatrix::RotateDeg(10), flutter::DlImageSampling::kLinear);
1536  flutter::DlPaint normal_if_paint =
1537  flutter::DlPaint() //
1538  .setMaskFilter(solid_filter) //
1539  .setImageFilter(rotate_if) //
1540  .setColor(flutter::DlColor::kGreen()) //
1541  .setAlpha(0x7f);
1542  for (int x = 1; x <= 4; x++) {
1543  for (int y = 1; y <= 4; y++) {
1544  builder.DrawRect(SkRect::MakeXYWH(x * 100, y * 100, 80, 80),
1545  normal_if_paint);
1546  }
1547  }
1548  builder.Restore();
1549 
1550  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1551 }
1552 
1553 } // namespace testing
1554 } // namespace impeller
point.h
impeller::Color::Red
static constexpr Color Red()
Definition: color.h:271
solid_color_contents.h
impeller::Color
Definition: color.h:123
aiks_context.h
dl_dispatcher.h
impeller::kPi
constexpr float kPi
Definition: constants.h:26
stroke_width
const Scalar stroke_width
Definition: stroke_path_geometry.cc:297
offset
SeparatedVector2 offset
Definition: stroke_path_geometry.cc:304
impeller::DlImageImpeller::Make
static sk_sp< DlImageImpeller > Make(std::shared_ptr< Texture > texture, OwningContext owning_context=OwningContext::kIO)
Definition: dl_image_impeller.cc:23
dl_playground.h
impeller::testing::INSTANTIATE_PLAYGROUND_SUITE
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
impeller::testing::toColor
flutter::DlColor toColor(const float *components)
Definition: dl_unittests.cc:41
impeller::Point
TPoint< Scalar > Point
Definition: point.h:327
impeller::Color::ToIColor
static constexpr uint32_t ToIColor(Color color)
Convert this color to a 32-bit representation.
Definition: color.h:158
widgets.h
impeller::Color::White
static constexpr Color White()
Definition: color.h:263
clip_contents.h
impeller::testing::TEST_P
TEST_P(AiksTest, DrawAtlasNoColor)
Definition: aiks_dl_atlas_unittests.cc:78
scalar.h
impeller::DrawPlaygroundPoint
Point DrawPlaygroundPoint(PlaygroundPoint &point)
Definition: widgets.cc:9
impeller::PlaygroundPoint
Definition: widgets.h:17
constants.h
impeller::TPoint< Scalar >
scale
const Scalar scale
Definition: stroke_path_geometry.cc:301
impeller
Definition: allocation.cc:12
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::DlPlayground
Definition: dl_playground.h:16