Flutter Impeller
dl_unittests.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <array>
6 #include <cmath>
7 #include <memory>
8 #include <vector>
9 
10 #include "flutter/display_list/dl_blend_mode.h"
11 #include "flutter/display_list/dl_builder.h"
12 #include "flutter/display_list/dl_color.h"
13 #include "flutter/display_list/dl_paint.h"
14 #include "flutter/display_list/dl_tile_mode.h"
15 #include "flutter/display_list/effects/dl_color_filter.h"
16 #include "flutter/display_list/effects/dl_color_source.h"
17 #include "flutter/display_list/effects/dl_image_filters.h"
18 #include "flutter/display_list/effects/dl_mask_filter.h"
19 #include "flutter/testing/testing.h"
20 #include "gtest/gtest.h"
32 #include "third_party/imgui/imgui.h"
33 #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::DlColorFilter::MakeBlend(
526  flutter::DlColor::kYellow(), 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::DlColorFilter::MakeBlend(
535  flutter::DlColor::kRed(), 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 = flutter::DlColorFilter::MakeMatrix(invert_color_matrix);
556  auto image_filter = flutter::DlImageFilter::MakeColorFilter(color_filter);
557 
558  paint.setImageFilter(image_filter);
559  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
560  flutter::DlImageSampling::kNearestNeighbor, &paint);
561 
562  builder.Translate(0, 700);
563  paint.setColorFilter(color_filter);
564  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
565  flutter::DlImageSampling::kNearestNeighbor, &paint);
566  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
567 }
568 
569 TEST_P(DisplayListTest, CanDrawWithImageBlurFilter) {
570  auto texture = CreateTextureForFixture("embarcadero.jpg");
571 
572  auto callback = [&]() {
573  static float sigma[] = {10, 10};
574 
575  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
576  ImGui::SliderFloat2("Sigma", sigma, 0, 100);
577  ImGui::End();
578 
579  flutter::DisplayListBuilder builder;
580  flutter::DlPaint paint;
581 
582  auto filter = flutter::DlBlurImageFilter(sigma[0], sigma[1],
583  flutter::DlTileMode::kClamp);
584  paint.setImageFilter(&filter);
585  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(200, 200),
586  flutter::DlImageSampling::kNearestNeighbor, &paint);
587 
588  return builder.Build();
589  };
590 
591  ASSERT_TRUE(OpenPlaygroundHere(callback));
592 }
593 
594 TEST_P(DisplayListTest, CanDrawWithComposeImageFilter) {
595  auto texture = CreateTextureForFixture("boston.jpg");
596  flutter::DisplayListBuilder builder;
597  flutter::DlPaint paint;
598 
599  auto dilate = std::make_shared<flutter::DlDilateImageFilter>(10.0, 10.0);
600  auto erode = std::make_shared<flutter::DlErodeImageFilter>(10.0, 10.0);
601  auto open = std::make_shared<flutter::DlComposeImageFilter>(dilate, erode);
602  auto close = std::make_shared<flutter::DlComposeImageFilter>(erode, dilate);
603 
604  paint.setImageFilter(open.get());
605  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
606  flutter::DlImageSampling::kNearestNeighbor, &paint);
607  builder.Translate(0, 700);
608  paint.setImageFilter(close.get());
609  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
610  flutter::DlImageSampling::kNearestNeighbor, &paint);
611  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
612 }
613 
614 TEST_P(DisplayListTest, CanClampTheResultingColorOfColorMatrixFilter) {
615  auto texture = CreateTextureForFixture("boston.jpg");
616  const float inner_color_matrix[20] = {
617  1, 0, 0, 0, 0, //
618  0, 1, 0, 0, 0, //
619  0, 0, 1, 0, 0, //
620  0, 0, 0, 2, 0, //
621  };
622  const float outer_color_matrix[20] = {
623  1, 0, 0, 0, 0, //
624  0, 1, 0, 0, 0, //
625  0, 0, 1, 0, 0, //
626  0, 0, 0, 0.5, 0, //
627  };
628  auto inner_color_filter =
629  flutter::DlColorFilter::MakeMatrix(inner_color_matrix);
630  auto outer_color_filter =
631  flutter::DlColorFilter::MakeMatrix(outer_color_matrix);
632  auto inner = flutter::DlImageFilter::MakeColorFilter(inner_color_filter);
633  auto outer = flutter::DlImageFilter::MakeColorFilter(outer_color_filter);
634  auto compose = std::make_shared<flutter::DlComposeImageFilter>(outer, inner);
635 
636  flutter::DisplayListBuilder builder;
637  flutter::DlPaint paint;
638  paint.setImageFilter(compose.get());
639  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
640  flutter::DlImageSampling::kNearestNeighbor, &paint);
641  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
642 }
643 
644 TEST_P(DisplayListTest, CanDrawBackdropFilter) {
645  auto texture = CreateTextureForFixture("embarcadero.jpg");
646 
647  auto callback = [&]() {
648  static float sigma[] = {10, 10};
649  static float ctm_scale = 1;
650  static bool use_bounds = true;
651  static bool draw_circle = true;
652  static bool add_clip = true;
653 
654  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
655  ImGui::SliderFloat2("Sigma", sigma, 0, 100);
656  ImGui::SliderFloat("Scale", &ctm_scale, 0, 10);
657  ImGui::NewLine();
658  ImGui::TextWrapped(
659  "If everything is working correctly, none of the options below should "
660  "impact the filter's appearance.");
661  ImGui::Checkbox("Use SaveLayer bounds", &use_bounds);
662  ImGui::Checkbox("Draw child element", &draw_circle);
663  ImGui::Checkbox("Add pre-clip", &add_clip);
664  ImGui::End();
665 
666  flutter::DisplayListBuilder builder;
667 
668  Vector2 scale = ctm_scale * GetContentScale();
669  builder.Scale(scale.x, scale.y);
670 
671  auto filter = flutter::DlBlurImageFilter(sigma[0], sigma[1],
672  flutter::DlTileMode::kClamp);
673 
674  std::optional<SkRect> bounds;
675  if (use_bounds) {
676  static PlaygroundPoint point_a(Point(350, 150), 20, Color::White());
677  static PlaygroundPoint point_b(Point(800, 600), 20, Color::White());
678  auto [p1, p2] = DrawPlaygroundLine(point_a, point_b);
679  bounds = SkRect::MakeLTRB(p1.x, p1.y, p2.x, p2.y);
680  }
681 
682  // Insert a clip to test that the backdrop filter handles stencil depths > 0
683  // correctly.
684  if (add_clip) {
685  builder.ClipRect(SkRect::MakeLTRB(0, 0, 99999, 99999),
686  flutter::DlCanvas::ClipOp::kIntersect, true);
687  }
688 
689  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(200, 200),
690  flutter::DlImageSampling::kNearestNeighbor, nullptr);
691  builder.SaveLayer(bounds.has_value() ? &bounds.value() : nullptr, nullptr,
692  &filter);
693 
694  if (draw_circle) {
695  static PlaygroundPoint center_point(Point(500, 400), 20, Color::Red());
696  auto circle_center = DrawPlaygroundPoint(center_point);
697 
698  flutter::DlPaint paint;
699  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
700  paint.setStrokeCap(flutter::DlStrokeCap::kButt);
701  paint.setStrokeJoin(flutter::DlStrokeJoin::kBevel);
702  paint.setStrokeWidth(10);
703  paint.setColor(flutter::DlColor::kRed().withAlpha(100));
704  builder.DrawCircle(SkPoint{circle_center.x, circle_center.y}, 100, paint);
705  }
706 
707  return builder.Build();
708  };
709 
710  ASSERT_TRUE(OpenPlaygroundHere(callback));
711 }
712 
713 TEST_P(DisplayListTest, CanDrawNinePatchImage) {
714  // Image is drawn with corners to scale and center pieces stretched to fit.
715  auto texture = CreateTextureForFixture("embarcadero.jpg");
716  flutter::DisplayListBuilder builder;
717  auto size = texture->GetSize();
718  builder.DrawImageNine(
719  DlImageImpeller::Make(texture),
720  SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
721  size.height * 3 / 4),
722  SkRect::MakeLTRB(0, 0, size.width * 2, size.height * 2),
723  flutter::DlFilterMode::kNearest, nullptr);
724  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
725 }
726 
727 TEST_P(DisplayListTest, CanDrawNinePatchImageCenterWidthBiggerThanDest) {
728  // Edge case, the width of the corners does not leave any room for the
729  // center slice. The center (across the vertical axis) is folded out of the
730  // resulting image.
731  auto texture = CreateTextureForFixture("embarcadero.jpg");
732  flutter::DisplayListBuilder builder;
733  auto size = texture->GetSize();
734  builder.DrawImageNine(
735  DlImageImpeller::Make(texture),
736  SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
737  size.height * 3 / 4),
738  SkRect::MakeLTRB(0, 0, size.width / 2, size.height),
739  flutter::DlFilterMode::kNearest, nullptr);
740  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
741 }
742 
743 TEST_P(DisplayListTest, CanDrawNinePatchImageCenterHeightBiggerThanDest) {
744  // Edge case, the height of the corners does not leave any room for the
745  // center slice. The center (across the horizontal axis) is folded out of the
746  // resulting image.
747  auto texture = CreateTextureForFixture("embarcadero.jpg");
748  flutter::DisplayListBuilder builder;
749  auto size = texture->GetSize();
750  builder.DrawImageNine(
751  DlImageImpeller::Make(texture),
752  SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
753  size.height * 3 / 4),
754  SkRect::MakeLTRB(0, 0, size.width, size.height / 2),
755  flutter::DlFilterMode::kNearest, nullptr);
756  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
757 }
758 
759 TEST_P(DisplayListTest, CanDrawNinePatchImageCenterBiggerThanDest) {
760  // Edge case, the width and height of the corners does not leave any
761  // room for the center slices. Only the corners are displayed.
762  auto texture = CreateTextureForFixture("embarcadero.jpg");
763  flutter::DisplayListBuilder builder;
764  auto size = texture->GetSize();
765  builder.DrawImageNine(
766  DlImageImpeller::Make(texture),
767  SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
768  size.height * 3 / 4),
769  SkRect::MakeLTRB(0, 0, size.width / 2, size.height / 2),
770  flutter::DlFilterMode::kNearest, nullptr);
771  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
772 }
773 
774 TEST_P(DisplayListTest, CanDrawNinePatchImageCornersScaledDown) {
775  // Edge case, there is not enough room for the corners to be drawn
776  // without scaling them down.
777  auto texture = CreateTextureForFixture("embarcadero.jpg");
778  flutter::DisplayListBuilder builder;
779  auto size = texture->GetSize();
780  builder.DrawImageNine(
781  DlImageImpeller::Make(texture),
782  SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
783  size.height * 3 / 4),
784  SkRect::MakeLTRB(0, 0, size.width / 4, size.height / 4),
785  flutter::DlFilterMode::kNearest, nullptr);
786  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
787 }
788 
789 TEST_P(DisplayListTest, NinePatchImagePrecision) {
790  // Draw a nine patch image with colored corners and verify that the corner
791  // color does not leak outside the intended region.
792  auto texture = CreateTextureForFixture("nine_patch_corners.png");
793  flutter::DisplayListBuilder builder;
794  builder.DrawImageNine(DlImageImpeller::Make(texture),
795  SkIRect::MakeXYWH(10, 10, 1, 1),
796  SkRect::MakeXYWH(0, 0, 200, 100),
797  flutter::DlFilterMode::kNearest, nullptr);
798  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
799 }
800 
801 TEST_P(DisplayListTest, CanDrawPoints) {
802  flutter::DisplayListBuilder builder;
803  SkPoint points[7] = {
804  {0, 0}, //
805  {100, 100}, //
806  {100, 0}, //
807  {0, 100}, //
808  {0, 0}, //
809  {48, 48}, //
810  {52, 52}, //
811  };
812  std::vector<flutter::DlStrokeCap> caps = {
813  flutter::DlStrokeCap::kButt,
814  flutter::DlStrokeCap::kRound,
815  flutter::DlStrokeCap::kSquare,
816  };
817  flutter::DlPaint paint =
818  flutter::DlPaint() //
819  .setColor(flutter::DlColor::kYellow().withAlpha(127)) //
820  .setStrokeWidth(20);
821  builder.Translate(50, 50);
822  for (auto cap : caps) {
823  paint.setStrokeCap(cap);
824  builder.Save();
825  builder.DrawPoints(flutter::DlCanvas::PointMode::kPoints, 7, points, paint);
826  builder.Translate(150, 0);
827  builder.DrawPoints(flutter::DlCanvas::PointMode::kLines, 5, points, paint);
828  builder.Translate(150, 0);
829  builder.DrawPoints(flutter::DlCanvas::PointMode::kPolygon, 5, points,
830  paint);
831  builder.Restore();
832  builder.Translate(0, 150);
833  }
834  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
835 }
836 
837 TEST_P(DisplayListTest, CanDrawZeroLengthLine) {
838  flutter::DisplayListBuilder builder;
839  std::vector<flutter::DlStrokeCap> caps = {
840  flutter::DlStrokeCap::kButt,
841  flutter::DlStrokeCap::kRound,
842  flutter::DlStrokeCap::kSquare,
843  };
844  flutter::DlPaint paint =
845  flutter::DlPaint() //
846  .setColor(flutter::DlColor::kYellow().withAlpha(127)) //
847  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
848  .setStrokeCap(flutter::DlStrokeCap::kButt) //
849  .setStrokeWidth(20);
850  SkPath path = SkPath().addPoly({{150, 50}, {150, 50}}, false);
851  for (auto cap : caps) {
852  paint.setStrokeCap(cap);
853  builder.DrawLine(SkPoint{50, 50}, SkPoint{50, 50}, paint);
854  builder.DrawPath(path, paint);
855  builder.Translate(0, 150);
856  }
857  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
858 }
859 
860 TEST_P(DisplayListTest, CanDrawShadow) {
861  flutter::DisplayListBuilder builder;
862  flutter::DlPaint paint;
863 
864  auto content_scale = GetContentScale() * 0.8;
865  builder.Scale(content_scale.x, content_scale.y);
866 
867  constexpr size_t star_spikes = 5;
868  constexpr SkScalar half_spike_rotation = kPi / star_spikes;
869  constexpr SkScalar radius = 40;
870  constexpr SkScalar spike_size = 10;
871  constexpr SkScalar outer_radius = radius + spike_size;
872  constexpr SkScalar inner_radius = radius - spike_size;
873  std::array<SkPoint, star_spikes * 2> star;
874  for (size_t i = 0; i < star_spikes; i++) {
875  const SkScalar rotation = half_spike_rotation * i * 2;
876  star[i * 2] = SkPoint::Make(50 + std::sin(rotation) * outer_radius,
877  50 - std::cos(rotation) * outer_radius);
878  star[i * 2 + 1] = SkPoint::Make(
879  50 + std::sin(rotation + half_spike_rotation) * inner_radius,
880  50 - std::cos(rotation + half_spike_rotation) * inner_radius);
881  }
882 
883  std::array<SkPath, 4> paths = {
884  SkPath{}.addRect(SkRect::MakeXYWH(0, 0, 200, 100)),
885  SkPath{}.addRRect(
886  SkRRect::MakeRectXY(SkRect::MakeXYWH(20, 0, 200, 100), 30, 30)),
887  SkPath{}.addCircle(100, 50, 50),
888  SkPath{}.addPoly(star.data(), star.size(), true),
889  };
890  paint.setColor(flutter::DlColor::kWhite());
891  builder.DrawPaint(paint);
892  paint.setColor(flutter::DlColor::kCyan());
893  builder.Translate(100, 50);
894  for (size_t x = 0; x < paths.size(); x++) {
895  builder.Save();
896  for (size_t y = 0; y < 6; y++) {
897  builder.DrawShadow(paths[x], flutter::DlColor::kBlack(), 3 + y * 8, false,
898  1);
899  builder.DrawPath(paths[x], paint);
900  builder.Translate(0, 150);
901  }
902  builder.Restore();
903  builder.Translate(250, 0);
904  }
905 
906  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
907 }
908 
909 TEST_P(DisplayListTest, CanDrawZeroWidthLine) {
910  flutter::DisplayListBuilder builder;
911  std::vector<flutter::DlStrokeCap> caps = {
912  flutter::DlStrokeCap::kButt,
913  flutter::DlStrokeCap::kRound,
914  flutter::DlStrokeCap::kSquare,
915  };
916  flutter::DlPaint paint = //
917  flutter::DlPaint() //
918  .setColor(flutter::DlColor::kWhite()) //
919  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
920  .setStrokeWidth(0);
921  flutter::DlPaint outline_paint = //
922  flutter::DlPaint() //
923  .setColor(flutter::DlColor::kYellow()) //
924  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
925  .setStrokeCap(flutter::DlStrokeCap::kSquare) //
926  .setStrokeWidth(1);
927  SkPath path = SkPath().addPoly({{150, 50}, {160, 50}}, false);
928  for (auto cap : caps) {
929  paint.setStrokeCap(cap);
930  builder.DrawLine(SkPoint{50, 50}, SkPoint{60, 50}, paint);
931  builder.DrawRect(SkRect{45, 45, 65, 55}, outline_paint);
932  builder.DrawLine(SkPoint{100, 50}, SkPoint{100, 50}, paint);
933  if (cap != flutter::DlStrokeCap::kButt) {
934  builder.DrawRect(SkRect{95, 45, 105, 55}, outline_paint);
935  }
936  builder.DrawPath(path, paint);
937  builder.DrawRect(path.getBounds().makeOutset(5, 5), outline_paint);
938  builder.Translate(0, 150);
939  }
940  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
941 }
942 
943 TEST_P(DisplayListTest, CanDrawWithMatrixFilter) {
944  auto boston = CreateTextureForFixture("boston.jpg");
945 
946  auto callback = [&]() {
947  static int selected_matrix_type = 0;
948  const char* matrix_type_names[] = {"Matrix", "Local Matrix"};
949 
950  static float ctm_translation[2] = {200, 200};
951  static float ctm_scale[2] = {0.65, 0.65};
952  static float ctm_skew[2] = {0, 0};
953 
954  static bool enable = true;
955  static float translation[2] = {100, 100};
956  static float scale[2] = {0.8, 0.8};
957  static float skew[2] = {0.2, 0.2};
958 
959  static bool enable_savelayer = true;
960 
961  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
962  {
963  ImGui::Combo("Filter type", &selected_matrix_type, matrix_type_names,
964  sizeof(matrix_type_names) / sizeof(char*));
965 
966  ImGui::TextWrapped("Current Transform");
967  ImGui::SliderFloat2("CTM Translation", ctm_translation, 0, 1000);
968  ImGui::SliderFloat2("CTM Scale", ctm_scale, 0, 3);
969  ImGui::SliderFloat2("CTM Skew", ctm_skew, -3, 3);
970 
971  ImGui::TextWrapped(
972  "MatrixFilter and LocalMatrixFilter modify the CTM in the same way. "
973  "The only difference is that MatrixFilter doesn't affect the effect "
974  "transform, whereas LocalMatrixFilter does.");
975  // Note: See this behavior in:
976  // https://fiddle.skia.org/c/6cbb551ab36d06f163db8693972be954
977  ImGui::Checkbox("Enable", &enable);
978  ImGui::SliderFloat2("Filter Translation", translation, 0, 1000);
979  ImGui::SliderFloat2("Filter Scale", scale, 0, 3);
980  ImGui::SliderFloat2("Filter Skew", skew, -3, 3);
981 
982  ImGui::TextWrapped(
983  "Rendering the filtered image within a layer can expose bounds "
984  "issues. If the rendered image gets cut off when this setting is "
985  "enabled, there's a coverage bug in the filter.");
986  ImGui::Checkbox("Render in layer", &enable_savelayer);
987  }
988  ImGui::End();
989 
990  flutter::DisplayListBuilder builder;
991  flutter::DlPaint paint;
992 
993  if (enable_savelayer) {
994  builder.SaveLayer(nullptr, nullptr);
995  }
996  {
997  auto content_scale = GetContentScale();
998  builder.Scale(content_scale.x, content_scale.y);
999 
1000  // Set the current transform
1001  auto ctm_matrix =
1002  SkMatrix::MakeAll(ctm_scale[0], ctm_skew[0], ctm_translation[0], //
1003  ctm_skew[1], ctm_scale[1], ctm_translation[1], //
1004  0, 0, 1);
1005  builder.Transform(ctm_matrix);
1006 
1007  // Set the matrix filter
1008  auto filter_matrix =
1009  Matrix::MakeRow(scale[0], skew[0], 0.0f, translation[0], //
1010  skew[1], scale[1], 0.0f, translation[1], //
1011  0.0f, 0.0f, 1.0f, 0.0f, //
1012  0.0f, 0.0f, 0.0f, 1.0f);
1013 
1014  if (enable) {
1015  switch (selected_matrix_type) {
1016  case 0: {
1017  auto filter = flutter::DlMatrixImageFilter(
1018  filter_matrix, flutter::DlImageSampling::kLinear);
1019  paint.setImageFilter(&filter);
1020  break;
1021  }
1022  case 1: {
1023  auto internal_filter =
1024  flutter::DlBlurImageFilter(10, 10, flutter::DlTileMode::kDecal)
1025  .shared();
1026  auto filter = flutter::DlLocalMatrixImageFilter(filter_matrix,
1027  internal_filter);
1028  paint.setImageFilter(&filter);
1029  break;
1030  }
1031  }
1032  }
1033 
1034  builder.DrawImage(DlImageImpeller::Make(boston), SkPoint{},
1035  flutter::DlImageSampling::kLinear, &paint);
1036  }
1037  if (enable_savelayer) {
1038  builder.Restore();
1039  }
1040 
1041  return builder.Build();
1042  };
1043 
1044  ASSERT_TRUE(OpenPlaygroundHere(callback));
1045 }
1046 
1047 TEST_P(DisplayListTest, CanDrawWithMatrixFilterWhenSavingLayer) {
1048  auto callback = [&]() {
1049  static float translation[2] = {0, 0};
1050  static bool enable_save_layer = true;
1051 
1052  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1053  ImGui::SliderFloat2("Translation", translation, -130, 130);
1054  ImGui::Checkbox("Enable save layer", &enable_save_layer);
1055  ImGui::End();
1056 
1057  flutter::DisplayListBuilder builder;
1058  builder.Save();
1059  builder.Scale(2.0, 2.0);
1060  flutter::DlPaint paint;
1061  paint.setColor(flutter::DlColor::kYellow());
1062  builder.DrawRect(SkRect::MakeWH(300, 300), paint);
1063  paint.setStrokeWidth(1.0);
1064  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
1065  paint.setColor(flutter::DlColor::kBlack().withAlpha(0x80));
1066  builder.DrawLine(SkPoint::Make(150, 0), SkPoint::Make(150, 300), paint);
1067  builder.DrawLine(SkPoint::Make(0, 150), SkPoint::Make(300, 150), paint);
1068 
1069  flutter::DlPaint save_paint;
1070  SkRect bounds = SkRect::MakeXYWH(100, 100, 100, 100);
1071  Matrix translate_matrix =
1072  Matrix::MakeTranslation({translation[0], translation[1]});
1073  if (enable_save_layer) {
1074  auto filter = flutter::DlMatrixImageFilter(
1075  translate_matrix, flutter::DlImageSampling::kNearestNeighbor);
1076  save_paint.setImageFilter(filter.shared());
1077  builder.SaveLayer(&bounds, &save_paint);
1078  } else {
1079  builder.Save();
1080  builder.Transform(translate_matrix);
1081  }
1082 
1083  Matrix filter_matrix;
1084  filter_matrix.Translate({150, 150});
1085  filter_matrix.Scale({0.2f, 0.2f});
1086  filter_matrix.Translate({-150, -150});
1087  auto filter = flutter::DlMatrixImageFilter(
1088  filter_matrix, flutter::DlImageSampling::kNearestNeighbor);
1089 
1090  save_paint.setImageFilter(filter.shared());
1091 
1092  builder.SaveLayer(&bounds, &save_paint);
1093  flutter::DlPaint paint2;
1094  paint2.setColor(flutter::DlColor::kBlue());
1095  builder.DrawRect(bounds, paint2);
1096  builder.Restore();
1097  builder.Restore();
1098  return builder.Build();
1099  };
1100 
1101  ASSERT_TRUE(OpenPlaygroundHere(callback));
1102 }
1103 
1104 TEST_P(DisplayListTest, CanDrawRectWithLinearToSrgbColorFilter) {
1105  flutter::DlPaint paint;
1106  paint.setColor(flutter::DlColor(0xFF2196F3).withAlpha(128));
1107  flutter::DisplayListBuilder builder;
1108  paint.setColorFilter(flutter::DlColorFilter::MakeLinearToSrgbGamma());
1109  builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), paint);
1110  builder.Translate(0, 200);
1111 
1112  paint.setColorFilter(flutter::DlColorFilter::MakeSrgbToLinearGamma());
1113  builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), paint);
1114 
1115  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1116 }
1117 
1118 TEST_P(DisplayListTest, CanDrawPaintWithColorSource) {
1119  const flutter::DlColor colors[2] = {
1120  flutter::DlColor(0xFFF44336),
1121  flutter::DlColor(0xFF2196F3),
1122  };
1123  const float stops[2] = {0.0, 1.0};
1124  flutter::DlPaint paint;
1125  flutter::DisplayListBuilder builder;
1126  auto clip_bounds = SkRect::MakeWH(300.0, 300.0);
1127  builder.Save();
1128  builder.Translate(100, 100);
1129  builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
1130  auto linear =
1131  flutter::DlColorSource::MakeLinear({0.0, 0.0}, {100.0, 100.0}, 2, colors,
1132  stops, flutter::DlTileMode::kRepeat);
1133  paint.setColorSource(linear);
1134  builder.DrawPaint(paint);
1135  builder.Restore();
1136 
1137  builder.Save();
1138  builder.Translate(500, 100);
1139  builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
1140  auto radial = flutter::DlColorSource::MakeRadial(
1141  {100.0, 100.0}, 100.0, 2, colors, stops, flutter::DlTileMode::kRepeat);
1142  paint.setColorSource(radial);
1143  builder.DrawPaint(paint);
1144  builder.Restore();
1145 
1146  builder.Save();
1147  builder.Translate(100, 500);
1148  builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
1149  auto sweep =
1150  flutter::DlColorSource::MakeSweep({100.0, 100.0}, 180.0, 270.0, 2, colors,
1151  stops, flutter::DlTileMode::kRepeat);
1152  paint.setColorSource(sweep);
1153  builder.DrawPaint(paint);
1154  builder.Restore();
1155 
1156  builder.Save();
1157  builder.Translate(500, 500);
1158  builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
1159  auto texture = CreateTextureForFixture("table_mountain_nx.png");
1160  auto image = flutter::DlColorSource::MakeImage(DlImageImpeller::Make(texture),
1161  flutter::DlTileMode::kRepeat,
1162  flutter::DlTileMode::kRepeat);
1163  paint.setColorSource(image);
1164  builder.DrawPaint(paint);
1165  builder.Restore();
1166 
1167  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1168 }
1169 
1170 TEST_P(DisplayListTest, CanBlendDstOverAndDstCorrectly) {
1171  flutter::DisplayListBuilder builder;
1172 
1173  {
1174  builder.SaveLayer(nullptr, nullptr);
1175  builder.Translate(100, 100);
1176  flutter::DlPaint paint;
1177  paint.setColor(flutter::DlColor::kRed());
1178  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1179  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1180  paint.setBlendMode(flutter::DlBlendMode::kSrcOver);
1181  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1182  builder.Restore();
1183  }
1184  {
1185  builder.SaveLayer(nullptr, nullptr);
1186  builder.Translate(300, 100);
1187  flutter::DlPaint paint;
1188  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1189  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1190  paint.setColor(flutter::DlColor::kRed());
1191  paint.setBlendMode(flutter::DlBlendMode::kDstOver);
1192  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1193  builder.Restore();
1194  }
1195  {
1196  builder.SaveLayer(nullptr, nullptr);
1197  builder.Translate(100, 300);
1198  flutter::DlPaint paint;
1199  paint.setColor(flutter::DlColor::kRed());
1200  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1201  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1202  paint.setBlendMode(flutter::DlBlendMode::kSrc);
1203  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1204  builder.Restore();
1205  }
1206  {
1207  builder.SaveLayer(nullptr, nullptr);
1208  builder.Translate(300, 300);
1209  flutter::DlPaint paint;
1210  paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
1211  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1212  paint.setColor(flutter::DlColor::kRed());
1213  paint.setBlendMode(flutter::DlBlendMode::kDst);
1214  builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
1215  builder.Restore();
1216  }
1217 
1218  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1219 }
1220 
1221 TEST_P(DisplayListTest, CanDrawCorrectlyWithColorFilterAndImageFilter) {
1222  flutter::DisplayListBuilder builder;
1223  const float green_color_matrix[20] = {
1224  0, 0, 0, 0, 0, //
1225  0, 0, 0, 0, 1, //
1226  0, 0, 0, 0, 0, //
1227  0, 0, 0, 1, 0, //
1228  };
1229  const float blue_color_matrix[20] = {
1230  0, 0, 0, 0, 0, //
1231  0, 0, 0, 0, 0, //
1232  0, 0, 0, 0, 1, //
1233  0, 0, 0, 1, 0, //
1234  };
1235  auto green_color_filter =
1236  flutter::DlColorFilter::MakeMatrix(green_color_matrix);
1237  auto blue_color_filter =
1238  flutter::DlColorFilter::MakeMatrix(blue_color_matrix);
1239  auto blue_image_filter =
1240  flutter::DlImageFilter::MakeColorFilter(blue_color_filter);
1241 
1242  flutter::DlPaint paint;
1243  paint.setColor(flutter::DlColor::kRed());
1244  paint.setColorFilter(green_color_filter);
1245  paint.setImageFilter(blue_image_filter);
1246  builder.DrawRect(SkRect::MakeLTRB(100, 100, 500, 500), paint);
1247  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1248 }
1249 
1250 TEST_P(DisplayListTest, MaskBlursApplyCorrectlyToColorSources) {
1251  auto blur_filter = std::make_shared<flutter::DlBlurMaskFilter>(
1252  flutter::DlBlurStyle::kNormal, 10);
1253 
1254  flutter::DisplayListBuilder builder;
1255 
1256  std::array<flutter::DlColor, 2> colors = {flutter::DlColor::kBlue(),
1257  flutter::DlColor::kGreen()};
1258  std::array<float, 2> stops = {0, 1};
1259  auto texture = CreateTextureForFixture("airplane.jpg");
1260  auto matrix = flutter::DlMatrix::MakeTranslation({-300, -110});
1261  std::array<std::shared_ptr<flutter::DlColorSource>, 2> color_sources = {
1262  flutter::DlColorSource::MakeImage(
1263  DlImageImpeller::Make(texture), flutter::DlTileMode::kRepeat,
1264  flutter::DlTileMode::kRepeat, flutter::DlImageSampling::kLinear,
1265  &matrix),
1266  flutter::DlColorSource::MakeLinear(
1267  flutter::DlPoint(0, 0), flutter::DlPoint(100, 50), 2, colors.data(),
1268  stops.data(), flutter::DlTileMode::kClamp),
1269  };
1270 
1271  builder.Save();
1272  builder.Translate(0, 100);
1273  for (const auto& color_source : color_sources) {
1274  flutter::DlPaint paint;
1275  paint.setColorSource(color_source);
1276  paint.setMaskFilter(blur_filter);
1277 
1278  builder.Save();
1279  builder.Translate(100, 0);
1280  paint.setDrawStyle(flutter::DlDrawStyle::kFill);
1281  builder.DrawRRect(SkRRect::MakeRectXY(SkRect::MakeWH(100, 50), 30, 30),
1282  paint);
1283 
1284  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
1285  paint.setStrokeWidth(10);
1286  builder.Translate(200, 0);
1287  builder.DrawRRect(SkRRect::MakeRectXY(SkRect::MakeWH(100, 50), 30, 30),
1288  paint);
1289 
1290  builder.Restore();
1291  builder.Translate(0, 100);
1292  }
1293  builder.Restore();
1294 
1295  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1296 }
1297 
1298 TEST_P(DisplayListTest, DrawShapes) {
1299  flutter::DisplayListBuilder builder;
1300  std::vector<flutter::DlStrokeJoin> joins = {
1301  flutter::DlStrokeJoin::kBevel,
1302  flutter::DlStrokeJoin::kRound,
1303  flutter::DlStrokeJoin::kMiter,
1304  };
1305  flutter::DlPaint paint = //
1306  flutter::DlPaint() //
1307  .setColor(flutter::DlColor::kWhite()) //
1308  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1309  .setStrokeWidth(10);
1310  flutter::DlPaint stroke_paint = //
1311  flutter::DlPaint() //
1312  .setColor(flutter::DlColor::kWhite()) //
1313  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
1314  .setStrokeWidth(10);
1315  SkPath path = SkPath().addPoly({{150, 50}, {160, 50}}, false);
1316 
1317  builder.Translate(300, 50);
1318  builder.Scale(0.8, 0.8);
1319  for (auto join : joins) {
1320  paint.setStrokeJoin(join);
1321  stroke_paint.setStrokeJoin(join);
1322  builder.DrawRect(SkRect::MakeXYWH(0, 0, 100, 100), paint);
1323  builder.DrawRect(SkRect::MakeXYWH(0, 150, 100, 100), stroke_paint);
1324  builder.DrawRRect(
1325  SkRRect::MakeRectXY(SkRect::MakeXYWH(150, 0, 100, 100), 30, 30), paint);
1326  builder.DrawRRect(
1327  SkRRect::MakeRectXY(SkRect::MakeXYWH(150, 150, 100, 100), 30, 30),
1328  stroke_paint);
1329  builder.DrawCircle(SkPoint{350, 50}, 50, paint);
1330  builder.DrawCircle(SkPoint{350, 200}, 50, stroke_paint);
1331  builder.Translate(0, 300);
1332  }
1333  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1334 }
1335 
1336 TEST_P(DisplayListTest, ClipDrawRRectWithNonCircularRadii) {
1337  flutter::DisplayListBuilder builder;
1338 
1339  flutter::DlPaint fill_paint = //
1340  flutter::DlPaint() //
1341  .setColor(flutter::DlColor::kBlue()) //
1342  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1343  .setStrokeWidth(10);
1344  flutter::DlPaint stroke_paint = //
1345  flutter::DlPaint() //
1346  .setColor(flutter::DlColor::kGreen()) //
1347  .setDrawStyle(flutter::DlDrawStyle::kStroke) //
1348  .setStrokeWidth(10);
1349 
1350  builder.DrawRRect(
1351  SkRRect::MakeRectXY(SkRect::MakeXYWH(500, 100, 300, 300), 120, 40),
1352  fill_paint);
1353  builder.DrawRRect(
1354  SkRRect::MakeRectXY(SkRect::MakeXYWH(500, 100, 300, 300), 120, 40),
1355  stroke_paint);
1356 
1357  builder.DrawRRect(
1358  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 500, 300, 300), 40, 120),
1359  fill_paint);
1360  builder.DrawRRect(
1361  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 500, 300, 300), 40, 120),
1362  stroke_paint);
1363 
1364  flutter::DlPaint reference_paint = //
1365  flutter::DlPaint() //
1366  .setColor(flutter::DlColor::kMidGrey()) //
1367  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1368  .setStrokeWidth(10);
1369 
1370  builder.DrawRRect(
1371  SkRRect::MakeRectXY(SkRect::MakeXYWH(500, 500, 300, 300), 40, 40),
1372  reference_paint);
1373  builder.DrawRRect(
1374  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 100, 300, 300), 120, 120),
1375  reference_paint);
1376 
1377  flutter::DlPaint clip_fill_paint = //
1378  flutter::DlPaint() //
1379  .setColor(flutter::DlColor::kCyan()) //
1380  .setDrawStyle(flutter::DlDrawStyle::kFill) //
1381  .setStrokeWidth(10);
1382 
1383  builder.Save();
1384  builder.ClipRRect(
1385  SkRRect::MakeRectXY(SkRect::MakeXYWH(900, 100, 300, 300), 120, 40));
1386  builder.DrawPaint(clip_fill_paint);
1387  builder.Restore();
1388 
1389  builder.Save();
1390  builder.ClipRRect(
1391  SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 900, 300, 300), 40, 120));
1392  builder.DrawPaint(clip_fill_paint);
1393  builder.Restore();
1394 
1395  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1396 }
1397 
1398 TEST_P(DisplayListTest, DrawVerticesBlendModes) {
1399  std::vector<const char*> blend_mode_names;
1400  std::vector<flutter::DlBlendMode> blend_mode_values;
1401  {
1402  const std::vector<std::tuple<const char*, flutter::DlBlendMode>> blends = {
1403  // Pipeline blends (Porter-Duff alpha compositing)
1404  {"Clear", flutter::DlBlendMode::kClear},
1405  {"Source", flutter::DlBlendMode::kSrc},
1406  {"Destination", flutter::DlBlendMode::kDst},
1407  {"SourceOver", flutter::DlBlendMode::kSrcOver},
1408  {"DestinationOver", flutter::DlBlendMode::kDstOver},
1409  {"SourceIn", flutter::DlBlendMode::kSrcIn},
1410  {"DestinationIn", flutter::DlBlendMode::kDstIn},
1411  {"SourceOut", flutter::DlBlendMode::kSrcOut},
1412  {"DestinationOut", flutter::DlBlendMode::kDstOut},
1413  {"SourceATop", flutter::DlBlendMode::kSrcATop},
1414  {"DestinationATop", flutter::DlBlendMode::kDstATop},
1415  {"Xor", flutter::DlBlendMode::kXor},
1416  {"Plus", flutter::DlBlendMode::kPlus},
1417  {"Modulate", flutter::DlBlendMode::kModulate},
1418  // Advanced blends (color component blends)
1419  {"Screen", flutter::DlBlendMode::kScreen},
1420  {"Overlay", flutter::DlBlendMode::kOverlay},
1421  {"Darken", flutter::DlBlendMode::kDarken},
1422  {"Lighten", flutter::DlBlendMode::kLighten},
1423  {"ColorDodge", flutter::DlBlendMode::kColorDodge},
1424  {"ColorBurn", flutter::DlBlendMode::kColorBurn},
1425  {"HardLight", flutter::DlBlendMode::kHardLight},
1426  {"SoftLight", flutter::DlBlendMode::kSoftLight},
1427  {"Difference", flutter::DlBlendMode::kDifference},
1428  {"Exclusion", flutter::DlBlendMode::kExclusion},
1429  {"Multiply", flutter::DlBlendMode::kMultiply},
1430  {"Hue", flutter::DlBlendMode::kHue},
1431  {"Saturation", flutter::DlBlendMode::kSaturation},
1432  {"Color", flutter::DlBlendMode::kColor},
1433  {"Luminosity", flutter::DlBlendMode::kLuminosity},
1434  };
1435  assert(blends.size() ==
1436  static_cast<size_t>(flutter::DlBlendMode::kLastMode) + 1);
1437  for (const auto& [name, mode] : blends) {
1438  blend_mode_names.push_back(name);
1439  blend_mode_values.push_back(mode);
1440  }
1441  }
1442 
1443  auto callback = [&]() {
1444  static int current_blend_index = 3;
1445  static float dst_alpha = 1;
1446  static float src_alpha = 1;
1447  static float color0[4] = {1.0f, 0.0f, 0.0f, 1.0f};
1448  static float color1[4] = {0.0f, 1.0f, 0.0f, 1.0f};
1449  static float color2[4] = {0.0f, 0.0f, 1.0f, 1.0f};
1450  static float src_color[4] = {1.0f, 1.0f, 1.0f, 1.0f};
1451 
1452  ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1453  {
1454  ImGui::ListBox("Blending mode", &current_blend_index,
1455  blend_mode_names.data(), blend_mode_names.size());
1456  ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1);
1457  ImGui::ColorEdit4("Color A", color0);
1458  ImGui::ColorEdit4("Color B", color1);
1459  ImGui::ColorEdit4("Color C", color2);
1460  ImGui::ColorEdit4("Source Color", src_color);
1461  ImGui::SliderFloat("Destination alpha", &dst_alpha, 0, 1);
1462  }
1463  ImGui::End();
1464 
1465  std::vector<DlPoint> positions = {DlPoint(100, 300), //
1466  DlPoint(200, 100), //
1467  DlPoint(300, 300)};
1468  std::vector<flutter::DlColor> colors = {
1469  toColor(color0).modulateOpacity(dst_alpha),
1470  toColor(color1).modulateOpacity(dst_alpha),
1471  toColor(color2).modulateOpacity(dst_alpha)};
1472 
1473  auto vertices = flutter::DlVertices::Make(
1474  flutter::DlVertexMode::kTriangles, 3, positions.data(),
1475  /*texture_coordinates=*/nullptr, colors.data());
1476 
1477  flutter::DisplayListBuilder builder;
1478  flutter::DlPaint paint;
1479 
1480  paint.setColor(toColor(src_color).modulateOpacity(src_alpha));
1481  builder.DrawVertices(vertices, blend_mode_values[current_blend_index],
1482  paint);
1483  return builder.Build();
1484  };
1485 
1486  ASSERT_TRUE(OpenPlaygroundHere(callback));
1487 }
1488 
1489 TEST_P(DisplayListTest, DrawPaintIgnoresMaskFilter) {
1490  flutter::DisplayListBuilder builder;
1491  builder.DrawPaint(flutter::DlPaint().setColor(flutter::DlColor::kWhite()));
1492 
1493  auto filter = flutter::DlBlurMaskFilter(flutter::DlBlurStyle::kNormal, 10.0f);
1494  builder.DrawCircle(SkPoint{300, 300}, 200,
1495  flutter::DlPaint().setMaskFilter(&filter));
1496 
1497  std::vector<flutter::DlColor> colors = {flutter::DlColor::kGreen(),
1498  flutter::DlColor::kGreen()};
1499  const float stops[2] = {0.0, 1.0};
1500  auto linear = flutter::DlColorSource::MakeLinear(
1501  {100.0, 100.0}, {300.0, 300.0}, 2, colors.data(), stops,
1502  flutter::DlTileMode::kRepeat);
1503  flutter::DlPaint blend_paint =
1504  flutter::DlPaint() //
1505  .setColorSource(linear) //
1506  .setBlendMode(flutter::DlBlendMode::kScreen);
1507  builder.DrawPaint(blend_paint);
1508 
1509  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1510 }
1511 
1512 TEST_P(DisplayListTest, DrawMaskBlursThatMightUseSaveLayers) {
1513  flutter::DisplayListBuilder builder;
1514  builder.DrawColor(flutter::DlColor::kWhite(), flutter::DlBlendMode::kSrc);
1515  Vector2 scale = GetContentScale();
1516  builder.Scale(scale.x, scale.y);
1517 
1518  builder.Save();
1519  // We need a small transform op to avoid a deferred save
1520  builder.Translate(1.0f, 1.0f);
1521  auto solid_filter =
1522  flutter::DlBlurMaskFilter::Make(flutter::DlBlurStyle::kSolid, 5.0f);
1523  flutter::DlPaint solid_alpha_paint =
1524  flutter::DlPaint() //
1525  .setMaskFilter(solid_filter) //
1526  .setColor(flutter::DlColor::kBlue()) //
1527  .setAlpha(0x7f);
1528  for (int x = 1; x <= 4; x++) {
1529  for (int y = 1; y <= 4; y++) {
1530  builder.DrawRect(SkRect::MakeXYWH(x * 100, y * 100, 80, 80),
1531  solid_alpha_paint);
1532  }
1533  }
1534  builder.Restore();
1535 
1536  builder.Save();
1537  builder.Translate(500.0f, 0.0f);
1538  auto normal_filter =
1539  flutter::DlBlurMaskFilter::Make(flutter::DlBlurStyle::kNormal, 5.0f);
1540  auto rotate_if = flutter::DlMatrixImageFilter::Make(
1541  Matrix::MakeRotationZ(Degrees(10)), flutter::DlImageSampling::kLinear);
1542  flutter::DlPaint normal_if_paint =
1543  flutter::DlPaint() //
1544  .setMaskFilter(solid_filter) //
1545  .setImageFilter(rotate_if) //
1546  .setColor(flutter::DlColor::kGreen()) //
1547  .setAlpha(0x7f);
1548  for (int x = 1; x <= 4; x++) {
1549  for (int y = 1; y <= 4; y++) {
1550  builder.DrawRect(SkRect::MakeXYWH(x * 100, y * 100, 80, 80),
1551  normal_if_paint);
1552  }
1553  }
1554  builder.Restore();
1555 
1556  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1557 }
1558 
1559 } // namespace testing
1560 } // namespace impeller
static sk_sp< DlImageImpeller > Make(std::shared_ptr< Texture > texture, OwningContext owning_context=OwningContext::kIO)
int32_t x
TEST_P(AiksTest, DrawAtlasNoColor)
flutter::DlColor toColor(const float *components)
Definition: dl_unittests.cc:41
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
constexpr float kPi
Definition: constants.h:26
Point DrawPlaygroundPoint(PlaygroundPoint &point)
Definition: widgets.cc:9
std::tuple< Point, Point > DrawPlaygroundLine(PlaygroundPoint &point_a, PlaygroundPoint &point_b)
Definition: widgets.cc:50
TPoint< Scalar > Point
Definition: point.h:327
flutter::DlPoint DlPoint
Definition: dl_dispatcher.h:24
const Scalar stroke_width
const Scalar scale
static constexpr uint32_t ToIColor(Color color)
Convert this color to a 32-bit representation.
Definition: color.h:158
static constexpr Color White()
Definition: color.h:263
static constexpr Color Red()
Definition: color.h:271
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
constexpr Matrix Translate(const Vector3 &t) const
Definition: matrix.h:250
static constexpr Matrix MakeRow(Scalar m0, Scalar m1, Scalar m2, Scalar m3, Scalar m4, Scalar m5, Scalar m6, Scalar m7, Scalar m8, Scalar m9, Scalar m10, Scalar m11, Scalar m12, Scalar m13, Scalar m14, Scalar m15)
Definition: matrix.h:83
constexpr Matrix Scale(const Vector3 &s) const
Definition: matrix.h:262
static Matrix MakeRotationZ(Radians r)
Definition: matrix.h:223