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