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