| Flutter Impeller |

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