Flutter Impeller
aiks_dl_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 
5 #include "flutter/display_list/display_list.h"
6 #include "flutter/display_list/dl_blend_mode.h"
7 #include "flutter/display_list/dl_builder.h"
8 #include "flutter/display_list/dl_color.h"
9 #include "flutter/display_list/dl_paint.h"
10 #include "flutter/display_list/dl_sampling_options.h"
11 #include "flutter/display_list/dl_tile_mode.h"
12 #include "flutter/display_list/effects/dl_color_filter.h"
13 #include "flutter/display_list/effects/dl_color_source.h"
14 #include "flutter/display_list/effects/dl_image_filter.h"
15 #include "flutter/display_list/effects/dl_mask_filter.h"
16 #include "flutter/display_list/geometry/dl_path_builder.h"
18 
19 #include "gmock/gmock.h"
23 #include "impeller/renderer/testing/mocks.h"
24 #include "third_party/imgui/imgui.h"
25 
26 ////////////////////////////////////////////////////////////////////////////////
27 // This is for tests of Canvas that are interested the results of rendering
28 // blurs.
29 ////////////////////////////////////////////////////////////////////////////////
30 
31 namespace impeller {
32 namespace testing {
33 
34 using namespace flutter;
35 
36 // The shapes of these ovals should appear equal. They are demonstrating the
37 // difference between the fast pass and not.
38 TEST_P(AiksTest, SolidColorOvalsMaskBlurTinySigma) {
39  DisplayListBuilder builder;
40  builder.Scale(GetContentScale().x, GetContentScale().y);
41 
42  std::vector<float> sigmas = {0.0, 0.01, 1.0};
43  std::vector<DlColor> colors = {DlColor::kGreen(), DlColor::kYellow(),
44  DlColor::kRed()};
45  for (uint32_t i = 0; i < sigmas.size(); ++i) {
46  DlPaint paint;
47  paint.setColor(colors[i]);
48  paint.setMaskFilter(
49  DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigmas[i]));
50 
51  builder.Save();
52  builder.Translate(100 + (i * 100), 100);
53  DlRoundRect rrect =
54  DlRoundRect::MakeRectXY(DlRect::MakeXYWH(0, 0, 60.0f, 160.0f), 50, 100);
55  builder.DrawRoundRect(rrect, paint);
56  builder.Restore();
57  }
58 
59  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
60 }
61 
62 sk_sp<flutter::DisplayList> DoGradientOvalStrokeMaskBlur(Vector2 content_Scale,
63  Scalar sigma,
64  DlBlurStyle style) {
65  DisplayListBuilder builder;
66  builder.Scale(content_Scale.x, content_Scale.y);
67 
68  DlPaint background_paint;
69  background_paint.setColor(DlColor(1, 0.1, 0.1, 0.1, DlColorSpace::kSRGB));
70  builder.DrawPaint(background_paint);
71 
72  std::vector<DlColor> colors = {DlColor::kRed(), DlColor::kBlue()};
73  std::vector<Scalar> stops = {0.0, 1.0};
74 
75  DlPaint paint;
76  paint.setMaskFilter(DlBlurMaskFilter::Make(style, sigma));
77  auto gradient = DlColorSource::MakeLinear(
78  {0, 0}, {200, 200}, 2, colors.data(), stops.data(), DlTileMode::kClamp);
79  paint.setColorSource(gradient);
80  paint.setColor(DlColor::kWhite());
81  paint.setDrawStyle(DlDrawStyle::kStroke);
82  paint.setStrokeWidth(20);
83 
84  builder.Save();
85  builder.Translate(100, 100);
86 
87  {
88  DlPaint line_paint;
89  line_paint.setColor(DlColor::kWhite());
90  builder.DrawLine(DlPoint(100, 0), DlPoint(100, 60), line_paint);
91  builder.DrawLine(DlPoint(0, 30), DlPoint(200, 30), line_paint);
92  }
93 
94  DlRoundRect rrect =
95  DlRoundRect::MakeRectXY(DlRect::MakeXYWH(0, 0, 200.0f, 60.0f), 50, 100);
96  builder.DrawRoundRect(rrect, paint);
97  builder.Restore();
98 
99  return builder.Build();
100 }
101 
102 // https://github.com/flutter/flutter/issues/155930
103 TEST_P(AiksTest, GradientOvalStrokeMaskBlur) {
104  ASSERT_TRUE(OpenPlaygroundHere(DoGradientOvalStrokeMaskBlur(
105  GetContentScale(), /*sigma=*/10, DlBlurStyle::kNormal)));
106 }
107 
108 TEST_P(AiksTest, GradientOvalStrokeMaskBlurSigmaZero) {
109  ASSERT_TRUE(OpenPlaygroundHere(DoGradientOvalStrokeMaskBlur(
110  GetContentScale(), /*sigma=*/0, DlBlurStyle::kNormal)));
111 }
112 
113 TEST_P(AiksTest, GradientOvalStrokeMaskBlurOuter) {
114  ASSERT_TRUE(OpenPlaygroundHere(DoGradientOvalStrokeMaskBlur(
115  GetContentScale(), /*sigma=*/10, DlBlurStyle::kOuter)));
116 }
117 
118 TEST_P(AiksTest, GradientOvalStrokeMaskBlurInner) {
119  ASSERT_TRUE(OpenPlaygroundHere(DoGradientOvalStrokeMaskBlur(
120  GetContentScale(), /*sigma=*/10, DlBlurStyle::kInner)));
121 }
122 
123 TEST_P(AiksTest, GradientOvalStrokeMaskBlurSolid) {
124  ASSERT_TRUE(OpenPlaygroundHere(DoGradientOvalStrokeMaskBlur(
125  GetContentScale(), /*sigma=*/10, DlBlurStyle::kSolid)));
126 }
127 
128 TEST_P(AiksTest, SolidColorCircleMaskBlurTinySigma) {
129  DisplayListBuilder builder;
130  builder.Scale(GetContentScale().x, GetContentScale().y);
131 
132  std::vector<float> sigmas = {0.0, 0.01, 1.0};
133  std::vector<DlColor> colors = {DlColor::kGreen(), DlColor::kYellow(),
134  DlColor::kRed()};
135  for (uint32_t i = 0; i < sigmas.size(); ++i) {
136  DlPaint paint;
137  paint.setColor(colors[i]);
138  paint.setMaskFilter(
139  DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigmas[i]));
140 
141  builder.Save();
142  builder.Translate(100 + (i * 100), 100);
143  DlRoundRect rrect = DlRoundRect::MakeRectXY(
144  DlRect::MakeXYWH(0, 0, 100.0f, 100.0f), 100, 100);
145  builder.DrawRoundRect(rrect, paint);
146  builder.Restore();
147  }
148 
149  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
150 }
151 
152 TEST_P(AiksTest, CanRenderMaskBlurHugeSigma) {
153  DisplayListBuilder builder;
154 
155  DlPaint paint;
156  paint.setColor(DlColor::kGreen());
157  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 99999));
158  builder.DrawCircle(DlPoint(400, 400), 300, paint);
159  builder.Restore();
160 
161  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
162 }
163 
164 TEST_P(AiksTest, CanRenderForegroundBlendWithMaskBlur) {
165  // This case triggers the ForegroundPorterDuffBlend path. The color filter
166  // should apply to the color only, and respect the alpha mask.
167  DisplayListBuilder builder;
168  builder.ClipRect(DlRect::MakeXYWH(100, 150, 400, 400));
169 
170  DlPaint paint;
171  paint.setColor(DlColor::kWhite());
172 
173  Sigma sigma = Radius(20);
174  paint.setMaskFilter(
175  DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma.sigma));
176  paint.setColorFilter(
177  DlColorFilter::MakeBlend(DlColor::kGreen(), DlBlendMode::kSrc));
178  builder.DrawCircle(DlPoint(400, 400), 200, paint);
179 
180  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
181 }
182 
183 TEST_P(AiksTest, CanRenderForegroundAdvancedBlendWithMaskBlur) {
184  // This case triggers the ForegroundAdvancedBlend path. The color filter
185  // should apply to the color only, and respect the alpha mask.
186  DisplayListBuilder builder;
187  builder.ClipRect(DlRect::MakeXYWH(100, 150, 400, 400));
188 
189  DlPaint paint;
190  paint.setColor(
191  DlColor::RGBA(128.0f / 255.0f, 128.0f / 255.0f, 128.0f / 255.0f, 1.0f));
192 
193  Sigma sigma = Radius(20);
194  paint.setMaskFilter(
195  DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma.sigma));
196  paint.setColorFilter(
197  DlColorFilter::MakeBlend(DlColor::kGreen(), DlBlendMode::kColor));
198  builder.DrawCircle(DlPoint(400, 400), 200, paint);
199  builder.Restore();
200 
201  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
202 }
203 
204 TEST_P(AiksTest, CanRenderBackdropBlurInteractive) {
205  auto callback = [&]() -> sk_sp<DisplayList> {
206  static PlaygroundPoint point_a(Point(50, 50), 30, Color::White());
207  static PlaygroundPoint point_b(Point(300, 200), 30, Color::White());
208  auto [a, b] = DrawPlaygroundLine(point_a, point_b);
209 
210  DisplayListBuilder builder;
211  DlPaint paint;
212  paint.setColor(DlColor::kCornflowerBlue());
213  builder.DrawCircle(DlPoint(100, 100), 50, paint);
214 
215  paint.setColor(DlColor::kGreenYellow());
216  builder.DrawCircle(DlPoint(300, 200), 100, paint);
217 
218  paint.setColor(DlColor::kDarkMagenta());
219  builder.DrawCircle(DlPoint(140, 170), 75, paint);
220 
221  paint.setColor(DlColor::kOrangeRed());
222  builder.DrawCircle(DlPoint(180, 120), 100, paint);
223 
224  DlRoundRect rrect =
225  DlRoundRect::MakeRectXY(DlRect::MakeLTRB(a.x, a.y, b.x, b.y), 20, 20);
226  builder.ClipRoundRect(rrect);
227 
228  DlPaint save_paint;
229  save_paint.setBlendMode(DlBlendMode::kSrc);
230 
231  auto backdrop_filter = DlImageFilter::MakeBlur(20, 20, DlTileMode::kClamp);
232  builder.SaveLayer(std::nullopt, &save_paint, backdrop_filter.get());
233  builder.Restore();
234 
235  return builder.Build();
236  };
237 
238  ASSERT_TRUE(OpenPlaygroundHere(callback));
239 }
240 
241 TEST_P(AiksTest, CanRenderBackdropBlur) {
242  DisplayListBuilder builder;
243 
244  DlPaint paint;
245  paint.setColor(DlColor::kCornflowerBlue());
246  builder.DrawCircle(DlPoint(100, 100), 50, paint);
247 
248  paint.setColor(DlColor::kGreenYellow());
249  builder.DrawCircle(DlPoint(300, 200), 100, paint);
250 
251  paint.setColor(DlColor::kDarkMagenta());
252  builder.DrawCircle(DlPoint(140, 170), 75, paint);
253 
254  paint.setColor(DlColor::kOrangeRed());
255  builder.DrawCircle(DlPoint(180, 120), 100, paint);
256 
257  DlRoundRect rrect =
258  DlRoundRect::MakeRectXY(DlRect::MakeLTRB(75, 50, 375, 275), 20, 20);
259  builder.ClipRoundRect(rrect);
260 
261  DlPaint save_paint;
262  save_paint.setBlendMode(DlBlendMode::kSrc);
263  auto backdrop_filter = DlImageFilter::MakeBlur(30, 30, DlTileMode::kClamp);
264  builder.SaveLayer(std::nullopt, &save_paint, backdrop_filter.get());
265  builder.Restore();
266 
267  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
268 }
269 
270 TEST_P(AiksTest, CanRenderBackdropBlurWithSingleBackdropId) {
271  auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg"));
272 
273  DisplayListBuilder builder;
274 
275  DlPaint paint;
276  builder.DrawImage(image, DlPoint(50.0, 50.0),
277  DlImageSampling::kNearestNeighbor, &paint);
278 
279  DlRoundRect rrect =
280  DlRoundRect::MakeRectXY(DlRect::MakeXYWH(50, 250, 100, 100), 20, 20);
281  builder.Save();
282  builder.ClipRoundRect(rrect);
283 
284  DlPaint save_paint;
285  save_paint.setBlendMode(DlBlendMode::kSrc);
286  auto backdrop_filter = DlImageFilter::MakeBlur(30, 30, DlTileMode::kClamp);
287  builder.SaveLayer(std::nullopt, &save_paint, backdrop_filter.get(),
288  /*backdrop_id=*/1);
289  builder.Restore();
290  builder.Restore();
291 
292  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
293 }
294 
295 TEST_P(AiksTest, CanRenderMultipleBackdropBlurWithSingleBackdropId) {
296  auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg"));
297 
298  DisplayListBuilder builder;
299 
300  DlPaint paint;
301  builder.DrawImage(image, DlPoint(50.0, 50.0),
302  DlImageSampling::kNearestNeighbor, &paint);
303 
304  for (int i = 0; i < 6; i++) {
305  DlRoundRect rrect = DlRoundRect::MakeRectXY(
306  DlRect::MakeXYWH(50 + (i * 100), 250, 100, 100), 20, 20);
307  builder.Save();
308  builder.ClipRoundRect(rrect);
309 
310  DlPaint save_paint;
311  save_paint.setBlendMode(DlBlendMode::kSrc);
312  auto backdrop_filter = DlImageFilter::MakeBlur(30, 30, DlTileMode::kClamp);
313  builder.SaveLayer(std::nullopt, &save_paint, backdrop_filter.get(),
314  /*backdrop_id=*/1);
315  builder.Restore();
316  builder.Restore();
317  }
318 
319  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
320 }
321 
323  CanRenderMultipleBackdropBlurWithSingleBackdropIdAndDistinctFilters) {
324  auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg"));
325 
326  DisplayListBuilder builder;
327 
328  DlPaint paint;
329  builder.DrawImage(image, DlPoint(50.0, 50.0),
330  DlImageSampling::kNearestNeighbor, &paint);
331 
332  for (int i = 0; i < 6; i++) {
333  DlRoundRect rrect = DlRoundRect::MakeRectXY(
334  DlRect::MakeXYWH(50 + (i * 100), 250, 100, 100), 20, 20);
335  builder.Save();
336  builder.ClipRoundRect(rrect);
337 
338  DlPaint save_paint;
339  save_paint.setBlendMode(DlBlendMode::kSrc);
340  auto backdrop_filter =
341  DlImageFilter::MakeBlur(30 + i, 30, DlTileMode::kClamp);
342  builder.SaveLayer(std::nullopt, &save_paint, backdrop_filter.get(),
343  /*backdrop_id=*/1);
344  builder.Restore();
345  builder.Restore();
346  }
347 
348  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
349 }
350 
351 TEST_P(AiksTest, CanRenderBackdropBlurHugeSigma) {
352  DisplayListBuilder builder;
353 
354  DlPaint paint;
355  paint.setColor(DlColor::kGreen());
356  builder.DrawCircle(DlPoint(400, 400), 300, paint);
357 
358  DlPaint save_paint;
359  save_paint.setBlendMode(DlBlendMode::kSrc);
360 
361  auto backdrop_filter =
362  DlImageFilter::MakeBlur(999999, 999999, DlTileMode::kClamp);
363  builder.SaveLayer(std::nullopt, &save_paint, backdrop_filter.get());
364  builder.Restore();
365 
366  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
367 }
368 
369 TEST_P(AiksTest, CanRenderClippedBlur) {
370  DisplayListBuilder builder;
371  builder.ClipRect(DlRect::MakeXYWH(100, 150, 400, 400));
372 
373  DlPaint paint;
374  paint.setColor(DlColor::kGreen());
375  paint.setImageFilter(DlImageFilter::MakeBlur(20, 20, DlTileMode::kDecal));
376  builder.DrawCircle(DlPoint(400, 400), 200, paint);
377  builder.Restore();
378 
379  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
380 }
381 
382 TEST_P(AiksTest, ClippedBlurFilterRendersCorrectlyInteractive) {
383  auto callback = [&]() -> sk_sp<DisplayList> {
384  static PlaygroundPoint playground_point(Point(400, 400), 20,
385  Color::Green());
386  auto point = DrawPlaygroundPoint(playground_point);
387 
388  DisplayListBuilder builder;
389  auto location = point - Point(400, 400);
390  builder.Translate(location.x, location.y);
391 
392  DlPaint paint;
393  Sigma sigma = Radius{120 * 3};
394  paint.setMaskFilter(
395  DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma.sigma));
396  paint.setColor(DlColor::kRed());
397 
398  DlPath path = DlPath::MakeRect(DlRect::MakeLTRB(0, 0, 800, 800));
399  path = path + DlPath::MakeCircle(DlPoint(0, 0), 0.5);
400  builder.DrawPath(path, paint);
401  return builder.Build();
402  };
403  ASSERT_TRUE(OpenPlaygroundHere(callback));
404 }
405 
406 TEST_P(AiksTest, ClippedBlurFilterRendersCorrectly) {
407  DisplayListBuilder builder;
408  builder.Translate(0, -400);
409  DlPaint paint;
410 
411  Sigma sigma = Radius{120 * 3};
412  paint.setMaskFilter(
413  DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma.sigma));
414  paint.setColor(DlColor::kRed());
415 
416  DlPath path = DlPath::MakeRect(DlRect::MakeLTRB(0, 0, 800, 800));
417  path = path + DlPath::MakeCircle(DlPoint(0, 0), 0.5);
418  builder.DrawPath(path, paint);
419 
420  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
421 }
422 
423 TEST_P(AiksTest, ClearBlendWithBlur) {
424  DisplayListBuilder builder;
425  DlPaint paint;
426  paint.setColor(DlColor::kBlue());
427  builder.DrawRect(DlRect::MakeXYWH(0, 0, 600.0, 600.0), paint);
428 
429  DlPaint clear;
430  clear.setBlendMode(DlBlendMode::kClear);
431  clear.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 20));
432 
433  builder.DrawCircle(DlPoint(300.0, 300.0), 200.0, clear);
434 
435  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
436 }
437 
438 TEST_P(AiksTest, BlurHasNoEdge) {
439  Scalar sigma = 47.6;
440  auto callback = [&]() -> sk_sp<DisplayList> {
441  if (AiksTest::ImGuiBegin("Controls", nullptr,
442  ImGuiWindowFlags_AlwaysAutoResize)) {
443  ImGui::SliderFloat("Sigma", &sigma, 0, 50);
444  ImGui::End();
445  }
446  DisplayListBuilder builder;
447  builder.Scale(GetContentScale().x, GetContentScale().y);
448  builder.DrawPaint({});
449 
450  DlPaint paint;
451  paint.setColor(DlColor::kGreen());
452  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma));
453 
454  builder.DrawRect(DlRect::MakeXYWH(300, 300, 200, 200), paint);
455  return builder.Build();
456  };
457 
458  ASSERT_TRUE(OpenPlaygroundHere(callback));
459 }
460 
461 TEST_P(AiksTest, MaskBlurWithZeroSigmaIsSkipped) {
462  DisplayListBuilder builder;
463 
464  DlPaint paint;
465  paint.setColor(DlColor::kBlue());
466  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 0));
467 
468  builder.DrawCircle(DlPoint(300, 300), 200, paint);
469  builder.DrawRect(DlRect::MakeLTRB(100, 300, 500, 600), paint);
470 
471  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
472 }
473 
474 TEST_P(AiksTest, MaskBlurOnZeroDimensionIsSkippedWideGamut) {
475  // Making sure this test is run on a wide gamut enabled backend
476  EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(),
478 
479  DisplayListBuilder builder;
480  builder.DrawColor(DlColor::kWhite(), DlBlendMode::kSrc);
481 
482  DlPaint paint;
483  paint.setColor(DlColor::kBlue());
484  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 10));
485 
486  // Zero height above
487  builder.DrawRect(DlRect::MakeLTRB(100, 250, 500, 250), paint);
488  // Regular rect
489  builder.DrawRect(DlRect::MakeLTRB(100, 300, 500, 600), paint);
490  // Zero width to the right
491  builder.DrawRect(DlRect::MakeLTRB(550, 300, 550, 600), paint);
492 
493  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
494 }
495 
497  DlBlurStyle style = DlBlurStyle::kNormal;
498  Scalar sigma = 1.0f;
499  Scalar alpha = 1.0f;
500  std::shared_ptr<DlImageFilter> image_filter;
501  bool invert_colors = false;
502  DlBlendMode blend_mode = DlBlendMode::kSrcOver;
503 };
504 
505 static sk_sp<DisplayList> MaskBlurVariantTest(
506  const AiksTest& test_context,
507  const MaskBlurTestConfig& config) {
508  DisplayListBuilder builder;
509  builder.Scale(test_context.GetContentScale().x,
510  test_context.GetContentScale().y);
511  builder.Scale(0.8f, 0.8f);
512  builder.Translate(50.f, 50.f);
513 
514  DlPaint draw_paint;
515  draw_paint.setColor(
516  DlColor::RGBA(Color::AntiqueWhite().red, Color::AntiqueWhite().green,
517  Color::AntiqueWhite().blue, Color::AntiqueWhite().alpha));
518  builder.DrawPaint(draw_paint);
519 
520  DlPaint paint;
521  paint.setMaskFilter(DlBlurMaskFilter::Make(config.style, config.sigma));
522  paint.setInvertColors(config.invert_colors);
523  paint.setImageFilter(config.image_filter);
524  paint.setBlendMode(config.blend_mode);
525 
526  const Scalar x = 50;
527  const Scalar radius = 20.0f;
528  const Scalar y_spacing = 100.0f;
529  Scalar alpha = config.alpha * 255;
530 
531  Scalar y = 50;
532  paint.setColor(DlColor::kCrimson().withAlpha(alpha));
533  builder.DrawRect(DlRect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
534  radius, 60.0f - radius),
535  paint);
536 
537  y += y_spacing;
538  paint.setColor(DlColor::kBlue().withAlpha(alpha));
539  builder.DrawCircle(DlPoint{x + 25, y + 25}, radius, paint);
540 
541  y += y_spacing;
542  paint.setColor(DlColor::kGreen().withAlpha(alpha));
543  builder.DrawOval(DlRect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
544  radius, 60.0f - radius),
545  paint);
546 
547  y += y_spacing;
548  paint.setColor(DlColor::kPurple().withAlpha(alpha));
549  DlRoundRect rrect = DlRoundRect::MakeRectXY(
550  DlRect::MakeXYWH(x, y, 60.0f, 60.0f), radius, radius);
551  builder.DrawRoundRect(rrect, paint);
552 
553  y += y_spacing;
554  paint.setColor(DlColor::kOrange().withAlpha(alpha));
555 
556  rrect = DlRoundRect::MakeRectXY(DlRect::MakeXYWH(x, y, 60.0f, 60.0f), //
557  radius, 5.0);
558  builder.DrawRoundRect(rrect, paint);
559 
560  y += y_spacing;
561  paint.setColor(DlColor::kMaroon().withAlpha(alpha));
562 
563  {
564  DlPathBuilder path_builder;
565  path_builder.MoveTo(DlPoint(x + 0, y + 60));
566  path_builder.LineTo(DlPoint(x + 30, y + 0));
567  path_builder.LineTo(DlPoint(x + 60, y + 60));
568  path_builder.Close();
569 
570  builder.DrawPath(path_builder.TakePath(), paint);
571  }
572 
573  y += y_spacing;
574  paint.setColor(DlColor::kMaroon().withAlpha(alpha));
575  {
576  DlPath path = DlPath::MakeArc(Rect::MakeXYWH(x + 5, y, 50, 50), //
577  Degrees(90), Degrees(180), false) +
578  DlPath::MakeArc(Rect::MakeXYWH(x + 25, y, 50, 50), //
579  Degrees(90), Degrees(180), false);
580  builder.DrawPath(path, paint);
581  }
582 
583  return builder.Build();
584 }
585 
586 static const std::map<std::string, MaskBlurTestConfig> kPaintVariations = {
587  // 1. Normal style, translucent, zero sigma.
588  {"NormalTranslucentZeroSigma",
589  {.style = DlBlurStyle::kNormal, .sigma = 0.0f, .alpha = 0.5f}},
590  // 2. Normal style, translucent.
591  {"NormalTranslucent",
592  {.style = DlBlurStyle::kNormal, .sigma = 8.0f, .alpha = 0.5f}},
593  // 3. Solid style, translucent.
594  {"SolidTranslucent",
595  {.style = DlBlurStyle::kSolid, .sigma = 8.0f, .alpha = 0.5f}},
596  // 4. Solid style, opaque.
597  {"SolidOpaque", {.style = DlBlurStyle::kSolid, .sigma = 8.0f}},
598  // 5. Solid style, translucent, color & image filtered.
599  {"SolidTranslucentWithFilters",
600  {.style = DlBlurStyle::kSolid,
601  .sigma = 8.0f,
602  .alpha = 0.5f,
603  .image_filter = DlImageFilter::MakeBlur(3, 3, DlTileMode::kClamp),
604  .invert_colors = true}},
605  // 6. Solid style, translucent, exclusion blended.
606  {"SolidTranslucentExclusionBlend",
607  {.style = DlBlurStyle::kSolid,
608  .sigma = 8.0f,
609  .alpha = 0.5f,
610  .blend_mode = DlBlendMode::kExclusion}},
611  // 7. Inner style, translucent.
612  {"InnerTranslucent",
613  {.style = DlBlurStyle::kInner, .sigma = 8.0f, .alpha = 0.5f}},
614  // 8. Inner style, translucent, blurred.
615  {"InnerTranslucentWithBlurImageFilter",
616  {.style = DlBlurStyle::kInner,
617  .sigma = 8.0f,
618  .alpha = 0.5f,
619  .image_filter = DlImageFilter::MakeBlur(3, 3, DlTileMode::kClamp)}},
620  // 9. Outer style, translucent.
621  {"OuterTranslucent",
622  {.style = DlBlurStyle::kOuter, .sigma = 8.0f, .alpha = 0.5f}},
623  // 10. Outer style, opaque, image filtered.
624  {"OuterOpaqueWithBlurImageFilter",
625  {.style = DlBlurStyle::kOuter,
626  .sigma = 8.0f,
627  .image_filter = DlImageFilter::MakeBlur(3, 3, DlTileMode::kClamp)}}};
628 
629 #define MASK_BLUR_VARIANT_TEST(config) \
630  TEST_P(AiksTest, MaskBlurVariantTest##config) { \
631  ASSERT_TRUE(OpenPlaygroundHere( \
632  MaskBlurVariantTest(*this, kPaintVariations.at(#config)))); \
633  }
634 
635 MASK_BLUR_VARIANT_TEST(NormalTranslucentZeroSigma)
636 MASK_BLUR_VARIANT_TEST(NormalTranslucent)
637 MASK_BLUR_VARIANT_TEST(SolidTranslucent)
638 MASK_BLUR_VARIANT_TEST(SolidOpaque)
639 MASK_BLUR_VARIANT_TEST(SolidTranslucentWithFilters)
640 MASK_BLUR_VARIANT_TEST(SolidTranslucentExclusionBlend)
641 MASK_BLUR_VARIANT_TEST(InnerTranslucent)
642 MASK_BLUR_VARIANT_TEST(InnerTranslucentWithBlurImageFilter)
643 MASK_BLUR_VARIANT_TEST(OuterTranslucent)
644 MASK_BLUR_VARIANT_TEST(OuterOpaqueWithBlurImageFilter)
645 
646 #undef MASK_BLUR_VARIANT_TEST
647 
648 TEST_P(AiksTest, GaussianBlurStyleInner) {
649  DisplayListBuilder builder;
650  builder.Scale(GetContentScale().x, GetContentScale().y);
651 
652  DlPaint paint;
653  paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1));
654  builder.DrawPaint(paint);
655 
656  paint.setColor(DlColor::kGreen());
657  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kInner, 30));
658 
659  DlPathBuilder path_builder;
660  path_builder.MoveTo(DlPoint(200, 200));
661  path_builder.LineTo(DlPoint(300, 400));
662  path_builder.LineTo(DlPoint(100, 400));
663  path_builder.Close();
664 
665  builder.DrawPath(path_builder.TakePath(), paint);
666 
667  // Draw another thing to make sure the clip area is reset.
668  DlPaint red;
669  red.setColor(DlColor::kRed());
670  builder.DrawRect(DlRect::MakeXYWH(0, 0, 200, 200), red);
671 
672  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
673 }
674 
675 TEST_P(AiksTest, GaussianBlurStyleOuter) {
676  DisplayListBuilder builder;
677  builder.Scale(GetContentScale().x, GetContentScale().y);
678 
679  DlPaint paint;
680  paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0));
681  builder.DrawPaint(paint);
682 
683  paint.setColor(DlColor::kGreen());
684  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kOuter, 30));
685 
686  DlPathBuilder path_builder;
687  path_builder.MoveTo(DlPoint(200, 200));
688  path_builder.LineTo(DlPoint(300, 400));
689  path_builder.LineTo(DlPoint(100, 400));
690  path_builder.Close();
691 
692  builder.DrawPath(path_builder.TakePath(), paint);
693 
694  // Draw another thing to make sure the clip area is reset.
695  DlPaint red;
696  red.setColor(DlColor::kRed());
697  builder.DrawRect(DlRect::MakeXYWH(0, 0, 200, 200), red);
698 
699  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
700 }
701 
702 TEST_P(AiksTest, GaussianBlurStyleSolid) {
703  DisplayListBuilder builder;
704  builder.Scale(GetContentScale().x, GetContentScale().y);
705 
706  DlPaint paint;
707  paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0));
708  builder.DrawPaint(paint);
709 
710  paint.setColor(DlColor::kGreen());
711  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kSolid, 30));
712 
713  DlPathBuilder path_builder;
714  path_builder.MoveTo(DlPoint(200, 200));
715  path_builder.LineTo(DlPoint(300, 400));
716  path_builder.LineTo(DlPoint(100, 400));
717  path_builder.Close();
718 
719  builder.DrawPath(path_builder.TakePath(), paint);
720 
721  // Draw another thing to make sure the clip area is reset.
722  DlPaint red;
723  red.setColor(DlColor::kRed());
724  builder.DrawRect(DlRect::MakeXYWH(0, 0, 200, 200), red);
725 
726  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
727 }
728 
729 TEST_P(AiksTest, MaskBlurTexture) {
730  Scalar sigma = 30;
731  auto callback = [&]() -> sk_sp<DisplayList> {
732  if (AiksTest::ImGuiBegin("Controls", nullptr,
733  ImGuiWindowFlags_AlwaysAutoResize)) {
734  ImGui::SliderFloat("Sigma", &sigma, 0, 500);
735  ImGui::End();
736  }
737 
738  DisplayListBuilder builder;
739  builder.Scale(GetContentScale().x, GetContentScale().y);
740 
741  DlPaint paint;
742  paint.setColor(DlColor::kGreen());
743  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma));
744 
745  builder.DrawImage(
746  DlImageImpeller::Make(CreateTextureForFixture("boston.jpg")),
747  DlPoint(200, 200), DlImageSampling::kNearestNeighbor, &paint);
748 
749  DlPaint red;
750  red.setColor(DlColor::kRed());
751  builder.DrawRect(DlRect::MakeXYWH(0, 0, 200, 200), red);
752 
753  return builder.Build();
754  };
755  ASSERT_TRUE(OpenPlaygroundHere(callback));
756 }
757 
758 TEST_P(AiksTest, MaskBlurDoesntStretchContents) {
759  Scalar sigma = 70;
760  auto callback = [&]() -> sk_sp<DisplayList> {
761  if (AiksTest::ImGuiBegin("Controls", nullptr,
762  ImGuiWindowFlags_AlwaysAutoResize)) {
763  ImGui::SliderFloat("Sigma", &sigma, 0, 500);
764  ImGui::End();
765  }
766 
767  DisplayListBuilder builder;
768  builder.Scale(GetContentScale().x, GetContentScale().y);
769 
770  DlPaint paint;
771  paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0));
772  builder.DrawPaint(paint);
773 
774  std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
775 
776  builder.Transform(Matrix::MakeTranslation({100, 100}) *
777  Matrix::MakeScale({0.5, 0.5, 1.0f}));
778 
779  paint.setColorSource(DlColorSource::MakeImage(
780  DlImageImpeller::Make(boston), DlTileMode::kRepeat, DlTileMode::kRepeat,
781  DlImageSampling::kMipmapLinear));
782  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma));
783 
784  builder.DrawRect(DlRect::MakeXYWH(0, 0, boston->GetSize().width,
785  boston->GetSize().height),
786  paint);
787 
788  return builder.Build();
789  };
790  ASSERT_TRUE(OpenPlaygroundHere(callback));
791 }
792 
793 TEST_P(AiksTest, GaussianBlurAtPeripheryVertical) {
794  DisplayListBuilder builder;
795 
796  DlPaint paint;
797  builder.Scale(GetContentScale().x, GetContentScale().y);
798 
799  paint.setColor(DlColor::kLimeGreen());
800  DlRoundRect rrect = DlRoundRect::MakeRectXY(
801  DlRect::MakeLTRB(0, 0, GetWindowSize().width, 100), 10, 10);
802  builder.DrawRoundRect(rrect, paint);
803 
804  paint.setColor(DlColor::kMagenta());
805  rrect = DlRoundRect::MakeRectXY(
806  DlRect::MakeLTRB(0, 110, GetWindowSize().width, 210), 10, 10);
807  builder.DrawRoundRect(rrect, paint);
808  builder.ClipRect(DlRect::MakeLTRB(100, 0, 200, GetWindowSize().height));
809 
810  DlPaint save_paint;
811  save_paint.setBlendMode(DlBlendMode::kSrc);
812 
813  auto backdrop_filter = DlImageFilter::MakeBlur(20, 20, DlTileMode::kClamp);
814 
815  builder.SaveLayer(std::nullopt, &save_paint, backdrop_filter.get());
816  builder.Restore();
817 
818  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
819 }
820 
821 TEST_P(AiksTest, GaussianBlurAtPeripheryHorizontal) {
822  DisplayListBuilder builder;
823 
824  builder.Scale(GetContentScale().x, GetContentScale().y);
825  std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
826  builder.DrawImageRect(
827  DlImageImpeller::Make(boston),
828  DlRect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height),
829  DlRect::MakeLTRB(0, 0, GetWindowSize().width, 100),
830  DlImageSampling::kNearestNeighbor);
831 
832  DlPaint paint;
833  paint.setColor(DlColor::kMagenta());
834 
835  DlRoundRect rrect = DlRoundRect::MakeRectXY(
836  DlRect::MakeLTRB(0, 110, GetWindowSize().width, 210), 10, 10);
837  builder.DrawRoundRect(rrect, paint);
838  builder.ClipRect(DlRect::MakeLTRB(0, 50, GetWindowSize().width, 150));
839 
840  DlPaint save_paint;
841  save_paint.setBlendMode(DlBlendMode::kSrc);
842 
843  auto backdrop_filter = DlImageFilter::MakeBlur(20, 20, DlTileMode::kClamp);
844  builder.SaveLayer(std::nullopt, &save_paint, backdrop_filter.get());
845 
846  builder.Restore();
847  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
848 }
849 
850 TEST_P(AiksTest, GaussianBlurAnimatedBackdrop) {
851  // This test is for checking out how stable rendering is when content is
852  // translated underneath a blur. Animating under a blur can cause
853  // *shimmering* to happen as a result of pixel alignment.
854  // See also: https://github.com/flutter/flutter/issues/140193
855  auto boston =
856  CreateTextureForFixture("boston.jpg", /*enable_mipmapping=*/true);
857  ASSERT_TRUE(boston);
858  int64_t count = 0;
859  Scalar sigma = 20.0;
860  Scalar freq = 0.1;
861  Scalar amp = 50.0;
862  auto callback = [&]() -> sk_sp<DisplayList> {
863  if (AiksTest::ImGuiBegin("Controls", nullptr,
864  ImGuiWindowFlags_AlwaysAutoResize)) {
865  ImGui::SliderFloat("Sigma", &sigma, 0, 200);
866  ImGui::SliderFloat("Frequency", &freq, 0.01, 2.0);
867  ImGui::SliderFloat("Amplitude", &amp, 1, 100);
868  ImGui::End();
869  }
870 
871  DisplayListBuilder builder;
872  builder.Scale(GetContentScale().x, GetContentScale().y);
873  Scalar y = amp * sin(freq * 2.0 * M_PI * count / 60);
874  builder.DrawImage(DlImageImpeller::Make(boston),
875  DlPoint(1024 / 2 - boston->GetSize().width / 2,
876  (768 / 2 - boston->GetSize().height / 2) + y),
877  DlImageSampling::kMipmapLinear);
878  static PlaygroundPoint point_a(Point(100, 100), 20, Color::Red());
879  static PlaygroundPoint point_b(Point(900, 700), 20, Color::Red());
880  auto [handle_a, handle_b] = DrawPlaygroundLine(point_a, point_b);
881 
882  builder.ClipRect(
883  DlRect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y));
884  builder.ClipRect(DlRect::MakeLTRB(100, 100, 900, 700));
885 
886  DlPaint paint;
887  paint.setBlendMode(DlBlendMode::kSrc);
888 
889  auto backdrop_filter =
890  DlImageFilter::MakeBlur(sigma, sigma, DlTileMode::kClamp);
891  builder.SaveLayer(std::nullopt, &paint, backdrop_filter.get());
892  count += 1;
893  return builder.Build();
894  };
895  ASSERT_TRUE(OpenPlaygroundHere(callback));
896 }
897 
898 TEST_P(AiksTest, GaussianBlurStyleInnerGradient) {
899  DisplayListBuilder builder;
900 
901  builder.Scale(GetContentScale().x, GetContentScale().y);
902 
903  DlPaint paint;
904  paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0));
905  builder.DrawPaint(paint);
906 
907  std::vector<DlColor> colors = {DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0),
908  DlColor::RGBA(0.7568, 0.2627, 0.2118, 1.0)};
909  std::vector<Scalar> stops = {0.0, 1.0};
910 
911  paint = DlPaint{};
912  paint.setColorSource(DlColorSource::MakeLinear(
913  /*start_point=*/{0, 0},
914  /*end_point=*/{200, 200},
915  /*stop_count=*/colors.size(),
916  /*colors=*/colors.data(),
917  /*stops=*/stops.data(),
918  /*tile_mode=*/DlTileMode::kMirror));
919  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kInner, 30));
920 
921  DlPathBuilder path_builder;
922  path_builder.MoveTo(DlPoint(200, 200));
923  path_builder.LineTo(DlPoint(300, 400));
924  path_builder.LineTo(DlPoint(100, 400));
925  path_builder.Close();
926  builder.DrawPath(path_builder.TakePath(), paint);
927 
928  // Draw another thing to make sure the clip area is reset.
929  DlPaint red;
930  red.setColor(DlColor::kRed());
931  builder.DrawRect(DlRect::MakeXYWH(0, 0, 200, 200), red);
932 
933  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
934 }
935 
936 TEST_P(AiksTest, GaussianBlurStyleSolidGradient) {
937  DisplayListBuilder builder;
938  builder.Scale(GetContentScale().x, GetContentScale().y);
939 
940  DlPaint paint;
941  paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0));
942  builder.DrawPaint(paint);
943 
944  std::vector<DlColor> colors = {DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0),
945  DlColor::RGBA(0.7568, 0.2627, 0.2118, 1.0)};
946  std::vector<Scalar> stops = {0.0, 1.0};
947 
948  paint = DlPaint{};
949  paint.setColorSource(DlColorSource::MakeLinear(
950  /*start_point=*/{0, 0},
951  /*end_point=*/{200, 200},
952  /*stop_count=*/colors.size(),
953  /*colors=*/colors.data(),
954  /*stops=*/stops.data(),
955  /*tile_mode=*/DlTileMode::kMirror));
956  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kSolid, 30));
957 
958  DlPathBuilder path_builder;
959  path_builder.MoveTo(DlPoint(200, 200));
960  path_builder.LineTo(DlPoint(300, 400));
961  path_builder.LineTo(DlPoint(100, 400));
962  path_builder.Close();
963  builder.DrawPath(path_builder.TakePath(), paint);
964 
965  // Draw another thing to make sure the clip area is reset.
966  DlPaint red;
967  red.setColor(DlColor::kRed());
968  builder.DrawRect(DlRect::MakeXYWH(0, 0, 200, 200), red);
969  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
970 }
971 
972 TEST_P(AiksTest, GaussianBlurStyleOuterGradient) {
973  DisplayListBuilder builder;
974  builder.Scale(GetContentScale().x, GetContentScale().y);
975 
976  DlPaint paint;
977  paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0));
978  builder.DrawPaint(paint);
979 
980  std::vector<DlColor> colors = {DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0),
981  DlColor::RGBA(0.7568, 0.2627, 0.2118, 1.0)};
982  std::vector<Scalar> stops = {0.0, 1.0};
983 
984  paint = DlPaint{};
985  paint.setColorSource(DlColorSource::MakeLinear(
986  /*start_point=*/{0, 0},
987  /*end_point=*/{200, 200},
988  /*stop_count=*/colors.size(),
989  /*colors=*/colors.data(),
990  /*stops=*/stops.data(),
991  /*tile_mode=*/DlTileMode::kMirror));
992  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kOuter, 30));
993 
994  DlPathBuilder path_builder;
995  path_builder.MoveTo(DlPoint(200, 200));
996  path_builder.LineTo(DlPoint(300, 400));
997  path_builder.LineTo(DlPoint(100, 400));
998  path_builder.Close();
999  builder.DrawPath(path_builder.TakePath(), paint);
1000 
1001  // Draw another thing to make sure the clip area is reset.
1002  DlPaint red;
1003  red.setColor(DlColor::kRed());
1004  builder.DrawRect(DlRect::MakeXYWH(0, 0, 200, 200), red);
1005  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1006 }
1007 
1008 TEST_P(AiksTest, GaussianBlurScaledAndClipped) {
1009  DisplayListBuilder builder;
1010  std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
1011  Rect bounds =
1012  Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
1013  Vector2 image_center = Vector2(bounds.GetSize() / 2);
1014 
1015  DlPaint paint;
1016  paint.setImageFilter(DlImageFilter::MakeBlur(20, 20, DlTileMode::kDecal));
1017 
1018  Vector2 clip_size = {150, 75};
1019  Vector2 center = Vector2(1024, 768) / 2;
1020  builder.Scale(GetContentScale().x, GetContentScale().y);
1021 
1022  auto rect =
1023  Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size);
1024  builder.ClipRect(DlRect::MakeLTRB(rect.GetLeft(), rect.GetTop(),
1025  rect.GetRight(), rect.GetBottom()));
1026  builder.Translate(center.x, center.y);
1027  builder.Scale(0.6, 0.6);
1028 
1029  DlRect sk_bounds = DlRect::MakeLTRB(bounds.GetLeft(), bounds.GetTop(),
1030  bounds.GetRight(), bounds.GetBottom());
1031  Rect dest = bounds.Shift(-image_center);
1032  DlRect sk_dst = DlRect::MakeLTRB(dest.GetLeft(), dest.GetTop(),
1033  dest.GetRight(), dest.GetBottom());
1034  builder.DrawImageRect(DlImageImpeller::Make(boston), /*src=*/sk_bounds,
1035  /*dst=*/sk_dst, DlImageSampling::kNearestNeighbor,
1036  &paint);
1037 
1038  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1039 }
1040 
1041 TEST_P(AiksTest, GaussianBlurRotatedAndClippedInteractive) {
1042  std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
1043 
1044  auto callback = [&]() -> sk_sp<DisplayList> {
1045  const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
1046  const DlTileMode tile_modes[] = {DlTileMode::kClamp, DlTileMode::kRepeat,
1047  DlTileMode::kMirror, DlTileMode::kDecal};
1048 
1049  static float rotation = 0;
1050  static float scale = 0.6;
1051  static int selected_tile_mode = 3;
1052 
1053  if (AiksTest::ImGuiBegin("Controls", nullptr,
1054  ImGuiWindowFlags_AlwaysAutoResize)) {
1055  ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180);
1056  ImGui::SliderFloat("Scale", &scale, 0, 2.0);
1057  ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
1058  sizeof(tile_mode_names) / sizeof(char*));
1059  ImGui::End();
1060  }
1061 
1062  DisplayListBuilder builder;
1063  Rect bounds =
1064  Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
1065  Vector2 image_center = Vector2(bounds.GetSize() / 2);
1066  DlPaint paint;
1067  paint.setImageFilter(
1068  DlImageFilter::MakeBlur(20, 20, tile_modes[selected_tile_mode]));
1069 
1070  static PlaygroundPoint point_a(Point(362, 309), 20, Color::Red());
1071  static PlaygroundPoint point_b(Point(662, 459), 20, Color::Red());
1072  auto [handle_a, handle_b] = DrawPlaygroundLine(point_a, point_b);
1073  Vector2 center = Vector2(1024, 768) / 2;
1074 
1075  builder.Scale(GetContentScale().x, GetContentScale().y);
1076  builder.ClipRect(
1077  DlRect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y));
1078  builder.Translate(center.x, center.y);
1079  builder.Scale(scale, scale);
1080  builder.Rotate(rotation);
1081 
1082  DlRect sk_bounds = DlRect::MakeLTRB(bounds.GetLeft(), bounds.GetTop(),
1083  bounds.GetRight(), bounds.GetBottom());
1084  Rect dest = bounds.Shift(-image_center);
1085  DlRect sk_dst = DlRect::MakeLTRB(dest.GetLeft(), dest.GetTop(),
1086  dest.GetRight(), dest.GetBottom());
1087  builder.DrawImageRect(DlImageImpeller::Make(boston), /*src=*/sk_bounds,
1088  /*dst=*/sk_dst, DlImageSampling::kNearestNeighbor,
1089  &paint);
1090  return builder.Build();
1091  };
1092 
1093  ASSERT_TRUE(OpenPlaygroundHere(callback));
1094 }
1095 
1096 TEST_P(AiksTest, GaussianBlurOneDimension) {
1097  DisplayListBuilder builder;
1098 
1099  builder.Scale(GetContentScale().x, GetContentScale().y);
1100  builder.Scale(0.5, 0.5);
1101 
1102  std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
1103  builder.DrawImage(DlImageImpeller::Make(boston), DlPoint(100, 100), {});
1104 
1105  DlPaint paint;
1106  paint.setBlendMode(DlBlendMode::kSrc);
1107 
1108  auto backdrop_filter = DlImageFilter::MakeBlur(50, 0, DlTileMode::kClamp);
1109  builder.SaveLayer(std::nullopt, &paint, backdrop_filter.get());
1110  builder.Restore();
1111  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1112 }
1113 
1114 // Smoketest to catch issues with the coverage hint.
1115 // Draws a rotated blurred image within a rectangle clip. The center of the clip
1116 // rectangle is the center of the rotated image. The entire area of the clip
1117 // rectangle should be filled with opaque colors output by the blur.
1118 TEST_P(AiksTest, GaussianBlurRotatedAndClipped) {
1119  DisplayListBuilder builder;
1120 
1121  std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
1122  Rect bounds =
1123  Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
1124 
1125  DlPaint paint;
1126  paint.setImageFilter(DlImageFilter::MakeBlur(20, 20, DlTileMode::kDecal));
1127 
1128  Vector2 image_center = Vector2(bounds.GetSize() / 2);
1129  Vector2 clip_size = {150, 75};
1130  Vector2 center = Vector2(1024, 768) / 2;
1131  builder.Scale(GetContentScale().x, GetContentScale().y);
1132 
1133  auto clip_bounds =
1134  Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size);
1135  builder.ClipRect(DlRect::MakeLTRB(clip_bounds.GetLeft(), clip_bounds.GetTop(),
1136  clip_bounds.GetRight(),
1137  clip_bounds.GetBottom()));
1138  builder.Translate(center.x, center.y);
1139  builder.Scale(0.6, 0.6);
1140  builder.Rotate(25);
1141 
1142  auto dst_rect = bounds.Shift(-image_center);
1143  builder.DrawImageRect(
1144  DlImageImpeller::Make(boston), /*src=*/
1145  DlRect::MakeLTRB(bounds.GetLeft(), bounds.GetTop(), bounds.GetRight(),
1146  bounds.GetBottom()),
1147  /*dst=*/
1148  DlRect::MakeLTRB(dst_rect.GetLeft(), dst_rect.GetTop(),
1149  dst_rect.GetRight(), dst_rect.GetBottom()),
1150  DlImageSampling::kMipmapLinear, &paint);
1151 
1152  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1153 }
1154 
1155 TEST_P(AiksTest, GaussianBlurRotatedNonUniform) {
1156  auto callback = [&]() -> sk_sp<DisplayList> {
1157  const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
1158  const DlTileMode tile_modes[] = {DlTileMode::kClamp, DlTileMode::kRepeat,
1159  DlTileMode::kMirror, DlTileMode::kDecal};
1160 
1161  static float rotation = 45;
1162  static float scale = 0.6;
1163  static int selected_tile_mode = 3;
1164 
1165  if (AiksTest::ImGuiBegin("Controls", nullptr,
1166  ImGuiWindowFlags_AlwaysAutoResize)) {
1167  ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180);
1168  ImGui::SliderFloat("Scale", &scale, 0, 2.0);
1169  ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
1170  sizeof(tile_mode_names) / sizeof(char*));
1171  ImGui::End();
1172  }
1173 
1174  DisplayListBuilder builder;
1175 
1176  DlPaint paint;
1177  paint.setColor(DlColor::kGreen());
1178  paint.setImageFilter(
1179  DlImageFilter::MakeBlur(50, 0, tile_modes[selected_tile_mode]));
1180 
1181  Vector2 center = Vector2(1024, 768) / 2;
1182  builder.Scale(GetContentScale().x, GetContentScale().y);
1183  builder.Translate(center.x, center.y);
1184  builder.Scale(scale, scale);
1185  builder.Rotate(rotation);
1186 
1187  DlRoundRect rrect =
1188  DlRoundRect::MakeRectXY(DlRect::MakeXYWH(-100, -100, 200, 200), 10, 10);
1189  builder.DrawRoundRect(rrect, paint);
1190  return builder.Build();
1191  };
1192 
1193  ASSERT_TRUE(OpenPlaygroundHere(callback));
1194 }
1195 
1196 TEST_P(AiksTest, BlurredRectangleWithShader) {
1197  DisplayListBuilder builder;
1198  builder.Scale(GetContentScale().x, GetContentScale().y);
1199 
1200  auto paint_lines = [&builder](Scalar dx, Scalar dy, DlPaint paint) {
1201  auto draw_line = [&builder, &paint](DlPoint a, DlPoint b) {
1202  DlPath line = DlPath::MakeLine(a, b);
1203  builder.DrawPath(line, paint);
1204  };
1205  paint.setStrokeWidth(5);
1206  paint.setDrawStyle(DlDrawStyle::kStroke);
1207  draw_line(DlPoint(dx + 100, dy + 100), DlPoint(dx + 200, dy + 200));
1208  draw_line(DlPoint(dx + 100, dy + 200), DlPoint(dx + 200, dy + 100));
1209  draw_line(DlPoint(dx + 150, dy + 100), DlPoint(dx + 200, dy + 150));
1210  draw_line(DlPoint(dx + 100, dy + 150), DlPoint(dx + 150, dy + 200));
1211  };
1212 
1213  AiksContext renderer(GetContext(), nullptr);
1214  DisplayListBuilder recorder_builder;
1215  for (int x = 0; x < 5; ++x) {
1216  for (int y = 0; y < 5; ++y) {
1217  DlRect rect = DlRect::MakeXYWH(x * 20, y * 20, 20, 20);
1218  DlPaint paint;
1219  paint.setColor(((x + y) & 1) == 0 ? DlColor::kYellow()
1220  : DlColor::kBlue());
1221 
1222  recorder_builder.DrawRect(rect, paint);
1223  }
1224  }
1225  auto texture =
1226  DisplayListToTexture(recorder_builder.Build(), {100, 100}, renderer);
1227 
1228  auto image_source = DlColorSource::MakeImage(
1229  DlImageImpeller::Make(texture), DlTileMode::kRepeat, DlTileMode::kRepeat);
1230  auto blur_filter = DlImageFilter::MakeBlur(5, 5, DlTileMode::kDecal);
1231 
1232  DlPaint paint;
1233  paint.setColor(DlColor::kDarkGreen());
1234  builder.DrawRect(DlRect::MakeLTRB(0, 0, 300, 600), paint);
1235 
1236  paint.setColorSource(image_source);
1237  builder.DrawRect(DlRect::MakeLTRB(100, 100, 200, 200), paint);
1238 
1239  paint.setColorSource(nullptr);
1240  paint.setColor(DlColor::kRed());
1241  builder.DrawRect(DlRect::MakeLTRB(300, 0, 600, 600), paint);
1242 
1243  paint.setColorSource(image_source);
1244  paint.setImageFilter(blur_filter);
1245  builder.DrawRect(DlRect::MakeLTRB(400, 100, 500, 200), paint);
1246 
1247  paint.setImageFilter(nullptr);
1248  paint_lines(0, 300, paint);
1249 
1250  paint.setImageFilter(blur_filter);
1251  paint_lines(300, 300, paint);
1252 
1253  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1254 }
1255 
1256 // This addresses a bug where tiny blurs could result in mip maps that beyond
1257 // the limits for the textures used for blurring.
1258 // See also: b/323402168
1259 TEST_P(AiksTest, GaussianBlurSolidColorTinyMipMap) {
1260  AiksContext renderer(GetContext(), nullptr);
1261 
1262  for (int32_t i = 1; i < 5; ++i) {
1263  DisplayListBuilder builder;
1264  Scalar fi = i;
1265  DlPathBuilder path_builder;
1266  path_builder.MoveTo(DlPoint(100, 100));
1267  path_builder.LineTo(DlPoint(100 + fi, 100 + fi));
1268 
1269  DlPaint paint;
1270  paint.setColor(DlColor::kChartreuse());
1271  auto blur_filter = DlImageFilter::MakeBlur(0.1, 0.1, DlTileMode::kClamp);
1272  paint.setImageFilter(blur_filter);
1273 
1274  builder.DrawPath(path_builder.TakePath(), paint);
1275 
1276  auto image = DisplayListToTexture(builder.Build(), {1024, 768}, renderer);
1277  EXPECT_TRUE(image) << " length " << i;
1278  }
1279 }
1280 
1281 // This addresses a bug where tiny blurs could result in mip maps that beyond
1282 // the limits for the textures used for blurring.
1283 // See also: b/323402168
1284 TEST_P(AiksTest, GaussianBlurBackdropTinyMipMap) {
1285  AiksContext renderer(GetContext(), nullptr);
1286  for (int32_t i = 1; i < 5; ++i) {
1287  DisplayListBuilder builder;
1288 
1289  ISize clip_size = ISize(i, i);
1290  builder.Save();
1291  builder.ClipRect(
1292  DlRect::MakeXYWH(400, 400, clip_size.width, clip_size.height));
1293 
1294  DlPaint paint;
1295  paint.setColor(DlColor::kGreen());
1296  auto blur_filter = DlImageFilter::MakeBlur(0.1, 0.1, DlTileMode::kDecal);
1297  paint.setImageFilter(blur_filter);
1298 
1299  builder.DrawCircle(DlPoint(400, 400), 200, paint);
1300  builder.Restore();
1301 
1302  auto image = DisplayListToTexture(builder.Build(), {1024, 768}, renderer);
1303  EXPECT_TRUE(image) << " length " << i;
1304  }
1305 }
1306 
1308  CanRenderMultipleBackdropBlurWithSingleBackdropIdDifferentLayers) {
1309  auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg"));
1310 
1311  DisplayListBuilder builder;
1312 
1313  DlPaint paint;
1314  builder.DrawImage(image, DlPoint(50.0, 50.0),
1315  DlImageSampling::kNearestNeighbor, &paint);
1316 
1317  for (int i = 0; i < 6; i++) {
1318  if (i != 0) {
1319  DlPaint paint;
1320  paint.setColor(DlColor::kWhite().withAlphaF(0.95));
1321  builder.SaveLayer(std::nullopt, &paint);
1322  }
1323  DlRoundRect rrect = DlRoundRect::MakeRectXY(
1324  DlRect::MakeXYWH(50 + (i * 100), 250, 100, 100), 20, 20);
1325  builder.Save();
1326  builder.ClipRoundRect(rrect);
1327 
1328  DlPaint save_paint;
1329  save_paint.setBlendMode(DlBlendMode::kSrc);
1330  auto backdrop_filter = DlImageFilter::MakeBlur(30, 30, DlTileMode::kClamp);
1331  builder.SaveLayer(std::nullopt, &save_paint, backdrop_filter.get(),
1332  /*backdrop_id=*/1);
1333  builder.Restore();
1334  builder.Restore();
1335  if (i != 0) {
1336  builder.Restore();
1337  }
1338  }
1339 
1340  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1341 }
1342 
1343 TEST_P(AiksTest, BlurGradientWithOpacity) {
1344  DisplayListBuilder builder;
1345  builder.Scale(GetContentScale().x, GetContentScale().y);
1346 
1347  std::vector<DlColor> colors = {DlColor(0xFFFF0000), DlColor(0xFF00FF00)};
1348  std::vector<Scalar> stops = {0.0, 1.0};
1349 
1350  auto gradient = DlColorSource::MakeLinear(
1351  {0, 0}, {400, 400}, 2, colors.data(), stops.data(), DlTileMode::kClamp);
1352 
1353  DlPaint save_paint;
1354  save_paint.setOpacity(0.5);
1355  builder.SaveLayer(std::nullopt, &save_paint);
1356 
1357  DlPaint paint;
1358  paint.setColorSource(gradient);
1359  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 1));
1360  builder.DrawRect(DlRect::MakeXYWH(100, 100, 200, 200), paint);
1361 
1362  builder.Restore();
1363 
1364  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1365 }
1366 
1367 // The artifacts this test attempts to reproduce don't reproduce on playgrounds.
1368 // The test is animated as an attempt to catch any ghosting that may happen
1369 // when DontCare is used instead of Clear.
1370 // https://github.com/flutter/flutter/issues/171772
1371 TEST_P(AiksTest, CanRenderNestedBackdropBlur) {
1372  int64_t count = 0;
1373  auto callback = [&]() -> sk_sp<DisplayList> {
1374  DisplayListBuilder builder;
1375 
1376  Scalar freq = 1.0;
1377  Scalar amp = 50.0;
1378  Scalar offset = amp * sin(freq * 2.0 * M_PI * count / 60.0);
1379 
1380  // Draw some background content to be blurred.
1381  DlPaint paint;
1382  paint.setColor(DlColor::kCornflowerBlue());
1383  builder.DrawCircle(DlPoint(100 + offset, 100), 50, paint);
1384  paint.setColor(DlColor::kGreenYellow());
1385  builder.DrawCircle(DlPoint(300, 200 + offset), 100, paint);
1386  paint.setColor(DlColor::kDarkMagenta());
1387  builder.DrawCircle(DlPoint(140, 170), 75, paint);
1388  paint.setColor(DlColor::kOrangeRed());
1389  builder.DrawCircle(DlPoint(180 + offset, 120 + offset), 100, paint);
1390 
1391  // This is the first backdrop blur, simulating the navigation transition.
1392  auto backdrop_filter1 = DlImageFilter::MakeBlur(15, 15, DlTileMode::kClamp);
1393  builder.SaveLayer(std::nullopt, nullptr, backdrop_filter1.get());
1394 
1395  // Draw the semi-transparent container from the second screen.
1396  DlPaint transparent_paint;
1397  transparent_paint.setColor(DlColor::kWhite().withAlpha(0.1 * 255));
1398  builder.DrawPaint(transparent_paint);
1399 
1400  {
1401  // This is the second, nested backdrop blur.
1402  auto backdrop_filter2 =
1403  DlImageFilter::MakeBlur(10, 10, DlTileMode::kClamp);
1404  builder.Save();
1405  builder.ClipRect(DlRect::MakeXYWH(150, 150, 300, 300));
1406  builder.SaveLayer(std::nullopt, nullptr, backdrop_filter2.get());
1407  builder.Restore(); // Restore from SaveLayer
1408  builder.Restore(); // Restore from ClipRect
1409  }
1410 
1411  builder.Restore(); // Restore from the first SaveLayer
1412 
1413  count++;
1414  return builder.Build();
1415  };
1416  ASSERT_TRUE(OpenPlaygroundHere(callback));
1417 }
1418 
1419 } // namespace testing
1420 } // namespace impeller
#define MASK_BLUR_VARIANT_TEST(config)
static bool ImGuiBegin(const char *name, bool *p_open, ImGuiWindowFlags flags)
static sk_sp< DlImageImpeller > Make(std::shared_ptr< Texture > texture, OwningContext owning_context=OwningContext::kIO)
Point GetContentScale() const
Definition: playground.cc:189
int32_t x
sk_sp< flutter::DisplayList > DoGradientOvalStrokeMaskBlur(Vector2 content_Scale, Scalar sigma, DlBlurStyle style)
static sk_sp< DisplayList > MaskBlurVariantTest(const AiksTest &test_context, const MaskBlurTestConfig &config)
TEST_P(AiksTest, DrawAtlasNoColor)
static const std::map< std::string, MaskBlurTestConfig > kPaintVariations
std::shared_ptr< Texture > DisplayListToTexture(const sk_sp< flutter::DisplayList > &display_list, ISize size, AiksContext &context, bool reset_host_buffer, bool generate_mips)
Render the provided display list to a texture with the given size.
Point Vector2
Definition: point.h:331
flutter::DlRect DlRect
Definition: dl_dispatcher.h:25
float Scalar
Definition: scalar.h:19
Point DrawPlaygroundPoint(PlaygroundPoint &point)
Definition: widgets.cc:11
std::tuple< Point, Point > DrawPlaygroundLine(PlaygroundPoint &point_a, PlaygroundPoint &point_b)
Definition: widgets.cc:51
flutter::DlRoundRect DlRoundRect
Definition: dl_dispatcher.h:27
TPoint< Scalar > Point
Definition: point.h:327
flutter::DlPoint DlPoint
Definition: dl_dispatcher.h:24
flutter::DlPath DlPath
Definition: dl_dispatcher.h:29
ISize64 ISize
Definition: size.h:162
static constexpr Color White()
Definition: color.h:264
static constexpr Color Red()
Definition: color.h:272
static constexpr Color AntiqueWhite()
Definition: color.h:286
static constexpr Color Green()
Definition: color.h:274
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
For convolution filters, the "radius" is the size of the convolution kernel to use on the local space...
Definition: sigma.h:48
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition: sigma.h:32
Scalar sigma
Definition: sigma.h:33
constexpr TPoint Rotate(const Radians &angle) const
Definition: point.h:226
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:618
constexpr auto GetBottom() const
Definition: rect.h:357
constexpr auto GetTop() const
Definition: rect.h:353
constexpr auto GetLeft() const
Definition: rect.h:351
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:327
constexpr auto GetRight() const
Definition: rect.h:355
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
constexpr TRect< T > Shift(T dx, T dy) const
Returns a new rectangle translated by the given offset.
Definition: rect.h:602
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
Type height
Definition: size.h:29
Type width
Definition: size.h:28
std::shared_ptr< DlImageFilter > image_filter