Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
aiks_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 "flutter/impeller/aiks/aiks_unittests.h"
6
7#include <algorithm>
8#include <array>
9#include <cmath>
10#include <cstdlib>
11#include <memory>
12#include <tuple>
13#include <utility>
14#include <vector>
15
16#include "flutter/testing/testing.h"
17#include "gtest/gtest.h"
20#include "impeller/aiks/image.h"
43#include "third_party/imgui/imgui.h"
45#include "txt/platform.h"
46
47namespace impeller {
48namespace testing {
49
51
52TEST_P(AiksTest, CanvasCTMCanBeUpdated) {
53 Canvas canvas;
54 Matrix identity;
55 ASSERT_MATRIX_NEAR(canvas.GetCurrentTransform(), identity);
56 canvas.Translate(Size{100, 100});
58 Matrix::MakeTranslation({100.0, 100.0, 0.0}));
59}
60
61TEST_P(AiksTest, CanvasCanPushPopCTM) {
62 Canvas canvas;
63 ASSERT_EQ(canvas.GetSaveCount(), 1u);
64 ASSERT_EQ(canvas.Restore(), false);
65
66 canvas.Translate(Size{100, 100});
67 canvas.Save();
68 ASSERT_EQ(canvas.GetSaveCount(), 2u);
70 Matrix::MakeTranslation({100.0, 100.0, 0.0}));
71 ASSERT_TRUE(canvas.Restore());
72 ASSERT_EQ(canvas.GetSaveCount(), 1u);
74 Matrix::MakeTranslation({100.0, 100.0, 0.0}));
75}
76
77TEST_P(AiksTest, CanRenderColoredRect) {
78 Canvas canvas;
80 paint.color = Color::Blue();
81 canvas.DrawPath(PathBuilder{}
82 .AddRect(Rect::MakeXYWH(100.0, 100.0, 100.0, 100.0))
83 .TakePath(),
84 paint);
85 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
86}
87
88TEST_P(AiksTest, CanRenderImage) {
89 Canvas canvas;
91 auto image = std::make_shared<Image>(CreateTextureForFixture("kalimba.jpg"));
92 paint.color = Color::Red();
93 canvas.DrawImage(image, Point::MakeXY(100.0, 100.0), paint);
94 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
95}
96
97TEST_P(AiksTest, CanRenderInvertedImageWithColorFilter) {
98 Canvas canvas;
100 auto image = std::make_shared<Image>(CreateTextureForFixture("kalimba.jpg"));
101 paint.color = Color::Red();
102 paint.color_filter =
104 paint.invert_colors = true;
105
106 canvas.DrawImage(image, Point::MakeXY(100.0, 100.0), paint);
107 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
108}
109
110TEST_P(AiksTest, CanRenderColorFilterWithInvertColors) {
111 Canvas canvas;
112 Paint paint;
113 paint.color = Color::Red();
114 paint.color_filter =
116 paint.invert_colors = true;
117
118 canvas.DrawRect(Rect::MakeLTRB(0, 0, 100, 100), paint);
119 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
120}
121
122TEST_P(AiksTest, CanRenderColorFilterWithInvertColorsDrawPaint) {
123 Canvas canvas;
124 Paint paint;
125 paint.color = Color::Red();
126 paint.color_filter =
128 paint.invert_colors = true;
129
130 canvas.DrawPaint(paint);
131 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
132}
133
134namespace {
135bool GenerateMipmap(const std::shared_ptr<Context>& context,
136 std::shared_ptr<Texture> texture,
137 std::string label) {
138 auto buffer = context->CreateCommandBuffer();
139 if (!buffer) {
140 return false;
141 }
142 auto pass = buffer->CreateBlitPass();
143 if (!pass) {
144 return false;
145 }
146 pass->GenerateMipmap(std::move(texture), std::move(label));
147
148 pass->EncodeCommands(context->GetResourceAllocator());
149 return context->GetCommandQueue()->Submit({buffer}).ok();
150}
151
152void CanRenderTiledTexture(AiksTest* aiks_test,
153 Entity::TileMode tile_mode,
154 Matrix local_matrix = {}) {
155 auto context = aiks_test->GetContext();
156 ASSERT_TRUE(context);
157 auto texture = aiks_test->CreateTextureForFixture("table_mountain_nx.png",
158 /*enable_mipmapping=*/true);
159 GenerateMipmap(context, texture, "table_mountain_nx");
160 Canvas canvas;
161 canvas.Scale(aiks_test->GetContentScale());
162 canvas.Translate({100.0f, 100.0f, 0});
163 Paint paint;
164 paint.color_source =
165 ColorSource::MakeImage(texture, tile_mode, tile_mode, {}, local_matrix);
166 paint.color = Color(1, 1, 1, 1);
167 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint);
168
169 // Should not change the image.
170 constexpr auto stroke_width = 64;
172 paint.stroke_width = stroke_width;
173 if (tile_mode == Entity::TileMode::kDecal) {
174 canvas.DrawRect(Rect::MakeXYWH(stroke_width, stroke_width, 600, 600),
175 paint);
176 } else {
177 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint);
178 }
179
180 {
181 // Should not change the image.
182 PathBuilder path_builder;
183 path_builder.AddCircle({150, 150}, 150);
184 path_builder.AddRoundedRect(Rect::MakeLTRB(300, 300, 600, 600), 10);
186 canvas.DrawPath(path_builder.TakePath(), paint);
187 }
188
189 {
190 // Should not change the image. Tests the Convex short-cut code.
191 PathBuilder path_builder;
192 path_builder.AddCircle({150, 450}, 150);
193 path_builder.SetConvexity(Convexity::kConvex);
195 canvas.DrawPath(path_builder.TakePath(), paint);
196 }
197
198 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
199}
200} // namespace
201
202TEST_P(AiksTest, CanRenderTiledTextureClamp) {
203 CanRenderTiledTexture(this, Entity::TileMode::kClamp);
204}
205
206TEST_P(AiksTest, CanRenderTiledTextureRepeat) {
207 CanRenderTiledTexture(this, Entity::TileMode::kRepeat);
208}
209
210TEST_P(AiksTest, CanRenderTiledTextureMirror) {
211 CanRenderTiledTexture(this, Entity::TileMode::kMirror);
212}
213
214TEST_P(AiksTest, CanRenderTiledTextureDecal) {
215 CanRenderTiledTexture(this, Entity::TileMode::kDecal);
216}
217
218TEST_P(AiksTest, CanRenderTiledTextureClampWithTranslate) {
219 CanRenderTiledTexture(this, Entity::TileMode::kClamp,
220 Matrix::MakeTranslation({172.f, 172.f, 0.f}));
221}
222
223TEST_P(AiksTest, CanRenderImageRect) {
224 Canvas canvas;
225 Paint paint;
226 auto image = std::make_shared<Image>(CreateTextureForFixture("kalimba.jpg"));
227 Size image_half_size = Size(image->GetSize()) * 0.5;
228
229 // Render the bottom right quarter of the source image in a stretched rect.
230 auto source_rect = Rect::MakeSize(image_half_size);
231 source_rect = source_rect.Shift(Point(image_half_size));
232
233 canvas.DrawImageRect(image, source_rect, Rect::MakeXYWH(100, 100, 600, 600),
234 paint);
235 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
236}
237
238TEST_P(AiksTest, CanRenderSimpleClips) {
239 Canvas canvas;
240 canvas.Scale(GetContentScale());
241 Paint paint;
242
243 paint.color = Color::White();
244 canvas.DrawPaint(paint);
245
246 auto draw = [&canvas](const Paint& paint, Scalar x, Scalar y) {
247 canvas.Save();
248 canvas.Translate({x, y});
249 {
250 canvas.Save();
251 canvas.ClipRect(Rect::MakeLTRB(50, 50, 150, 150));
252 canvas.DrawPaint(paint);
253 canvas.Restore();
254 }
255 {
256 canvas.Save();
257 canvas.ClipOval(Rect::MakeLTRB(200, 50, 300, 150));
258 canvas.DrawPaint(paint);
259 canvas.Restore();
260 }
261 {
262 canvas.Save();
263 canvas.ClipRRect(Rect::MakeLTRB(50, 200, 150, 300), {20, 20});
264 canvas.DrawPaint(paint);
265 canvas.Restore();
266 }
267 {
268 canvas.Save();
269 canvas.ClipRRect(Rect::MakeLTRB(200, 230, 300, 270), {20, 20});
270 canvas.DrawPaint(paint);
271 canvas.Restore();
272 }
273 {
274 canvas.Save();
275 canvas.ClipRRect(Rect::MakeLTRB(230, 200, 270, 300), {20, 20});
276 canvas.DrawPaint(paint);
277 canvas.Restore();
278 }
279 canvas.Restore();
280 };
281
282 paint.color = Color::Blue();
283 draw(paint, 0, 0);
284
285 std::vector<Color> gradient_colors = {
286 Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
287 Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
288 Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
289 Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
290 Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
291 Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
292 Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
293 std::vector<Scalar> stops = {
294 0.0,
295 (1.0 / 6.0) * 1,
296 (1.0 / 6.0) * 2,
297 (1.0 / 6.0) * 3,
298 (1.0 / 6.0) * 4,
299 (1.0 / 6.0) * 5,
300 1.0,
301 };
302 auto texture = CreateTextureForFixture("airplane.jpg",
303 /*enable_mipmapping=*/true);
304
306 {500, 600}, 75, std::move(gradient_colors), std::move(stops),
308 draw(paint, 0, 300);
309
310 paint.color_source = ColorSource::MakeImage(
313 draw(paint, 300, 0);
314
315 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
316}
317
318TEST_P(AiksTest, CanRenderNestedClips) {
319 Canvas canvas;
320 Paint paint;
321 paint.color = Color::Fuchsia();
322 canvas.Save();
323 canvas.ClipPath(PathBuilder{}.AddCircle({200, 400}, 300).TakePath());
324 canvas.Restore();
325 canvas.ClipPath(PathBuilder{}.AddCircle({600, 400}, 300).TakePath());
326 canvas.ClipPath(PathBuilder{}.AddCircle({400, 600}, 300).TakePath());
327 canvas.DrawRect(Rect::MakeXYWH(200, 200, 400, 400), paint);
328 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
329}
330
331TEST_P(AiksTest, CanRenderDifferenceClips) {
332 Paint paint;
333 Canvas canvas;
334 canvas.Translate({400, 400});
335
336 // Limit drawing to face circle with a clip.
337 canvas.ClipPath(PathBuilder{}.AddCircle(Point(), 200).TakePath());
338 canvas.Save();
339
340 // Cut away eyes/mouth using difference clips.
341 canvas.ClipPath(PathBuilder{}.AddCircle({-100, -50}, 30).TakePath(),
343 canvas.ClipPath(PathBuilder{}.AddCircle({100, -50}, 30).TakePath(),
345 canvas.ClipPath(PathBuilder{}
346 .AddQuadraticCurve({-100, 50}, {0, 150}, {100, 50})
347 .TakePath(),
349
350 // Draw a huge yellow rectangle to prove the clipping works.
351 paint.color = Color::Yellow();
352 canvas.DrawRect(Rect::MakeXYWH(-1000, -1000, 2000, 2000), paint);
353
354 // Remove the difference clips and draw hair that partially covers the eyes.
355 canvas.Restore();
356 paint.color = Color::Maroon();
357 canvas.DrawPath(PathBuilder{}
358 .MoveTo({200, -200})
359 .HorizontalLineTo(-200)
360 .VerticalLineTo(-40)
361 .CubicCurveTo({0, -40}, {0, -80}, {200, -80})
362 .TakePath(),
363 paint);
364
365 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
366}
367
368TEST_P(AiksTest, CanRenderWithContiguousClipRestores) {
369 Canvas canvas;
370
371 // Cover the whole canvas with red.
372 canvas.DrawPaint({.color = Color::Red()});
373
374 canvas.Save();
375
376 // Append two clips, the second resulting in empty coverage.
377 canvas.ClipPath(
378 PathBuilder{}.AddRect(Rect::MakeXYWH(100, 100, 100, 100)).TakePath());
379 canvas.ClipPath(
380 PathBuilder{}.AddRect(Rect::MakeXYWH(300, 300, 100, 100)).TakePath());
381
382 // Restore to no clips.
383 canvas.Restore();
384
385 // Replace the whole canvas with green.
386 canvas.DrawPaint({.color = Color::Green()});
387
388 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
389}
390
391TEST_P(AiksTest, ClipsUseCurrentTransform) {
392 std::array<Color, 5> colors = {Color::White(), Color::Black(),
394 Color::Yellow()};
395 Canvas canvas;
396 Paint paint;
397
398 canvas.Translate(Vector3(300, 300));
399 for (int i = 0; i < 15; i++) {
400 canvas.Scale(Vector3(0.8, 0.8));
401
402 paint.color = colors[i % colors.size()];
403 canvas.ClipPath(PathBuilder{}.AddCircle({0, 0}, 300).TakePath());
404 canvas.DrawRect(Rect::MakeXYWH(-300, -300, 600, 600), paint);
405 }
406 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
407}
408
409TEST_P(AiksTest, CanSaveLayerStandalone) {
410 Canvas canvas;
411
412 Paint red;
413 red.color = Color::Red();
414
415 Paint alpha;
416 alpha.color = Color::Red().WithAlpha(0.5);
417
418 canvas.SaveLayer(alpha);
419
420 canvas.DrawCircle({125, 125}, 125, red);
421
422 canvas.Restore();
423
424 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
425}
426
427TEST_P(AiksTest, CanRenderDifferentShapesWithSameColorSource) {
428 Canvas canvas;
429 Paint paint;
430
431 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
432 Color{0.1294, 0.5882, 0.9529, 1.0}};
433 std::vector<Scalar> stops = {
434 0.0,
435 1.0,
436 };
437
439 {0, 0}, {100, 100}, std::move(colors), std::move(stops),
441
442 canvas.Save();
443 canvas.Translate({100, 100, 0});
444 canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), paint);
445 canvas.Restore();
446
447 canvas.Save();
448 canvas.Translate({100, 400, 0});
449 canvas.DrawCircle({100, 100}, 100, paint);
450 canvas.Restore();
451 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
452}
453
454TEST_P(AiksTest, CanPictureConvertToImage) {
455 Canvas recorder_canvas;
456 Paint paint;
457 paint.color = Color{0.9568, 0.2627, 0.2118, 1.0};
458 recorder_canvas.DrawRect(Rect::MakeXYWH(100.0, 100.0, 600, 600), paint);
459 paint.color = Color{0.1294, 0.5882, 0.9529, 1.0};
460 recorder_canvas.DrawRect(Rect::MakeXYWH(200.0, 200.0, 600, 600), paint);
461
462 Canvas canvas;
463 AiksContext renderer(GetContext(), nullptr);
465 canvas.DrawPaint(paint);
466 Picture picture = recorder_canvas.EndRecordingAsPicture();
467 auto image = picture.ToImage(renderer, ISize{1000, 1000});
468 if (image) {
469 canvas.DrawImage(image, Point(), Paint());
470 paint.color = Color{0.1, 0.1, 0.1, 0.2};
471 canvas.DrawRect(Rect::MakeSize(ISize{1000, 1000}), paint);
472 }
473
474 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
475}
476
477// Regression test for https://github.com/flutter/flutter/issues/142358 .
478// Without a change to force render pass construction the image is left in an
479// undefined layout and triggers a validation error.
480TEST_P(AiksTest, CanEmptyPictureConvertToImage) {
481 Canvas recorder_canvas;
482
483 Canvas canvas;
484 AiksContext renderer(GetContext(), nullptr);
485 Paint paint;
487 canvas.DrawPaint(paint);
488 Picture picture = recorder_canvas.EndRecordingAsPicture();
489 auto image = picture.ToImage(renderer, ISize{1000, 1000});
490 if (image) {
491 canvas.DrawImage(image, Point(), Paint());
492 paint.color = Color{0.1, 0.1, 0.1, 0.2};
493 canvas.DrawRect(Rect::MakeSize(ISize{1000, 1000}), paint);
494 }
495
496 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
497}
498
499TEST_P(AiksTest, CanRenderGroupOpacity) {
500 Canvas canvas;
501
502 Paint red;
503 red.color = Color::Red();
504 Paint green;
505 green.color = Color::Green().WithAlpha(0.5);
506 Paint blue;
507 blue.color = Color::Blue();
508
509 Paint alpha;
510 alpha.color = Color::Red().WithAlpha(0.5);
511
512 canvas.SaveLayer(alpha);
513
514 canvas.DrawRect(Rect::MakeXYWH(000, 000, 100, 100), red);
515 canvas.DrawRect(Rect::MakeXYWH(020, 020, 100, 100), green);
516 canvas.DrawRect(Rect::MakeXYWH(040, 040, 100, 100), blue);
517
518 canvas.Restore();
519
520 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
521}
522
523TEST_P(AiksTest, CoordinateConversionsAreCorrect) {
524 Canvas canvas;
525
526 // Render a texture directly.
527 {
528 Paint paint;
529 auto image =
530 std::make_shared<Image>(CreateTextureForFixture("kalimba.jpg"));
531 paint.color = Color::Red();
532
533 canvas.Save();
534 canvas.Translate({100, 200, 0});
535 canvas.Scale(Vector2{0.5, 0.5});
536 canvas.DrawImage(image, Point::MakeXY(100.0, 100.0), paint);
537 canvas.Restore();
538 }
539
540 // Render an offscreen rendered texture.
541 {
542 Paint red;
543 red.color = Color::Red();
544 Paint green;
545 green.color = Color::Green();
546 Paint blue;
547 blue.color = Color::Blue();
548
549 Paint alpha;
550 alpha.color = Color::Red().WithAlpha(0.5);
551
552 canvas.SaveLayer(alpha);
553
554 canvas.DrawRect(Rect::MakeXYWH(000, 000, 100, 100), red);
555 canvas.DrawRect(Rect::MakeXYWH(020, 020, 100, 100), green);
556 canvas.DrawRect(Rect::MakeXYWH(040, 040, 100, 100), blue);
557
558 canvas.Restore();
559 }
560
561 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
562}
563
564TEST_P(AiksTest, CanPerformFullScreenMSAA) {
565 Canvas canvas;
566
567 Paint red;
568 red.color = Color::Red();
569
570 canvas.DrawCircle({250, 250}, 125, red);
571
572 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
573}
574
575TEST_P(AiksTest, CanPerformSkew) {
576 Canvas canvas;
577
578 Paint red;
579 red.color = Color::Red();
580
581 canvas.Skew(2, 5);
582 canvas.DrawRect(Rect::MakeXYWH(0, 0, 100, 100), red);
583
584 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
585}
586
587TEST_P(AiksTest, CanPerformSaveLayerWithBounds) {
588 Canvas canvas;
589
590 Paint red;
591 red.color = Color::Red();
592
593 Paint green;
594 green.color = Color::Green();
595
596 Paint blue;
597 blue.color = Color::Blue();
598
599 Paint save;
600 save.color = Color::Black();
601
602 canvas.SaveLayer(save, Rect::MakeXYWH(0, 0, 50, 50));
603
604 canvas.DrawRect(Rect::MakeXYWH(0, 0, 100, 100), red);
605 canvas.DrawRect(Rect::MakeXYWH(10, 10, 100, 100), green);
606 canvas.DrawRect(Rect::MakeXYWH(20, 20, 100, 100), blue);
607
608 canvas.Restore();
609
610 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
611}
612
614 CanPerformSaveLayerWithBoundsAndLargerIntermediateIsNotAllocated) {
615 Canvas canvas;
616
617 Paint red;
618 red.color = Color::Red();
619
620 Paint green;
621 green.color = Color::Green();
622
623 Paint blue;
624 blue.color = Color::Blue();
625
626 Paint save;
627 save.color = Color::Black().WithAlpha(0.5);
628
629 canvas.SaveLayer(save, Rect::MakeXYWH(0, 0, 100000, 100000));
630
631 canvas.DrawRect(Rect::MakeXYWH(0, 0, 100, 100), red);
632 canvas.DrawRect(Rect::MakeXYWH(10, 10, 100, 100), green);
633 canvas.DrawRect(Rect::MakeXYWH(20, 20, 100, 100), blue);
634
635 canvas.Restore();
636
637 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
638}
639
640TEST_P(AiksTest, CanRenderRoundedRectWithNonUniformRadii) {
641 Canvas canvas;
642
643 Paint paint;
644 paint.color = Color::Red();
645
647 radii.top_left = {50, 25};
648 radii.top_right = {25, 50};
649 radii.bottom_right = {50, 25};
650 radii.bottom_left = {25, 50};
651
652 auto path = PathBuilder{}
653 .AddRoundedRect(Rect::MakeXYWH(100, 100, 500, 500), radii)
654 .TakePath();
655
656 canvas.DrawPath(path, paint);
657
658 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
659}
660
664 Point position = Vector2(100, 200);
665 std::optional<Paint::MaskBlurDescriptor> mask_blur_descriptor;
666};
667
668bool RenderTextInCanvasSkia(const std::shared_ptr<Context>& context,
669 Canvas& canvas,
670 const std::string& text,
671 const std::string_view& font_fixture,
673 // Draw the baseline.
674 canvas.DrawRect(
675 Rect::MakeXYWH(options.position.x - 50, options.position.y, 900, 10),
676 Paint{.color = Color::Aqua().WithAlpha(0.25)});
677
678 // Mark the point at which the text is drawn.
679 canvas.DrawCircle(options.position, 5.0,
680 Paint{.color = Color::Red().WithAlpha(0.25)});
681
682 // Construct the text blob.
683 auto c_font_fixture = std::string(font_fixture);
684 auto mapping = flutter::testing::OpenFixtureAsSkData(c_font_fixture.c_str());
685 if (!mapping) {
686 return false;
687 }
689 SkFont sk_font(font_mgr->makeFromData(mapping), options.font_size);
690 auto blob = SkTextBlob::MakeFromString(text.c_str(), sk_font);
691 if (!blob) {
692 return false;
693 }
694
695 // Create the Impeller text frame and draw it at the designated baseline.
697
698 Paint text_paint;
699 text_paint.color = options.color;
700 text_paint.mask_blur_descriptor = options.mask_blur_descriptor;
701 canvas.DrawTextFrame(frame, options.position, text_paint);
702 return true;
703}
704
705bool RenderTextInCanvasSTB(const std::shared_ptr<Context>& context,
706 Canvas& canvas,
707 const std::string& text,
708 const std::string& font_fixture,
710 // Draw the baseline.
711 canvas.DrawRect(
712 Rect::MakeXYWH(options.position.x - 50, options.position.y, 900, 10),
713 Paint{.color = Color::Aqua().WithAlpha(0.25)});
714
715 // Mark the point at which the text is drawn.
716 canvas.DrawCircle(options.position, 5.0,
717 Paint{.color = Color::Red().WithAlpha(0.25)});
718
719 // Construct the text blob.
720 auto mapping = flutter::testing::OpenFixtureAsMapping(font_fixture.c_str());
721 if (!mapping) {
722 return false;
723 }
724 auto typeface_stb = std::make_shared<TypefaceSTB>(std::move(mapping));
725
726 auto frame = MakeTextFrameSTB(
727 typeface_stb, Font::Metrics{.point_size = options.font_size}, text);
728
729 Paint text_paint;
730 text_paint.color = options.color;
731 canvas.DrawTextFrame(frame, options.position, text_paint);
732 return true;
733}
734
735TEST_P(AiksTest, CanRenderTextFrame) {
736 Canvas canvas;
737 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
738 ASSERT_TRUE(RenderTextInCanvasSkia(
739 GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?",
740 "Roboto-Regular.ttf"));
741 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
742}
743
744TEST_P(AiksTest, CanRenderTextFrameSTB) {
745 Canvas canvas;
746 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
747 ASSERT_TRUE(RenderTextInCanvasSTB(
748 GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?",
749 "Roboto-Regular.ttf"));
750
751 SetTypographerContext(TypographerContextSTB::Make());
752 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
753}
754
755TEST_P(AiksTest, TextFrameSubpixelAlignment) {
756 // "Random" numbers between 0 and 1. Hardcoded to avoid flakiness in goldens.
757 std::array<Scalar, 20> phase_offsets = {
758 7.82637e-06, 0.131538, 0.755605, 0.45865, 0.532767,
759 0.218959, 0.0470446, 0.678865, 0.679296, 0.934693,
760 0.383502, 0.519416, 0.830965, 0.0345721, 0.0534616,
761 0.5297, 0.671149, 0.00769819, 0.383416, 0.0668422};
762 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
763 static float font_size = 20;
764 static float phase_variation = 0.2;
765 static float speed = 0.5;
766 static float magnitude = 100;
767 if (AiksTest::ImGuiBegin("Controls", nullptr,
768 ImGuiWindowFlags_AlwaysAutoResize)) {
769 ImGui::SliderFloat("Font size", &font_size, 5, 50);
770 ImGui::SliderFloat("Phase variation", &phase_variation, 0, 1);
771 ImGui::SliderFloat("Oscillation speed", &speed, 0, 2);
772 ImGui::SliderFloat("Oscillation magnitude", &magnitude, 0, 300);
773 ImGui::End();
774 }
775
776 Canvas canvas;
777 canvas.Scale(GetContentScale());
778
779 for (size_t i = 0; i < phase_offsets.size(); i++) {
780 auto position =
781 Point(200 + magnitude *
782 std::sin((-phase_offsets[i] * k2Pi * phase_variation +
783 GetSecondsElapsed() * speed)), //
784 200 + i * font_size * 1.1 //
785 );
787 GetContext(), canvas,
788 "the quick brown fox jumped over "
789 "the lazy dog!.?",
790 "Roboto-Regular.ttf",
791 {.font_size = font_size, .position = position})) {
792 return std::nullopt;
793 }
794 }
795 return canvas.EndRecordingAsPicture();
796 };
797
798 ASSERT_TRUE(OpenPlaygroundHere(callback));
799}
800
801TEST_P(AiksTest, CanRenderItalicizedText) {
802 Canvas canvas;
803 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
804
805 ASSERT_TRUE(RenderTextInCanvasSkia(
806 GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?",
807 "HomemadeApple.ttf"));
808 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
809}
810
811static constexpr std::string_view kFontFixture =
812#if FML_OS_MACOSX
813 "Apple Color Emoji.ttc";
814#else
815 "NotoColorEmoji.ttf";
816#endif
817
818TEST_P(AiksTest, CanRenderEmojiTextFrame) {
819 Canvas canvas;
820 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
821
822 ASSERT_TRUE(RenderTextInCanvasSkia(
823 GetContext(), canvas, "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 😊", kFontFixture));
824 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
825}
826
827TEST_P(AiksTest, CanRenderEmojiTextFrameWithBlur) {
828 Canvas canvas;
829 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
830
831 ASSERT_TRUE(RenderTextInCanvasSkia(
832 GetContext(), canvas, "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 😊", kFontFixture,
834 .mask_blur_descriptor = Paint::MaskBlurDescriptor{
836 .sigma = Sigma(4)}}));
837 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
838}
839
840TEST_P(AiksTest, CanRenderEmojiTextFrameWithAlpha) {
841 Canvas canvas;
842 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
843
844 ASSERT_TRUE(RenderTextInCanvasSkia(
845 GetContext(), canvas, "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 😊", kFontFixture,
846 {.color = Color::Black().WithAlpha(0.5)}));
847 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
848}
849
850TEST_P(AiksTest, CanRenderTextInSaveLayer) {
851 Canvas canvas;
852 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
853
854 canvas.Translate({100, 100});
855 canvas.Scale(Vector2{0.5, 0.5});
856
857 // Blend the layer with the parent pass using kClear to expose the coverage.
858 canvas.SaveLayer({.blend_mode = BlendMode::kClear});
859 ASSERT_TRUE(RenderTextInCanvasSkia(
860 GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?",
861 "Roboto-Regular.ttf"));
862 canvas.Restore();
863
864 // Render the text again over the cleared coverage rect.
865 ASSERT_TRUE(RenderTextInCanvasSkia(
866 GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?",
867 "Roboto-Regular.ttf"));
868
869 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
870}
871
872TEST_P(AiksTest, CanRenderTextOutsideBoundaries) {
873 Canvas canvas;
874 canvas.Translate({200, 150});
875
876 // Construct the text blob.
877 auto mapping = flutter::testing::OpenFixtureAsSkData("wtf.otf");
878 ASSERT_NE(mapping, nullptr);
879
880 Scalar font_size = 80;
882 SkFont sk_font(font_mgr->makeFromData(mapping), font_size);
883
884 Paint text_paint;
885 text_paint.color = Color::Blue().WithAlpha(0.8);
886
887 struct {
888 Point position;
889 const char* text;
890 } text[] = {{Point(0, 0), "0F0F0F0"},
891 {Point(1, 2), "789"},
892 {Point(1, 3), "456"},
893 {Point(1, 4), "123"},
894 {Point(0, 6), "0F0F0F0"}};
895 for (auto& t : text) {
896 canvas.Save();
897 canvas.Translate(t.position * Point(font_size * 2, font_size * 1.1));
898 {
899 auto blob = SkTextBlob::MakeFromString(t.text, sk_font);
900 ASSERT_NE(blob, nullptr);
902 canvas.DrawTextFrame(frame, Point(), text_paint);
903 }
904 canvas.Restore();
905 }
906
907 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
908}
909
910TEST_P(AiksTest, TextRotated) {
911 Canvas canvas;
912 canvas.Scale(GetContentScale());
913 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
914
915 canvas.Transform(Matrix(0.25, -0.3, 0, -0.002, //
916 0, 0.5, 0, 0, //
917 0, 0, 0.3, 0, //
918 100, 100, 0, 1.3));
919 ASSERT_TRUE(RenderTextInCanvasSkia(
920 GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?",
921 "Roboto-Regular.ttf"));
922
923 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
924}
925
926TEST_P(AiksTest, CanDrawPaint) {
927 Canvas canvas;
928 canvas.Scale(Vector2(0.2, 0.2));
929 canvas.DrawPaint({.color = Color::MediumTurquoise()});
930 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
931}
932
933TEST_P(AiksTest, CanDrawPaintMultipleTimes) {
934 Canvas canvas;
935 canvas.Scale(Vector2(0.2, 0.2));
936 canvas.DrawPaint({.color = Color::MediumTurquoise()});
937 canvas.DrawPaint({.color = Color::Color::OrangeRed().WithAlpha(0.5)});
938 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
939}
940
941// This makes sure the WideGamut named tests use 16bit float pixel format.
942TEST_P(AiksTest, FormatWideGamut) {
943 EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(),
945}
946
947TEST_P(AiksTest, FormatSRGB) {
948 PixelFormat pixel_format =
949 GetContext()->GetCapabilities()->GetDefaultColorFormat();
951 pixel_format == PixelFormat::kB8G8R8A8UNormInt)
952 << "pixel format: " << PixelFormatToString(pixel_format);
953}
954
955TEST_P(AiksTest, TransformMultipliesCorrectly) {
956 Canvas canvas;
958
959 // clang-format off
960 canvas.Translate(Vector3(100, 200));
962 canvas.GetCurrentTransform(),
963 Matrix( 1, 0, 0, 0,
964 0, 1, 0, 0,
965 0, 0, 1, 0,
966 100, 200, 0, 1));
967
968 canvas.Rotate(Radians(kPiOver2));
970 canvas.GetCurrentTransform(),
971 Matrix( 0, 1, 0, 0,
972 -1, 0, 0, 0,
973 0, 0, 1, 0,
974 100, 200, 0, 1));
975
976 canvas.Scale(Vector3(2, 3));
978 canvas.GetCurrentTransform(),
979 Matrix( 0, 2, 0, 0,
980 -3, 0, 0, 0,
981 0, 0, 0, 0,
982 100, 200, 0, 1));
983
984 canvas.Translate(Vector3(100, 200));
986 canvas.GetCurrentTransform(),
987 Matrix( 0, 2, 0, 0,
988 -3, 0, 0, 0,
989 0, 0, 0, 0,
990 -500, 400, 0, 1));
991 // clang-format on
992}
993
994TEST_P(AiksTest, FilledCirclesRenderCorrectly) {
995 Canvas canvas;
996 canvas.Scale(GetContentScale());
997 Paint paint;
998 const int color_count = 3;
999 Color colors[color_count] = {
1000 Color::Blue(),
1001 Color::Green(),
1003 };
1004
1005 paint.color = Color::White();
1006 canvas.DrawPaint(paint);
1007
1008 int c_index = 0;
1009 int radius = 600;
1010 while (radius > 0) {
1011 paint.color = colors[(c_index++) % color_count];
1012 canvas.DrawCircle({10, 10}, radius, paint);
1013 if (radius > 30) {
1014 radius -= 10;
1015 } else {
1016 radius -= 2;
1017 }
1018 }
1019
1020 std::vector<Color> gradient_colors = {
1021 Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
1022 Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
1023 Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
1024 Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
1025 Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
1026 Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
1027 Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
1028 std::vector<Scalar> stops = {
1029 0.0,
1030 (1.0 / 6.0) * 1,
1031 (1.0 / 6.0) * 2,
1032 (1.0 / 6.0) * 3,
1033 (1.0 / 6.0) * 4,
1034 (1.0 / 6.0) * 5,
1035 1.0,
1036 };
1037 auto texture = CreateTextureForFixture("airplane.jpg",
1038 /*enable_mipmapping=*/true);
1039
1041 {500, 600}, 75, std::move(gradient_colors), std::move(stops),
1043 canvas.DrawCircle({500, 600}, 100, paint);
1044
1045 paint.color_source = ColorSource::MakeImage(
1047 Matrix::MakeTranslation({700, 200}));
1048 canvas.DrawCircle({800, 300}, 100, paint);
1049
1050 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1051}
1052
1053TEST_P(AiksTest, StrokedCirclesRenderCorrectly) {
1054 Canvas canvas;
1055 canvas.Scale(GetContentScale());
1056 Paint paint;
1057 const int color_count = 3;
1058 Color colors[color_count] = {
1059 Color::Blue(),
1060 Color::Green(),
1062 };
1063
1064 paint.color = Color::White();
1065 canvas.DrawPaint(paint);
1066
1067 int c_index = 0;
1068
1069 auto draw = [&paint, &colors, &c_index](Canvas& canvas, Point center,
1070 Scalar r, Scalar dr, int n) {
1071 for (int i = 0; i < n; i++) {
1072 paint.color = colors[(c_index++) % color_count];
1073 canvas.DrawCircle(center, r, paint);
1074 r += dr;
1075 }
1076 };
1077
1079 paint.stroke_width = 1;
1080 draw(canvas, {10, 10}, 2, 2, 14); // r = [2, 28], covers [1,29]
1081 paint.stroke_width = 5;
1082 draw(canvas, {10, 10}, 35, 10, 56); // r = [35, 585], covers [30,590]
1083
1084 std::vector<Color> gradient_colors = {
1085 Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
1086 Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
1087 Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
1088 Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
1089 Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
1090 Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
1091 Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
1092 std::vector<Scalar> stops = {
1093 0.0,
1094 (1.0 / 6.0) * 1,
1095 (1.0 / 6.0) * 2,
1096 (1.0 / 6.0) * 3,
1097 (1.0 / 6.0) * 4,
1098 (1.0 / 6.0) * 5,
1099 1.0,
1100 };
1101 auto texture = CreateTextureForFixture("airplane.jpg",
1102 /*enable_mipmapping=*/true);
1103
1105 {500, 600}, 75, std::move(gradient_colors), std::move(stops),
1107 draw(canvas, {500, 600}, 5, 10, 10);
1108
1109 paint.color_source = ColorSource::MakeImage(
1111 Matrix::MakeTranslation({700, 200}));
1112 draw(canvas, {800, 300}, 5, 10, 10);
1113
1114 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1115}
1116
1117TEST_P(AiksTest, FilledEllipsesRenderCorrectly) {
1118 Canvas canvas;
1119 canvas.Scale(GetContentScale());
1120 Paint paint;
1121 const int color_count = 3;
1122 Color colors[color_count] = {
1123 Color::Blue(),
1124 Color::Green(),
1126 };
1127
1128 paint.color = Color::White();
1129 canvas.DrawPaint(paint);
1130
1131 int c_index = 0;
1132 int long_radius = 600;
1133 int short_radius = 600;
1134 while (long_radius > 0 && short_radius > 0) {
1135 paint.color = colors[(c_index++) % color_count];
1136 canvas.DrawOval(Rect::MakeXYWH(10 - long_radius, 10 - short_radius,
1137 long_radius * 2, short_radius * 2),
1138 paint);
1139 canvas.DrawOval(Rect::MakeXYWH(1000 - short_radius, 750 - long_radius,
1140 short_radius * 2, long_radius * 2),
1141 paint);
1142 if (short_radius > 30) {
1143 short_radius -= 10;
1144 long_radius -= 5;
1145 } else {
1146 short_radius -= 2;
1147 long_radius -= 1;
1148 }
1149 }
1150
1151 std::vector<Color> gradient_colors = {
1152 Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
1153 Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
1154 Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
1155 Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
1156 Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
1157 Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
1158 Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
1159 std::vector<Scalar> stops = {
1160 0.0,
1161 (1.0 / 6.0) * 1,
1162 (1.0 / 6.0) * 2,
1163 (1.0 / 6.0) * 3,
1164 (1.0 / 6.0) * 4,
1165 (1.0 / 6.0) * 5,
1166 1.0,
1167 };
1168 auto texture = CreateTextureForFixture("airplane.jpg",
1169 /*enable_mipmapping=*/true);
1170
1171 paint.color = Color::White().WithAlpha(0.5);
1172
1174 {300, 650}, 75, std::move(gradient_colors), std::move(stops),
1176 canvas.DrawOval(Rect::MakeXYWH(200, 625, 200, 50), paint);
1177 canvas.DrawOval(Rect::MakeXYWH(275, 550, 50, 200), paint);
1178
1179 paint.color_source = ColorSource::MakeImage(
1181 Matrix::MakeTranslation({610, 15}));
1182 canvas.DrawOval(Rect::MakeXYWH(610, 90, 200, 50), paint);
1183 canvas.DrawOval(Rect::MakeXYWH(685, 15, 50, 200), paint);
1184
1185 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1186}
1187
1188TEST_P(AiksTest, FilledRoundRectsRenderCorrectly) {
1189 Canvas canvas;
1190 canvas.Scale(GetContentScale());
1191 Paint paint;
1192 const int color_count = 3;
1193 Color colors[color_count] = {
1194 Color::Blue(),
1195 Color::Green(),
1197 };
1198
1199 paint.color = Color::White();
1200 canvas.DrawPaint(paint);
1201
1202 int c_index = 0;
1203 for (int i = 0; i < 4; i++) {
1204 for (int j = 0; j < 4; j++) {
1205 paint.color = colors[(c_index++) % color_count];
1206 canvas.DrawRRect(Rect::MakeXYWH(i * 100 + 10, j * 100 + 20, 80, 80),
1207 Size(i * 5 + 10, j * 5 + 10), paint);
1208 }
1209 }
1210 paint.color = colors[(c_index++) % color_count];
1211 canvas.DrawRRect(Rect::MakeXYWH(10, 420, 380, 80), Size(40, 40), paint);
1212 paint.color = colors[(c_index++) % color_count];
1213 canvas.DrawRRect(Rect::MakeXYWH(410, 20, 80, 380), Size(40, 40), paint);
1214
1215 std::vector<Color> gradient_colors = {
1216 Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
1217 Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
1218 Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
1219 Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
1220 Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
1221 Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
1222 Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
1223 std::vector<Scalar> stops = {
1224 0.0,
1225 (1.0 / 6.0) * 1,
1226 (1.0 / 6.0) * 2,
1227 (1.0 / 6.0) * 3,
1228 (1.0 / 6.0) * 4,
1229 (1.0 / 6.0) * 5,
1230 1.0,
1231 };
1232 auto texture = CreateTextureForFixture("airplane.jpg",
1233 /*enable_mipmapping=*/true);
1234
1235 paint.color = Color::White().WithAlpha(0.1);
1237 {550, 550}, 75, gradient_colors, stops, Entity::TileMode::kMirror, {});
1238 for (int i = 1; i <= 10; i++) {
1239 int j = 11 - i;
1240 canvas.DrawRRect(Rect::MakeLTRB(550 - i * 20, 550 - j * 20, //
1241 550 + i * 20, 550 + j * 20),
1242 Size(i * 10, j * 10), paint);
1243 }
1244 paint.color = Color::White().WithAlpha(0.5);
1246 {200, 650}, 75, std::move(gradient_colors), std::move(stops),
1248 canvas.DrawRRect(Rect::MakeLTRB(100, 610, 300, 690), Size(40, 40), paint);
1249 canvas.DrawRRect(Rect::MakeLTRB(160, 550, 240, 750), Size(40, 40), paint);
1250
1251 paint.color = Color::White().WithAlpha(0.1);
1252 paint.color_source = ColorSource::MakeImage(
1254 Matrix::MakeTranslation({520, 20}));
1255 for (int i = 1; i <= 10; i++) {
1256 int j = 11 - i;
1257 canvas.DrawRRect(Rect::MakeLTRB(720 - i * 20, 220 - j * 20, //
1258 720 + i * 20, 220 + j * 20),
1259 Size(i * 10, j * 10), paint);
1260 }
1261 paint.color = Color::White().WithAlpha(0.5);
1262 paint.color_source = ColorSource::MakeImage(
1264 Matrix::MakeTranslation({800, 300}));
1265 canvas.DrawRRect(Rect::MakeLTRB(800, 410, 1000, 490), Size(40, 40), paint);
1266 canvas.DrawRRect(Rect::MakeLTRB(860, 350, 940, 550), Size(40, 40), paint);
1267
1268 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1269}
1270
1271TEST_P(AiksTest, SolidColorCirclesOvalsRRectsMaskBlurCorrectly) {
1272 Canvas canvas;
1273 canvas.Scale(GetContentScale());
1274 Paint paint;
1275 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
1277 .sigma = Sigma{1},
1278 };
1279
1280 canvas.DrawPaint({.color = Color::White()});
1281
1282 paint.color = Color::Crimson();
1283 Scalar y = 100.0f;
1284 for (int i = 0; i < 5; i++) {
1285 Scalar x = (i + 1) * 100;
1286 Scalar radius = x / 10.0f;
1287 canvas.DrawRect(Rect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
1288 radius, 60.0f - radius),
1289 paint);
1290 }
1291
1292 paint.color = Color::Blue();
1293 y += 100.0f;
1294 for (int i = 0; i < 5; i++) {
1295 Scalar x = (i + 1) * 100;
1296 Scalar radius = x / 10.0f;
1297 canvas.DrawCircle({x + 25, y + 25}, radius, paint);
1298 }
1299
1300 paint.color = Color::Green();
1301 y += 100.0f;
1302 for (int i = 0; i < 5; i++) {
1303 Scalar x = (i + 1) * 100;
1304 Scalar radius = x / 10.0f;
1305 canvas.DrawOval(Rect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
1306 radius, 60.0f - radius),
1307 paint);
1308 }
1309
1310 paint.color = Color::Purple();
1311 y += 100.0f;
1312 for (int i = 0; i < 5; i++) {
1313 Scalar x = (i + 1) * 100;
1314 Scalar radius = x / 20.0f;
1315 canvas.DrawRRect(Rect::MakeXYWH(x, y, 60.0f, 60.0f), //
1316 {radius, radius}, //
1317 paint);
1318 }
1319
1320 paint.color = Color::Orange();
1321 y += 100.0f;
1322 for (int i = 0; i < 5; i++) {
1323 Scalar x = (i + 1) * 100;
1324 Scalar radius = x / 20.0f;
1325 canvas.DrawRRect(Rect::MakeXYWH(x, y, 60.0f, 60.0f), //
1326 {radius, 5.0f}, paint);
1327 }
1328
1329 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1330}
1331
1332TEST_P(AiksTest, FilledRoundRectPathsRenderCorrectly) {
1333 Canvas canvas;
1334 canvas.Scale(GetContentScale());
1335 Paint paint;
1336 const int color_count = 3;
1337 Color colors[color_count] = {
1338 Color::Blue(),
1339 Color::Green(),
1341 };
1342
1343 paint.color = Color::White();
1344 canvas.DrawPaint(paint);
1345
1346 auto draw_rrect_as_path = [&canvas](const Rect& rect, const Size& radii,
1347 const Paint& paint) {
1348 PathBuilder builder = PathBuilder();
1349 builder.AddRoundedRect(rect, radii);
1350 canvas.DrawPath(builder.TakePath(), paint);
1351 };
1352
1353 int c_index = 0;
1354 for (int i = 0; i < 4; i++) {
1355 for (int j = 0; j < 4; j++) {
1356 paint.color = colors[(c_index++) % color_count];
1357 draw_rrect_as_path(Rect::MakeXYWH(i * 100 + 10, j * 100 + 20, 80, 80),
1358 Size(i * 5 + 10, j * 5 + 10), paint);
1359 }
1360 }
1361 paint.color = colors[(c_index++) % color_count];
1362 draw_rrect_as_path(Rect::MakeXYWH(10, 420, 380, 80), Size(40, 40), paint);
1363 paint.color = colors[(c_index++) % color_count];
1364 draw_rrect_as_path(Rect::MakeXYWH(410, 20, 80, 380), Size(40, 40), paint);
1365
1366 std::vector<Color> gradient_colors = {
1367 Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
1368 Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
1369 Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
1370 Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
1371 Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
1372 Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
1373 Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
1374 std::vector<Scalar> stops = {
1375 0.0,
1376 (1.0 / 6.0) * 1,
1377 (1.0 / 6.0) * 2,
1378 (1.0 / 6.0) * 3,
1379 (1.0 / 6.0) * 4,
1380 (1.0 / 6.0) * 5,
1381 1.0,
1382 };
1383 auto texture = CreateTextureForFixture("airplane.jpg",
1384 /*enable_mipmapping=*/true);
1385
1386 paint.color = Color::White().WithAlpha(0.1);
1388 {550, 550}, 75, gradient_colors, stops, Entity::TileMode::kMirror, {});
1389 for (int i = 1; i <= 10; i++) {
1390 int j = 11 - i;
1391 draw_rrect_as_path(Rect::MakeLTRB(550 - i * 20, 550 - j * 20, //
1392 550 + i * 20, 550 + j * 20),
1393 Size(i * 10, j * 10), paint);
1394 }
1395 paint.color = Color::White().WithAlpha(0.5);
1397 {200, 650}, 75, std::move(gradient_colors), std::move(stops),
1399 draw_rrect_as_path(Rect::MakeLTRB(100, 610, 300, 690), Size(40, 40), paint);
1400 draw_rrect_as_path(Rect::MakeLTRB(160, 550, 240, 750), Size(40, 40), paint);
1401
1402 paint.color = Color::White().WithAlpha(0.1);
1403 paint.color_source = ColorSource::MakeImage(
1405 Matrix::MakeTranslation({520, 20}));
1406 for (int i = 1; i <= 10; i++) {
1407 int j = 11 - i;
1408 draw_rrect_as_path(Rect::MakeLTRB(720 - i * 20, 220 - j * 20, //
1409 720 + i * 20, 220 + j * 20),
1410 Size(i * 10, j * 10), paint);
1411 }
1412 paint.color = Color::White().WithAlpha(0.5);
1413 paint.color_source = ColorSource::MakeImage(
1415 Matrix::MakeTranslation({800, 300}));
1416 draw_rrect_as_path(Rect::MakeLTRB(800, 410, 1000, 490), Size(40, 40), paint);
1417 draw_rrect_as_path(Rect::MakeLTRB(860, 350, 940, 550), Size(40, 40), paint);
1418
1419 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1420}
1421
1422TEST_P(AiksTest, CoverageOriginShouldBeAccountedForInSubpasses) {
1423 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
1424 Canvas canvas;
1425 canvas.Scale(GetContentScale());
1426
1427 Paint alpha;
1428 alpha.color = Color::Red().WithAlpha(0.5);
1429
1430 auto current = Point{25, 25};
1431 const auto offset = Point{25, 25};
1432 const auto size = Size(100, 100);
1433
1434 static PlaygroundPoint point_a(Point(40, 40), 10, Color::White());
1435 static PlaygroundPoint point_b(Point(160, 160), 10, Color::White());
1436 auto [b0, b1] = DrawPlaygroundLine(point_a, point_b);
1437 auto bounds = Rect::MakeLTRB(b0.x, b0.y, b1.x, b1.y);
1438
1439 canvas.DrawRect(bounds, Paint{.color = Color::Yellow(),
1440 .stroke_width = 5.0f,
1441 .style = Paint::Style::kStroke});
1442
1443 canvas.SaveLayer(alpha, bounds);
1444
1445 canvas.DrawRect(Rect::MakeOriginSize(current, size),
1446 Paint{.color = Color::Red()});
1447 canvas.DrawRect(Rect::MakeOriginSize(current += offset, size),
1448 Paint{.color = Color::Green()});
1449 canvas.DrawRect(Rect::MakeOriginSize(current += offset, size),
1450 Paint{.color = Color::Blue()});
1451
1452 canvas.Restore();
1453
1454 return canvas.EndRecordingAsPicture();
1455 };
1456
1457 ASSERT_TRUE(OpenPlaygroundHere(callback));
1458}
1459
1460TEST_P(AiksTest, SaveLayerDrawsBehindSubsequentEntities) {
1461 // Compare with https://fiddle.skia.org/c/9e03de8567ffb49e7e83f53b64bcf636
1462 Canvas canvas;
1463 Paint paint;
1464
1465 paint.color = Color::Black();
1466 Rect rect = Rect::MakeXYWH(25, 25, 25, 25);
1467 canvas.DrawRect(rect, paint);
1468
1469 canvas.Translate({10, 10});
1470 canvas.SaveLayer({});
1471
1472 paint.color = Color::Green();
1473 canvas.DrawRect(rect, paint);
1474
1475 canvas.Restore();
1476
1477 canvas.Translate({10, 10});
1478 paint.color = Color::Red();
1479 canvas.DrawRect(rect, paint);
1480
1481 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1482}
1483
1484TEST_P(AiksTest, SiblingSaveLayerBoundsAreRespected) {
1485 Canvas canvas;
1486 Paint paint;
1487 Rect rect = Rect::MakeXYWH(0, 0, 1000, 1000);
1488
1489 // Black, green, and red squares offset by [10, 10].
1490 {
1491 canvas.SaveLayer({}, Rect::MakeXYWH(25, 25, 25, 25));
1492 paint.color = Color::Black();
1493 canvas.DrawRect(rect, paint);
1494 canvas.Restore();
1495 }
1496
1497 {
1498 canvas.SaveLayer({}, Rect::MakeXYWH(35, 35, 25, 25));
1499 paint.color = Color::Green();
1500 canvas.DrawRect(rect, paint);
1501 canvas.Restore();
1502 }
1503
1504 {
1505 canvas.SaveLayer({}, Rect::MakeXYWH(45, 45, 25, 25));
1506 paint.color = Color::Red();
1507 canvas.DrawRect(rect, paint);
1508 canvas.Restore();
1509 }
1510
1511 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1512}
1513
1514TEST_P(AiksTest, CanRenderClippedLayers) {
1515 Canvas canvas;
1516
1517 canvas.DrawPaint({.color = Color::White()});
1518
1519 // Draw a green circle on the screen.
1520 {
1521 // Increase the clip depth for the savelayer to contend with.
1522 canvas.ClipPath(PathBuilder{}.AddCircle({100, 100}, 50).TakePath());
1523
1524 canvas.SaveLayer({}, Rect::MakeXYWH(50, 50, 100, 100));
1525
1526 // Fill the layer with white.
1527 canvas.DrawRect(Rect::MakeSize(Size{400, 400}), {.color = Color::White()});
1528 // Fill the layer with green, but do so with a color blend that can't be
1529 // collapsed into the parent pass.
1530 // TODO(jonahwilliams): this blend mode was changed from color burn to
1531 // hardlight to work around https://github.com/flutter/flutter/issues/136554
1532 // .
1533 canvas.DrawRect(
1534 Rect::MakeSize(Size{400, 400}),
1535 {.color = Color::Green(), .blend_mode = BlendMode::kHardLight});
1536 }
1537
1538 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1539}
1540
1541TEST_P(AiksTest, SaveLayerFiltersScaleWithTransform) {
1542 Canvas canvas;
1543 canvas.Scale(GetContentScale());
1544 canvas.Translate(Vector2(100, 100));
1545
1546 auto texture = std::make_shared<Image>(CreateTextureForFixture("boston.jpg"));
1547 auto draw_image_layer = [&canvas, &texture](const Paint& paint) {
1548 canvas.SaveLayer(paint);
1549 canvas.DrawImage(texture, {}, Paint{});
1550 canvas.Restore();
1551 };
1552
1553 Paint effect_paint;
1556 .sigma = Sigma{6},
1557 };
1558 draw_image_layer(effect_paint);
1559
1560 canvas.Translate(Vector2(300, 300));
1561 canvas.Scale(Vector2(3, 3));
1562 draw_image_layer(effect_paint);
1563
1564 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1565}
1566
1567#if IMPELLER_ENABLE_3D
1568TEST_P(AiksTest, SceneColorSource) {
1569 // Load up the scene.
1570 auto mapping =
1571 flutter::testing::OpenFixtureAsMapping("flutter_logo_baked.glb.ipscene");
1572 ASSERT_NE(mapping, nullptr);
1573
1574 std::shared_ptr<scene::Node> gltf_scene = scene::Node::MakeFromFlatbuffer(
1575 *mapping, *GetContext()->GetResourceAllocator());
1576 ASSERT_NE(gltf_scene, nullptr);
1577
1578 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
1579 Paint paint;
1580
1581 static Scalar distance = 2;
1582 static Scalar y_pos = 0;
1583 static Scalar fov = 45;
1584 if (AiksTest::ImGuiBegin("Controls", nullptr,
1585 ImGuiWindowFlags_AlwaysAutoResize)) {
1586 ImGui::SliderFloat("Distance", &distance, 0, 4);
1587 ImGui::SliderFloat("Y", &y_pos, -3, 3);
1588 ImGui::SliderFloat("FOV", &fov, 1, 180);
1589 ImGui::End();
1590 }
1591
1592 Scalar angle = GetSecondsElapsed();
1593 auto camera_position =
1594 Vector3(distance * std::sin(angle), y_pos, -distance * std::cos(angle));
1595
1596 paint.color_source = ColorSource::MakeScene(
1597 gltf_scene,
1598 Matrix::MakePerspective(Degrees(fov), GetWindowSize(), 0.1, 1000) *
1599 Matrix::MakeLookAt(camera_position, {0, 0, 0}, {0, 1, 0}));
1600
1601 Canvas canvas;
1602 canvas.DrawPaint(Paint{.color = Color::MakeRGBA8(0xf9, 0xf9, 0xf9, 0xff)});
1603 canvas.Scale(GetContentScale());
1604 canvas.DrawPaint(paint);
1605 return canvas.EndRecordingAsPicture();
1606 };
1607
1608 ASSERT_TRUE(OpenPlaygroundHere(callback));
1609}
1610#endif // IMPELLER_ENABLE_3D
1611
1612TEST_P(AiksTest, PaintWithFilters) {
1613 // validate that a paint with a color filter "HasFilters", no other filters
1614 // impact this setting.
1615 Paint paint;
1616
1617 ASSERT_FALSE(paint.HasColorFilter());
1618
1619 paint.color_filter =
1621
1622 ASSERT_TRUE(paint.HasColorFilter());
1623
1624 paint.image_filter = ImageFilter::MakeBlur(Sigma(1.0), Sigma(1.0),
1627
1628 ASSERT_TRUE(paint.HasColorFilter());
1629
1630 paint.mask_blur_descriptor = {};
1631
1632 ASSERT_TRUE(paint.HasColorFilter());
1633
1634 paint.color_filter = nullptr;
1635
1636 ASSERT_FALSE(paint.HasColorFilter());
1637}
1638
1639TEST_P(AiksTest, OpacityPeepHoleApplicationTest) {
1640 auto entity_pass = std::make_shared<EntityPass>();
1641 auto rect = Rect::MakeLTRB(0, 0, 100, 100);
1642 Paint paint;
1643 paint.color = Color::White().WithAlpha(0.5);
1644 paint.color_filter =
1646
1647 // Paint has color filter, can't elide.
1648 auto delegate = std::make_shared<OpacityPeepholePassDelegate>(paint);
1649 ASSERT_FALSE(delegate->CanCollapseIntoParentPass(entity_pass.get()));
1650
1651 paint.color_filter = nullptr;
1652 paint.image_filter = ImageFilter::MakeBlur(Sigma(1.0), Sigma(1.0),
1655
1656 // Paint has image filter, can't elide.
1657 delegate = std::make_shared<OpacityPeepholePassDelegate>(paint);
1658 ASSERT_FALSE(delegate->CanCollapseIntoParentPass(entity_pass.get()));
1659
1660 paint.image_filter = nullptr;
1661 paint.color = Color::Red();
1662
1663 // Paint has no alpha, can't elide;
1664 delegate = std::make_shared<OpacityPeepholePassDelegate>(paint);
1665 ASSERT_FALSE(delegate->CanCollapseIntoParentPass(entity_pass.get()));
1666
1667 // Positive test.
1668 Entity entity;
1670 PathBuilder{}.AddRect(rect).TakePath(), Color::Red()));
1671 entity_pass->AddEntity(std::move(entity));
1672 paint.color = Color::Red().WithAlpha(0.5);
1673
1674 delegate = std::make_shared<OpacityPeepholePassDelegate>(paint);
1675 ASSERT_TRUE(delegate->CanCollapseIntoParentPass(entity_pass.get()));
1676}
1677
1678TEST_P(AiksTest, DrawPaintAbsorbsClears) {
1679 Canvas canvas;
1680 canvas.DrawPaint({.color = Color::Red(), .blend_mode = BlendMode::kSource});
1681 canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75),
1682 .blend_mode = BlendMode::kSourceOver});
1683
1684 Picture picture = canvas.EndRecordingAsPicture();
1685 auto expected = Color::Red().Blend(Color::CornflowerBlue().WithAlpha(0.75),
1687 ASSERT_EQ(picture.pass->GetClearColor(), expected);
1688
1689 std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
1690 std::shared_ptr<Context> real_context = GetContext();
1691 std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
1692 AiksContext renderer(mock_context, nullptr);
1693 std::shared_ptr<Image> image = picture.ToImage(renderer, {300, 300});
1694
1695 ASSERT_EQ(spy->render_passes_.size(), 1llu);
1696 std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
1697 ASSERT_EQ(render_pass->GetCommands().size(), 0llu);
1698}
1699
1700// This is important to enforce with texture reuse, since cached textures need
1701// to be cleared before reuse.
1703 ParentSaveLayerCreatesRenderPassWhenChildBackdropFilterIsPresent) {
1704 Canvas canvas;
1705 canvas.SaveLayer({}, std::nullopt, ImageFilter::MakeMatrix(Matrix(), {}));
1706 canvas.DrawPaint({.color = Color::Red(), .blend_mode = BlendMode::kSource});
1707 canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75),
1708 .blend_mode = BlendMode::kSourceOver});
1709 canvas.Restore();
1710
1711 Picture picture = canvas.EndRecordingAsPicture();
1712
1713 std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
1714 std::shared_ptr<Context> real_context = GetContext();
1715 std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
1716 AiksContext renderer(mock_context, nullptr);
1717 std::shared_ptr<Image> image = picture.ToImage(renderer, {300, 300});
1718
1719 ASSERT_EQ(spy->render_passes_.size(),
1720 GetBackend() == PlaygroundBackend::kOpenGLES ? 4llu : 3llu);
1721 std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
1722 ASSERT_EQ(render_pass->GetCommands().size(), 0llu);
1723}
1724
1725TEST_P(AiksTest, DrawRectAbsorbsClears) {
1726 Canvas canvas;
1727 canvas.DrawRect(Rect::MakeXYWH(0, 0, 300, 300),
1728 {.color = Color::Red(), .blend_mode = BlendMode::kSource});
1729 canvas.DrawRect(Rect::MakeXYWH(0, 0, 300, 300),
1730 {.color = Color::CornflowerBlue().WithAlpha(0.75),
1731 .blend_mode = BlendMode::kSourceOver});
1732
1733 std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
1734 Picture picture = canvas.EndRecordingAsPicture();
1735 std::shared_ptr<Context> real_context = GetContext();
1736 std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
1737 AiksContext renderer(mock_context, nullptr);
1738 std::shared_ptr<Image> image = picture.ToImage(renderer, {300, 300});
1739
1740 ASSERT_EQ(spy->render_passes_.size(), 1llu);
1741 std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
1742 ASSERT_EQ(render_pass->GetCommands().size(), 0llu);
1743}
1744
1745TEST_P(AiksTest, DrawRectAbsorbsClearsNegativeRRect) {
1746 Canvas canvas;
1747 canvas.DrawRRect(Rect::MakeXYWH(0, 0, 300, 300), {5.0, 5.0},
1748 {.color = Color::Red(), .blend_mode = BlendMode::kSource});
1749 canvas.DrawRRect(Rect::MakeXYWH(0, 0, 300, 300), {5.0, 5.0},
1750 {.color = Color::CornflowerBlue().WithAlpha(0.75),
1751 .blend_mode = BlendMode::kSourceOver});
1752
1753 std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
1754 Picture picture = canvas.EndRecordingAsPicture();
1755 std::shared_ptr<Context> real_context = GetContext();
1756 std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
1757 AiksContext renderer(mock_context, nullptr);
1758 std::shared_ptr<Image> image = picture.ToImage(renderer, {300, 300});
1759
1760 ASSERT_EQ(spy->render_passes_.size(), 1llu);
1761 std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
1762 ASSERT_EQ(render_pass->GetCommands().size(), 2llu);
1763}
1764
1765TEST_P(AiksTest, DrawRectAbsorbsClearsNegativeRotation) {
1766 Canvas canvas;
1767 canvas.Translate(Vector3(150.0, 150.0, 0.0));
1768 canvas.Rotate(Degrees(45.0));
1769 canvas.Translate(Vector3(-150.0, -150.0, 0.0));
1770 canvas.DrawRect(Rect::MakeXYWH(0, 0, 300, 300),
1771 {.color = Color::Red(), .blend_mode = BlendMode::kSource});
1772
1773 std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
1774 Picture picture = canvas.EndRecordingAsPicture();
1775 std::shared_ptr<Context> real_context = GetContext();
1776 std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
1777 AiksContext renderer(mock_context, nullptr);
1778 std::shared_ptr<Image> image = picture.ToImage(renderer, {300, 300});
1779
1780 ASSERT_EQ(spy->render_passes_.size(), 1llu);
1781 std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
1782 ASSERT_EQ(render_pass->GetCommands().size(), 1llu);
1783}
1784
1785TEST_P(AiksTest, DrawRectAbsorbsClearsNegative) {
1786 Canvas canvas;
1787 canvas.DrawRect(Rect::MakeXYWH(0, 0, 300, 300),
1788 {.color = Color::Red(), .blend_mode = BlendMode::kSource});
1789 canvas.DrawRect(Rect::MakeXYWH(0, 0, 300, 300),
1790 {.color = Color::CornflowerBlue().WithAlpha(0.75),
1791 .blend_mode = BlendMode::kSourceOver});
1792
1793 std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
1794 Picture picture = canvas.EndRecordingAsPicture();
1795 std::shared_ptr<Context> real_context = GetContext();
1796 std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
1797 AiksContext renderer(mock_context, nullptr);
1798 std::shared_ptr<Image> image = picture.ToImage(renderer, {301, 301});
1799
1800 ASSERT_EQ(spy->render_passes_.size(), 1llu);
1801 std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
1802 ASSERT_EQ(render_pass->GetCommands().size(), 2llu);
1803}
1804
1805TEST_P(AiksTest, ClipRectElidesNoOpClips) {
1806 Canvas canvas(Rect::MakeXYWH(0, 0, 100, 100));
1807 canvas.ClipRect(Rect::MakeXYWH(0, 0, 100, 100));
1808 canvas.ClipRect(Rect::MakeXYWH(-100, -100, 300, 300));
1809 canvas.DrawPaint({.color = Color::Red(), .blend_mode = BlendMode::kSource});
1810 canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75),
1811 .blend_mode = BlendMode::kSourceOver});
1812
1813 Picture picture = canvas.EndRecordingAsPicture();
1814 auto expected = Color::Red().Blend(Color::CornflowerBlue().WithAlpha(0.75),
1816 ASSERT_EQ(picture.pass->GetClearColor(), expected);
1817
1818 std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
1819 std::shared_ptr<Context> real_context = GetContext();
1820 std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
1821 AiksContext renderer(mock_context, nullptr);
1822 std::shared_ptr<Image> image = picture.ToImage(renderer, {300, 300});
1823
1824 ASSERT_EQ(spy->render_passes_.size(), 1llu);
1825 std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
1826 ASSERT_EQ(render_pass->GetCommands().size(), 0llu);
1827}
1828
1829TEST_P(AiksTest, ClearColorOptimizationDoesNotApplyForBackdropFilters) {
1830 Canvas canvas;
1831 canvas.SaveLayer({}, std::nullopt,
1835 canvas.DrawPaint({.color = Color::Red(), .blend_mode = BlendMode::kSource});
1836 canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75),
1837 .blend_mode = BlendMode::kSourceOver});
1838 canvas.Restore();
1839
1840 Picture picture = canvas.EndRecordingAsPicture();
1841
1842 std::optional<Color> actual_color;
1843 bool found_subpass = false;
1844 picture.pass->IterateAllElements([&](EntityPass::Element& element) -> bool {
1845 if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
1846 actual_color = subpass->get()->GetClearColor();
1847 found_subpass = true;
1848 }
1849 // Fail if the first element isn't a subpass.
1850 return true;
1851 });
1852
1853 EXPECT_TRUE(found_subpass);
1854 EXPECT_FALSE(actual_color.has_value());
1855}
1856
1857TEST_P(AiksTest, CollapsedDrawPaintInSubpass) {
1858 Canvas canvas;
1859 canvas.DrawPaint(
1860 {.color = Color::Yellow(), .blend_mode = BlendMode::kSource});
1861 canvas.SaveLayer({.blend_mode = BlendMode::kMultiply});
1862 canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75),
1863 .blend_mode = BlendMode::kSourceOver});
1864
1865 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1866}
1867
1868TEST_P(AiksTest, CollapsedDrawPaintInSubpassBackdropFilter) {
1869 // Bug: https://github.com/flutter/flutter/issues/131576
1870 Canvas canvas;
1871 canvas.DrawPaint(
1872 {.color = Color::Yellow(), .blend_mode = BlendMode::kSource});
1873 canvas.SaveLayer({}, {},
1874 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
1877 canvas.DrawPaint(
1878 {.color = Color::CornflowerBlue(), .blend_mode = BlendMode::kSourceOver});
1879
1880 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1881}
1882
1883TEST_P(AiksTest, ColorMatrixFilterSubpassCollapseOptimization) {
1884 Canvas canvas;
1885
1886 canvas.SaveLayer({
1887 .color_filter =
1888 ColorFilter::MakeMatrix({.array =
1889 {
1890 -1.0, 0, 0, 1.0, 0, //
1891 0, -1.0, 0, 1.0, 0, //
1892 0, 0, -1.0, 1.0, 0, //
1893 1.0, 1.0, 1.0, 1.0, 0 //
1894 }}),
1895 });
1896
1897 canvas.Translate({500, 300, 0});
1898 canvas.Rotate(Radians(2 * kPi / 3));
1899 canvas.DrawRect(Rect::MakeXYWH(100, 100, 200, 200), {.color = Color::Blue()});
1900
1901 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1902}
1903
1904TEST_P(AiksTest, LinearToSrgbFilterSubpassCollapseOptimization) {
1905 Canvas canvas;
1906
1907 canvas.SaveLayer({
1908 .color_filter = ColorFilter::MakeLinearToSrgb(),
1909 });
1910
1911 canvas.Translate({500, 300, 0});
1912 canvas.Rotate(Radians(2 * kPi / 3));
1913 canvas.DrawRect(Rect::MakeXYWH(100, 100, 200, 200), {.color = Color::Blue()});
1914
1915 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1916}
1917
1918TEST_P(AiksTest, SrgbToLinearFilterSubpassCollapseOptimization) {
1919 Canvas canvas;
1920
1921 canvas.SaveLayer({
1922 .color_filter = ColorFilter::MakeSrgbToLinear(),
1923 });
1924
1925 canvas.Translate({500, 300, 0});
1926 canvas.Rotate(Radians(2 * kPi / 3));
1927 canvas.DrawRect(Rect::MakeXYWH(100, 100, 200, 200), {.color = Color::Blue()});
1928
1929 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1930}
1931
1932TEST_P(AiksTest, TranslucentSaveLayerDrawsCorrectly) {
1933 Canvas canvas;
1934
1935 canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
1936
1937 canvas.SaveLayer({.color = Color::Black().WithAlpha(0.5)});
1938 canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
1939 canvas.Restore();
1940
1941 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1942}
1943
1944TEST_P(AiksTest, TranslucentSaveLayerWithBlendColorFilterDrawsCorrectly) {
1945 Canvas canvas;
1946
1947 canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
1948
1949 canvas.SaveLayer({
1950 .color = Color::Black().WithAlpha(0.5),
1951 .color_filter =
1953 });
1954 canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
1955 canvas.Restore();
1956
1957 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1958}
1959
1960TEST_P(AiksTest, TranslucentSaveLayerWithBlendImageFilterDrawsCorrectly) {
1961 Canvas canvas;
1962
1963 canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
1964
1965 canvas.SaveLayer({
1966 .color = Color::Black().WithAlpha(0.5),
1967 .image_filter = ImageFilter::MakeFromColorFilter(
1969 });
1970
1971 canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
1972 canvas.Restore();
1973
1974 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1975}
1976
1977TEST_P(AiksTest, TranslucentSaveLayerWithColorAndImageFilterDrawsCorrectly) {
1978 Canvas canvas;
1979
1980 canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
1981
1982 canvas.SaveLayer({
1983 .color = Color::Black().WithAlpha(0.5),
1984 .color_filter =
1986 });
1987
1988 canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
1989 canvas.Restore();
1990
1991 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1992}
1993
1994TEST_P(AiksTest, ImageFilteredSaveLayerWithUnboundedContents) {
1995 Canvas canvas;
1996 canvas.Scale(GetContentScale());
1997
1998 auto test = [&canvas](const std::shared_ptr<ImageFilter>& filter) {
1999 auto DrawLine = [&canvas](const Point& p0, const Point& p1,
2000 const Paint& p) {
2001 auto path = PathBuilder{}
2002 .AddLine(p0, p1)
2004 .TakePath();
2005 Paint paint = p;
2007 canvas.DrawPath(path, paint);
2008 };
2009 // Registration marks for the edge of the SaveLayer
2010 DrawLine(Point(75, 100), Point(225, 100), {.color = Color::White()});
2011 DrawLine(Point(75, 200), Point(225, 200), {.color = Color::White()});
2012 DrawLine(Point(100, 75), Point(100, 225), {.color = Color::White()});
2013 DrawLine(Point(200, 75), Point(200, 225), {.color = Color::White()});
2014
2015 canvas.SaveLayer({.image_filter = filter},
2016 Rect::MakeLTRB(100, 100, 200, 200));
2017 {
2018 // DrawPaint to verify correct behavior when the contents are unbounded.
2019 canvas.DrawPaint({.color = Color::Yellow()});
2020
2021 // Contrasting rectangle to see interior blurring
2022 canvas.DrawRect(Rect::MakeLTRB(125, 125, 175, 175),
2023 {.color = Color::Blue()});
2024 }
2025 canvas.Restore();
2026 };
2027
2031
2032 canvas.Translate({200.0, 0.0});
2033
2035
2036 canvas.Translate({200.0, 0.0});
2037
2039
2040 canvas.Translate({-400.0, 200.0});
2041
2042 auto rotate_filter =
2045 Matrix::MakeTranslation({-150, -150}),
2047 test(rotate_filter);
2048
2049 canvas.Translate({200.0, 0.0});
2050
2051 auto rgb_swap_filter = ImageFilter::MakeFromColorFilter(
2052 *ColorFilter::MakeMatrix({.array = {
2053 0, 1, 0, 0, 0, //
2054 0, 0, 1, 0, 0, //
2055 1, 0, 0, 0, 0, //
2056 0, 0, 0, 1, 0 //
2057 }}));
2058 test(rgb_swap_filter);
2059
2060 canvas.Translate({200.0, 0.0});
2061
2062 test(ImageFilter::MakeCompose(*rotate_filter, *rgb_swap_filter));
2063
2064 canvas.Translate({-400.0, 200.0});
2065
2067 *rotate_filter));
2068
2069 canvas.Translate({200.0, 0.0});
2070
2072 *rgb_swap_filter));
2073
2074 canvas.Translate({200.0, 0.0});
2075
2077 Matrix::MakeTranslation({25.0, 25.0}),
2078 *ImageFilter::MakeCompose(*rotate_filter, *rgb_swap_filter)));
2079
2080 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2081}
2082
2083TEST_P(AiksTest, ImageFilteredUnboundedSaveLayerWithUnboundedContents) {
2084 Canvas canvas;
2085 canvas.Scale(GetContentScale());
2086
2087 auto blur_filter = ImageFilter::MakeBlur(Sigma{10.0}, Sigma{10.0},
2090
2091 canvas.SaveLayer({.image_filter = blur_filter}, std::nullopt);
2092 {
2093 // DrawPaint to verify correct behavior when the contents are unbounded.
2094 canvas.DrawPaint({.color = Color::Yellow()});
2095
2096 // Contrasting rectangle to see interior blurring
2097 canvas.DrawRect(Rect::MakeLTRB(125, 125, 175, 175),
2098 {.color = Color::Blue()});
2099 }
2100 canvas.Restore();
2101
2102 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2103}
2104
2105TEST_P(AiksTest, TranslucentSaveLayerImageDrawsCorrectly) {
2106 Canvas canvas;
2107
2108 auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2109 canvas.DrawImage(image, {100, 100}, {});
2110
2111 canvas.SaveLayer({.color = Color::Black().WithAlpha(0.5)});
2112 canvas.DrawImage(image, {100, 500}, {});
2113 canvas.Restore();
2114
2115 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2116}
2117
2118TEST_P(AiksTest, TranslucentSaveLayerWithColorMatrixColorFilterDrawsCorrectly) {
2119 Canvas canvas;
2120
2121 auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2122 canvas.DrawImage(image, {100, 100}, {});
2123
2124 canvas.SaveLayer({
2125 .color = Color::Black().WithAlpha(0.5),
2126 .color_filter = ColorFilter::MakeMatrix({.array =
2127 {
2128 1, 0, 0, 0, 0, //
2129 0, 1, 0, 0, 0, //
2130 0, 0, 1, 0, 0, //
2131 0, 0, 0, 2, 0 //
2132 }}),
2133 });
2134 canvas.DrawImage(image, {100, 500}, {});
2135 canvas.Restore();
2136
2137 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2138}
2139
2140TEST_P(AiksTest, TranslucentSaveLayerWithColorMatrixImageFilterDrawsCorrectly) {
2141 Canvas canvas;
2142
2143 auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2144 canvas.DrawImage(image, {100, 100}, {});
2145
2146 canvas.SaveLayer({
2147 .color = Color::Black().WithAlpha(0.5),
2148 .image_filter = ImageFilter::MakeFromColorFilter(
2149 *ColorFilter::MakeMatrix({.array =
2150 {
2151 1, 0, 0, 0, 0, //
2152 0, 1, 0, 0, 0, //
2153 0, 0, 1, 0, 0, //
2154 0, 0, 0, 2, 0 //
2155 }})),
2156 });
2157 canvas.DrawImage(image, {100, 500}, {});
2158 canvas.Restore();
2159
2160 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2161}
2162
2164 TranslucentSaveLayerWithColorFilterAndImageFilterDrawsCorrectly) {
2165 Canvas canvas;
2166
2167 auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2168 canvas.DrawImage(image, {100, 100}, {});
2169
2170 canvas.SaveLayer({
2171 .color = Color::Black().WithAlpha(0.5),
2172 .image_filter = ImageFilter::MakeFromColorFilter(
2173 *ColorFilter::MakeMatrix({.array =
2174 {
2175 1, 0, 0, 0, 0, //
2176 0, 1, 0, 0, 0, //
2177 0, 0.2, 1, 0, 0, //
2178 0, 0, 0, 0.5, 0 //
2179 }})),
2180 .color_filter =
2182 });
2183 canvas.DrawImage(image, {100, 500}, {});
2184 canvas.Restore();
2185
2186 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2187}
2188
2189TEST_P(AiksTest, TranslucentSaveLayerWithAdvancedBlendModeDrawsCorrectly) {
2190 Canvas canvas;
2191 canvas.DrawRect(Rect::MakeXYWH(0, 0, 400, 400), {.color = Color::Red()});
2192 canvas.SaveLayer({
2193 .color = Color::Black().WithAlpha(0.5),
2194 .blend_mode = BlendMode::kLighten,
2195 });
2196 canvas.DrawCircle({200, 200}, 100, {.color = Color::Green()});
2197 canvas.Restore();
2198 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2199}
2200
2201/// This is a regression check for https://github.com/flutter/engine/pull/41129
2202/// The entire screen is green if successful. If failing, no frames will render,
2203/// or the entire screen will be transparent black.
2204TEST_P(AiksTest, CanRenderTinyOverlappingSubpasses) {
2205 Canvas canvas;
2206 canvas.DrawPaint({.color = Color::Red()});
2207
2208 // Draw two overlapping subpixel circles.
2209 canvas.SaveLayer({});
2210 canvas.DrawCircle({100, 100}, 0.1, {.color = Color::Yellow()});
2211 canvas.Restore();
2212 canvas.SaveLayer({});
2213 canvas.DrawCircle({100, 100}, 0.1, {.color = Color::Yellow()});
2214 canvas.Restore();
2215
2216 canvas.DrawPaint({.color = Color::Green()});
2217
2218 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2219}
2220
2221/// Tests that the debug checkerboard displays for offscreen textures when
2222/// enabled. Most of the complexity here is just to future proof by making pass
2223/// collapsing hard.
2224TEST_P(AiksTest, CanRenderOffscreenCheckerboard) {
2225 Canvas canvas;
2227
2228 canvas.DrawPaint({.color = Color::AntiqueWhite()});
2229 canvas.DrawCircle({400, 300}, 200,
2230 {.color = Color::CornflowerBlue().WithAlpha(0.75)});
2231
2232 canvas.SaveLayer({.blend_mode = BlendMode::kMultiply});
2233 {
2234 canvas.DrawCircle({500, 400}, 200,
2235 {.color = Color::DarkBlue().WithAlpha(0.75)});
2236 canvas.DrawCircle({550, 450}, 200,
2237 {.color = Color::LightCoral().WithAlpha(0.75),
2238 .blend_mode = BlendMode::kLuminosity});
2239 }
2240 canvas.Restore();
2241
2242 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2243}
2244
2245TEST_P(AiksTest, OpaqueEntitiesGetCoercedToSource) {
2246 Canvas canvas;
2247 canvas.Scale(Vector2(1.618, 1.618));
2248 canvas.DrawCircle(Point(), 10,
2249 {
2250 .color = Color::CornflowerBlue(),
2251 .blend_mode = BlendMode::kSourceOver,
2252 });
2253 Picture picture = canvas.EndRecordingAsPicture();
2254
2255 // Extract the SolidColorSource.
2256 // Entity entity;
2257 std::vector<Entity> entity;
2258 std::shared_ptr<SolidColorContents> contents;
2259 picture.pass->IterateAllEntities([e = &entity, &contents](Entity& entity) {
2260 if (ScalarNearlyEqual(entity.GetTransform().GetScale().x, 1.618f)) {
2261 contents =
2262 std::static_pointer_cast<SolidColorContents>(entity.GetContents());
2263 e->emplace_back(entity.Clone());
2264 return false;
2265 }
2266 return true;
2267 });
2268
2269 ASSERT_TRUE(entity.size() >= 1);
2270 ASSERT_TRUE(contents->IsOpaque());
2271 ASSERT_EQ(entity[0].GetBlendMode(), BlendMode::kSource);
2272}
2273
2274TEST_P(AiksTest, CanRenderDestructiveSaveLayer) {
2275 Canvas canvas;
2276
2277 canvas.DrawPaint({.color = Color::Red()});
2278 // Draw an empty savelayer with a destructive blend mode, which will replace
2279 // the entire red screen with fully transparent black, except for the green
2280 // circle drawn within the layer.
2281 canvas.SaveLayer({.blend_mode = BlendMode::kSource});
2282 canvas.DrawCircle({300, 300}, 100, {.color = Color::Green()});
2283 canvas.Restore();
2284
2285 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2286}
2287
2288// Regression test for https://github.com/flutter/flutter/issues/126701 .
2289TEST_P(AiksTest, CanRenderClippedRuntimeEffects) {
2290 auto runtime_stages =
2291 OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
2292
2293 auto runtime_stage =
2294 runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
2295 ASSERT_TRUE(runtime_stage);
2296 ASSERT_TRUE(runtime_stage->IsDirty());
2297
2298 struct FragUniforms {
2299 Vector2 iResolution;
2300 Scalar iTime;
2301 } frag_uniforms = {.iResolution = Vector2(400, 400), .iTime = 100.0};
2302 auto uniform_data = std::make_shared<std::vector<uint8_t>>();
2303 uniform_data->resize(sizeof(FragUniforms));
2304 memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
2305
2306 std::vector<RuntimeEffectContents::TextureInput> texture_inputs;
2307
2308 Paint paint;
2310 runtime_stage, uniform_data, texture_inputs);
2311
2312 Canvas canvas;
2313 canvas.Save();
2314 canvas.ClipRRect(Rect::MakeXYWH(0, 0, 400, 400), {10.0, 10.0},
2316 canvas.DrawRect(Rect::MakeXYWH(0, 0, 400, 400), paint);
2317 canvas.Restore();
2318
2319 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2320}
2321
2322TEST_P(AiksTest, DrawPaintTransformsBounds) {
2323 auto runtime_stages = OpenAssetAsRuntimeStage("gradient.frag.iplr");
2324 auto runtime_stage =
2325 runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
2326 ASSERT_TRUE(runtime_stage);
2327 ASSERT_TRUE(runtime_stage->IsDirty());
2328
2329 struct FragUniforms {
2330 Size size;
2331 } frag_uniforms = {.size = Size::MakeWH(400, 400)};
2332 auto uniform_data = std::make_shared<std::vector<uint8_t>>();
2333 uniform_data->resize(sizeof(FragUniforms));
2334 memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
2335
2336 std::vector<RuntimeEffectContents::TextureInput> texture_inputs;
2337
2338 Paint paint;
2340 runtime_stage, uniform_data, texture_inputs);
2341
2342 Canvas canvas;
2343 canvas.Save();
2344 canvas.Scale(GetContentScale());
2345 canvas.DrawPaint(paint);
2346 canvas.Restore();
2347
2348 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2349}
2350
2351TEST_P(AiksTest, CanDrawPoints) {
2352 std::vector<Point> points = {
2353 {0, 0}, //
2354 {100, 100}, //
2355 {100, 0}, //
2356 {0, 100}, //
2357 {0, 0}, //
2358 {48, 48}, //
2359 {52, 52}, //
2360 };
2361 std::vector<PointStyle> caps = {
2364 };
2365 Paint paint;
2366 paint.color = Color::Yellow().WithAlpha(0.5);
2367
2368 Paint background;
2369 background.color = Color::Black();
2370
2371 Canvas canvas;
2372 canvas.DrawPaint(background);
2373 canvas.Translate({200, 200});
2375 canvas.Translate({150, 0});
2377
2378 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2379}
2380
2381// Regression test for https://github.com/flutter/flutter/issues/127374.
2382TEST_P(AiksTest, DrawAtlasWithColorAdvancedAndTransform) {
2383 // Draws the image as four squares stiched together.
2384 auto atlas = CreateTextureForFixture("bay_bridge.jpg");
2385 auto size = atlas->GetSize();
2386 auto image = std::make_shared<Image>(atlas);
2387 // Divide image into four quadrants.
2388 Scalar half_width = size.width / 2;
2389 Scalar half_height = size.height / 2;
2390 std::vector<Rect> texture_coordinates = {
2391 Rect::MakeLTRB(0, 0, half_width, half_height),
2392 Rect::MakeLTRB(half_width, 0, size.width, half_height),
2393 Rect::MakeLTRB(0, half_height, half_width, size.height),
2394 Rect::MakeLTRB(half_width, half_height, size.width, size.height)};
2395 // Position quadrants adjacent to eachother.
2396 std::vector<Matrix> transforms = {
2397 Matrix::MakeTranslation({0, 0, 0}),
2398 Matrix::MakeTranslation({half_width, 0, 0}),
2399 Matrix::MakeTranslation({0, half_height, 0}),
2400 Matrix::MakeTranslation({half_width, half_height, 0})};
2401 std::vector<Color> colors = {Color::Red(), Color::Green(), Color::Blue(),
2402 Color::Yellow()};
2403
2404 Paint paint;
2405
2406 Canvas canvas;
2407 canvas.Scale({0.25, 0.25, 1.0});
2408 canvas.DrawAtlas(image, transforms, texture_coordinates, colors,
2409 BlendMode::kModulate, {}, std::nullopt, paint);
2410
2411 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2412}
2413
2414// Regression test for https://github.com/flutter/flutter/issues/127374.
2415TEST_P(AiksTest, DrawAtlasAdvancedAndTransform) {
2416 // Draws the image as four squares stiched together.
2417 auto atlas = CreateTextureForFixture("bay_bridge.jpg");
2418 auto size = atlas->GetSize();
2419 auto image = std::make_shared<Image>(atlas);
2420 // Divide image into four quadrants.
2421 Scalar half_width = size.width / 2;
2422 Scalar half_height = size.height / 2;
2423 std::vector<Rect> texture_coordinates = {
2424 Rect::MakeLTRB(0, 0, half_width, half_height),
2425 Rect::MakeLTRB(half_width, 0, size.width, half_height),
2426 Rect::MakeLTRB(0, half_height, half_width, size.height),
2427 Rect::MakeLTRB(half_width, half_height, size.width, size.height)};
2428 // Position quadrants adjacent to eachother.
2429 std::vector<Matrix> transforms = {
2430 Matrix::MakeTranslation({0, 0, 0}),
2431 Matrix::MakeTranslation({half_width, 0, 0}),
2432 Matrix::MakeTranslation({0, half_height, 0}),
2433 Matrix::MakeTranslation({half_width, half_height, 0})};
2434
2435 Paint paint;
2436
2437 Canvas canvas;
2438 canvas.Scale({0.25, 0.25, 1.0});
2439 canvas.DrawAtlas(image, transforms, texture_coordinates, {},
2440 BlendMode::kModulate, {}, std::nullopt, paint);
2441
2442 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2443}
2444
2445TEST_P(AiksTest, CanDrawPointsWithTextureMap) {
2446 auto texture = CreateTextureForFixture("table_mountain_nx.png",
2447 /*enable_mipmapping=*/true);
2448
2449 std::vector<Point> points = {
2450 {0, 0}, //
2451 {100, 100}, //
2452 {100, 0}, //
2453 {0, 100}, //
2454 {0, 0}, //
2455 {48, 48}, //
2456 {52, 52}, //
2457 };
2458 std::vector<PointStyle> caps = {
2461 };
2462 Paint paint;
2464 Entity::TileMode::kClamp, {}, {});
2465
2466 Canvas canvas;
2467 canvas.Translate({200, 200});
2468 canvas.DrawPoints(points, 100, paint, PointStyle::kRound);
2469 canvas.Translate({150, 0});
2470 canvas.DrawPoints(points, 100, paint, PointStyle::kSquare);
2471
2472 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2473}
2474
2475// This currently renders solid blue, as the support for text color sources was
2476// moved into DLDispatching. Path data requires the SkTextBlobs which are not
2477// used in impeller::TextFrames.
2478TEST_P(AiksTest, TextForegroundShaderWithTransform) {
2479 auto mapping = flutter::testing::OpenFixtureAsSkData("Roboto-Regular.ttf");
2480 ASSERT_NE(mapping, nullptr);
2481
2482 Scalar font_size = 100;
2484 SkFont sk_font(font_mgr->makeFromData(mapping), font_size);
2485
2486 Paint text_paint;
2487 text_paint.color = Color::Blue();
2488
2489 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
2490 Color{0.1294, 0.5882, 0.9529, 1.0}};
2491 std::vector<Scalar> stops = {
2492 0.0,
2493 1.0,
2494 };
2496 {0, 0}, {100, 100}, std::move(colors), std::move(stops),
2498
2499 Canvas canvas;
2500 canvas.Translate({100, 100});
2501 canvas.Rotate(Radians(kPi / 4));
2502
2503 auto blob = SkTextBlob::MakeFromString("Hello", sk_font);
2504 ASSERT_NE(blob, nullptr);
2506 canvas.DrawTextFrame(frame, Point(), text_paint);
2507
2508 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2509}
2510
2511TEST_P(AiksTest, MatrixSaveLayerFilter) {
2512 Canvas canvas;
2513 canvas.DrawPaint({.color = Color::Black()});
2514 canvas.SaveLayer({}, std::nullopt);
2515 {
2516 canvas.DrawCircle(Point(200, 200), 100,
2517 {.color = Color::Green().WithAlpha(0.5),
2518 .blend_mode = BlendMode::kPlus});
2519 // Should render a second circle, centered on the bottom-right-most edge of
2520 // the circle.
2521 canvas.SaveLayer({.image_filter = ImageFilter::MakeMatrix(
2523 (200 + 100 * k1OverSqrt2)) *
2524 Matrix::MakeScale(Vector2(1, 1) * 0.5) *
2525 Matrix::MakeTranslation(Vector2(-200, -200)),
2527 std::nullopt);
2528 canvas.DrawCircle(Point(200, 200), 100,
2529 {.color = Color::Green().WithAlpha(0.5),
2530 .blend_mode = BlendMode::kPlus});
2531 canvas.Restore();
2532 }
2533 canvas.Restore();
2534
2535 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2536}
2537
2538TEST_P(AiksTest, MatrixBackdropFilter) {
2539 Canvas canvas;
2540 canvas.DrawPaint({.color = Color::Black()});
2541 canvas.SaveLayer({}, std::nullopt);
2542 {
2543 canvas.DrawCircle(Point(200, 200), 100,
2544 {.color = Color::Green().WithAlpha(0.5),
2545 .blend_mode = BlendMode::kPlus});
2546 // Should render a second circle, centered on the bottom-right-most edge of
2547 // the circle.
2548 canvas.SaveLayer(
2549 {}, std::nullopt,
2551 Matrix::MakeTranslation(Vector2(1, 1) * (100 + 100 * k1OverSqrt2)) *
2552 Matrix::MakeScale(Vector2(1, 1) * 0.5) *
2553 Matrix::MakeTranslation(Vector2(-100, -100)),
2555 canvas.Restore();
2556 }
2557 canvas.Restore();
2558
2559 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2560}
2561
2562TEST_P(AiksTest, SolidColorApplyColorFilter) {
2563 auto contents = SolidColorContents();
2564 contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75));
2565 auto result = contents.ApplyColorFilter([](const Color& color) {
2566 return color.Blend(Color::LimeGreen().WithAlpha(0.75), BlendMode::kScreen);
2567 });
2568 ASSERT_TRUE(result);
2569 ASSERT_COLOR_NEAR(contents.GetColor(),
2570 Color(0.424452, 0.828743, 0.79105, 0.9375));
2571}
2572
2573TEST_P(AiksTest, DrawScaledTextWithPerspectiveNoSaveLayer) {
2574 Canvas canvas;
2575 canvas.Transform(Matrix(1.0, 0.0, 0.0, 0.0, //
2576 0.0, 1.0, 0.0, 0.0, //
2577 0.0, 0.0, 1.0, 0.01, //
2578 0.0, 0.0, 0.0, 1.0) * //
2580
2581 ASSERT_TRUE(RenderTextInCanvasSkia(GetContext(), canvas, "Hello world",
2582 "Roboto-Regular.ttf"));
2583
2584 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2585}
2586
2587TEST_P(AiksTest, DrawScaledTextWithPerspectiveSaveLayer) {
2588 Canvas canvas;
2589 Paint save_paint;
2590 canvas.SaveLayer(save_paint);
2591 canvas.Transform(Matrix(1.0, 0.0, 0.0, 0.0, //
2592 0.0, 1.0, 0.0, 0.0, //
2593 0.0, 0.0, 1.0, 0.01, //
2594 0.0, 0.0, 0.0, 1.0) * //
2596
2597 ASSERT_TRUE(RenderTextInCanvasSkia(GetContext(), canvas, "Hello world",
2598 "Roboto-Regular.ttf"));
2599 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2600}
2601
2602TEST_P(AiksTest, PipelineBlendSingleParameter) {
2603 Canvas canvas;
2604
2605 // Should render a green square in the middle of a blue circle.
2606 canvas.SaveLayer({});
2607 {
2608 canvas.Translate(Point(100, 100));
2609 canvas.DrawCircle(Point(200, 200), 200, {.color = Color::Blue()});
2610 canvas.ClipRect(Rect::MakeXYWH(100, 100, 200, 200));
2611 canvas.DrawCircle(Point(200, 200), 200,
2612 {
2613 .color = Color::Green(),
2614 .blend_mode = BlendMode::kSourceOver,
2615 .image_filter = ImageFilter::MakeFromColorFilter(
2617 Color::White())),
2618 });
2619 canvas.Restore();
2620 }
2621
2622 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2623}
2624
2626 auto capture_context = CaptureContext::MakeAllowlist({"TestDocument"});
2627
2628 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
2629 Canvas canvas;
2630
2631 capture_context.Rewind();
2632 auto document = capture_context.GetDocument("TestDocument");
2633
2634 auto color = document.AddColor("Background color", Color::CornflowerBlue());
2635 canvas.DrawPaint({.color = color});
2636
2637 if (AiksTest::ImGuiBegin("TestDocument", nullptr,
2638 ImGuiWindowFlags_AlwaysAutoResize)) {
2639 document.GetElement()->properties.Iterate([](CaptureProperty& property) {
2640 property.Invoke({.color = [](CaptureColorProperty& p) {
2641 ImGui::ColorEdit4(p.label.c_str(),
2642 reinterpret_cast<float*>(&p.value));
2643 }});
2644 });
2645 ImGui::End();
2646 }
2647
2648 return canvas.EndRecordingAsPicture();
2649 };
2650 OpenPlaygroundHere(callback);
2651}
2652
2653TEST_P(AiksTest, CaptureInactivatedByDefault) {
2654 ASSERT_FALSE(GetContext()->capture.IsActive());
2655}
2656
2657// Regression test for https://github.com/flutter/flutter/issues/134678.
2658TEST_P(AiksTest, ReleasesTextureOnTeardown) {
2659 auto context = MakeContext();
2660 std::weak_ptr<Texture> weak_texture;
2661
2662 {
2663 auto texture = CreateTextureForFixture("table_mountain_nx.png");
2664
2665 Canvas canvas;
2666 canvas.Scale(GetContentScale());
2667 canvas.Translate({100.0f, 100.0f, 0});
2668
2669 Paint paint;
2670 paint.color_source = ColorSource::MakeImage(
2672 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint);
2673
2674 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2675 }
2676
2677 // See https://github.com/flutter/flutter/issues/134751.
2678 //
2679 // If the fence waiter was working this may not be released by the end of the
2680 // scope above. Adding a manual shutdown so that future changes to the fence
2681 // waiter will not flake this test.
2682 context->Shutdown();
2683
2684 // The texture should be released by now.
2685 ASSERT_TRUE(weak_texture.expired()) << "When the texture is no longer in use "
2686 "by the backend, it should be "
2687 "released.";
2688}
2689
2690// Regression test for https://github.com/flutter/flutter/issues/135441 .
2691TEST_P(AiksTest, VerticesGeometryUVPositionData) {
2692 Canvas canvas;
2693 Paint paint;
2694 auto texture = CreateTextureForFixture("table_mountain_nx.png");
2695
2697 Entity::TileMode::kClamp, {}, {});
2698
2699 auto vertices = {Point(0, 0), Point(texture->GetSize().width, 0),
2700 Point(0, texture->GetSize().height)};
2701 std::vector<uint16_t> indices = {0u, 1u, 2u};
2702 std::vector<Point> texture_coordinates = {};
2703 std::vector<Color> vertex_colors = {};
2704 auto geometry = std::make_shared<VerticesGeometry>(
2705 vertices, indices, texture_coordinates, vertex_colors,
2707
2708 canvas.DrawVertices(geometry, BlendMode::kSourceOver, paint);
2709 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2710}
2711
2712// Regression test for https://github.com/flutter/flutter/issues/135441 .
2713TEST_P(AiksTest, VerticesGeometryUVPositionDataWithTranslate) {
2714 Canvas canvas;
2715 Paint paint;
2716 auto texture = CreateTextureForFixture("table_mountain_nx.png");
2717
2718 paint.color_source = ColorSource::MakeImage(
2720 Matrix::MakeTranslation({100.0, 100.0}));
2721
2722 auto vertices = {Point(0, 0), Point(texture->GetSize().width, 0),
2723 Point(0, texture->GetSize().height)};
2724 std::vector<uint16_t> indices = {0u, 1u, 2u};
2725 std::vector<Point> texture_coordinates = {};
2726 std::vector<Color> vertex_colors = {};
2727 auto geometry = std::make_shared<VerticesGeometry>(
2728 vertices, indices, texture_coordinates, vertex_colors,
2730
2731 canvas.DrawVertices(geometry, BlendMode::kSourceOver, paint);
2732 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2733}
2734
2735// Regression test for https://github.com/flutter/flutter/issues/145707
2736TEST_P(AiksTest, VerticesGeometryColorUVPositionData) {
2737 Canvas canvas;
2738 Paint paint;
2739 auto texture = CreateTextureForFixture("table_mountain_nx.png");
2740
2741 paint.color_source =
2744
2745 auto vertices = {
2746 Point(0, 0),
2747 Point(texture->GetSize().width, 0),
2748 Point(0, texture->GetSize().height),
2749 Point(texture->GetSize().width, 0),
2750 Point(0, 0),
2751 Point(texture->GetSize().width, texture->GetSize().height),
2752 };
2753 std::vector<uint16_t> indices = {};
2754 std::vector<Point> texture_coordinates = {};
2755 std::vector<Color> vertex_colors = {
2759 };
2760 auto geometry = std::make_shared<VerticesGeometry>(
2761 vertices, indices, texture_coordinates, vertex_colors,
2763
2765 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2766}
2767
2768TEST_P(AiksTest, VerticesGeometryColorUVPositionDataAdvancedBlend) {
2769 Canvas canvas;
2770 Paint paint;
2771 auto texture = CreateTextureForFixture("table_mountain_nx.png");
2772
2773 paint.color_source =
2776
2777 auto vertices = {
2778 Point(0, 0),
2779 Point(texture->GetSize().width, 0),
2780 Point(0, texture->GetSize().height),
2781 Point(texture->GetSize().width, 0),
2782 Point(0, 0),
2783 Point(texture->GetSize().width, texture->GetSize().height),
2784 };
2785 std::vector<uint16_t> indices = {};
2786 std::vector<Point> texture_coordinates = {};
2787 std::vector<Color> vertex_colors = {
2791 };
2792 auto geometry = std::make_shared<VerticesGeometry>(
2793 vertices, indices, texture_coordinates, vertex_colors,
2795
2796 canvas.DrawVertices(geometry, BlendMode::kColorBurn, paint);
2797 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2798}
2799
2800TEST_P(AiksTest, MatrixImageFilterMagnify) {
2801 Canvas canvas;
2802 canvas.Scale(GetContentScale());
2803 auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2804 canvas.Translate({600, -200});
2805 canvas.SaveLayer({
2806 .image_filter = std::make_shared<MatrixImageFilter>(
2807 Matrix::MakeScale({2, 2, 2}), SamplerDescriptor{}),
2808 });
2809 canvas.DrawImage(image, {0, 0},
2810 Paint{.color = Color::White().WithAlpha(0.5)});
2811 canvas.Restore();
2812
2813 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2814}
2815
2816// Render a white circle at the top left corner of the screen.
2817TEST_P(AiksTest, MatrixImageFilterDoesntCullWhenTranslatedFromOffscreen) {
2818 Canvas canvas;
2819 canvas.Scale(GetContentScale());
2820 canvas.Translate({100, 100});
2821 // Draw a circle in a SaveLayer at -300, but move it back on-screen with a
2822 // +300 translation applied by a SaveLayer image filter.
2823 canvas.SaveLayer({
2824 .image_filter = std::make_shared<MatrixImageFilter>(
2826 });
2827 canvas.DrawCircle({-300, 0}, 100, {.color = Color::Green()});
2828 canvas.Restore();
2829
2830 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2831}
2832
2833// Render a white circle at the top left corner of the screen.
2835 MatrixImageFilterDoesntCullWhenScaledAndTranslatedFromOffscreen) {
2836 Canvas canvas;
2837 canvas.Scale(GetContentScale());
2838 canvas.Translate({100, 100});
2839 // Draw a circle in a SaveLayer at -300, but move it back on-screen with a
2840 // +300 translation applied by a SaveLayer image filter.
2841 canvas.SaveLayer({
2842 .image_filter = std::make_shared<MatrixImageFilter>(
2843 Matrix::MakeTranslation({300, 0}) * Matrix::MakeScale({2, 2, 2}),
2845 });
2846 canvas.DrawCircle({-150, 0}, 50, {.color = Color::Green()});
2847 canvas.Restore();
2848
2849 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2850}
2851
2852// This should be solid red, if you see a little red box this is broken.
2853TEST_P(AiksTest, ClearColorOptimizationWhenSubpassIsBiggerThanParentPass) {
2854 SetWindowSize({400, 400});
2855 Canvas canvas;
2856 canvas.Scale(GetContentScale());
2857 canvas.DrawRect(Rect::MakeLTRB(200, 200, 300, 300), {.color = Color::Red()});
2858 canvas.SaveLayer({
2859 .image_filter = std::make_shared<MatrixImageFilter>(
2860 Matrix::MakeScale({2, 2, 1}), SamplerDescriptor{}),
2861 });
2862 // Draw a rectangle that would fully cover the parent pass size, but not
2863 // the subpass that it is rendered in.
2864 canvas.DrawRect(Rect::MakeLTRB(0, 0, 400, 400), {.color = Color::Green()});
2865 // Draw a bigger rectangle to force the subpass to be bigger.
2866 canvas.DrawRect(Rect::MakeLTRB(0, 0, 800, 800), {.color = Color::Red()});
2867 canvas.Restore();
2868
2869 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2870}
2871
2872TEST_P(AiksTest, EmptySaveLayerIgnoresPaint) {
2873 Canvas canvas;
2874 canvas.Scale(GetContentScale());
2875 canvas.DrawPaint(Paint{.color = Color::Red()});
2876 canvas.ClipRect(Rect::MakeXYWH(100, 100, 200, 200));
2877 canvas.SaveLayer(Paint{.color = Color::Blue()});
2878 canvas.Restore();
2879 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2880}
2881
2882TEST_P(AiksTest, EmptySaveLayerRendersWithClear) {
2883 Canvas canvas;
2884 canvas.Scale(GetContentScale());
2885 auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2886 canvas.DrawImage(image, {10, 10}, {});
2887 canvas.ClipRect(Rect::MakeXYWH(100, 100, 200, 200));
2889 canvas.Restore();
2890 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2891}
2892
2893TEST_P(AiksTest, SubpassWithClearColorOptimization) {
2894 Canvas canvas;
2895
2896 // Use a non-srcOver blend mode to ensure that we don't detect this as an
2897 // opacity peephole optimization.
2898 canvas.SaveLayer(
2899 {.color = Color::Blue().WithAlpha(0.5), .blend_mode = BlendMode::kSource},
2900 Rect::MakeLTRB(0, 0, 200, 200));
2901 canvas.DrawPaint(
2902 {.color = Color::BlackTransparent(), .blend_mode = BlendMode::kSource});
2903 canvas.Restore();
2904
2905 canvas.SaveLayer(
2906 {.color = Color::Blue(), .blend_mode = BlendMode::kDestinationOver});
2907 canvas.Restore();
2908
2909 // This playground should appear blank on CI since we are only drawing
2910 // transparent black. If the clear color optimization is broken, the texture
2911 // will be filled with NaNs and may produce a magenta texture on macOS or iOS.
2912 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2913}
2914
2915TEST_P(AiksTest, ImageColorSourceEffectTransform) {
2916 // Compare with https://fiddle.skia.org/c/6cdc5aefb291fda3833b806ca347a885
2917
2918 Canvas canvas;
2919 auto texture = CreateTextureForFixture("monkey.png");
2920
2921 canvas.DrawPaint({.color = Color::White()});
2922
2923 // Translation
2924 {
2925 Paint paint;
2926 paint.color_source = ColorSource::MakeImage(
2928 Matrix::MakeTranslation({50, 50}));
2929 canvas.DrawRect(Rect::MakeLTRB(0, 0, 100, 100), paint);
2930 }
2931
2932 // Rotation/skew
2933 {
2934 canvas.Save();
2935 canvas.Rotate(Degrees(45));
2936 Paint paint;
2937 paint.color_source = ColorSource::MakeImage(
2939 Matrix(1, -1, 0, 0, //
2940 1, 1, 0, 0, //
2941 0, 0, 1, 0, //
2942 0, 0, 0, 1) //
2943 );
2944 canvas.DrawRect(Rect::MakeLTRB(100, 0, 200, 100), paint);
2945 canvas.Restore();
2946 }
2947
2948 // Scale
2949 {
2950 canvas.Translate(Vector2(100, 0));
2951 canvas.Scale(Vector2(100, 100));
2952 Paint paint;
2953 paint.color_source = ColorSource::MakeImage(
2955 Matrix::MakeScale(Vector2(0.005, 0.005)));
2956 canvas.DrawRect(Rect::MakeLTRB(0, 0, 1, 1), paint);
2957 }
2958
2959 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2960}
2961
2962TEST_P(AiksTest, CorrectClipDepthAssignedToEntities) {
2963 Canvas canvas; // Depth 1 (base pass)
2964 canvas.DrawRRect(Rect::MakeLTRB(0, 0, 100, 100), {10, 10}, {}); // Depth 2
2965 canvas.Save();
2966 {
2967 canvas.ClipRRect(Rect::MakeLTRB(0, 0, 50, 50), {10, 10}, {}); // Depth 4
2968 canvas.SaveLayer({}); // Depth 4
2969 {
2970 canvas.DrawRRect(Rect::MakeLTRB(0, 0, 50, 50), {10, 10}, {}); // Depth 3
2971 }
2972 canvas.Restore(); // Restore the savelayer.
2973 }
2974 canvas.Restore(); // Depth 5 -- this will no longer append a restore entity
2975 // once we switch to the clip depth approach.
2976
2977 auto picture = canvas.EndRecordingAsPicture();
2978
2979 std::vector<uint32_t> expected = {
2980 2, // DrawRRect
2981 4, // ClipRRect -- Has a depth value equal to the max depth of all the
2982 // content it affect. In this case, the SaveLayer and all
2983 // its contents are affected.
2984 4, // SaveLayer -- The SaveLayer is drawn to the parent pass after its
2985 // contents are rendered, so it should have a depth value
2986 // greater than all its contents.
2987 3, // DrawRRect
2988 5, // Restore (no longer necessary when clipping on the depth buffer)
2989 };
2990
2991 std::vector<uint32_t> actual;
2992
2993 picture.pass->IterateAllElements([&](EntityPass::Element& element) -> bool {
2994 if (auto* subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
2995 actual.push_back(subpass->get()->GetClipDepth());
2996 }
2997 if (Entity* entity = std::get_if<Entity>(&element)) {
2998 actual.push_back(entity->GetClipDepth());
2999 }
3000 return true;
3001 });
3002
3003 ASSERT_EQ(actual.size(), expected.size());
3004 for (size_t i = 0; i < expected.size(); i++) {
3005 EXPECT_EQ(expected[i], actual[i]) << "Index: " << i;
3006 }
3007}
3008
3009TEST_P(AiksTest, CanDrawPerspectiveTransformWithClips) {
3010 // Avoiding `GetSecondsElapsed()` to reduce risk of golden flakiness.
3011 int time = 0;
3012 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
3013 Canvas canvas;
3014
3015 canvas.Save();
3016 {
3017 canvas.Translate({300, 300});
3018
3019 // 1. Draw/restore a clip before drawing the image, which will get drawn
3020 // to the depth buffer behind the image.
3021 canvas.Save();
3022 {
3023 canvas.DrawPaint({.color = Color::Green()});
3024 canvas.ClipRect(Rect::MakeLTRB(-180, -180, 180, 180),
3026 canvas.DrawPaint({.color = Color::Black()});
3027 }
3028 canvas.Restore(); // Restore rectangle difference clip.
3029
3030 canvas.Save();
3031 {
3032 // 2. Draw an oval clip that applies to the image, which will get drawn
3033 // in front of the image on the depth buffer.
3034 canvas.ClipOval(Rect::MakeLTRB(-200, -200, 200, 200));
3035
3036 // 3. Draw the rotating image with a perspective transform.
3037 canvas.Transform(
3038 Matrix(1.0, 0.0, 0.0, 0.0, //
3039 0.0, 1.0, 0.0, 0.0, //
3040 0.0, 0.0, 1.0, 0.003, //
3041 0.0, 0.0, 0.0, 1.0) * //
3042 Matrix::MakeRotationY({Radians{-1.0f + (time++ / 60.0f)}}));
3043 auto image =
3044 std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
3045 canvas.DrawImage(image, -Point(image->GetSize()) / 2, {});
3046 }
3047 canvas.Restore(); // Restore oval intersect clip.
3048
3049 // 4. Draw a semi-translucent blue circle atop all previous draws.
3050 canvas.DrawCircle({}, 230, {.color = Color::Blue().WithAlpha(0.4)});
3051 }
3052 canvas.Restore(); // Restore translation.
3053
3054 return canvas.EndRecordingAsPicture();
3055 };
3056 ASSERT_TRUE(OpenPlaygroundHere(callback));
3057}
3058
3059TEST_P(AiksTest, CanRenderClippedBackdropFilter) {
3060 Canvas canvas;
3061 Paint paint;
3062
3063 canvas.Scale(GetContentScale());
3064
3065 // Draw something interesting in the background.
3066 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
3067 Color{0.1294, 0.5882, 0.9529, 1.0}};
3068 std::vector<Scalar> stops = {
3069 0.0,
3070 1.0,
3071 };
3073 {0, 0}, {100, 100}, std::move(colors), std::move(stops),
3075 canvas.DrawPaint(paint);
3076
3077 Rect clip_rect = Rect::MakeLTRB(50, 50, 400, 300);
3078
3079 // Draw a clipped SaveLayer, where the clip coverage and SaveLayer size are
3080 // the same.
3081 canvas.ClipRRect(clip_rect, Size(100, 100),
3083 canvas.SaveLayer({}, clip_rect,
3086 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3087}
3088
3089TEST_P(AiksTest, MipmapGenerationWorksCorrectly) {
3090 TextureDescriptor texture_descriptor;
3091 texture_descriptor.size = ISize{1024, 1024};
3092 texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt;
3093 texture_descriptor.storage_mode = StorageMode::kHostVisible;
3094 texture_descriptor.mip_count = texture_descriptor.size.MipCount();
3095
3096 std::vector<uint8_t> bytes(4194304);
3097 bool alternate = false;
3098 for (auto i = 0u; i < 4194304; i += 4) {
3099 if (alternate) {
3100 bytes[i] = 255;
3101 bytes[i + 1] = 0;
3102 bytes[i + 2] = 0;
3103 bytes[i + 3] = 255;
3104 } else {
3105 bytes[i] = 0;
3106 bytes[i + 1] = 255;
3107 bytes[i + 2] = 0;
3108 bytes[i + 3] = 255;
3109 }
3110 alternate = !alternate;
3111 }
3112
3113 ASSERT_EQ(texture_descriptor.GetByteSizeOfBaseMipLevel(), bytes.size());
3114 auto mapping = std::make_shared<fml::NonOwnedMapping>(
3115 bytes.data(), // data
3116 texture_descriptor.GetByteSizeOfBaseMipLevel() // size
3117 );
3118 auto texture =
3119 GetContext()->GetResourceAllocator()->CreateTexture(texture_descriptor);
3120
3121 ASSERT_TRUE(!!texture);
3122 ASSERT_TRUE(texture->SetContents(mapping));
3123
3124 auto command_buffer = GetContext()->CreateCommandBuffer();
3125 auto blit_pass = command_buffer->CreateBlitPass();
3126
3127 blit_pass->GenerateMipmap(texture);
3128 EXPECT_TRUE(blit_pass->EncodeCommands(GetContext()->GetResourceAllocator()));
3129 EXPECT_TRUE(GetContext()->GetCommandQueue()->Submit({command_buffer}).ok());
3130
3131 auto image = std::make_shared<Image>(texture);
3132
3133 Canvas canvas;
3134 canvas.DrawImageRect(image, Rect::MakeSize(texture->GetSize()),
3135 Rect::MakeLTRB(0, 0, 100, 100), {});
3136
3137 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3138}
3139
3140TEST_P(AiksTest, DrawAtlasPlusWideGamut) {
3141 EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(),
3143
3144 // Draws the image as four squares stiched together.
3145 auto atlas =
3146 std::make_shared<Image>(CreateTextureForFixture("bay_bridge.jpg"));
3147 auto size = atlas->GetSize();
3148 // Divide image into four quadrants.
3149 Scalar half_width = size.width / 2;
3150 Scalar half_height = size.height / 2;
3151 std::vector<Rect> texture_coordinates = {
3152 Rect::MakeLTRB(0, 0, half_width, half_height),
3153 Rect::MakeLTRB(half_width, 0, size.width, half_height),
3154 Rect::MakeLTRB(0, half_height, half_width, size.height),
3155 Rect::MakeLTRB(half_width, half_height, size.width, size.height)};
3156 // Position quadrants adjacent to eachother.
3157 std::vector<Matrix> transforms = {
3158 Matrix::MakeTranslation({0, 0, 0}),
3159 Matrix::MakeTranslation({half_width, 0, 0}),
3160 Matrix::MakeTranslation({0, half_height, 0}),
3161 Matrix::MakeTranslation({half_width, half_height, 0})};
3162 std::vector<Color> colors = {Color::Red(), Color::Green(), Color::Blue(),
3163 Color::Yellow()};
3164
3165 Canvas canvas;
3166 canvas.DrawAtlas(atlas, transforms, texture_coordinates, colors,
3167 BlendMode::kPlus, {}, std::nullopt, {});
3168
3169 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3170}
3171
3172// https://github.com/flutter/flutter/issues/146648
3173TEST_P(AiksTest, StrokedPathWithMoveToThenCloseDrawnCorrectly) {
3174 Path path = PathBuilder{}
3175 .MoveTo({0, 400})
3176 .LineTo({0, 0})
3177 .LineTo({400, 0})
3178 // MoveTo implicitly adds a contour, ensure that close doesn't
3179 // add another nearly-empty contour.
3180 .MoveTo({0, 400})
3181 .Close()
3182 .TakePath();
3183
3184 Canvas canvas;
3185 canvas.Translate({50, 50, 0});
3186 canvas.DrawPath(path, {
3187 .color = Color::Blue(),
3188 .stroke_width = 10,
3189 .stroke_cap = Cap::kRound,
3190 .style = Paint::Style::kStroke,
3191 });
3192 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3193}
3194
3195} // namespace testing
3196} // namespace impeller
3197
3198// █████████████████████████████████████████████████████████████████████████████
3199// █ NOTICE: Before adding new tests to this file consider adding it to one of
3200// █ the subdivisions of AiksTest to avoid having one massive file.
3201// █
3202// █ Subdivisions:
3203// █ - aiks_blend_unittests.cc
3204// █ - aiks_blur_unittests.cc
3205// █ - aiks_gradient_unittests.cc
3206// █ - aiks_path_unittests.cc
3207// █████████████████████████████████████████████████████████████████████████████
const char * options
#define test(name)
static const int points[]
SkColor4f color
static double magnitude(double a)
static bool ok(int result)
static SkBlendMode GetBlendMode(SkSVGFeBlend::Mode mode)
static SkScalar center(float pos0, float pos1)
static void draw(SkCanvas *canvas, SkRect &target, int x, int y)
Definition aaclip.cpp:27
static sk_sp< SkTextBlob > MakeFromString(const char *string, const SkFont &font, SkTextEncoding encoding=SkTextEncoding::kUTF8)
Definition SkTextBlob.h:115
static bool ImGuiBegin(const char *name, bool *p_open, ImGuiWindowFlags flags)
const Matrix & GetCurrentTransform() const
Definition canvas.cc:296
void DrawImageRect(const std::shared_ptr< Image > &image, Rect source, Rect dest, const Paint &paint, SamplerDescriptor sampler={}, SourceRectConstraint src_rect_constraint=SourceRectConstraint::kFast)
Definition canvas.cc:764
void DrawVertices(const std::shared_ptr< VerticesGeometry > &vertices, BlendMode blend_mode, const Paint &paint)
Definition canvas.cc:916
virtual void SaveLayer(const Paint &paint, std::optional< Rect > bounds=std::nullopt, const std::shared_ptr< ImageFilter > &backdrop_filter=nullptr, ContentBoundsPromise bounds_promise=ContentBoundsPromise::kUnknown, uint32_t total_content_depth=kMaxDepth)
Definition canvas.cc:839
void DrawOval(const Rect &rect, const Paint &paint)
Definition canvas.cc:512
void ClipPath(const Path &path, Entity::ClipOperation clip_op=Entity::ClipOperation::kIntersect)
Definition canvas.cc:587
void ClipRRect(const Rect &rect, const Size &corner_radii, Entity::ClipOperation clip_op=Entity::ClipOperation::kIntersect)
Definition canvas.cc:638
virtual bool Restore()
Definition canvas.cc:257
size_t GetSaveCount() const
Definition canvas.cc:329
void Transform(const Matrix &transform)
Definition canvas.cc:292
void DrawImage(const std::shared_ptr< Image > &image, Point offset, const Paint &paint, SamplerDescriptor sampler={})
Definition canvas.cc:750
void Rotate(Radians radians)
Definition canvas.cc:325
virtual void DrawTextFrame(const std::shared_ptr< TextFrame > &text_frame, Point position, const Paint &paint)
Definition canvas.cc:875
void DrawPaint(const Paint &paint)
Definition canvas.cc:350
void ClipRect(const Rect &rect, Entity::ClipOperation clip_op=Entity::ClipOperation::kIntersect)
Definition canvas.cc:597
void Skew(Scalar sx, Scalar sy)
Definition canvas.cc:321
void Scale(const Vector2 &scale)
Definition canvas.cc:313
Picture EndRecordingAsPicture()
Definition canvas.cc:802
void DrawPath(const Path &path, const Paint &paint)
Definition canvas.cc:341
virtual void Save(uint32_t total_content_depth=kMaxDepth)
Definition canvas.cc:184
void DrawPoints(std::vector< Point > points, Scalar radius, const Paint &paint, PointStyle point_style)
Definition canvas.cc:731
void ClipOval(const Rect &bounds, Entity::ClipOperation clip_op=Entity::ClipOperation::kIntersect)
Definition canvas.cc:618
void DrawRect(const Rect &rect, const Paint &paint)
Definition canvas.cc:493
void DrawRRect(const Rect &rect, const Size &corner_radii, const Paint &paint)
Definition canvas.cc:538
void DrawAtlas(const std::shared_ptr< Image > &atlas, std::vector< Matrix > transforms, std::vector< Rect > texture_coordinates, std::vector< Color > colors, BlendMode blend_mode, SamplerDescriptor sampler, std::optional< Rect > cull_rect, const Paint &paint)
Definition canvas.cc:1003
void Translate(const Vector3 &offset)
Definition canvas.cc:309
void DrawCircle(const Point &center, Scalar radius, const Paint &paint)
Definition canvas.cc:564
struct impeller::Canvas::DebugOptions debug_options
static CaptureContext MakeAllowlist(std::initializer_list< std::string > allowlist)
Definition capture.cc:159
static std::shared_ptr< ColorFilter > MakeMatrix(ColorMatrix color_matrix)
static std::shared_ptr< ColorFilter > MakeBlend(BlendMode blend_mode, Color color)
static std::shared_ptr< ColorFilter > MakeLinearToSrgb()
static std::shared_ptr< ColorFilter > MakeSrgbToLinear()
static ColorSource MakeLinearGradient(Point start_point, Point end_point, std::vector< Color > colors, std::vector< Scalar > stops, Entity::TileMode tile_mode, Matrix effect_transform)
static ColorSource MakeRadialGradient(Point center, Scalar radius, std::vector< Color > colors, std::vector< Scalar > stops, Entity::TileMode tile_mode, Matrix effect_transform)
static ColorSource MakeImage(std::shared_ptr< Texture > texture, Entity::TileMode x_tile_mode, Entity::TileMode y_tile_mode, SamplerDescriptor sampler_descriptor, Matrix effect_transform)
static ColorSource MakeRuntimeEffect(std::shared_ptr< RuntimeStage > runtime_stage, std::shared_ptr< std::vector< uint8_t > > uniform_data, std::vector< RuntimeEffectContents::TextureInput > texture_inputs)
std::variant< Entity, std::unique_ptr< EntityPass > > Element
Definition entity_pass.h:54
void SetContents(std::shared_ptr< Contents > contents)
Definition entity.cc:90
@ kNormal
Blurred inside and outside.
static std::shared_ptr< ImageFilter > MakeMatrix(const Matrix &matrix, SamplerDescriptor sampler_descriptor)
static std::shared_ptr< ImageFilter > MakeLocalMatrix(const Matrix &matrix, const ImageFilter &internal_filter)
static std::shared_ptr< ImageFilter > MakeErode(Radius radius_x, Radius radius_y)
static std::shared_ptr< ImageFilter > MakeBlur(Sigma sigma_x, Sigma sigma_y, FilterContents::BlurStyle blur_style, Entity::TileMode tile_mode)
static std::shared_ptr< ImageFilter > MakeDilate(Radius radius_x, Radius radius_y)
static std::shared_ptr< ImageFilter > MakeFromColorFilter(const ColorFilter &color_filter)
static std::shared_ptr< ImageFilter > MakeCompose(const ImageFilter &inner, const ImageFilter &outer)
PathBuilder & AddRect(Rect rect)
Path TakePath(FillType fill=FillType::kNonZero)
PathBuilder & MoveTo(Point point, bool relative=false)
PathBuilder & AddCircle(const Point &center, Scalar radius)
PathBuilder & AddLine(const Point &p1, const Point &p2)
Move to point p1, then insert a line from p1 to p2.
PathBuilder & AddRoundedRect(Rect rect, RoundingRadii radii)
PathBuilder & AddQuadraticCurve(Point p1, Point cp, Point p2)
Move to point p1, then insert a quadradic curve from p1 to p2 with the control point cp.
PathBuilder & SetConvexity(Convexity value)
Paths are lightweight objects that describe a collection of linear, quadratic, or cubic segments....
Definition path.h:51
static std::unique_ptr< SolidColorContents > Make(const Path &path, Color color)
static std::unique_ptr< TypographerContext > Make()
static std::shared_ptr< Node > MakeFromFlatbuffer(const fml::Mapping &ipscene_mapping, Allocator &allocator)
Definition node.cc:47
static std::shared_ptr< ContextSpy > Make()
const Paint & paint
sk_sp< SkImage > image
Definition examples.cpp:29
double frame
Definition examples.cpp:31
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
static const uint8_t buffer[]
GAsyncResult * result
#define ASSERT_MATRIX_NEAR(a, b)
#define ASSERT_COLOR_NEAR(a, b)
std::u16string text
static MockContext mock_context
Definition mock_epoxy.cc:51
FlTexture * texture
double y
double x
sk_sp< SkData > OpenFixtureAsSkData(const std::string &fixture_name)
Opens a fixture of the given file name and returns a Skia SkData holding its contents.
Definition testing.cc:64
std::unique_ptr< fml::Mapping > OpenFixtureAsMapping(const std::string &fixture_name)
Opens a fixture of the given file name and returns a mapping to its contents.
Definition testing.cc:59
AiksPlayground AiksTest
bool RenderTextInCanvasSTB(const std::shared_ptr< Context > &context, Canvas &canvas, const std::string &text, const std::string &font_fixture, TextRenderOptions options={})
TEST_P(AiksTest, CanRenderAdvancedBlendColorFilterWithSaveLayer)
static constexpr std::string_view kFontFixture
bool RenderTextInCanvasSkia(const std::shared_ptr< Context > &context, Canvas &canvas, const std::string &text, const std::string_view &font_fixture, TextRenderOptions options={})
constexpr float k2Pi
Definition constants.h:29
Point Vector2
Definition point.h:320
constexpr float kPi
Definition constants.h:26
float Scalar
Definition scalar.h:18
std::tuple< Point, Point > DrawPlaygroundLine(PlaygroundPoint &point_a, PlaygroundPoint &point_b)
Definition widgets.cc:50
std::shared_ptr< TextFrame > MakeTextFrameSTB(const std::shared_ptr< TypefaceSTB > &typeface_stb, Font::Metrics metrics, const std::string &text)
constexpr RuntimeStageBackend PlaygroundBackendToRuntimeStageBackend(PlaygroundBackend backend)
Definition playground.h:35
@ kRound
Points are drawn as squares.
@ kSquare
Points are drawn as circles.
TPoint< Scalar > Point
Definition point.h:316
PixelFormat
The Pixel formats supported by Impeller. The naming convention denotes the usage of the component,...
Definition formats.h:100
constexpr float kPiOver2
Definition constants.h:32
TSize< Scalar > Size
Definition size.h:137
void MoveTo(PathBuilder *builder, Scalar x, Scalar y)
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
void LineTo(PathBuilder *builder, Scalar x, Scalar y)
constexpr bool ScalarNearlyEqual(Scalar x, Scalar y, Scalar tolerance=kEhCloseEnough)
Definition scalar.h:30
constexpr float k1OverSqrt2
Definition constants.h:50
void Close(PathBuilder *builder)
constexpr const char * PixelFormatToString(PixelFormat format)
Definition formats.h:141
sk_sp< SkFontMgr > GetDefaultFontManager(uint32_t font_initialization_data)
Definition platform.cc:17
#define INSTANTIATE_PLAYGROUND_SUITE(playground)
const Scalar stroke_width
Point offset
A capturable property type.
Definition capture.h:68
static constexpr Color Crimson()
Definition color.h:342
static constexpr Color LimeGreen()
Definition color.h:594
static constexpr Color BlackTransparent()
Definition color.h:262
static constexpr Color Fuchsia()
Definition color.h:458
static constexpr Color Black()
Definition color.h:258
static constexpr Color DarkBlue()
Definition color.h:350
static constexpr Color CornflowerBlue()
Definition color.h:334
static constexpr Color MediumTurquoise()
Definition color.h:638
static constexpr Color White()
Definition color.h:256
static constexpr Color LightCoral()
Definition color.h:534
constexpr Color WithAlpha(Scalar new_alpha) const
Definition color.h:270
static constexpr Color SkyBlue()
Definition color.h:774
static constexpr Color Maroon()
Definition color.h:606
static constexpr Color Orange()
Definition color.h:682
static constexpr Color Purple()
Definition color.h:734
static constexpr Color Red()
Definition color.h:264
static constexpr Color MakeRGBA8(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Definition color.h:154
static constexpr Color AntiqueWhite()
Definition color.h:278
static constexpr Color Yellow()
Definition color.h:834
Color Blend(Color source, BlendMode blend_mode) const
Blends an unpremultiplied destination color into a given unpremultiplied source color to form a new u...
Definition color.cc:234
static constexpr Color Blue()
Definition color.h:268
static constexpr Color Green()
Definition color.h:266
A 4x4 matrix using column-major storage.
Definition matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition matrix.h:95
static constexpr Matrix MakePerspective(Radians fov_y, Scalar aspect_ratio, Scalar z_near, Scalar z_far)
Definition matrix.h:504
static Matrix MakeRotationY(Radians r)
Definition matrix.h:198
static constexpr Matrix MakeLookAt(Vector3 position, Vector3 target, Vector3 up)
Definition matrix.h:530
static Matrix MakeRotationZ(Radians r)
Definition matrix.h:213
static constexpr Matrix MakeScale(const Vector3 &s)
Definition matrix.h:104
FilterContents::BlurStyle style
Definition paint.h:40
ColorSource color_source
Definition paint.h:56
std::optional< MaskBlurDescriptor > mask_blur_descriptor
Definition paint.h:69
Color color
Definition paint.h:55
BlendMode blend_mode
Definition paint.h:64
For convolution filters, the "radius" is the size of the convolution kernel to use on the local space...
Definition sigma.h:48
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition sigma.h:32
static constexpr TPoint< Type > MakeXY(Type x, Type y)
Definition point.h:46
static constexpr TRect MakeOriginSize(const TPoint< Type > &origin, const TSize< Type > &size)
Definition rect.h:140
static constexpr TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition rect.h:136
static constexpr TRect MakeSize(const TSize< U > &size)
Definition rect.h:146
static constexpr TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition rect.h:129
constexpr size_t MipCount() const
Definition size.h:115
static constexpr TSize MakeWH(Type width, Type height)
Definition size.h:34
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...
constexpr size_t GetByteSizeOfBaseMipLevel() const
std::optional< Paint::MaskBlurDescriptor > mask_blur_descriptor
#define EXPECT_TRUE(handle)
Definition unit_test.h:685