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