Flutter Impeller
aiks_blur_unittests.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
6 
7 #include "impeller/aiks/canvas.h"
12 #include "impeller/renderer/testing/mocks.h"
13 #include "third_party/imgui/imgui.h"
14 
15 ////////////////////////////////////////////////////////////////////////////////
16 // This is for tests of Canvas that are interested the results of rendering
17 // blurs.
18 ////////////////////////////////////////////////////////////////////////////////
19 
20 namespace impeller {
21 namespace testing {
22 
23 TEST_P(AiksTest, CanRenderMaskBlurHugeSigma) {
24  Canvas canvas;
25  canvas.DrawCircle({400, 400}, 300,
26  {.color = Color::Green(),
27  .mask_blur_descriptor = Paint::MaskBlurDescriptor{
29  .sigma = Sigma(99999),
30  }});
31  canvas.Restore();
32 
33  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
34 }
35 
36 TEST_P(AiksTest, CanRenderForegroundBlendWithMaskBlur) {
37  // This case triggers the ForegroundPorterDuffBlend path. The color filter
38  // should apply to the color only, and respect the alpha mask.
39  Canvas canvas;
40  canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
41  canvas.DrawCircle({400, 400}, 200,
42  {
43  .color = Color::White(),
44  .color_filter = ColorFilter::MakeBlend(
46  .mask_blur_descriptor =
49  .sigma = Radius(20),
50  },
51  });
52  canvas.Restore();
53 
54  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
55 }
56 
57 TEST_P(AiksTest, CanRenderForegroundAdvancedBlendWithMaskBlur) {
58  // This case triggers the ForegroundAdvancedBlend path. The color filter
59  // should apply to the color only, and respect the alpha mask.
60  Canvas canvas;
61  canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
62  canvas.DrawCircle({400, 400}, 200,
63  {
64  .color = Color::Grey(),
65  .color_filter = ColorFilter::MakeBlend(
67  .mask_blur_descriptor =
70  .sigma = Radius(20),
71  },
72  });
73  canvas.Restore();
74 
75  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
76 }
77 
78 TEST_P(AiksTest, CanRenderBackdropBlurInteractive) {
79  auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
80  static PlaygroundPoint point_a(Point(50, 50), 30, Color::White());
81  static PlaygroundPoint point_b(Point(300, 200), 30, Color::White());
82  auto [a, b] = DrawPlaygroundLine(point_a, point_b);
83 
84  Canvas canvas;
85  canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
86  canvas.DrawCircle({300, 200}, 100, {.color = Color::GreenYellow()});
87  canvas.DrawCircle({140, 170}, 75, {.color = Color::DarkMagenta()});
88  canvas.DrawCircle({180, 120}, 100, {.color = Color::OrangeRed()});
89  canvas.ClipRRect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), {20, 20});
90  canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
91  ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
94  canvas.Restore();
95 
96  return canvas.EndRecordingAsPicture();
97  };
98 
99  ASSERT_TRUE(OpenPlaygroundHere(callback));
100 }
101 
102 TEST_P(AiksTest, CanRenderBackdropBlur) {
103  Canvas canvas;
104  canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
105  canvas.DrawCircle({300, 200}, 100, {.color = Color::GreenYellow()});
106  canvas.DrawCircle({140, 170}, 75, {.color = Color::DarkMagenta()});
107  canvas.DrawCircle({180, 120}, 100, {.color = Color::OrangeRed()});
108  canvas.ClipRRect(Rect::MakeLTRB(75, 50, 375, 275), {20, 20});
109  canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
110  ImageFilter::MakeBlur(Sigma(30.0), Sigma(30.0),
113  canvas.Restore();
114 
115  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
116 }
117 
118 TEST_P(AiksTest, CanRenderBackdropBlurHugeSigma) {
119  Canvas canvas;
120  canvas.DrawCircle({400, 400}, 300, {.color = Color::Green()});
121  canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
122  ImageFilter::MakeBlur(Sigma(999999), Sigma(999999),
125  canvas.Restore();
126 
127  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
128 }
129 
130 TEST_P(AiksTest, CanRenderClippedBlur) {
131  Canvas canvas;
132  canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
133  canvas.DrawCircle(
134  {400, 400}, 200,
135  {
136  .color = Color::Green(),
137  .image_filter = ImageFilter::MakeBlur(
140  });
141  canvas.Restore();
142 
143  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
144 }
145 
146 TEST_P(AiksTest, ClippedBlurFilterRendersCorrectlyInteractive) {
147  auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
148  static PlaygroundPoint playground_point(Point(400, 400), 20,
149  Color::Green());
150  auto point = DrawPlaygroundPoint(playground_point);
151 
152  Canvas canvas;
153  canvas.Translate(point - Point(400, 400));
154  Paint paint;
155  paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
157  .sigma = Radius{120 * 3},
158  };
159  paint.color = Color::Red();
160  PathBuilder builder{};
161  builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
162  canvas.DrawPath(builder.TakePath(), paint);
163  return canvas.EndRecordingAsPicture();
164  };
165  ASSERT_TRUE(OpenPlaygroundHere(callback));
166 }
167 
168 TEST_P(AiksTest, ClippedBlurFilterRendersCorrectly) {
169  Canvas canvas;
170  canvas.Translate(Point(0, -400));
171  Paint paint;
172  paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
174  .sigma = Radius{120 * 3},
175  };
176  paint.color = Color::Red();
177  PathBuilder builder{};
178  builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
179  canvas.DrawPath(builder.TakePath(), paint);
180  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
181 }
182 
183 TEST_P(AiksTest, ClearBlendWithBlur) {
184  Canvas canvas;
185  Paint white;
186  white.color = Color::Blue();
187  canvas.DrawRect(Rect::MakeXYWH(0, 0, 600.0, 600.0), white);
188 
189  Paint clear;
193  .sigma = Sigma(20),
194  };
195 
196  canvas.DrawCircle(Point::MakeXY(300.0, 300.0), 200.0, clear);
197 
198  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
199 }
200 
201 TEST_P(AiksTest, BlurHasNoEdge) {
202  Scalar sigma = 47.6;
203  auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
204  if (AiksTest::ImGuiBegin("Controls", nullptr,
205  ImGuiWindowFlags_AlwaysAutoResize)) {
206  ImGui::SliderFloat("Sigma", &sigma, 0, 50);
207  ImGui::End();
208  }
209  Canvas canvas;
210  canvas.Scale(GetContentScale());
211  canvas.DrawPaint({});
212  Paint blur = {
213  .color = Color::Green(),
214  .mask_blur_descriptor =
217  .sigma = Sigma(sigma),
218  },
219  };
220  canvas.DrawRect(Rect::MakeXYWH(300, 300, 200, 200), blur);
221  return canvas.EndRecordingAsPicture();
222  };
223 
224  ASSERT_TRUE(OpenPlaygroundHere(callback));
225 }
226 
227 TEST_P(AiksTest, BlurredRectangleWithShader) {
228  Canvas canvas;
229  canvas.Scale(GetContentScale());
230 
231  auto paint_lines = [&canvas](Scalar dx, Scalar dy, Paint paint) {
232  auto draw_line = [&canvas, &paint](Point a, Point b) {
233  canvas.DrawPath(PathBuilder{}.AddLine(a, b).TakePath(), paint);
234  };
235  paint.stroke_width = 5;
237  draw_line(Point(dx + 100, dy + 100), Point(dx + 200, dy + 200));
238  draw_line(Point(dx + 100, dy + 200), Point(dx + 200, dy + 100));
239  draw_line(Point(dx + 150, dy + 100), Point(dx + 200, dy + 150));
240  draw_line(Point(dx + 100, dy + 150), Point(dx + 150, dy + 200));
241  };
242 
243  AiksContext renderer(GetContext(), nullptr);
244  Canvas recorder_canvas;
245  for (int x = 0; x < 5; ++x) {
246  for (int y = 0; y < 5; ++y) {
247  Rect rect = Rect::MakeXYWH(x * 20, y * 20, 20, 20);
248  Paint paint{.color =
249  ((x + y) & 1) == 0 ? Color::Yellow() : Color::Blue()};
250  recorder_canvas.DrawRect(rect, paint);
251  }
252  }
253  Picture picture = recorder_canvas.EndRecordingAsPicture();
254  std::shared_ptr<Texture> texture =
255  picture.ToImage(renderer, ISize{100, 100})->GetTexture();
256 
257  ColorSource image_source = ColorSource::MakeImage(
259  std::shared_ptr<ImageFilter> blur_filter = ImageFilter::MakeBlur(
262  canvas.DrawRect(Rect::MakeLTRB(0, 0, 300, 600),
264  canvas.DrawRect(Rect::MakeLTRB(100, 100, 200, 200),
265  Paint{.color_source = image_source});
266  canvas.DrawRect(Rect::MakeLTRB(300, 0, 600, 600),
267  Paint{.color = Color::Red()});
268  canvas.DrawRect(
269  Rect::MakeLTRB(400, 100, 500, 200),
270  Paint{.color_source = image_source, .image_filter = blur_filter});
271  paint_lines(0, 300, Paint{.color_source = image_source});
272  paint_lines(300, 300,
273  Paint{.color_source = image_source, .image_filter = blur_filter});
274  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
275 }
276 
277 TEST_P(AiksTest, MaskBlurWithZeroSigmaIsSkipped) {
278  Canvas canvas;
279 
280  Paint paint = {
281  .color = Color::Blue(),
282  .mask_blur_descriptor =
285  .sigma = Sigma(0),
286  },
287  };
288 
289  canvas.DrawCircle({300, 300}, 200, paint);
290  canvas.DrawRect(Rect::MakeLTRB(100, 300, 500, 600), paint);
291 
292  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
293 }
294 
297  Scalar sigma = 1.0f;
298  Scalar alpha = 1.0f;
299  std::shared_ptr<ImageFilter> image_filter;
300  bool invert_colors = false;
302 };
303 
304 static Picture MaskBlurVariantTest(const AiksTest& test_context,
305  const MaskBlurTestConfig& config) {
306  Canvas canvas;
307  canvas.Scale(test_context.GetContentScale());
308  canvas.Scale(Vector2{0.8f, 0.8f});
309  Paint paint;
310  paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
312  .sigma = Sigma{1},
313  };
314 
315  canvas.DrawPaint({.color = Color::AntiqueWhite()});
316 
317  paint.mask_blur_descriptor->style = config.style;
318  paint.mask_blur_descriptor->sigma = Sigma{config.sigma};
319  paint.image_filter = config.image_filter;
320  paint.invert_colors = config.invert_colors;
321  paint.blend_mode = config.blend_mode;
322 
323  const Scalar x = 50;
324  const Scalar radius = 20.0f;
325  const Scalar y_spacing = 100.0f;
326 
327  Scalar y = 50;
328  paint.color = Color::Crimson().WithAlpha(config.alpha);
329  canvas.DrawRect(Rect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
330  radius, 60.0f - radius),
331  paint);
332 
333  y += y_spacing;
334  paint.color = Color::Blue().WithAlpha(config.alpha);
335  canvas.DrawCircle({x + 25, y + 25}, radius, paint);
336 
337  y += y_spacing;
338  paint.color = Color::Green().WithAlpha(config.alpha);
339  canvas.DrawOval(Rect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
340  radius, 60.0f - radius),
341  paint);
342 
343  y += y_spacing;
344  paint.color = Color::Purple().WithAlpha(config.alpha);
345  canvas.DrawRRect(Rect::MakeXYWH(x, y, 60.0f, 60.0f), //
346  {radius, radius}, //
347  paint);
348 
349  y += y_spacing;
350  paint.color = Color::Orange().WithAlpha(config.alpha);
351  canvas.DrawRRect(Rect::MakeXYWH(x, y, 60.0f, 60.0f), //
352  {radius, 5.0f}, paint);
353 
354  y += y_spacing;
355  paint.color = Color::Maroon().WithAlpha(config.alpha);
356  canvas.DrawPath(PathBuilder{}
357  .MoveTo({x + 0, y + 60})
358  .LineTo({x + 30, y + 0})
359  .LineTo({x + 60, y + 60})
360  .Close()
361  .TakePath(),
362  paint);
363 
364  y += y_spacing;
365  paint.color = Color::Maroon().WithAlpha(config.alpha);
366  canvas.DrawPath(PathBuilder{}
367  .AddArc(Rect::MakeXYWH(x + 5, y, 50, 50),
368  Radians{kPi / 2}, Radians{kPi})
369  .AddArc(Rect::MakeXYWH(x + 25, y, 50, 50),
370  Radians{kPi / 2}, Radians{kPi})
371  .Close()
372  .TakePath(),
373  paint);
374 
375  return canvas.EndRecordingAsPicture();
376 }
377 
378 static const std::map<std::string, MaskBlurTestConfig> kPaintVariations = {
379  // 1. Normal style, translucent, zero sigma.
380  {"NormalTranslucentZeroSigma",
382  .sigma = 0.0f,
383  .alpha = 0.5f}},
384  // 2. Normal style, translucent.
385  {"NormalTranslucent",
387  .sigma = 8.0f,
388  .alpha = 0.5f}},
389  // 3. Solid style, translucent.
390  {"SolidTranslucent",
392  .sigma = 8.0f,
393  .alpha = 0.5f}},
394  // 4. Solid style, opaque.
395  {"SolidOpaque",
396  {.style = FilterContents::BlurStyle::kSolid, .sigma = 8.0f}},
397  // 5. Solid style, translucent, color & image filtered.
398  {"SolidTranslucentWithFilters",
400  .sigma = 8.0f,
401  .alpha = 0.5f,
402  .image_filter = ImageFilter::MakeBlur(Sigma{3},
403  Sigma{3},
406  .invert_colors = true}},
407  // 6. Solid style, translucent, exclusion blended.
408  {"SolidTranslucentExclusionBlend",
410  .sigma = 8.0f,
411  .alpha = 0.5f,
412  .blend_mode = BlendMode::kExclusion}},
413  // 7. Inner style, translucent.
414  {"InnerTranslucent",
416  .sigma = 8.0f,
417  .alpha = 0.5f}},
418  // 8. Inner style, translucent, blurred.
419  {"InnerTranslucentWithBlurImageFilter",
421  .sigma = 8.0f,
422  .alpha = 0.5f,
423  .image_filter = ImageFilter::MakeBlur(Sigma{3},
424  Sigma{3},
427  // 9. Outer style, translucent.
428  {"OuterTranslucent",
430  .sigma = 8.0f,
431  .alpha = 0.5f}},
432  // 10. Outer style, opaque, image filtered.
433  {"OuterOpaqueWithBlurImageFilter",
435  .sigma = 8.0f,
436  .image_filter = ImageFilter::MakeBlur(Sigma{3},
437  Sigma{3},
440 };
441 
442 #define MASK_BLUR_VARIANT_TEST(config) \
443  TEST_P(AiksTest, MaskBlurVariantTest##config) { \
444  ASSERT_TRUE(OpenPlaygroundHere( \
445  MaskBlurVariantTest(*this, kPaintVariations.at(#config)))); \
446  }
447 
448 MASK_BLUR_VARIANT_TEST(NormalTranslucentZeroSigma)
449 MASK_BLUR_VARIANT_TEST(NormalTranslucent)
450 MASK_BLUR_VARIANT_TEST(SolidTranslucent)
451 MASK_BLUR_VARIANT_TEST(SolidOpaque)
452 MASK_BLUR_VARIANT_TEST(SolidTranslucentWithFilters)
453 MASK_BLUR_VARIANT_TEST(SolidTranslucentExclusionBlend)
454 MASK_BLUR_VARIANT_TEST(InnerTranslucent)
455 MASK_BLUR_VARIANT_TEST(InnerTranslucentWithBlurImageFilter)
456 MASK_BLUR_VARIANT_TEST(OuterTranslucent)
457 MASK_BLUR_VARIANT_TEST(OuterOpaqueWithBlurImageFilter)
458 
459 #undef MASK_BLUR_VARIANT_TEST
460 
461 TEST_P(AiksTest, GaussianBlurAtPeripheryVertical) {
462  Canvas canvas;
463 
464  canvas.Scale(GetContentScale());
465  canvas.DrawRRect(Rect::MakeLTRB(0, 0, GetWindowSize().width, 100),
466  Size(10, 10), Paint{.color = Color::LimeGreen()});
467  canvas.DrawRRect(Rect::MakeLTRB(0, 110, GetWindowSize().width, 210),
468  Size(10, 10), Paint{.color = Color::Magenta()});
469  canvas.ClipRect(Rect::MakeLTRB(100, 0, 200, GetWindowSize().height));
470  canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
471  ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
474  canvas.Restore();
475 
476  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
477 }
478 
479 TEST_P(AiksTest, GaussianBlurAtPeripheryHorizontal) {
480  Canvas canvas;
481 
482  canvas.Scale(GetContentScale());
483  std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
484  canvas.DrawImageRect(
485  std::make_shared<Image>(boston),
486  Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height),
487  Rect::MakeLTRB(0, 0, GetWindowSize().width, 100), Paint{});
488  canvas.DrawRRect(Rect::MakeLTRB(0, 110, GetWindowSize().width, 210),
489  Size(10, 10), Paint{.color = Color::Magenta()});
490  canvas.ClipRect(Rect::MakeLTRB(0, 50, GetWindowSize().width, 150));
491  canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
492  ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
495  canvas.Restore();
496  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
497 }
498 
499 #define FLT_FORWARD(mock, real, method) \
500  EXPECT_CALL(*mock, method()) \
501  .WillRepeatedly(::testing::Return(real->method()));
502 
503 TEST_P(AiksTest, GaussianBlurWithoutDecalSupport) {
504  if (GetParam() != PlaygroundBackend::kMetal) {
505  GTEST_SKIP_(
506  "This backend doesn't yet support setting device capabilities.");
507  }
508  if (!WillRenderSomething()) {
509  // Sometimes these tests are run without playgrounds enabled which is
510  // pointless for this test since we are asserting that
511  // `SupportsDecalSamplerAddressMode` is called.
512  GTEST_SKIP_("This test requires playgrounds.");
513  }
514 
515  std::shared_ptr<const Capabilities> old_capabilities =
516  GetContext()->GetCapabilities();
517  auto mock_capabilities = std::make_shared<MockCapabilities>();
518  EXPECT_CALL(*mock_capabilities, SupportsDecalSamplerAddressMode())
519  .Times(::testing::AtLeast(1))
520  .WillRepeatedly(::testing::Return(false));
521  FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultColorFormat);
522  FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultStencilFormat);
523  FLT_FORWARD(mock_capabilities, old_capabilities,
524  GetDefaultDepthStencilFormat);
525  FLT_FORWARD(mock_capabilities, old_capabilities, SupportsOffscreenMSAA);
526  FLT_FORWARD(mock_capabilities, old_capabilities,
527  SupportsImplicitResolvingMSAA);
528  FLT_FORWARD(mock_capabilities, old_capabilities, SupportsReadFromResolve);
529  FLT_FORWARD(mock_capabilities, old_capabilities, SupportsFramebufferFetch);
530  FLT_FORWARD(mock_capabilities, old_capabilities, SupportsSSBO);
531  FLT_FORWARD(mock_capabilities, old_capabilities, SupportsCompute);
532  FLT_FORWARD(mock_capabilities, old_capabilities,
533  SupportsTextureToTextureBlits);
534  FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultGlyphAtlasFormat);
535  ASSERT_TRUE(SetCapabilities(mock_capabilities).ok());
536 
537  auto texture = std::make_shared<Image>(CreateTextureForFixture("boston.jpg"));
538  Canvas canvas;
539  canvas.Scale(GetContentScale() * 0.5);
540  canvas.DrawPaint({.color = Color::Black()});
541  canvas.DrawImage(
542  texture, Point(200, 200),
543  {
544  .image_filter = ImageFilter::MakeBlur(
547  });
548  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
549 }
550 
551 TEST_P(AiksTest, GaussianBlurOneDimension) {
552  Canvas canvas;
553 
554  canvas.Scale(GetContentScale());
555  canvas.Scale({0.5, 0.5, 1.0});
556  std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
557  canvas.DrawImage(std::make_shared<Image>(boston), Point(100, 100), Paint{});
558  canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
559  ImageFilter::MakeBlur(Sigma(50.0), Sigma(0.0),
562  canvas.Restore();
563  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
564 }
565 
566 // Smoketest to catch issues with the coverage hint.
567 // Draws a rotated blurred image within a rectangle clip. The center of the clip
568 // rectangle is the center of the rotated image. The entire area of the clip
569 // rectangle should be filled with opaque colors output by the blur.
570 TEST_P(AiksTest, GaussianBlurRotatedAndClipped) {
571  Canvas canvas;
572  std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
573  Rect bounds =
574  Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
575  Vector2 image_center = Vector2(bounds.GetSize() / 2);
576  Paint paint = {.image_filter =
577  ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
580  Vector2 clip_size = {150, 75};
581  Vector2 center = Vector2(1024, 768) / 2;
582  canvas.Scale(GetContentScale());
583  canvas.ClipRect(
584  Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size));
585  canvas.Translate({center.x, center.y, 0});
586  canvas.Scale({0.6, 0.6, 1});
587  canvas.Rotate(Degrees(25));
588 
589  canvas.DrawImageRect(std::make_shared<Image>(boston), /*source=*/bounds,
590  /*dest=*/bounds.Shift(-image_center), paint);
591 
592  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
593 }
594 
595 TEST_P(AiksTest, GaussianBlurScaledAndClipped) {
596  Canvas canvas;
597  std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
598  Rect bounds =
599  Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
600  Vector2 image_center = Vector2(bounds.GetSize() / 2);
601  Paint paint = {.image_filter =
602  ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
605  Vector2 clip_size = {150, 75};
606  Vector2 center = Vector2(1024, 768) / 2;
607  canvas.Scale(GetContentScale());
608  canvas.ClipRect(
609  Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size));
610  canvas.Translate({center.x, center.y, 0});
611  canvas.Scale({0.6, 0.6, 1});
612 
613  canvas.DrawImageRect(std::make_shared<Image>(boston), /*source=*/bounds,
614  /*dest=*/bounds.Shift(-image_center), paint);
615 
616  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
617 }
618 
619 TEST_P(AiksTest, GaussianBlurRotatedAndClippedInteractive) {
620  std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
621 
622  auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
623  const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
624  const Entity::TileMode tile_modes[] = {
627 
628  static float rotation = 0;
629  static float scale = 0.6;
630  static int selected_tile_mode = 3;
631 
632  if (AiksTest::ImGuiBegin("Controls", nullptr,
633  ImGuiWindowFlags_AlwaysAutoResize)) {
634  ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180);
635  ImGui::SliderFloat("Scale", &scale, 0, 2.0);
636  ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
637  sizeof(tile_mode_names) / sizeof(char*));
638  ImGui::End();
639  }
640 
641  Canvas canvas;
642  Rect bounds =
643  Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
644  Vector2 image_center = Vector2(bounds.GetSize() / 2);
645  Paint paint = {.image_filter =
646  ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
648  tile_modes[selected_tile_mode])};
649  static PlaygroundPoint point_a(Point(362, 309), 20, Color::Red());
650  static PlaygroundPoint point_b(Point(662, 459), 20, Color::Red());
651  auto [handle_a, handle_b] = DrawPlaygroundLine(point_a, point_b);
652  Vector2 center = Vector2(1024, 768) / 2;
653  canvas.Scale(GetContentScale());
654  canvas.ClipRect(
655  Rect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y));
656  canvas.Translate({center.x, center.y, 0});
657  canvas.Scale({scale, scale, 1});
658  canvas.Rotate(Degrees(rotation));
659 
660  canvas.DrawImageRect(std::make_shared<Image>(boston), /*source=*/bounds,
661  /*dest=*/bounds.Shift(-image_center), paint);
662  return canvas.EndRecordingAsPicture();
663  };
664 
665  ASSERT_TRUE(OpenPlaygroundHere(callback));
666 }
667 
668 TEST_P(AiksTest, GaussianBlurRotatedNonUniform) {
669  auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
670  const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
671  const Entity::TileMode tile_modes[] = {
674 
675  static float rotation = 45;
676  static float scale = 0.6;
677  static int selected_tile_mode = 3;
678 
679  if (AiksTest::ImGuiBegin("Controls", nullptr,
680  ImGuiWindowFlags_AlwaysAutoResize)) {
681  ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180);
682  ImGui::SliderFloat("Scale", &scale, 0, 2.0);
683  ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
684  sizeof(tile_mode_names) / sizeof(char*));
685  ImGui::End();
686  }
687 
688  Canvas canvas;
689  Paint paint = {.color = Color::Green(),
690  .image_filter =
691  ImageFilter::MakeBlur(Sigma(50.0), Sigma(0.0),
693  tile_modes[selected_tile_mode])};
694  Vector2 center = Vector2(1024, 768) / 2;
695  canvas.Scale(GetContentScale());
696  canvas.Translate({center.x, center.y, 0});
697  canvas.Scale({scale, scale, 1});
698  canvas.Rotate(Degrees(rotation));
699 
700  canvas.DrawRRect(Rect::MakeXYWH(-100, -100, 200, 200), Size(10, 10), paint);
701  return canvas.EndRecordingAsPicture();
702  };
703 
704  ASSERT_TRUE(OpenPlaygroundHere(callback));
705 }
706 
707 // This addresses a bug where tiny blurs could result in mip maps that beyond
708 // the limits for the textures used for blurring.
709 // See also: b/323402168
710 TEST_P(AiksTest, GaussianBlurSolidColorTinyMipMap) {
711  for (int32_t i = 1; i < 5; ++i) {
712  Canvas canvas;
713  Scalar fi = i;
714  canvas.DrawPath(
715  PathBuilder{}
716  .MoveTo({100, 100})
717  .LineTo({100.f + fi, 100.f + fi})
718  .TakePath(),
719  {.color = Color::Chartreuse(),
720  .image_filter = ImageFilter::MakeBlur(
723 
724  Picture picture = canvas.EndRecordingAsPicture();
725  std::shared_ptr<RenderTargetCache> cache =
726  std::make_shared<RenderTargetCache>(
727  GetContext()->GetResourceAllocator());
728  AiksContext aiks_context(GetContext(), nullptr, cache);
729  std::shared_ptr<Image> image = picture.ToImage(aiks_context, {1024, 768});
730  EXPECT_TRUE(image) << " length " << i;
731  }
732 }
733 
734 // This addresses a bug where tiny blurs could result in mip maps that beyond
735 // the limits for the textures used for blurring.
736 // See also: b/323402168
737 TEST_P(AiksTest, GaussianBlurBackdropTinyMipMap) {
738  for (int32_t i = 0; i < 5; ++i) {
739  Canvas canvas;
740  ISize clip_size = ISize(i, i);
741  canvas.ClipRect(
742  Rect::MakeXYWH(400, 400, clip_size.width, clip_size.height));
743  canvas.DrawCircle(
744  {400, 400}, 200,
745  {
746  .color = Color::Green(),
747  .image_filter = ImageFilter::MakeBlur(
750  });
751  canvas.Restore();
752 
753  Picture picture = canvas.EndRecordingAsPicture();
754  std::shared_ptr<RenderTargetCache> cache =
755  std::make_shared<RenderTargetCache>(
756  GetContext()->GetResourceAllocator());
757  AiksContext aiks_context(GetContext(), nullptr, cache);
758  std::shared_ptr<Image> image = picture.ToImage(aiks_context, {1024, 768});
759  EXPECT_TRUE(image) << " clip rect " << i;
760  }
761 }
762 
763 TEST_P(AiksTest, GaussianBlurAnimatedBackdrop) {
764  // This test is for checking out how stable rendering is when content is
765  // translated underneath a blur. Animating under a blur can cause
766  // *shimmering* to happen as a result of pixel alignment.
767  // See also: https://github.com/flutter/flutter/issues/140193
768  auto boston = std::make_shared<Image>(
769  CreateTextureForFixture("boston.jpg", /*enable_mipmapping=*/true));
770  ASSERT_TRUE(boston);
771  int64_t count = 0;
772  Scalar sigma = 20.0;
773  Scalar freq = 0.1;
774  Scalar amp = 50.0;
775  auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
776  if (AiksTest::ImGuiBegin("Controls", nullptr,
777  ImGuiWindowFlags_AlwaysAutoResize)) {
778  ImGui::SliderFloat("Sigma", &sigma, 0, 200);
779  ImGui::SliderFloat("Frequency", &freq, 0.01, 2.0);
780  ImGui::SliderFloat("Amplitude", &amp, 1, 100);
781  ImGui::End();
782  }
783 
784  Canvas canvas;
785  canvas.Scale(GetContentScale());
786  Scalar y = amp * sin(freq * 2.0 * M_PI * count / 60);
787  canvas.DrawImage(boston,
788  Point(1024 / 2 - boston->GetSize().width / 2,
789  (768 / 2 - boston->GetSize().height / 2) + y),
790  {});
791  static PlaygroundPoint point_a(Point(100, 100), 20, Color::Red());
792  static PlaygroundPoint point_b(Point(900, 700), 20, Color::Red());
793  auto [handle_a, handle_b] = DrawPlaygroundLine(point_a, point_b);
794  canvas.ClipRect(
795  Rect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y));
796  canvas.ClipRect(Rect::MakeLTRB(100, 100, 900, 700));
797  canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
798  ImageFilter::MakeBlur(Sigma(sigma), Sigma(sigma),
801  count += 1;
802  return canvas.EndRecordingAsPicture();
803  };
804  ASSERT_TRUE(OpenPlaygroundHere(callback));
805 }
806 
807 TEST_P(AiksTest, GaussianBlurStyleInnerGradient) {
808  Canvas canvas;
809  canvas.Scale(GetContentScale());
810 
811  canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
812 
813  std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
814  Color{0.7568, 0.2627, 0.2118, 1.0}};
815  std::vector<Scalar> stops = {0.0, 1.0};
816 
817  Paint paint;
818  paint.color_source = ColorSource::MakeLinearGradient(
819  {0, 0}, {200, 200}, std::move(colors), std::move(stops),
821  paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
823  .sigma = Sigma(30),
824  };
825  canvas.DrawPath(PathBuilder()
826  .MoveTo({200, 200})
827  .LineTo({300, 400})
828  .LineTo({100, 400})
829  .Close()
830  .TakePath(),
831  paint);
832 
833  // Draw another thing to make sure the clip area is reset.
834  Paint red;
835  red.color = Color::Red();
836  canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
837  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
838 }
839 
840 TEST_P(AiksTest, GaussianBlurStyleSolidGradient) {
841  Canvas canvas;
842  canvas.Scale(GetContentScale());
843 
844  canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
845 
846  std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
847  Color{0.7568, 0.2627, 0.2118, 1.0}};
848  std::vector<Scalar> stops = {0.0, 1.0};
849 
850  Paint paint;
851  paint.color_source = ColorSource::MakeLinearGradient(
852  {0, 0}, {200, 200}, std::move(colors), std::move(stops),
854  paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
856  .sigma = Sigma(30),
857  };
858  canvas.DrawPath(PathBuilder()
859  .MoveTo({200, 200})
860  .LineTo({300, 400})
861  .LineTo({100, 400})
862  .Close()
863  .TakePath(),
864  paint);
865 
866  // Draw another thing to make sure the clip area is reset.
867  Paint red;
868  red.color = Color::Red();
869  canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
870  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
871 }
872 
873 TEST_P(AiksTest, GaussianBlurStyleOuterGradient) {
874  Canvas canvas;
875  canvas.Scale(GetContentScale());
876 
877  canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
878 
879  std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
880  Color{0.7568, 0.2627, 0.2118, 1.0}};
881  std::vector<Scalar> stops = {0.0, 1.0};
882 
883  Paint paint;
884  paint.color_source = ColorSource::MakeLinearGradient(
885  {0, 0}, {200, 200}, std::move(colors), std::move(stops),
887  paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
889  .sigma = Sigma(30),
890  };
891  canvas.DrawPath(PathBuilder()
892  .MoveTo({200, 200})
893  .LineTo({300, 400})
894  .LineTo({100, 400})
895  .Close()
896  .TakePath(),
897  paint);
898 
899  // Draw another thing to make sure the clip area is reset.
900  Paint red;
901  red.color = Color::Red();
902  canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
903  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
904 }
905 
906 TEST_P(AiksTest, GaussianBlurStyleInner) {
907  Canvas canvas;
908  canvas.Scale(GetContentScale());
909 
910  canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
911 
912  Paint paint;
913  paint.color = Color::Green();
914  paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
916  .sigma = Sigma(30),
917  };
918  canvas.DrawPath(PathBuilder()
919  .MoveTo({200, 200})
920  .LineTo({300, 400})
921  .LineTo({100, 400})
922  .Close()
923  .TakePath(),
924  paint);
925 
926  // Draw another thing to make sure the clip area is reset.
927  Paint red;
928  red.color = Color::Red();
929  canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
930 
931  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
932 }
933 
934 TEST_P(AiksTest, GaussianBlurStyleOuter) {
935  Canvas canvas;
936  canvas.Scale(GetContentScale());
937 
938  canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
939 
940  Paint paint;
941  paint.color = Color::Green();
942  paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
944  .sigma = Sigma(30),
945  };
946  canvas.DrawPath(PathBuilder()
947  .MoveTo({200, 200})
948  .LineTo({300, 400})
949  .LineTo({100, 400})
950  .Close()
951  .TakePath(),
952  paint);
953 
954  // Draw another thing to make sure the clip area is reset.
955  Paint red;
956  red.color = Color::Red();
957  canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
958 
959  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
960 }
961 
962 TEST_P(AiksTest, GaussianBlurStyleSolid) {
963  Canvas canvas;
964  canvas.Scale(GetContentScale());
965 
966  canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
967 
968  Paint paint;
969  paint.color = Color::Green();
970  paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
972  .sigma = Sigma(30),
973  };
974  canvas.DrawPath(PathBuilder()
975  .MoveTo({200, 200})
976  .LineTo({300, 400})
977  .LineTo({100, 400})
978  .Close()
979  .TakePath(),
980  paint);
981 
982  // Draw another thing to make sure the clip area is reset.
983  Paint red;
984  red.color = Color::Red();
985  canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
986 
987  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
988 }
989 
990 TEST_P(AiksTest, MaskBlurTexture) {
991  Scalar sigma = 30;
992  auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
993  if (AiksTest::ImGuiBegin("Controls", nullptr,
994  ImGuiWindowFlags_AlwaysAutoResize)) {
995  ImGui::SliderFloat("Sigma", &sigma, 0, 500);
996  ImGui::End();
997  }
998  Canvas canvas;
999  canvas.Scale(GetContentScale());
1000  Paint paint;
1001  paint.color = Color::Green();
1002  paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
1004  .sigma = Sigma(sigma),
1005  };
1006  std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
1007  canvas.DrawImage(std::make_shared<Image>(boston), {200, 200}, paint);
1008  Paint red;
1009  red.color = Color::Red();
1010  canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
1011  return canvas.EndRecordingAsPicture();
1012  };
1013  ASSERT_TRUE(OpenPlaygroundHere(callback));
1014 }
1015 
1016 TEST_P(AiksTest, GuassianBlurUpdatesMipmapContents) {
1017  // This makes sure if mip maps are recycled across invocations of blurs the
1018  // contents get updated each frame correctly. If they aren't updated the color
1019  // inside the blur and outside the blur will be different.
1020  //
1021  // If there is some change to render target caching this could display a false
1022  // positive in the future. Also, if the LOD that is rendered is 1 it could
1023  // present a false positive.
1024  int32_t count = 0;
1025  auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
1026  Canvas canvas;
1027  if (count++ == 0) {
1028  canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
1029  } else {
1030  canvas.DrawCircle({100, 100}, 50, {.color = Color::Chartreuse()});
1031  }
1032  canvas.ClipRRect(Rect::MakeLTRB(75, 50, 375, 275), {20, 20});
1033  canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
1034  ImageFilter::MakeBlur(Sigma(30.0), Sigma(30.0),
1037  canvas.Restore();
1038  return canvas.EndRecordingAsPicture();
1039  };
1040 
1041  ASSERT_TRUE(OpenPlaygroundHere(callback));
1042 }
1043 
1044 TEST_P(AiksTest, GaussianBlurSetsMipCountOnPass) {
1045  Canvas canvas;
1046  canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
1047  canvas.SaveLayer({}, std::nullopt,
1051  canvas.Restore();
1052 
1053  Picture picture = canvas.EndRecordingAsPicture();
1054  EXPECT_EQ(4, picture.pass->GetRequiredMipCount());
1055 }
1056 
1057 TEST_P(AiksTest, GaussianBlurAllocatesCorrectMipCountRenderTarget) {
1058  size_t blur_required_mip_count =
1059  GetParam() == PlaygroundBackend::kOpenGLES ? 1 : 4;
1060 
1061  Canvas canvas;
1062  canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
1063  canvas.SaveLayer({}, std::nullopt,
1067  canvas.Restore();
1068 
1069  Picture picture = canvas.EndRecordingAsPicture();
1070  std::shared_ptr<RenderTargetCache> cache =
1071  std::make_shared<RenderTargetCache>(GetContext()->GetResourceAllocator());
1072  AiksContext aiks_context(GetContext(), nullptr, cache);
1073  picture.ToImage(aiks_context, {100, 100});
1074 
1075  size_t max_mip_count = 0;
1076  for (auto it = cache->GetRenderTargetDataBegin();
1077  it != cache->GetRenderTargetDataEnd(); ++it) {
1078  max_mip_count = std::max(it->config.mip_count, max_mip_count);
1079  }
1080  EXPECT_EQ(max_mip_count, blur_required_mip_count);
1081 }
1082 
1083 TEST_P(AiksTest, GaussianBlurMipMapNestedLayer) {
1084  fml::testing::LogCapture log_capture;
1085  size_t blur_required_mip_count =
1086  GetParam() == PlaygroundBackend::kOpenGLES ? 1 : 4;
1087 
1088  Canvas canvas;
1089  canvas.DrawPaint({.color = Color::Wheat()});
1090  canvas.SaveLayer({.blend_mode = BlendMode::kMultiply});
1091  canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
1092  canvas.SaveLayer({}, std::nullopt,
1096  canvas.DrawCircle({200, 200}, 50, {.color = Color::Chartreuse()});
1097 
1098  Picture picture = canvas.EndRecordingAsPicture();
1099  std::shared_ptr<RenderTargetCache> cache =
1100  std::make_shared<RenderTargetCache>(GetContext()->GetResourceAllocator());
1101  AiksContext aiks_context(GetContext(), nullptr, cache);
1102  picture.ToImage(aiks_context, {100, 100});
1103 
1104  size_t max_mip_count = 0;
1105  for (auto it = cache->GetRenderTargetDataBegin();
1106  it != cache->GetRenderTargetDataEnd(); ++it) {
1107  max_mip_count = std::max(it->config.mip_count, max_mip_count);
1108  }
1109  EXPECT_EQ(max_mip_count, blur_required_mip_count);
1110  // The log is FML_DLOG, so only check in debug builds.
1111 #ifndef NDEBUG
1112  if (GetParam() != PlaygroundBackend::kOpenGLES) {
1113  EXPECT_EQ(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1114  std::string::npos);
1115  } else {
1116  EXPECT_NE(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1117  std::string::npos);
1118  }
1119 #endif
1120 }
1121 
1122 TEST_P(AiksTest, GaussianBlurMipMapImageFilter) {
1123  size_t blur_required_mip_count =
1124  GetParam() == PlaygroundBackend::kOpenGLES ? 1 : 4;
1125  fml::testing::LogCapture log_capture;
1126  Canvas canvas;
1127  canvas.SaveLayer(
1128  {.image_filter = ImageFilter::MakeBlur(Sigma(30), Sigma(30),
1131  canvas.DrawCircle({200, 200}, 50, {.color = Color::Chartreuse()});
1132 
1133  Picture picture = canvas.EndRecordingAsPicture();
1134  std::shared_ptr<RenderTargetCache> cache =
1135  std::make_shared<RenderTargetCache>(GetContext()->GetResourceAllocator());
1136  AiksContext aiks_context(GetContext(), nullptr, cache);
1137  picture.ToImage(aiks_context, {1024, 768});
1138 
1139  size_t max_mip_count = 0;
1140  for (auto it = cache->GetRenderTargetDataBegin();
1141  it != cache->GetRenderTargetDataEnd(); ++it) {
1142  max_mip_count = std::max(it->config.mip_count, max_mip_count);
1143  }
1144  EXPECT_EQ(max_mip_count, blur_required_mip_count);
1145  // The log is FML_DLOG, so only check in debug builds.
1146 #ifndef NDEBUG
1147  if (GetParam() != PlaygroundBackend::kOpenGLES) {
1148  EXPECT_EQ(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1149  std::string::npos);
1150  } else {
1151  EXPECT_NE(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1152  std::string::npos);
1153  }
1154 #endif
1155 }
1156 
1157 TEST_P(AiksTest, GaussianBlurMipMapSolidColor) {
1158  size_t blur_required_mip_count =
1159  GetParam() == PlaygroundBackend::kOpenGLES ? 1 : 4;
1160  fml::testing::LogCapture log_capture;
1161  Canvas canvas;
1162  canvas.DrawPath(PathBuilder{}
1163  .MoveTo({100, 100})
1164  .LineTo({200, 100})
1165  .LineTo({150, 200})
1166  .LineTo({50, 200})
1167  .Close()
1168  .TakePath(),
1169  {.color = Color::Chartreuse(),
1170  .image_filter = ImageFilter::MakeBlur(
1173 
1174  Picture picture = canvas.EndRecordingAsPicture();
1175  std::shared_ptr<RenderTargetCache> cache =
1176  std::make_shared<RenderTargetCache>(GetContext()->GetResourceAllocator());
1177  AiksContext aiks_context(GetContext(), nullptr, cache);
1178  picture.ToImage(aiks_context, {1024, 768});
1179 
1180  size_t max_mip_count = 0;
1181  for (auto it = cache->GetRenderTargetDataBegin();
1182  it != cache->GetRenderTargetDataEnd(); ++it) {
1183  max_mip_count = std::max(it->config.mip_count, max_mip_count);
1184  }
1185  EXPECT_EQ(max_mip_count, blur_required_mip_count);
1186  // The log is FML_DLOG, so only check in debug builds.
1187 #ifndef NDEBUG
1188  if (GetParam() != PlaygroundBackend::kOpenGLES) {
1189  EXPECT_EQ(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1190  std::string::npos);
1191  } else {
1192  EXPECT_NE(log_capture.str().find(GaussianBlurFilterContents::kNoMipsError),
1193  std::string::npos);
1194  }
1195 #endif
1196 }
1197 
1198 TEST_P(AiksTest, MaskBlurDoesntStretchContents) {
1199  Scalar sigma = 70;
1200  auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
1201  if (AiksTest::ImGuiBegin("Controls", nullptr,
1202  ImGuiWindowFlags_AlwaysAutoResize)) {
1203  ImGui::SliderFloat("Sigma", &sigma, 0, 500);
1204  ImGui::End();
1205  }
1206  Canvas canvas;
1207  canvas.Scale(GetContentScale());
1208  canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
1209 
1210  std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
1211  ColorSource image_source = ColorSource::MakeImage(
1213 
1214  canvas.Transform(Matrix::MakeTranslation({100, 100, 0}) *
1215  Matrix::MakeScale({0.5, 0.5, 1.0}));
1216  Paint paint = {
1217  .color_source = image_source,
1218  .mask_blur_descriptor =
1221  .sigma = Sigma(sigma),
1222  },
1223  };
1224  canvas.DrawRect(
1225  Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height),
1226  paint);
1227 
1228  return canvas.EndRecordingAsPicture();
1229  };
1230  ASSERT_TRUE(OpenPlaygroundHere(callback));
1231 }
1232 
1233 } // namespace testing
1234 } // namespace impeller
impeller::Color::DarkMagenta
static constexpr Color DarkMagenta()
Definition: color.h:388
impeller::ISize
ISize64 ISize
Definition: size.h:140
impeller::Color::Blue
static constexpr Color Blue()
Definition: color.h:278
impeller::testing::MaskBlurTestConfig::alpha
Scalar alpha
Definition: aiks_blur_unittests.cc:298
impeller::AiksPlayground
Definition: aiks_playground.h:17
impeller::Entity::TileMode::kClamp
@ kClamp
impeller::Canvas::EndRecordingAsPicture
Picture EndRecordingAsPicture()
Definition: canvas.cc:804
impeller::Picture::pass
std::unique_ptr< EntityPass > pass
Definition: picture.h:18
impeller::Canvas::DrawRRect
void DrawRRect(const Rect &rect, const Size &corner_radii, const Paint &paint)
Definition: canvas.cc:540
impeller::TPoint::y
Type y
Definition: point.h:31
impeller::Scalar
float Scalar
Definition: scalar.h:18
impeller::AiksContext
Definition: aiks_context.h:19
impeller::Canvas::DrawImageRect
void DrawImageRect(const std::shared_ptr< Image > &image, Rect source, Rect dest, const Paint &paint, SamplerDescriptor sampler={}, SourceRectConstraint src_rect_constraint=SourceRectConstraint::kFast)
Definition: canvas.cc:766
impeller::testing::MaskBlurTestConfig::sigma
Scalar sigma
Definition: aiks_blur_unittests.cc:297
impeller::Color::Red
static constexpr Color Red()
Definition: color.h:274
impeller::Paint::Style::kStroke
@ kStroke
aiks_unittests.h
impeller::ColorSource::MakeLinearGradient
static ColorSource MakeLinearGradient(Point start_point, Point end_point, std::vector< Color > colors, std::vector< Scalar > stops, Entity::TileMode tile_mode, Matrix effect_transform)
Definition: color_source.cc:166
impeller::Paint
Definition: paint.h:23
impeller::TRect< Scalar >::MakeXYWH
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
impeller::BlendMode
BlendMode
Definition: color.h:59
impeller::FilterContents::BlurStyle
BlurStyle
Definition: filter_contents.h:26
impeller::Color
Definition: color.h:124
impeller::Paint::color
Color color
Definition: paint.h:68
impeller::Entity::TileMode::kDecal
@ kDecal
impeller::BlendMode::kSource
@ kSource
impeller::PlaygroundBackend::kMetal
@ kMetal
impeller::Color::Purple
static constexpr Color Purple()
Definition: color.h:744
impeller::Canvas
Definition: canvas.h:60
impeller::testing::MaskBlurTestConfig::blend_mode
BlendMode blend_mode
Definition: aiks_blur_unittests.cc:301
impeller::PathBuilder
Definition: path_builder.h:14
impeller::Paint::MaskBlurDescriptor::style
FilterContents::BlurStyle style
Definition: paint.h:49
impeller::Vector2
Point Vector2
Definition: point.h:326
impeller::BlendMode::kColor
@ kColor
impeller::kPi
constexpr float kPi
Definition: constants.h:26
impeller::ImageFilter::MakeBlur
static std::shared_ptr< ImageFilter > MakeBlur(Sigma sigma_x, Sigma sigma_y, FilterContents::BlurStyle blur_style, Entity::TileMode tile_mode)
Definition: image_filter.cc:20
impeller::Paint::MaskBlurDescriptor
Definition: paint.h:48
impeller::testing::MaskBlurVariantTest
static Picture MaskBlurVariantTest(const AiksTest &test_context, const MaskBlurTestConfig &config)
Definition: aiks_blur_unittests.cc:304
impeller::Color::CornflowerBlue
static constexpr Color CornflowerBlue()
Definition: color.h:344
impeller::testing::MaskBlurTestConfig
Definition: aiks_blur_unittests.cc:295
impeller::Color::Yellow
static constexpr Color Yellow()
Definition: color.h:844
gaussian_blur_filter_contents.h
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:225
impeller::Size
TSize< Scalar > Size
Definition: size.h:137
impeller::FilterContents::BlurStyle::kNormal
@ kNormal
Blurred inside and outside.
impeller::Entity::TileMode::kRepeat
@ kRepeat
impeller::Matrix::MakeTranslation
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
impeller::Color::Wheat
static constexpr Color Wheat()
Definition: color.h:836
impeller::Paint::color_source
ColorSource color_source
Definition: paint.h:69
impeller::Canvas::DrawRect
void DrawRect(const Rect &rect, const Paint &paint)
Definition: canvas.cc:495
impeller::MoveTo
void MoveTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:20
impeller::PathBuilder::AddRect
PathBuilder & AddRect(Rect rect)
Definition: path_builder.cc:117
impeller::Color::Maroon
static constexpr Color Maroon()
Definition: color.h:616
impeller::TRect::Shift
constexpr TRect< T > Shift(T dx, T dy) const
Returns a new rectangle translated by the given offset.
Definition: rect.h:589
impeller::Canvas::DrawImage
void DrawImage(const std::shared_ptr< Image > &image, Point offset, const Paint &paint, SamplerDescriptor sampler={})
Definition: canvas.cc:752
impeller::testing::TEST_P
TEST_P(AiksTest, CanRenderAdvancedBlendColorFilterWithSaveLayer)
Definition: aiks_blend_unittests.cc:21
impeller::Entity::TileMode::kMirror
@ kMirror
path_builder.h
impeller::Color::Magenta
static constexpr Color Magenta()
Definition: color.h:612
impeller::Picture
Definition: picture.h:17
impeller::TSize
Definition: size.h:19
impeller::Point
TPoint< Scalar > Point
Definition: point.h:322
impeller::testing::kPaintVariations
static const std::map< std::string, MaskBlurTestConfig > kPaintVariations
Definition: aiks_blur_unittests.cc:378
impeller::ColorSource
Definition: color_source.h:99
MASK_BLUR_VARIANT_TEST
#define MASK_BLUR_VARIANT_TEST(config)
Definition: aiks_blur_unittests.cc:442
impeller::Canvas::Scale
void Scale(const Vector2 &scale)
Definition: canvas.cc:315
impeller::Color::OrangeRed
static constexpr Color OrangeRed()
Definition: color.h:696
impeller::Radius
For convolution filters, the "radius" is the size of the convolution kernel to use on the local space...
Definition: sigma.h:48
impeller::FilterContents::BlurStyle::kSolid
@ kSolid
Solid inside, blurred outside.
impeller::testing::MaskBlurTestConfig::image_filter
std::shared_ptr< ImageFilter > image_filter
Definition: aiks_blur_unittests.cc:299
impeller::ColorFilter::MakeBlend
static std::shared_ptr< ColorFilter > MakeBlend(BlendMode blend_mode, Color color)
Definition: color_filter.cc:23
widgets.h
impeller::testing::MaskBlurTestConfig::style
FilterContents::BlurStyle style
Definition: aiks_blur_unittests.cc:296
impeller::BlendMode::kClear
@ kClear
impeller::Color::WithAlpha
constexpr Color WithAlpha(Scalar new_alpha) const
Definition: color.h:280
impeller::Canvas::DrawCircle
void DrawCircle(const Point &center, Scalar radius, const Paint &paint)
Definition: canvas.cc:566
impeller::Color::Grey
static constexpr Color Grey()
Definition: color.h:496
impeller::Color::White
static constexpr Color White()
Definition: color.h:266
impeller::Sigma
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition: sigma.h:32
impeller::Canvas::Restore
virtual bool Restore()
Definition: canvas.cc:257
impeller::Radians
Definition: scalar.h:38
impeller::Color::Chartreuse
static constexpr Color Chartreuse()
Definition: color.h:332
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:424
canvas.h
impeller::Color::Green
static constexpr Color Green()
Definition: color.h:276
impeller::Canvas::DrawPath
void DrawPath(const Path &path, const Paint &paint)
Definition: canvas.cc:343
impeller::PathBuilder::TakePath
Path TakePath(FillType fill=FillType::kNonZero)
Definition: path_builder.cc:22
impeller::Canvas::DrawPaint
void DrawPaint(const Paint &paint)
Definition: canvas.cc:352
impeller::FilterContents::BlurStyle::kInner
@ kInner
Blurred inside, nothing outside.
impeller::Entity::TileMode
TileMode
Definition: entity.h:42
impeller::TSize::width
Type width
Definition: size.h:22
impeller::TPoint::x
Type x
Definition: point.h:30
impeller::Canvas::ClipRRect
void ClipRRect(const Rect &rect, const Size &corner_radii, Entity::ClipOperation clip_op=Entity::ClipOperation::kIntersect)
Definition: canvas.cc:640
impeller::Close
void Close(PathBuilder *builder)
Definition: tessellator.cc:38
impeller::Color::AntiqueWhite
static constexpr Color AntiqueWhite()
Definition: color.h:288
impeller::Picture::ToImage
std::shared_ptr< Image > ToImage(AiksContext &context, ISize size) const
Definition: picture.cc:31
impeller::Canvas::DrawOval
void DrawOval(const Rect &rect, const Paint &paint)
Definition: canvas.cc:514
impeller::BlendMode::kExclusion
@ kExclusion
impeller::Color::GreenYellow
static constexpr Color GreenYellow()
Definition: color.h:492
impeller::FilterContents::BlurStyle::kOuter
@ kOuter
Nothing inside, blurred outside.
impeller::TRect::GetSize
constexpr TSize< Type > GetSize() const
Returns the size of the rectangle which may be negative in either width or height and may have been c...
Definition: rect.h:317
impeller::PlaygroundBackend::kOpenGLES
@ kOpenGLES
impeller::Canvas::Rotate
void Rotate(Radians radians)
Definition: canvas.cc:327
impeller::LineTo
void LineTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:24
impeller::Playground::GetContentScale
Point GetContentScale() const
Definition: playground.cc:192
impeller::DrawPlaygroundPoint
Point DrawPlaygroundPoint(PlaygroundPoint &point)
Definition: widgets.cc:9
impeller::Color::Orange
static constexpr Color Orange()
Definition: color.h:692
impeller::PlaygroundPoint
Definition: widgets.h:17
impeller::GaussianBlurFilterContents::kNoMipsError
static std::string_view kNoMipsError
Definition: gaussian_blur_filter_contents.h:49
FLT_FORWARD
#define FLT_FORWARD(mock, real, method)
Definition: aiks_blur_unittests.cc:499
impeller::TPoint< Scalar >
impeller::Canvas::Transform
void Transform(const Matrix &transform)
Definition: canvas.cc:294
impeller::saturated::b
SI b
Definition: saturated_math.h:87
impeller::PathBuilder::MoveTo
PathBuilder & MoveTo(Point point, bool relative=false)
Definition: path_builder.cc:33
impeller::Color::Black
static constexpr Color Black()
Definition: color.h:268
scale
const Scalar scale
Definition: stroke_path_geometry.cc:308
paint
const Paint & paint
Definition: color_source.cc:38
impeller::AiksPlayground::ImGuiBegin
static bool ImGuiBegin(const char *name, bool *p_open, ImGuiWindowFlags flags)
Definition: aiks_playground.cc:69
impeller::Degrees
Definition: scalar.h:46
impeller::Color::LimeGreen
static constexpr Color LimeGreen()
Definition: color.h:604
impeller::TSize::height
Type height
Definition: size.h:23
impeller::Color::DarkGreen
static constexpr Color DarkGreen()
Definition: color.h:376
impeller::Canvas::ClipRect
void ClipRect(const Rect &rect, Entity::ClipOperation clip_op=Entity::ClipOperation::kIntersect)
Definition: canvas.cc:599
impeller::TRect< Scalar >::MakeLTRB
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
render_target_cache.h
impeller
Definition: aiks_blend_unittests.cc:18
impeller::Matrix::MakeScale
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
impeller::Paint::mask_blur_descriptor
std::optional< MaskBlurDescriptor > mask_blur_descriptor
Definition: paint.h:81
impeller::BlendMode::kMultiply
@ kMultiply
impeller::Canvas::SaveLayer
virtual void SaveLayer(const Paint &paint, std::optional< Rect > bounds=std::nullopt, const std::shared_ptr< ImageFilter > &backdrop_filter=nullptr, ContentBoundsPromise bounds_promise=ContentBoundsPromise::kUnknown, uint32_t total_content_depth=kMaxDepth, bool can_distribute_opacity=false)
Definition: canvas.cc:842
impeller::TRect
Definition: rect.h:122
impeller::PathBuilder::AddArc
PathBuilder & AddArc(const Rect &oval_bounds, Radians start, Radians sweep, bool use_center=false)
Definition: path_builder.cc:318
impeller::BlendMode::kSourceOver
@ kSourceOver
impeller::Paint::blend_mode
BlendMode blend_mode
Definition: paint.h:76
impeller::testing::MaskBlurTestConfig::invert_colors
bool invert_colors
Definition: aiks_blur_unittests.cc:300
impeller::Color::Crimson
static constexpr Color Crimson()
Definition: color.h:352
impeller::TRect::Expand
constexpr TRect< T > Expand(T left, T top, T right, T bottom) const
Returns a rectangle with expanded edges. Negative expansion results in shrinking.
Definition: rect.h:605
impeller::DrawPlaygroundLine
std::tuple< Point, Point > DrawPlaygroundLine(PlaygroundPoint &point_a, PlaygroundPoint &point_b)
Definition: widgets.cc:50
impeller::TPoint< Scalar >::MakeXY
static constexpr TPoint< Type > MakeXY(Type x, Type y)
Definition: point.h:46
impeller::Canvas::Translate
void Translate(const Vector3 &offset)
Definition: canvas.cc:311