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 
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"
18 #include "impeller/aiks/image.h"
21 #include "impeller/aiks/testing/context_spy.h"
22 #include "impeller/core/capture.h"
33 #include "impeller/geometry/path.h"
38 #include "impeller/renderer/testing/mocks.h"
40 #include "impeller/scene/node.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 
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});
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);
74  Matrix::MakeTranslation({100.0, 100.0, 0.0}));
75  ASSERT_TRUE(canvas.Restore());
76  ASSERT_EQ(canvas.GetSaveCount(), 1u);
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 =
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 =
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 =
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 
319  {500, 600}, 75, std::move(gradient_colors), std::move(stops),
321  draw(paint, 0, 300);
322 
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(),
356  canvas.ClipPath(PathBuilder{}.AddCircle({100, -50}, 30).TakePath(),
358  canvas.ClipPath(PathBuilder{}
359  .AddQuadraticCurve({-100, 50}, {0, 150}, {100, 50})
360  .TakePath(),
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(),
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 
452  {0, 0}, {100, 100}, std::move(colors), std::move(stops),
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 
498  canvas.SaveLayer(paint);
499 
500  paint.color = Color::White();
501  canvas.DrawRect(Rect::MakeXYWH(100, 100, 400, 400), paint);
502 
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 
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 
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 
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  );
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,
847  .mask_blur_descriptor = Paint::MaskBlurDescriptor{
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 =
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 = {
987  {0, 0}, {100, 100}, std::move(colors), std::move(stops),
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 
1003  std::vector<const char*> blend_mode_names;
1004  std::vector<BlendMode> blend_mode_values;
1005 };
1006 
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 = {
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;
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;
1211 
1212  // clang-format off
1213  canvas.Translate(Vector3(100, 200));
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));
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));
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));
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 
1294  {500, 600}, 75, std::move(gradient_colors), std::move(stops),
1296  canvas.DrawCircle({500, 600}, 100, paint);
1297 
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 
1358  {500, 600}, 75, std::move(gradient_colors), std::move(stops),
1360  draw(canvas, {500, 600}, 5, 10, 10);
1361 
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 
1427  {300, 650}, 75, std::move(gradient_colors), std::move(stops),
1429  canvas.DrawOval(Rect::MakeXYWH(200, 625, 200, 50), paint);
1430  canvas.DrawOval(Rect::MakeXYWH(275, 550, 50, 200), paint);
1431 
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);
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);
1499  {200, 650}, 75, std::move(gradient_colors), std::move(stops),
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);
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);
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;
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);
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);
1650  {200, 650}, 75, std::move(gradient_colors), std::move(stops),
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);
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);
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;
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 =
1871 
1872  ASSERT_TRUE(paint.HasColorFilter());
1873 
1874  paint.image_filter = ImageFilter::MakeBlur(Sigma(1.0), Sigma(1.0),
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 =
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),
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;
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),
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.
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),
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,
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),
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 =
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  }
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 =
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(
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 =
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)
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},
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 =
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 
2423  *rotate_filter));
2424 
2425  canvas.Translate({200.0, 0.0});
2426 
2428  *rgb_swap_filter));
2429 
2430  canvas.Translate({200.0, 0.0});
2431 
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},
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 
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 =
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;
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{
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),
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),
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),
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(
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(
2734  .mask_blur_descriptor =
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(
2755  .mask_blur_descriptor =
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;
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},
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;
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 = {
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 = {
2946  };
2947  Paint paint;
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  };
2981  {0, 0}, {100, 100}, std::move(colors), std::move(stops),
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),
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(
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,
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(
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;
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;
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 
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;
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 
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,
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 
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,
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;
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>(
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.
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 =
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));
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(
3527  std::shared_ptr<ImageFilter> blur_filter = ImageFilter::MakeBlur(
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 =
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),
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),
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(
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),
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),
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),
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[] = {
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),
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