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  bool found_subpass = false;
2420  picture.pass->IterateAllElements([&](EntityPass::Element& element) -> bool {
2421  if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
2422  actual_color = subpass->get()->GetClearColor();
2423  found_subpass = true;
2424  }
2425  // Fail if the first element isn't a subpass.
2426  return true;
2427  });
2428 
2429  EXPECT_TRUE(found_subpass);
2430  EXPECT_FALSE(actual_color.has_value());
2431 }
2432 
2433 TEST_P(AiksTest, CollapsedDrawPaintInSubpass) {
2434  Canvas canvas;
2435  canvas.DrawPaint(
2436  {.color = Color::Yellow(), .blend_mode = BlendMode::kSource});
2437  canvas.SaveLayer({.blend_mode = BlendMode::kMultiply});
2438  canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75),
2439  .blend_mode = BlendMode::kSourceOver});
2440 
2441  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2442 }
2443 
2444 TEST_P(AiksTest, CollapsedDrawPaintInSubpassBackdropFilter) {
2445  // Bug: https://github.com/flutter/flutter/issues/131576
2446  Canvas canvas;
2447  canvas.DrawPaint(
2448  {.color = Color::Yellow(), .blend_mode = BlendMode::kSource});
2449  canvas.SaveLayer({}, {},
2450  ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
2451  FilterContents::BlurStyle::kNormal,
2452  Entity::TileMode::kDecal));
2453  canvas.DrawPaint(
2454  {.color = Color::CornflowerBlue(), .blend_mode = BlendMode::kSourceOver});
2455 
2456  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2457 }
2458 
2459 TEST_P(AiksTest, ForegroundBlendSubpassCollapseOptimization) {
2460  Canvas canvas;
2461 
2462  canvas.SaveLayer({
2463  .color_filter =
2464  ColorFilter::MakeBlend(BlendMode::kColorDodge, Color::Red()),
2465  });
2466 
2467  canvas.Translate({500, 300, 0});
2468  canvas.Rotate(Radians(2 * kPi / 3));
2469  canvas.DrawRect({100, 100, 200, 200}, {.color = Color::Blue()});
2470 
2471  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2472 }
2473 
2474 TEST_P(AiksTest, ColorMatrixFilterSubpassCollapseOptimization) {
2475  Canvas canvas;
2476 
2477  canvas.SaveLayer({
2478  .color_filter =
2479  ColorFilter::MakeMatrix({.array =
2480  {
2481  -1.0, 0, 0, 1.0, 0, //
2482  0, -1.0, 0, 1.0, 0, //
2483  0, 0, -1.0, 1.0, 0, //
2484  1.0, 1.0, 1.0, 1.0, 0 //
2485  }}),
2486  });
2487 
2488  canvas.Translate({500, 300, 0});
2489  canvas.Rotate(Radians(2 * kPi / 3));
2490  canvas.DrawRect({100, 100, 200, 200}, {.color = Color::Blue()});
2491 
2492  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2493 }
2494 
2495 TEST_P(AiksTest, LinearToSrgbFilterSubpassCollapseOptimization) {
2496  Canvas canvas;
2497 
2498  canvas.SaveLayer({
2499  .color_filter = ColorFilter::MakeLinearToSrgb(),
2500  });
2501 
2502  canvas.Translate({500, 300, 0});
2503  canvas.Rotate(Radians(2 * kPi / 3));
2504  canvas.DrawRect({100, 100, 200, 200}, {.color = Color::Blue()});
2505 
2506  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2507 }
2508 
2509 TEST_P(AiksTest, SrgbToLinearFilterSubpassCollapseOptimization) {
2510  Canvas canvas;
2511 
2512  canvas.SaveLayer({
2513  .color_filter = ColorFilter::MakeSrgbToLinear(),
2514  });
2515 
2516  canvas.Translate({500, 300, 0});
2517  canvas.Rotate(Radians(2 * kPi / 3));
2518  canvas.DrawRect({100, 100, 200, 200}, {.color = Color::Blue()});
2519 
2520  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2521 }
2522 
2523 static Picture BlendModeTest(BlendMode blend_mode,
2524  const std::shared_ptr<Image>& src_image,
2525  const std::shared_ptr<Image>& dst_image) {
2526  Color destination_color = Color::CornflowerBlue().WithAlpha(0.75);
2527  auto source_colors = std::vector<Color>({Color::White().WithAlpha(0.75),
2528  Color::LimeGreen().WithAlpha(0.75),
2529  Color::Black().WithAlpha(0.75)});
2530 
2531  Canvas canvas;
2532 
2533  canvas.DrawPaint({.color = Color::Black()});
2534 
2535  //----------------------------------------------------------------------------
2536  /// 1. Save layer blending (top squares).
2537  ///
2538 
2539  canvas.Save();
2540  for (const auto& color : source_colors) {
2541  canvas.Save();
2542  {
2543  canvas.ClipRect(Rect::MakeXYWH(50, 50, 100, 100));
2544  // Perform the blend in a SaveLayer so that the initial backdrop color is
2545  // fully transparent black. SourceOver blend the result onto the parent
2546  // pass.
2547  canvas.SaveLayer({});
2548  {
2549  canvas.DrawPaint({.color = destination_color});
2550  // Draw the source color in an offscreen pass and blend it to the parent
2551  // pass.
2552  canvas.SaveLayer({.blend_mode = blend_mode});
2553  { //
2554  canvas.DrawRect({50, 50, 100, 100}, {.color = color});
2555  }
2556  canvas.Restore();
2557  }
2558  canvas.Restore();
2559  }
2560  canvas.Restore();
2561  canvas.Translate(Vector2(100, 0));
2562  }
2563  canvas.RestoreToCount(0);
2564 
2565  //----------------------------------------------------------------------------
2566  /// 2. CPU blend modes (bottom squares).
2567  ///
2568 
2569  canvas.Save();
2570  canvas.Translate({0, 100});
2571 
2572  // Perform the blend in a SaveLayer so that the initial backdrop color is
2573  // fully transparent black. SourceOver blend the result onto the parent pass.
2574  canvas.SaveLayer({});
2575  // canvas.DrawPaint({.color = destination_color});
2576  for (const auto& color : source_colors) {
2577  // Simply write the CPU blended color to the pass.
2578  canvas.DrawRect({50, 50, 100, 100},
2579  {.color = destination_color.Blend(color, blend_mode),
2580  .blend_mode = BlendMode::kSourceOver});
2581  canvas.Translate(Vector2(100, 0));
2582  }
2583  canvas.RestoreToCount(0);
2584 
2585  //----------------------------------------------------------------------------
2586  /// 3. Image blending (top right).
2587  ///
2588  /// Compare these results with the images in the Flutter blend mode
2589  /// documentation: https://api.flutter.dev/flutter/dart-ui/BlendMode.html
2590  ///
2591 
2592  canvas.Save();
2593  // canvas.ClipRect(Rect::MakeXYWH(500, 0, 500, 500));
2594  canvas.SaveLayer({.blend_mode = BlendMode::kSourceOver});
2595  {
2596  canvas.DrawImage(dst_image, {400, 50}, {.blend_mode = BlendMode::kSource});
2597  canvas.DrawImage(src_image, {400, 50}, {.blend_mode = blend_mode});
2598  }
2599  canvas.RestoreToCount(0);
2600 
2601  return canvas.EndRecordingAsPicture();
2602 }
2603 
2604 #define BLEND_MODE_TEST(blend_mode) \
2605  TEST_P(AiksTest, BlendMode##blend_mode) { \
2606  auto src_image = std::make_shared<Image>( \
2607  CreateTextureForFixture("blend_mode_src.png")); \
2608  auto dst_image = std::make_shared<Image>( \
2609  CreateTextureForFixture("blend_mode_dst.png")); \
2610  OpenPlaygroundHere( \
2611  BlendModeTest(BlendMode::k##blend_mode, src_image, dst_image)); \
2612  }
2614 
2615 TEST_P(AiksTest, TranslucentSaveLayerDrawsCorrectly) {
2616  Canvas canvas;
2617 
2618  canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
2619 
2620  canvas.SaveLayer({.color = Color::Black().WithAlpha(0.5)});
2621  canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
2622  canvas.Restore();
2623 
2624  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2625 }
2626 
2627 TEST_P(AiksTest, TranslucentSaveLayerWithBlendColorFilterDrawsCorrectly) {
2628  Canvas canvas;
2629 
2630  canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
2631 
2632  canvas.SaveLayer({
2633  .color = Color::Black().WithAlpha(0.5),
2634  .color_filter =
2635  ColorFilter::MakeBlend(BlendMode::kDestinationOver, Color::Red()),
2636  });
2637  canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
2638  canvas.Restore();
2639 
2640  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2641 }
2642 
2643 TEST_P(AiksTest, TranslucentSaveLayerWithBlendImageFilterDrawsCorrectly) {
2644  Canvas canvas;
2645 
2646  canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
2647 
2648  canvas.SaveLayer({
2649  .color = Color::Black().WithAlpha(0.5),
2650  .image_filter = ImageFilter::MakeFromColorFilter(
2651  *ColorFilter::MakeBlend(BlendMode::kDestinationOver, Color::Red())),
2652  });
2653 
2654  canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
2655  canvas.Restore();
2656 
2657  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2658 }
2659 
2660 TEST_P(AiksTest, TranslucentSaveLayerWithColorAndImageFilterDrawsCorrectly) {
2661  Canvas canvas;
2662 
2663  canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
2664 
2665  canvas.SaveLayer({
2666  .color = Color::Black().WithAlpha(0.5),
2667  .color_filter =
2668  ColorFilter::MakeBlend(BlendMode::kDestinationOver, Color::Red()),
2669  });
2670 
2671  canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
2672  canvas.Restore();
2673 
2674  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2675 }
2676 
2677 TEST_P(AiksTest, TranslucentSaveLayerImageDrawsCorrectly) {
2678  Canvas canvas;
2679 
2680  auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2681  canvas.DrawImage(image, {100, 100}, {});
2682 
2683  canvas.SaveLayer({.color = Color::Black().WithAlpha(0.5)});
2684  canvas.DrawImage(image, {100, 500}, {});
2685  canvas.Restore();
2686 
2687  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2688 }
2689 
2690 TEST_P(AiksTest, TranslucentSaveLayerWithColorMatrixColorFilterDrawsCorrectly) {
2691  Canvas canvas;
2692 
2693  auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2694  canvas.DrawImage(image, {100, 100}, {});
2695 
2696  canvas.SaveLayer({
2697  .color = Color::Black().WithAlpha(0.5),
2698  .color_filter = ColorFilter::MakeMatrix({.array =
2699  {
2700  1, 0, 0, 0, 0, //
2701  0, 1, 0, 0, 0, //
2702  0, 0, 1, 0, 0, //
2703  0, 0, 0, 2, 0 //
2704  }}),
2705  });
2706  canvas.DrawImage(image, {100, 500}, {});
2707  canvas.Restore();
2708 
2709  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2710 }
2711 
2712 TEST_P(AiksTest, TranslucentSaveLayerWithColorMatrixImageFilterDrawsCorrectly) {
2713  Canvas canvas;
2714 
2715  auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2716  canvas.DrawImage(image, {100, 100}, {});
2717 
2718  canvas.SaveLayer({
2719  .color = Color::Black().WithAlpha(0.5),
2720  .image_filter = ImageFilter::MakeFromColorFilter(
2721  *ColorFilter::MakeMatrix({.array =
2722  {
2723  1, 0, 0, 0, 0, //
2724  0, 1, 0, 0, 0, //
2725  0, 0, 1, 0, 0, //
2726  0, 0, 0, 2, 0 //
2727  }})),
2728  });
2729  canvas.DrawImage(image, {100, 500}, {});
2730  canvas.Restore();
2731 
2732  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2733 }
2734 
2736  TranslucentSaveLayerWithColorFilterAndImageFilterDrawsCorrectly) {
2737  Canvas canvas;
2738 
2739  auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2740  canvas.DrawImage(image, {100, 100}, {});
2741 
2742  canvas.SaveLayer({
2743  .color = Color::Black().WithAlpha(0.5),
2744  .image_filter = ImageFilter::MakeFromColorFilter(
2745  *ColorFilter::MakeMatrix({.array =
2746  {
2747  1, 0, 0, 0, 0, //
2748  0, 1, 0, 0, 0, //
2749  0, 0.2, 1, 0, 0, //
2750  0, 0, 0, 0.5, 0 //
2751  }})),
2752  .color_filter =
2753  ColorFilter::MakeBlend(BlendMode::kModulate, Color::Green()),
2754  });
2755  canvas.DrawImage(image, {100, 500}, {});
2756  canvas.Restore();
2757 
2758  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2759 }
2760 
2761 TEST_P(AiksTest, TranslucentSaveLayerWithAdvancedBlendModeDrawsCorrectly) {
2762  Canvas canvas;
2763  canvas.DrawRect({0, 0, 400, 400}, {.color = Color::Red()});
2764  canvas.SaveLayer({
2765  .color = Color::Black().WithAlpha(0.5),
2766  .blend_mode = BlendMode::kLighten,
2767  });
2768  canvas.DrawCircle({200, 200}, 100, {.color = Color::Green()});
2769  canvas.Restore();
2770  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2771 }
2772 
2773 /// This is a regression check for https://github.com/flutter/engine/pull/41129
2774 /// The entire screen is green if successful. If failing, no frames will render,
2775 /// or the entire screen will be transparent black.
2776 TEST_P(AiksTest, CanRenderTinyOverlappingSubpasses) {
2777  Canvas canvas;
2778  canvas.DrawPaint({.color = Color::Red()});
2779 
2780  // Draw two overlapping subpixel circles.
2781  canvas.SaveLayer({});
2782  canvas.DrawCircle({100, 100}, 0.1, {.color = Color::Yellow()});
2783  canvas.Restore();
2784  canvas.SaveLayer({});
2785  canvas.DrawCircle({100, 100}, 0.1, {.color = Color::Yellow()});
2786  canvas.Restore();
2787 
2788  canvas.DrawPaint({.color = Color::Green()});
2789 
2790  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2791 }
2792 
2793 /// Tests that the debug checkerboard displays for offscreen textures when
2794 /// enabled. Most of the complexity here is just to future proof by making pass
2795 /// collapsing hard.
2796 TEST_P(AiksTest, CanRenderOffscreenCheckerboard) {
2797  Canvas canvas;
2799 
2800  canvas.DrawPaint({.color = Color::AntiqueWhite()});
2801  canvas.DrawCircle({400, 300}, 200,
2802  {.color = Color::CornflowerBlue().WithAlpha(0.75)});
2803 
2804  canvas.SaveLayer({.blend_mode = BlendMode::kMultiply});
2805  {
2806  canvas.DrawCircle({500, 400}, 200,
2807  {.color = Color::DarkBlue().WithAlpha(0.75)});
2808  canvas.DrawCircle({550, 450}, 200,
2809  {.color = Color::LightCoral().WithAlpha(0.75),
2810  .blend_mode = BlendMode::kLuminosity});
2811  }
2812  canvas.Restore();
2813 
2814  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2815 }
2816 
2817 TEST_P(AiksTest, OpaqueEntitiesGetCoercedToSource) {
2818  Canvas canvas;
2819  canvas.Scale(Vector2(1.618, 1.618));
2820  canvas.DrawCircle(Point(), 10,
2821  {
2822  .color = Color::CornflowerBlue(),
2823  .blend_mode = BlendMode::kSourceOver,
2824  });
2825  Picture picture = canvas.EndRecordingAsPicture();
2826 
2827  // Extract the SolidColorSource.
2828  Entity entity;
2829  std::shared_ptr<SolidColorContents> contents;
2830  picture.pass->IterateAllEntities([&e = entity, &contents](Entity& entity) {
2831  if (ScalarNearlyEqual(entity.GetTransformation().GetScale().x, 1.618f)) {
2832  e = entity;
2833  contents =
2834  std::static_pointer_cast<SolidColorContents>(entity.GetContents());
2835  return false;
2836  }
2837  return true;
2838  });
2839 
2840  ASSERT_TRUE(contents->IsOpaque());
2841  ASSERT_EQ(entity.GetBlendMode(), BlendMode::kSource);
2842 }
2843 
2844 TEST_P(AiksTest, CanRenderDestructiveSaveLayer) {
2845  Canvas canvas;
2846 
2847  canvas.DrawPaint({.color = Color::Red()});
2848  // Draw an empty savelayer with a destructive blend mode, which will replace
2849  // the entire red screen with fully transparent black, except for the green
2850  // circle drawn within the layer.
2851  canvas.SaveLayer({.blend_mode = BlendMode::kSource});
2852  canvas.DrawCircle({300, 300}, 100, {.color = Color::Green()});
2853  canvas.Restore();
2854 
2855  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2856 }
2857 
2858 TEST_P(AiksTest, CanRenderMaskBlurHugeSigma) {
2859  Canvas canvas;
2860  canvas.DrawCircle({400, 400}, 300,
2861  {.color = Color::Green(),
2862  .mask_blur_descriptor = Paint::MaskBlurDescriptor{
2863  .style = FilterContents::BlurStyle::kNormal,
2864  .sigma = Sigma(99999),
2865  }});
2866  canvas.Restore();
2867 
2868  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2869 }
2870 
2871 TEST_P(AiksTest, CanRenderBackdropBlurInteractive) {
2872  auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
2873  auto [a, b] = IMPELLER_PLAYGROUND_LINE(Point(50, 50), Point(300, 200), 30,
2874  Color::White(), Color::White());
2875 
2876  Canvas canvas;
2877  canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
2878  canvas.DrawCircle({300, 200}, 100, {.color = Color::GreenYellow()});
2879  canvas.DrawCircle({140, 170}, 75, {.color = Color::DarkMagenta()});
2880  canvas.DrawCircle({180, 120}, 100, {.color = Color::OrangeRed()});
2881  canvas.ClipRRect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), 20);
2882  canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
2883  ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
2884  FilterContents::BlurStyle::kNormal,
2885  Entity::TileMode::kClamp));
2886  canvas.Restore();
2887 
2888  return canvas.EndRecordingAsPicture();
2889  };
2890 
2891  ASSERT_TRUE(OpenPlaygroundHere(callback));
2892 }
2893 
2894 TEST_P(AiksTest, CanRenderBackdropBlur) {
2895  Canvas canvas;
2896  canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
2897  canvas.DrawCircle({300, 200}, 100, {.color = Color::GreenYellow()});
2898  canvas.DrawCircle({140, 170}, 75, {.color = Color::DarkMagenta()});
2899  canvas.DrawCircle({180, 120}, 100, {.color = Color::OrangeRed()});
2900  canvas.ClipRRect(Rect::MakeLTRB(75, 50, 375, 275), 20);
2901  canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
2902  ImageFilter::MakeBlur(Sigma(30.0), Sigma(30.0),
2903  FilterContents::BlurStyle::kNormal,
2904  Entity::TileMode::kClamp));
2905  canvas.Restore();
2906 
2907  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2908 }
2909 
2910 TEST_P(AiksTest, CanRenderBackdropBlurHugeSigma) {
2911  Canvas canvas;
2912  canvas.DrawCircle({400, 400}, 300, {.color = Color::Green()});
2913  canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
2914  ImageFilter::MakeBlur(Sigma(999999), Sigma(999999),
2915  FilterContents::BlurStyle::kNormal,
2916  Entity::TileMode::kClamp));
2917  canvas.Restore();
2918 
2919  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2920 }
2921 
2922 TEST_P(AiksTest, CanRenderClippedBlur) {
2923  Canvas canvas;
2924  canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
2925  canvas.DrawCircle(
2926  {400, 400}, 200,
2927  {
2928  .color = Color::Green(),
2929  .image_filter = ImageFilter::MakeBlur(
2930  Sigma(20.0), Sigma(20.0), FilterContents::BlurStyle::kNormal,
2931  Entity::TileMode::kClamp),
2932  });
2933  canvas.Restore();
2934 
2935  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2936 }
2937 
2938 TEST_P(AiksTest, CanRenderForegroundBlendWithMaskBlur) {
2939  // This case triggers the ForegroundPorterDuffBlend path. The color filter
2940  // should apply to the color only, and respect the alpha mask.
2941  Canvas canvas;
2942  canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
2943  canvas.DrawCircle({400, 400}, 200,
2944  {
2945  .color = Color::White(),
2946  .color_filter = ColorFilter::MakeBlend(
2947  BlendMode::kSource, Color::Green()),
2948  .mask_blur_descriptor =
2950  .style = FilterContents::BlurStyle::kNormal,
2951  .sigma = Radius(20),
2952  },
2953  });
2954  canvas.Restore();
2955 
2956  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2957 }
2958 
2959 TEST_P(AiksTest, CanRenderForegroundAdvancedBlendWithMaskBlur) {
2960  // This case triggers the ForegroundAdvancedBlend path. The color filter
2961  // should apply to the color only, and respect the alpha mask.
2962  Canvas canvas;
2963  canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
2964  canvas.DrawCircle({400, 400}, 200,
2965  {
2966  .color = Color::Grey(),
2967  .color_filter = ColorFilter::MakeBlend(
2968  BlendMode::kColor, Color::Green()),
2969  .mask_blur_descriptor =
2971  .style = FilterContents::BlurStyle::kNormal,
2972  .sigma = Radius(20),
2973  },
2974  });
2975  canvas.Restore();
2976 
2977  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2978 }
2979 
2980 // Regression test for https://github.com/flutter/flutter/issues/126701 .
2981 TEST_P(AiksTest, CanRenderClippedRuntimeEffects) {
2982  if (GetParam() != PlaygroundBackend::kMetal) {
2983  GTEST_SKIP_("This backend doesn't support runtime effects.");
2984  }
2985 
2986  auto runtime_stage =
2987  OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
2988  ASSERT_TRUE(runtime_stage->IsDirty());
2989 
2990  struct FragUniforms {
2991  Vector2 iResolution;
2992  Scalar iTime;
2993  } frag_uniforms = {.iResolution = Vector2(400, 400), .iTime = 100.0};
2994  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
2995  uniform_data->resize(sizeof(FragUniforms));
2996  memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
2997 
2998  std::vector<RuntimeEffectContents::TextureInput> texture_inputs;
2999 
3000  Paint paint;
3001  paint.color_source = ColorSource::MakeRuntimeEffect(
3002  runtime_stage, uniform_data, texture_inputs);
3003 
3004  Canvas canvas;
3005  canvas.Save();
3006  canvas.ClipRRect(Rect{0, 0, 400, 400}, 10.0,
3007  Entity::ClipOperation::kIntersect);
3008  canvas.DrawRect(Rect{0, 0, 400, 400}, paint);
3009  canvas.Restore();
3010 
3011  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3012 }
3013 
3014 TEST_P(AiksTest, DrawPaintTransformsBounds) {
3015  if (GetParam() != PlaygroundBackend::kMetal) {
3016  GTEST_SKIP_("This backend doesn't support runtime effects.");
3017  }
3018 
3019  auto runtime_stage = OpenAssetAsRuntimeStage("gradient.frag.iplr");
3020  ASSERT_TRUE(runtime_stage->IsDirty());
3021 
3022  struct FragUniforms {
3023  Size size;
3024  } frag_uniforms = {.size = Size::MakeWH(400, 400)};
3025  auto uniform_data = std::make_shared<std::vector<uint8_t>>();
3026  uniform_data->resize(sizeof(FragUniforms));
3027  memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
3028 
3029  std::vector<RuntimeEffectContents::TextureInput> texture_inputs;
3030 
3031  Paint paint;
3032  paint.color_source = ColorSource::MakeRuntimeEffect(
3033  runtime_stage, uniform_data, texture_inputs);
3034 
3035  Canvas canvas;
3036  canvas.Save();
3037  canvas.Scale(GetContentScale());
3038  canvas.DrawPaint(paint);
3039  canvas.Restore();
3040 
3041  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3042 }
3043 
3044 TEST_P(AiksTest, CanDrawPoints) {
3045  std::vector<Point> points = {
3046  {0, 0}, //
3047  {100, 100}, //
3048  {100, 0}, //
3049  {0, 100}, //
3050  {0, 0}, //
3051  {48, 48}, //
3052  {52, 52}, //
3053  };
3054  std::vector<PointStyle> caps = {
3055  PointStyle::kRound,
3056  PointStyle::kSquare,
3057  };
3058  Paint paint;
3059  paint.color = Color::Yellow().WithAlpha(0.5);
3060 
3061  Paint background;
3062  background.color = Color::Black();
3063 
3064  Canvas canvas;
3065  canvas.DrawPaint(background);
3066  canvas.Translate({200, 200});
3067  canvas.DrawPoints(points, 10, paint, PointStyle::kRound);
3068  canvas.Translate({150, 0});
3069  canvas.DrawPoints(points, 10, paint, PointStyle::kSquare);
3070 
3071  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3072 }
3073 
3074 // Regression test for https://github.com/flutter/flutter/issues/127374.
3075 TEST_P(AiksTest, DrawAtlasWithColorAdvancedAndTransform) {
3076  // Draws the image as four squares stiched together.
3077  auto atlas = CreateTextureForFixture("bay_bridge.jpg");
3078  auto size = atlas->GetSize();
3079  auto image = std::make_shared<Image>(atlas);
3080  // Divide image into four quadrants.
3081  Scalar half_width = size.width / 2;
3082  Scalar half_height = size.height / 2;
3083  std::vector<Rect> texture_coordinates = {
3084  Rect::MakeLTRB(0, 0, half_width, half_height),
3085  Rect::MakeLTRB(half_width, 0, size.width, half_height),
3086  Rect::MakeLTRB(0, half_height, half_width, size.height),
3087  Rect::MakeLTRB(half_width, half_height, size.width, size.height)};
3088  // Position quadrants adjacent to eachother.
3089  std::vector<Matrix> transforms = {
3090  Matrix::MakeTranslation({0, 0, 0}),
3091  Matrix::MakeTranslation({half_width, 0, 0}),
3092  Matrix::MakeTranslation({0, half_height, 0}),
3093  Matrix::MakeTranslation({half_width, half_height, 0})};
3094  std::vector<Color> colors = {Color::Red(), Color::Green(), Color::Blue(),
3095  Color::Yellow()};
3096 
3097  Paint paint;
3098 
3099  Canvas canvas;
3100  canvas.Scale({0.25, 0.25, 1.0});
3101  canvas.DrawAtlas(image, transforms, texture_coordinates, colors,
3102  BlendMode::kModulate, {}, std::nullopt, paint);
3103 
3104  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3105 }
3106 
3107 // Regression test for https://github.com/flutter/flutter/issues/127374.
3108 TEST_P(AiksTest, DrawAtlasAdvancedAndTransform) {
3109  // Draws the image as four squares stiched together.
3110  auto atlas = CreateTextureForFixture("bay_bridge.jpg");
3111  auto size = atlas->GetSize();
3112  auto image = std::make_shared<Image>(atlas);
3113  // Divide image into four quadrants.
3114  Scalar half_width = size.width / 2;
3115  Scalar half_height = size.height / 2;
3116  std::vector<Rect> texture_coordinates = {
3117  Rect::MakeLTRB(0, 0, half_width, half_height),
3118  Rect::MakeLTRB(half_width, 0, size.width, half_height),
3119  Rect::MakeLTRB(0, half_height, half_width, size.height),
3120  Rect::MakeLTRB(half_width, half_height, size.width, size.height)};
3121  // Position quadrants adjacent to eachother.
3122  std::vector<Matrix> transforms = {
3123  Matrix::MakeTranslation({0, 0, 0}),
3124  Matrix::MakeTranslation({half_width, 0, 0}),
3125  Matrix::MakeTranslation({0, half_height, 0}),
3126  Matrix::MakeTranslation({half_width, half_height, 0})};
3127 
3128  Paint paint;
3129 
3130  Canvas canvas;
3131  canvas.Scale({0.25, 0.25, 1.0});
3132  canvas.DrawAtlas(image, transforms, texture_coordinates, {},
3133  BlendMode::kModulate, {}, std::nullopt, paint);
3134 
3135  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3136 }
3137 
3138 TEST_P(AiksTest, CanDrawPointsWithTextureMap) {
3139  auto texture = CreateTextureForFixture("table_mountain_nx.png",
3140  /*enable_mipmapping=*/true);
3141 
3142  std::vector<Point> points = {
3143  {0, 0}, //
3144  {100, 100}, //
3145  {100, 0}, //
3146  {0, 100}, //
3147  {0, 0}, //
3148  {48, 48}, //
3149  {52, 52}, //
3150  };
3151  std::vector<PointStyle> caps = {
3152  PointStyle::kRound,
3153  PointStyle::kSquare,
3154  };
3155  Paint paint;
3156  paint.color_source = ColorSource::MakeImage(texture, Entity::TileMode::kClamp,
3157  Entity::TileMode::kClamp, {}, {});
3158 
3159  Canvas canvas;
3160  canvas.Translate({200, 200});
3161  canvas.DrawPoints(points, 100, paint, PointStyle::kRound);
3162  canvas.Translate({150, 0});
3163  canvas.DrawPoints(points, 100, paint, PointStyle::kSquare);
3164 
3165  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3166 }
3167 
3168 // This currently renders solid blue, as the support for text color sources was
3169 // moved into DLDispatching. Path data requires the SkTextBlobs which are not
3170 // used in impeller::TextFrames.
3171 TEST_P(AiksTest, TextForegroundShaderWithTransform) {
3172  auto mapping = OpenFixtureAsSkData("Roboto-Regular.ttf");
3173  ASSERT_NE(mapping, nullptr);
3174 
3175  Scalar font_size = 100;
3176  SkFont sk_font(SkTypeface::MakeFromData(mapping), font_size);
3177 
3178  Paint text_paint;
3179  text_paint.color = Color::Blue();
3180 
3181  std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
3182  Color{0.1294, 0.5882, 0.9529, 1.0}};
3183  std::vector<Scalar> stops = {
3184  0.0,
3185  1.0,
3186  };
3187  text_paint.color_source = ColorSource::MakeLinearGradient(
3188  {0, 0}, {100, 100}, std::move(colors), std::move(stops),
3189  Entity::TileMode::kRepeat, {});
3190 
3191  Canvas canvas;
3192  canvas.Translate({100, 100});
3193  canvas.Rotate(Radians(kPi / 4));
3194 
3195  auto blob = SkTextBlob::MakeFromString("Hello", sk_font);
3196  ASSERT_NE(blob, nullptr);
3197  auto frame = MakeTextFrameFromTextBlobSkia(blob);
3198  canvas.DrawTextFrame(frame, Point(), text_paint);
3199 
3200  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3201 }
3202 
3203 TEST_P(AiksTest, CanCanvasDrawPicture) {
3204  Canvas subcanvas;
3205  subcanvas.DrawRect(Rect::MakeLTRB(-100, -50, 100, 50),
3206  {.color = Color::CornflowerBlue()});
3207  auto picture = subcanvas.EndRecordingAsPicture();
3208 
3209  Canvas canvas;
3210  canvas.Translate({200, 200});
3211  canvas.Rotate(Radians(kPi / 4));
3212  canvas.DrawPicture(picture);
3213 
3214  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3215 }
3216 
3217 TEST_P(AiksTest, CanCanvasDrawPictureWithAdvancedBlend) {
3218  Canvas subcanvas;
3219  subcanvas.DrawRect(
3220  Rect::MakeLTRB(-100, -50, 100, 50),
3221  {.color = Color::CornflowerBlue(), .blend_mode = BlendMode::kColorDodge});
3222  auto picture = subcanvas.EndRecordingAsPicture();
3223 
3224  Canvas canvas;
3225  canvas.DrawPaint({.color = Color::Black()});
3226  canvas.DrawCircle(Point::MakeXY(150, 150), 25, {.color = Color::Red()});
3227  canvas.DrawPicture(picture);
3228 
3229  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3230 }
3231 
3232 TEST_P(AiksTest, CanCanvasDrawPictureWithBackdropFilter) {
3233  Canvas subcanvas;
3234  subcanvas.SaveLayer({}, {},
3235  ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
3236  FilterContents::BlurStyle::kNormal,
3237  Entity::TileMode::kDecal));
3238  auto image = std::make_shared<Image>(CreateTextureForFixture("kalimba.jpg"));
3239  Paint paint;
3240  paint.color = Color::Red().WithAlpha(0.5);
3241  subcanvas.DrawImage(image, Point::MakeXY(100.0, 100.0), paint);
3242 
3243  auto picture = subcanvas.EndRecordingAsPicture();
3244 
3245  Canvas canvas;
3246  canvas.DrawPaint({.color = Color::Black()});
3247  canvas.DrawCircle(Point::MakeXY(150, 150), 25, {.color = Color::Red()});
3248  canvas.DrawPicture(picture);
3249 
3250  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3251 }
3252 
3253 TEST_P(AiksTest, DrawPictureWithText) {
3254  Canvas subcanvas;
3255  ASSERT_TRUE(RenderTextInCanvasSkia(
3256  GetContext(), subcanvas,
3257  "the quick brown fox jumped over the lazy dog!.?", "Roboto-Regular.ttf"));
3258  subcanvas.Translate({0, 10});
3259  subcanvas.Scale(Vector2(3, 3));
3260  ASSERT_TRUE(RenderTextInCanvasSkia(
3261  GetContext(), subcanvas,
3262  "the quick brown fox jumped over the very big lazy dog!.?",
3263  "Roboto-Regular.ttf"));
3264  auto picture = subcanvas.EndRecordingAsPicture();
3265 
3266  Canvas canvas;
3267  canvas.Scale(Vector2(.2, .2));
3268  canvas.Save();
3269  canvas.Translate({200, 200});
3270  canvas.Scale(Vector2(3.5, 3.5)); // The text must not be blurry after this.
3271  canvas.DrawPicture(picture);
3272  canvas.Restore();
3273 
3274  canvas.Scale(Vector2(1.5, 1.5));
3275  ASSERT_TRUE(RenderTextInCanvasSkia(
3276  GetContext(), canvas,
3277  "the quick brown fox jumped over the smaller lazy dog!.?",
3278  "Roboto-Regular.ttf"));
3279  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3280 }
3281 
3282 TEST_P(AiksTest, DrawPictureClipped) {
3283  Canvas subcanvas;
3284  subcanvas.ClipRRect(Rect::MakeLTRB(100, 100, 400, 400), 15);
3285  subcanvas.DrawPaint({.color = Color::Red()});
3286  auto picture = subcanvas.EndRecordingAsPicture();
3287 
3288  Canvas canvas;
3289  canvas.DrawPaint({.color = Color::CornflowerBlue()});
3290 
3291  // Draw a red RRect via DrawPicture.
3292  canvas.DrawPicture(picture);
3293 
3294  // Draw over the picture with a larger green rectangle, completely covering it
3295  // up.
3296  canvas.ClipRRect(Rect::MakeLTRB(100, 100, 400, 400).Expand(20), 15);
3297  canvas.DrawPaint({.color = Color::Green()});
3298 
3299  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3300 }
3301 
3302 TEST_P(AiksTest, MatrixSaveLayerFilter) {
3303  Canvas canvas;
3304  canvas.DrawPaint({.color = Color::Black()});
3305  canvas.SaveLayer({}, std::nullopt);
3306  {
3307  canvas.DrawCircle(Point(200, 200), 100,
3308  {.color = Color::Green().WithAlpha(0.5),
3309  .blend_mode = BlendMode::kPlus});
3310  // Should render a second circle, centered on the bottom-right-most edge of
3311  // the circle.
3312  canvas.SaveLayer({.image_filter = ImageFilter::MakeMatrix(
3313  Matrix::MakeTranslation(Vector2(1, 1) *
3314  (200 + 100 * k1OverSqrt2)) *
3315  Matrix::MakeScale(Vector2(1, 1) * 0.5) *
3316  Matrix::MakeTranslation(Vector2(-200, -200)),
3317  SamplerDescriptor{})},
3318  std::nullopt);
3319  canvas.DrawCircle(Point(200, 200), 100,
3320  {.color = Color::Green().WithAlpha(0.5),
3321  .blend_mode = BlendMode::kPlus});
3322  canvas.Restore();
3323  }
3324  canvas.Restore();
3325 
3326  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3327 }
3328 
3329 TEST_P(AiksTest, MatrixBackdropFilter) {
3330  Canvas canvas;
3331  canvas.DrawPaint({.color = Color::Black()});
3332  canvas.SaveLayer({}, std::nullopt);
3333  {
3334  canvas.DrawCircle(Point(200, 200), 100,
3335  {.color = Color::Green().WithAlpha(0.5),
3336  .blend_mode = BlendMode::kPlus});
3337  // Should render a second circle, centered on the bottom-right-most edge of
3338  // the circle.
3339  canvas.SaveLayer(
3340  {}, std::nullopt,
3341  ImageFilter::MakeMatrix(
3342  Matrix::MakeTranslation(Vector2(1, 1) * (100 + 100 * k1OverSqrt2)) *
3343  Matrix::MakeScale(Vector2(1, 1) * 0.5) *
3344  Matrix::MakeTranslation(Vector2(-100, -100)),
3345  SamplerDescriptor{}));
3346  canvas.Restore();
3347  }
3348  canvas.Restore();
3349 
3350  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3351 }
3352 
3353 TEST_P(AiksTest, SolidColorApplyColorFilter) {
3354  auto contents = SolidColorContents();
3355  contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75));
3356  auto result = contents.ApplyColorFilter([](const Color& color) {
3357  return color.Blend(Color::LimeGreen().WithAlpha(0.75), BlendMode::kScreen);
3358  });
3359  ASSERT_TRUE(result);
3360  ASSERT_COLOR_NEAR(contents.GetColor(),
3361  Color(0.433247, 0.879523, 0.825324, 0.75));
3362 }
3363 
3364 #define APPLY_COLOR_FILTER_GRADIENT_TEST(name) \
3365  TEST_P(AiksTest, name##GradientApplyColorFilter) { \
3366  auto contents = name##GradientContents(); \
3367  contents.SetColors({Color::CornflowerBlue().WithAlpha(0.75)}); \
3368  auto result = contents.ApplyColorFilter([](const Color& color) { \
3369  return color.Blend(Color::LimeGreen().WithAlpha(0.75), \
3370  BlendMode::kScreen); \
3371  }); \
3372  ASSERT_TRUE(result); \
3373  \
3374  std::vector<Color> expected = {Color(0.433247, 0.879523, 0.825324, 0.75)}; \
3375  ASSERT_COLORS_NEAR(contents.GetColors(), expected); \
3376  }
3377 
3382 
3383 TEST_P(AiksTest, DrawScaledTextWithPerspectiveNoSaveLayer) {
3384  Canvas canvas;
3385  // clang-format off
3386  canvas.Transform(Matrix(
3387  2.000000, 0.000000, 0.000000, 0.000000,
3388  1.445767, 2.637070, -0.507928, 0.001524,
3389  -2.451887, -0.534662, 0.861399, -0.002584,
3390  1063.481934, 1025.951416, -48.300270, 1.144901
3391  ));
3392  // clang-format on
3393 
3394  ASSERT_TRUE(RenderTextInCanvasSkia(GetContext(), canvas, "Hello world",
3395  "Roboto-Regular.ttf"));
3396 
3397  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3398 }
3399 
3400 TEST_P(AiksTest, DrawScaledTextWithPerspectiveSaveLayer) {
3401  Canvas canvas;
3402  Paint save_paint;
3403  canvas.SaveLayer(save_paint);
3404  // clang-format off
3405  canvas.Transform(Matrix(
3406  2.000000, 0.000000, 0.000000, 0.000000,
3407  1.445767, 2.637070, -0.507928, 0.001524,
3408  -2.451887, -0.534662, 0.861399, -0.002584,
3409  1063.481934, 1025.951416, -48.300270, 1.144901
3410  ));
3411  // clang-format on
3412 
3413  ASSERT_TRUE(RenderTextInCanvasSkia(GetContext(), canvas, "Hello world",
3414  "Roboto-Regular.ttf"));
3415 }
3416 
3417 TEST_P(AiksTest, PipelineBlendSingleParameter) {
3418  Canvas canvas;
3419 
3420  // Should render a green square in the middle of a blue circle.
3421  canvas.SaveLayer({});
3422  {
3423  canvas.Translate(Point(100, 100));
3424  canvas.DrawCircle(Point(200, 200), 200, {.color = Color::Blue()});
3425  canvas.ClipRect(Rect(100, 100, 200, 200));
3426  canvas.DrawCircle(Point(200, 200), 200,
3427  {
3428  .color = Color::Green(),
3429  .blend_mode = BlendMode::kSourceOver,
3430  .image_filter = ImageFilter::MakeFromColorFilter(
3431  *ColorFilter::MakeBlend(BlendMode::kDestination,
3432  Color::White())),
3433  });
3434  canvas.Restore();
3435  }
3436 
3437  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3438 }
3439 
3440 TEST_P(AiksTest, ClippedBlurFilterRendersCorrectlyInteractive) {
3441  auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
3442  auto point = IMPELLER_PLAYGROUND_POINT(Point(400, 400), 20, Color::Green());
3443 
3444  Canvas canvas;
3445  canvas.Translate(point - Point(400, 400));
3446  Paint paint;
3448  .style = FilterContents::BlurStyle::kNormal,
3449  .sigma = Radius{120 * 3},
3450  };
3451  paint.color = Color::Red();
3452  PathBuilder builder{};
3453  builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
3454  canvas.DrawPath(builder.TakePath(), paint);
3455  return canvas.EndRecordingAsPicture();
3456  };
3457  ASSERT_TRUE(OpenPlaygroundHere(callback));
3458 }
3459 
3460 TEST_P(AiksTest, ClippedBlurFilterRendersCorrectly) {
3461  Canvas canvas;
3462  canvas.Translate(Point(0, -400));
3463  Paint paint;
3465  .style = FilterContents::BlurStyle::kNormal,
3466  .sigma = Radius{120 * 3},
3467  };
3468  paint.color = Color::Red();
3469  PathBuilder builder{};
3470  builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
3471  canvas.DrawPath(builder.TakePath(), paint);
3472  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3473 }
3474 
3476  auto capture_context = CaptureContext::MakeAllowlist({"TestDocument"});
3477 
3478  auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
3479  Canvas canvas;
3480 
3481  capture_context.Rewind();
3482  auto document = capture_context.GetDocument("TestDocument");
3483 
3484  auto color = document.AddColor("Background color", Color::CornflowerBlue());
3485  canvas.DrawPaint({.color = color});
3486 
3487  ImGui::Begin("TestDocument", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
3488  document.GetElement()->properties.Iterate([](CaptureProperty& property) {
3489  property.Invoke({.color = [](CaptureColorProperty& p) {
3490  ImGui::ColorEdit4(p.label.c_str(), reinterpret_cast<float*>(&p.value));
3491  }});
3492  });
3493  ImGui::End();
3494 
3495  return canvas.EndRecordingAsPicture();
3496  };
3497  OpenPlaygroundHere(callback);
3498 }
3499 
3500 TEST_P(AiksTest, CaptureInactivatedByDefault) {
3501  ASSERT_FALSE(GetContext()->capture.IsActive());
3502 }
3503 
3504 // Regression test for https://github.com/flutter/flutter/issues/134678.
3505 TEST_P(AiksTest, ReleasesTextureOnTeardown) {
3506  auto context = GetContext();
3507  std::weak_ptr<Texture> weak_texture;
3508 
3509  {
3510  auto texture = CreateTextureForFixture("table_mountain_nx.png");
3511 
3512  Canvas canvas;
3513  canvas.Scale(GetContentScale());
3514  canvas.Translate({100.0f, 100.0f, 0});
3515 
3516  Paint paint;
3517  paint.color_source = ColorSource::MakeImage(
3518  texture, Entity::TileMode::kClamp, Entity::TileMode::kClamp, {}, {});
3519  canvas.DrawRect({0, 0, 600, 600}, paint);
3520 
3521  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3522  }
3523 
3524  // See https://github.com/flutter/flutter/issues/134751.
3525  //
3526  // If the fence waiter was working this may not be released by the end of the
3527  // scope above. Adding a manual shutdown so that future changes to the fence
3528  // waiter will not flake this test.
3529  context->Shutdown();
3530 
3531  // The texture should be released by now.
3532  ASSERT_TRUE(weak_texture.expired()) << "When the texture is no longer in use "
3533  "by the backend, it should be "
3534  "released.";
3535 }
3536 
3537 // Regression test for https://github.com/flutter/flutter/issues/135441 .
3538 TEST_P(AiksTest, VerticesGeometryUVPositionData) {
3539  Canvas canvas;
3540  Paint paint;
3541  auto texture = CreateTextureForFixture("table_mountain_nx.png");
3542 
3543  paint.color_source = ColorSource::MakeImage(texture, Entity::TileMode::kClamp,
3544  Entity::TileMode::kClamp, {}, {});
3545 
3546  auto vertices = {Point(0, 0), Point(texture->GetSize().width, 0),
3547  Point(0, texture->GetSize().height)};
3548  std::vector<uint16_t> indices = {0u, 1u, 2u};
3549  std::vector<Point> texture_coordinates = {};
3550  std::vector<Color> vertex_colors = {};
3551  auto geometry = std::make_shared<VerticesGeometry>(
3552  vertices, indices, texture_coordinates, vertex_colors,
3553  Rect::MakeLTRB(0, 0, 1, 1), VerticesGeometry::VertexMode::kTriangleStrip);
3554 
3555  canvas.DrawVertices(geometry, BlendMode::kSourceOver, paint);
3556  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3557 }
3558 
3559 TEST_P(AiksTest, ClearBlendWithBlur) {
3560  Canvas canvas;
3561  Paint white;
3562  white.color = Color::Blue();
3563  canvas.DrawRect(Rect::MakeXYWH(0, 0, 600.0, 600.0), white);
3564 
3565  Paint clear;
3566  clear.blend_mode = BlendMode::kClear;
3568  .style = FilterContents::BlurStyle::kNormal,
3569  .sigma = Sigma(20),
3570  };
3571 
3572  canvas.DrawCircle(Point::MakeXY(300.0, 300.0), 200.0, clear);
3573 
3574  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3575 }
3576 
3577 TEST_P(AiksTest, ClearBlend) {
3578  Canvas canvas;
3579  Paint white;
3580  white.color = Color::Blue();
3581  canvas.DrawRect(Rect::MakeXYWH(0, 0, 600.0, 600.0), white);
3582 
3583  Paint clear;
3584  clear.blend_mode = BlendMode::kClear;
3585 
3586  canvas.DrawCircle(Point::MakeXY(300.0, 300.0), 200.0, clear);
3587 }
3588 
3589 TEST_P(AiksTest, MatrixImageFilterMagnify) {
3590  Canvas canvas;
3591  canvas.Scale(GetContentScale());
3592  auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
3593  canvas.Translate({600, -200});
3594  canvas.SaveLayer({
3595  .image_filter = std::make_shared<MatrixImageFilter>(
3596  Matrix{
3597  2, 0, 0, 0, //
3598  0, 2, 0, 0, //
3599  0, 0, 2, 0, //
3600  0, 0, 0, 1 //
3601  },
3602  SamplerDescriptor{}),
3603  });
3604  canvas.DrawImage(image, {0, 0}, Paint{.color = Color(1.0, 1.0, 1.0, 0.5)});
3605  canvas.Restore();
3606 
3607  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3608 }
3609 
3610 // This should be solid red, if you see a little red box this is broken.
3611 TEST_P(AiksTest, ClearColorOptimizationWhenSubpassIsBiggerThanParentPass) {
3612  Canvas canvas;
3613  canvas.Scale(GetContentScale());
3614  canvas.DrawRect(Rect::MakeLTRB(200, 200, 300, 300), {.color = Color::Red()});
3615  canvas.SaveLayer({
3616  .image_filter = std::make_shared<MatrixImageFilter>(
3617  Matrix::MakeScale({2, 2, 1}), SamplerDescriptor{}),
3618  });
3619  // Draw a rectangle that would fully cover the parent pass size, but not
3620  // the subpass that it is rendered in.
3621  canvas.DrawRect(Rect::MakeLTRB(0, 0, 400, 400), {.color = Color::Green()});
3622  // Draw a bigger rectangle to force the subpass to be bigger.
3623  canvas.DrawRect(Rect::MakeLTRB(0, 0, 800, 800), {.color = Color::Red()});
3624  canvas.Restore();
3625 
3626  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3627 }
3628 
3629 TEST_P(AiksTest, MaskBlurWithZeroSigmaIsSkipped) {
3630  Canvas canvas;
3631 
3632  Paint paint = {
3633  .color = Color::White(),
3634  .mask_blur_descriptor =
3636  .style = FilterContents::BlurStyle::kNormal,
3637  .sigma = Sigma(0),
3638  },
3639  };
3640 
3641  canvas.DrawCircle({300, 300}, 200, paint);
3642  canvas.DrawRect(Rect::MakeLTRB(100, 300, 500, 600), paint);
3643 
3644  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3645 }
3646 
3647 TEST_P(AiksTest, SubpassWithClearColorOptimization) {
3648  Canvas canvas;
3649 
3650  // Use a non-srcOver blend mode to ensure that we don't detect this as an
3651  // opacity peephole optimization.
3652  canvas.SaveLayer(
3653  {.color = Color::Blue().WithAlpha(0.5), .blend_mode = BlendMode::kSource},
3654  Rect::MakeLTRB(0, 0, 200, 200));
3655  canvas.DrawPaint(
3656  {.color = Color::BlackTransparent(), .blend_mode = BlendMode::kSource});
3657  canvas.Restore();
3658 
3659  canvas.SaveLayer(
3660  {.color = Color::Blue(), .blend_mode = BlendMode::kDestinationOver});
3661  canvas.Restore();
3662 
3663  // This playground should appear blank on CI since we are only drawing
3664  // transparent black. If the clear color optimization is broken, the texture
3665  // will be filled with NaNs and may produce a magenta texture on macOS or iOS.
3666  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3667 }
3668 
3669 } // namespace testing
3670 } // namespace impeller
BLEND_MODE_TEST
#define BLEND_MODE_TEST(blend_mode)
Definition: aiks_unittests.cc:2604
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::testing::TEST_P
TEST_P(AiksTest, SubpassWithClearColorOptimization)
Definition: aiks_unittests.cc:3647
impeller::Canvas::EndRecordingAsPicture
Picture EndRecordingAsPicture()
Definition: canvas.cc:519
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:2523
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:154
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:563
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:606
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:538
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:473
impeller::Canvas::DrawPicture
void DrawPicture(const Picture &picture)
Definition: canvas.cc:443
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:81
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:488
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:146
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:56
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:277
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:255
APPLY_COLOR_FILTER_GRADIENT_TEST
#define APPLY_COLOR_FILTER_GRADIENT_TEST(name)
Definition: aiks_unittests.cc:3364
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:85
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:92
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:667
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:174
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:184
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:162
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:105
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:125
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:321
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:158
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:129
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:113
impeller::Canvas::DrawRect
void DrawRect(Rect rect, const Paint &paint)
Definition: canvas.cc:235
impeller::Degrees
Definition: scalar.h:43
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:424
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:300
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:142
impeller::Canvas::ClipPath
void ClipPath(const Path &path, Entity::ClipOperation clip_op=Entity::ClipOperation::kIntersect)
Definition: canvas.cc:290
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