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