| Flutter Impeller |

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