Flutter Impeller
aiks_dl_blend_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 <memory>
6 
7 #include "display_list/display_list.h"
8 #include "display_list/dl_sampling_options.h"
9 #include "display_list/dl_tile_mode.h"
10 #include "display_list/effects/dl_color_filter.h"
11 #include "display_list/effects/dl_color_source.h"
12 #include "display_list/effects/dl_mask_filter.h"
14 
15 #include "flutter/display_list/dl_blend_mode.h"
16 #include "flutter/display_list/dl_builder.h"
17 #include "flutter/display_list/dl_color.h"
18 #include "flutter/display_list/dl_paint.h"
25 #include "impeller/renderer/testing/mocks.h"
26 #include "include/core/SkMatrix.h"
27 
28 ////////////////////////////////////////////////////////////////////////////////
29 // This is for tests of Canvas that are interested the results of rendering
30 // blends.
31 ////////////////////////////////////////////////////////////////////////////////
32 
33 namespace impeller {
34 namespace testing {
35 
36 using namespace flutter;
37 
38 #define BLEND_MODE_TUPLE(blend_mode) {#blend_mode, BlendMode::k##blend_mode},
39 
41  std::vector<const char*> blend_mode_names;
42  std::vector<BlendMode> blend_mode_values;
43 };
44 
46  std::vector<const char*> blend_mode_names;
47  std::vector<BlendMode> blend_mode_values;
48  {
49  const std::vector<std::tuple<const char*, BlendMode>> blends = {
51  assert(blends.size() ==
52  static_cast<size_t>(Entity::kLastAdvancedBlendMode) + 1);
53  for (const auto& [name, mode] : blends) {
54  blend_mode_names.push_back(name);
55  blend_mode_values.push_back(mode);
56  }
57  }
58 
59  return {blend_mode_names, blend_mode_values};
60 }
61 
62 TEST_P(AiksTest, CanRenderAdvancedBlendColorFilterWithSaveLayer) {
63  DisplayListBuilder builder;
64 
65  SkRect layer_rect = SkRect::MakeXYWH(0, 0, 500, 500);
66  builder.ClipRect(layer_rect);
67 
68  DlPaint save_paint;
69  save_paint.setColorFilter(DlColorFilter::MakeBlend(
70  DlColor::RGBA(0, 1, 0, 0.5), DlBlendMode::kDifference));
71  builder.SaveLayer(&layer_rect, &save_paint);
72 
73  DlPaint paint;
74  paint.setColor(DlColor::kBlack());
75  builder.DrawPaint(paint);
76  paint.setColor(DlColor::kWhite());
77  builder.DrawRect(SkRect::MakeXYWH(100, 100, 300, 300), paint);
78  builder.Restore();
79 
80  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
81 }
82 
83 TEST_P(AiksTest, BlendModeShouldCoverWholeScreen) {
84  DisplayListBuilder builder;
85  DlPaint paint;
86 
87  paint.setColor(DlColor::kRed());
88  builder.DrawPaint(paint);
89 
90  paint.setBlendMode(DlBlendMode::kSrcOver);
91  builder.SaveLayer(nullptr, &paint);
92 
93  paint.setColor(DlColor::kWhite());
94  builder.DrawRect(SkRect::MakeXYWH(100, 100, 400, 400), paint);
95 
96  paint.setBlendMode(DlBlendMode::kSrc);
97  builder.SaveLayer(nullptr, &paint);
98 
99  paint.setColor(DlColor::kBlue());
100  builder.DrawRect(SkRect::MakeXYWH(200, 200, 200, 200), paint);
101 
102  builder.Restore();
103  builder.Restore();
104 
105  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
106 }
107 
108 TEST_P(AiksTest, CanDrawPaintWithAdvancedBlend) {
109  DisplayListBuilder builder;
110 
111  builder.Scale(0.2, 0.2);
112  DlPaint paint;
113  paint.setColor(DlColor::RGBA(
116  builder.DrawPaint(paint);
117 
118  paint.setColor(DlColor::RGBA(Color::OrangeRed().red, Color::OrangeRed().green,
119  Color::OrangeRed().blue, 0.5));
120  paint.setBlendMode(DlBlendMode::kHue);
121  builder.DrawPaint(paint);
122 
123  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
124 }
125 
126 TEST_P(AiksTest, DrawPaintWithAdvancedBlendOverFilter) {
127  DlPaint paint;
128  paint.setColor(DlColor::kBlack());
129  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 60));
130 
131  DisplayListBuilder builder;
132  paint.setColor(DlColor::kWhite());
133  builder.DrawPaint(paint);
134  paint.setColor(DlColor::kBlack());
135  builder.DrawCircle(SkPoint{300, 300}, 200, paint);
136  paint.setColor(DlColor::kGreen());
137  paint.setBlendMode(DlBlendMode::kScreen);
138  builder.DrawPaint(paint);
139 
140  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
141 }
142 
143 TEST_P(AiksTest, DrawAdvancedBlendPartlyOffscreen) {
144  DisplayListBuilder builder;
145 
146  DlPaint draw_paint;
147  draw_paint.setColor(DlColor::kBlue());
148  builder.DrawPaint(draw_paint);
149  builder.Scale(2, 2);
150  builder.ClipRect(SkRect::MakeLTRB(0, 0, 200, 200));
151 
152  std::vector<DlColor> colors = {DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0),
153  DlColor::RGBA(0.1294, 0.5882, 0.9529, 1.0)};
154  std::vector<Scalar> stops = {0.0, 1.0};
155 
156  DlPaint paint;
157  DlMatrix matrix = DlMatrix::MakeScale({0.3, 0.3, 1.0});
158  paint.setColorSource(DlColorSource::MakeLinear(
159  /*start_point=*/{0, 0}, //
160  /*end_point=*/{100, 100}, //
161  /*stop_count=*/colors.size(), //
162  /*colors=*/colors.data(), //
163  /*stops=*/stops.data(), //
164  /*tile_mode=*/DlTileMode::kRepeat, //
165  /*matrix=*/&matrix //
166  ));
167  paint.setBlendMode(DlBlendMode::kLighten);
168 
169  builder.DrawCircle(SkPoint{100, 100}, 100, paint);
170  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
171 }
172 
173 TEST_P(AiksTest, PaintBlendModeIsRespected) {
174  DlPaint paint;
175  DisplayListBuilder builder;
176  // Default is kSourceOver.
177 
178  paint.setColor(DlColor::RGBA(1, 0, 0, 0.5));
179  builder.DrawCircle(SkPoint{150, 200}, 100, paint);
180 
181  paint.setColor(DlColor::RGBA(0, 1, 0, 0.5));
182  builder.DrawCircle(SkPoint{250, 200}, 100, paint);
183 
184  paint.setBlendMode(DlBlendMode::kPlus);
185 
186  paint.setColor(DlColor::kRed());
187  builder.DrawCircle(SkPoint{450, 250}, 100, paint);
188 
189  paint.setColor(DlColor::kGreen());
190  builder.DrawCircle(SkPoint{550, 250}, 100, paint);
191 
192  paint.setColor(DlColor::kBlue());
193  builder.DrawCircle(SkPoint{500, 150}, 100, paint);
194 
195  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
196 }
197 
198 // Compare results with https://api.flutter.dev/flutter/dart-ui/BlendMode.html
199 TEST_P(AiksTest, ColorFilterBlend) {
200  bool has_color_filter = true;
201  auto callback = [&]() -> sk_sp<DisplayList> {
202  if (AiksTest::ImGuiBegin("Controls", nullptr,
203  ImGuiWindowFlags_AlwaysAutoResize)) {
204  ImGui::Checkbox("has color filter", &has_color_filter);
205  ImGui::End();
206  }
207 
208  DisplayListBuilder builder;
209  builder.Scale(GetContentScale().x, GetContentScale().y);
210 
211  auto src_image =
212  DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png"));
213  auto dst_image =
214  DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png"));
215 
216  std::vector<DlBlendMode> blend_modes = {
217  DlBlendMode::kSrc, DlBlendMode::kSrcATop, DlBlendMode::kSrcOver,
218  DlBlendMode::kSrcIn, DlBlendMode::kSrcOut, DlBlendMode::kDst,
219  DlBlendMode::kDstATop, DlBlendMode::kDstOver, DlBlendMode::kDstIn,
220  DlBlendMode::kDstOut, DlBlendMode::kClear, DlBlendMode::kXor};
221 
222  for (uint32_t i = 0; i < blend_modes.size(); ++i) {
223  builder.Save();
224  builder.Translate((i % 5) * 200, (i / 5) * 200);
225  builder.Scale(0.4, 0.4);
226  {
227  DlPaint dstPaint;
228  builder.DrawImage(dst_image, SkPoint{0, 0},
229  DlImageSampling::kMipmapLinear, &dstPaint);
230  }
231  {
232  DlPaint srcPaint;
233  srcPaint.setBlendMode(blend_modes[i]);
234  if (has_color_filter) {
235  std::shared_ptr<const DlColorFilter> color_filter =
236  DlColorFilter::MakeBlend(DlColor::RGBA(0.9, 0.5, 0.0, 1.0),
237  DlBlendMode::kSrcIn);
238  srcPaint.setColorFilter(color_filter);
239  }
240  builder.DrawImage(src_image, SkPoint{0, 0},
241  DlImageSampling::kMipmapLinear, &srcPaint);
242  }
243  builder.Restore();
244  }
245  return builder.Build();
246  };
247  ASSERT_TRUE(OpenPlaygroundHere(callback));
248 }
249 
250 // Verification for: https://github.com/flutter/flutter/issues/155691
251 TEST_P(AiksTest, ColorFilterAdvancedBlend) {
252  bool has_color_filter = true;
253  auto callback = [&]() -> sk_sp<DisplayList> {
254  if (AiksTest::ImGuiBegin("Controls", nullptr,
255  ImGuiWindowFlags_AlwaysAutoResize)) {
256  ImGui::Checkbox("has color filter", &has_color_filter);
257  ImGui::End();
258  }
259 
260  DisplayListBuilder builder;
261  builder.Scale(GetContentScale().x, GetContentScale().y);
262 
263  auto src_image =
264  DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png"));
265  auto dst_image =
266  DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png"));
267 
268  std::vector<DlBlendMode> blend_modes = {
269  DlBlendMode::kScreen, DlBlendMode::kOverlay,
270  DlBlendMode::kDarken, DlBlendMode::kLighten,
271  DlBlendMode::kColorDodge, DlBlendMode::kColorBurn,
272  DlBlendMode::kHardLight, DlBlendMode::kSoftLight,
273  DlBlendMode::kDifference, DlBlendMode::kExclusion,
274  DlBlendMode::kMultiply, DlBlendMode::kHue,
275  DlBlendMode::kSaturation, DlBlendMode::kColor,
276  DlBlendMode::kLuminosity,
277  };
278 
279  for (uint32_t i = 0; i < blend_modes.size(); ++i) {
280  builder.Save();
281  builder.Translate((i % 5) * 200, (i / 5) * 200);
282  builder.Scale(0.4, 0.4);
283  {
284  DlPaint dstPaint;
285  builder.DrawImage(dst_image, SkPoint{0, 0},
286  DlImageSampling::kMipmapLinear, &dstPaint);
287  }
288  {
289  DlPaint srcPaint;
290  srcPaint.setBlendMode(blend_modes[i]);
291  if (has_color_filter) {
292  std::shared_ptr<const DlColorFilter> color_filter =
293  DlColorFilter::MakeBlend(DlColor::RGBA(0.9, 0.5, 0.0, 1.0),
294  DlBlendMode::kSrcIn);
295  srcPaint.setColorFilter(color_filter);
296  }
297  builder.DrawImage(src_image, SkPoint{0, 0},
298  DlImageSampling::kMipmapLinear, &srcPaint);
299  }
300  builder.Restore();
301  }
302  return builder.Build();
303  };
304  ASSERT_TRUE(OpenPlaygroundHere(callback));
305 }
306 
307 // Variant of the https://github.com/flutter/flutter/issues/155691 test that
308 // uses an advanced blend in the color filter and disables framebuffer fetch
309 // to force usage of BlendFilterContents::CreateForegroundAdvancedBlend.
310 TEST_P(AiksTest, ColorFilterAdvancedBlendNoFbFetch) {
311  if (GetParam() != PlaygroundBackend::kMetal) {
312  GTEST_SKIP()
313  << "This backend doesn't yet support setting device capabilities.";
314  }
315  if (!WillRenderSomething()) {
316  GTEST_SKIP() << "This test requires playgrounds.";
317  }
318 
319  std::shared_ptr<const Capabilities> old_capabilities =
320  GetContext()->GetCapabilities();
321  auto mock_capabilities = std::make_shared<MockCapabilities>();
322  EXPECT_CALL(*mock_capabilities, SupportsFramebufferFetch())
323  .Times(::testing::AtLeast(1))
324  .WillRepeatedly(::testing::Return(false));
325  FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultColorFormat);
326  FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultStencilFormat);
327  FLT_FORWARD(mock_capabilities, old_capabilities,
328  GetDefaultDepthStencilFormat);
329  FLT_FORWARD(mock_capabilities, old_capabilities, SupportsOffscreenMSAA);
330  FLT_FORWARD(mock_capabilities, old_capabilities,
331  SupportsImplicitResolvingMSAA);
332  FLT_FORWARD(mock_capabilities, old_capabilities, SupportsReadFromResolve);
333  FLT_FORWARD(mock_capabilities, old_capabilities, SupportsSSBO);
334  FLT_FORWARD(mock_capabilities, old_capabilities, SupportsCompute);
335  FLT_FORWARD(mock_capabilities, old_capabilities,
336  SupportsTextureToTextureBlits);
337  FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultGlyphAtlasFormat);
338  FLT_FORWARD(mock_capabilities, old_capabilities, SupportsTriangleFan);
339  FLT_FORWARD(mock_capabilities, old_capabilities,
340  SupportsDecalSamplerAddressMode);
341  FLT_FORWARD(mock_capabilities, old_capabilities, SupportsPrimitiveRestart);
342  ASSERT_TRUE(SetCapabilities(mock_capabilities).ok());
343 
344  bool has_color_filter = true;
345  auto callback = [&]() -> sk_sp<DisplayList> {
346  if (AiksTest::ImGuiBegin("Controls", nullptr,
347  ImGuiWindowFlags_AlwaysAutoResize)) {
348  ImGui::Checkbox("has color filter", &has_color_filter);
349  ImGui::End();
350  }
351 
352  DisplayListBuilder builder;
353  builder.Scale(GetContentScale().x, GetContentScale().y);
354 
355  auto src_image =
356  DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png"));
357  auto dst_image =
358  DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png"));
359 
360  std::vector<DlBlendMode> blend_modes = {
361  DlBlendMode::kScreen, DlBlendMode::kOverlay,
362  DlBlendMode::kDarken, DlBlendMode::kLighten,
363  DlBlendMode::kColorDodge, DlBlendMode::kColorBurn,
364  DlBlendMode::kHardLight, DlBlendMode::kSoftLight,
365  DlBlendMode::kDifference, DlBlendMode::kExclusion,
366  DlBlendMode::kMultiply, DlBlendMode::kHue,
367  DlBlendMode::kSaturation, DlBlendMode::kColor,
368  DlBlendMode::kLuminosity,
369  };
370 
371  for (uint32_t i = 0; i < blend_modes.size(); ++i) {
372  builder.Save();
373  builder.Translate((i % 5) * 200, (i / 5) * 200);
374  builder.Scale(0.4, 0.4);
375  {
376  DlPaint dstPaint;
377  builder.DrawImage(dst_image, SkPoint{0, 0},
378  DlImageSampling::kMipmapLinear, &dstPaint);
379  }
380  {
381  DlPaint srcPaint;
382  srcPaint.setBlendMode(blend_modes[i]);
383  if (has_color_filter) {
384  std::shared_ptr<const DlColorFilter> color_filter =
385  DlColorFilter::MakeBlend(DlColor::RGBA(0.9, 0.5, 0.0, 1.0),
386  DlBlendMode::kMultiply);
387  srcPaint.setColorFilter(color_filter);
388  }
389  builder.DrawImage(src_image, SkPoint{0, 0},
390  DlImageSampling::kMipmapLinear, &srcPaint);
391  }
392  builder.Restore();
393  }
394  return builder.Build();
395  };
396  ASSERT_TRUE(OpenPlaygroundHere(callback));
397 }
398 
399 // Bug: https://github.com/flutter/flutter/issues/142549
400 TEST_P(AiksTest, BlendModePlusAlphaWideGamut) {
401  EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(),
403  auto texture = CreateTextureForFixture("airplane.jpg",
404  /*enable_mipmapping=*/true);
405 
406  DisplayListBuilder builder;
407  DlPaint paint;
408  builder.Scale(GetContentScale().x, GetContentScale().y);
409 
410  paint.setColor(DlColor::RGBA(0.9, 1, 0.9, 1.0));
411  builder.DrawPaint(paint);
412  builder.SaveLayer(nullptr);
413 
414  paint.setBlendMode(DlBlendMode::kPlus);
415  paint.setColor(DlColor::kRed());
416 
417  builder.DrawRect(SkRect::MakeXYWH(100, 100, 400, 400), paint);
418  paint.setColor(DlColor::kWhite());
419 
420  auto rect = Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100);
421  builder.DrawImageRect(
422  DlImageImpeller::Make(texture),
423  SkRect::MakeSize(
424  SkSize::Make(texture->GetSize().width, texture->GetSize().height)),
425  SkRect::MakeLTRB(rect.GetLeft(), rect.GetTop(), rect.GetRight(),
426  rect.GetBottom()),
427  DlImageSampling::kMipmapLinear, &paint);
428  builder.Restore();
429  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
430 }
431 
432 // Bug: https://github.com/flutter/flutter/issues/142549
433 TEST_P(AiksTest, BlendModePlusAlphaColorFilterWideGamut) {
434  EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(),
436  auto texture = CreateTextureForFixture("airplane.jpg",
437  /*enable_mipmapping=*/true);
438 
439  DisplayListBuilder builder;
440  builder.Scale(GetContentScale().x, GetContentScale().y);
441 
442  DlPaint paint;
443  paint.setColor(DlColor::RGBA(0.1, 0.2, 0.1, 1.0));
444  builder.DrawPaint(paint);
445 
446  DlPaint save_paint;
447  save_paint.setColorFilter(
448  DlColorFilter::MakeBlend(DlColor::RGBA(1, 0, 0, 1), DlBlendMode::kPlus));
449  builder.SaveLayer(nullptr, &save_paint);
450 
451  paint.setColor(DlColor::kRed());
452  builder.DrawRect(SkRect::MakeXYWH(100, 100, 400, 400), paint);
453 
454  paint.setColor(DlColor::kWhite());
455 
456  auto rect = Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100);
457  builder.DrawImageRect(
458  DlImageImpeller::Make(texture),
459  SkRect::MakeSize(
460  SkSize::Make(texture->GetSize().width, texture->GetSize().height)),
461  SkRect::MakeLTRB(rect.GetLeft(), rect.GetTop(), rect.GetRight(),
462  rect.GetBottom()),
463  DlImageSampling::kMipmapLinear, &paint);
464  builder.Restore();
465 
466  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
467 }
468 
469 TEST_P(AiksTest, ForegroundBlendSubpassCollapseOptimization) {
470  DisplayListBuilder builder;
471 
472  DlPaint save_paint;
473  save_paint.setColorFilter(
474  DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kColorDodge));
475  builder.SaveLayer(nullptr, &save_paint);
476 
477  builder.Translate(500, 300);
478  builder.Rotate(120);
479 
480  DlPaint paint;
481  paint.setColor(DlColor::kBlue());
482  builder.DrawRect(SkRect::MakeXYWH(100, 100, 200, 200), paint);
483 
484  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
485 }
486 
487 TEST_P(AiksTest, ClearBlend) {
488  DisplayListBuilder builder;
489 
490  DlPaint blue;
491  blue.setColor(DlColor::kBlue());
492  builder.DrawRect(SkRect::MakeXYWH(0, 0, 600.0, 600.0), blue);
493 
494  DlPaint clear;
495  clear.setBlendMode(DlBlendMode::kClear);
496 
497  builder.DrawCircle(SkPoint{300.0, 300.0}, 200.0, clear);
498 
499  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
500 }
501 
502 static sk_sp<DisplayList> BlendModeTest(Vector2 content_scale,
503  BlendMode blend_mode,
504  const sk_sp<DlImageImpeller>& src_image,
505  const sk_sp<DlImageImpeller>& dst_image,
506  Scalar src_alpha) {
507  if (AiksTest::ImGuiBegin("Controls", nullptr,
508  ImGuiWindowFlags_AlwaysAutoResize)) {
509  ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1);
510  ImGui::End();
511  }
512 
513  Color destination_color = Color::CornflowerBlue().WithAlpha(0.75);
514  auto source_colors = std::vector<Color>({Color::White().WithAlpha(0.75),
515  Color::LimeGreen().WithAlpha(0.75),
516  Color::Black().WithAlpha(0.75)});
517 
518  DisplayListBuilder builder;
519  {
520  DlPaint paint;
521  paint.setColor(DlColor::kBlack());
522  builder.DrawPaint(paint);
523  }
524  // TODO(bdero): Why does this cause the left image to double scale on high DPI
525  // displays.
526  // builder.Scale(content_scale);
527 
528  //----------------------------------------------------------------------------
529  /// 1. Save layer blending (top squares).
530  ///
531 
532  builder.Save();
533  for (const auto& color : source_colors) {
534  builder.Save();
535  {
536  builder.ClipRect(SkRect::MakeXYWH(25, 25, 100, 100));
537  // Perform the blend in a SaveLayer so that the initial backdrop color is
538  // fully transparent black. SourceOver blend the result onto the parent
539  // pass.
540  builder.SaveLayer({});
541  {
542  DlPaint draw_paint;
543  draw_paint.setColor(
544  DlColor::RGBA(destination_color.red, destination_color.green,
545  destination_color.blue, destination_color.alpha));
546  builder.DrawPaint(draw_paint);
547 
548  // Draw the source color in an offscreen pass and blend it to the parent
549  // pass.
550  DlPaint save_paint;
551  save_paint.setBlendMode(static_cast<DlBlendMode>(blend_mode));
552  builder.SaveLayer(nullptr, &save_paint);
553  { //
554  DlPaint paint;
555  paint.setColor(
556  DlColor::RGBA(color.red, color.green, color.blue, color.alpha));
557  builder.DrawRect(SkRect::MakeXYWH(25, 25, 100, 100), paint);
558  }
559  builder.Restore();
560  }
561  builder.Restore();
562  }
563  builder.Restore();
564  builder.Translate(100, 0);
565  }
566  builder.RestoreToCount(0);
567 
568  //----------------------------------------------------------------------------
569  /// 2. CPU blend modes (bottom squares).
570  ///
571 
572  builder.Save();
573  builder.Translate(0, 100);
574  // Perform the blend in a SaveLayer so that the initial backdrop color is
575  // fully transparent black. SourceOver blend the result onto the parent pass.
576  builder.SaveLayer({});
577  for (const auto& color : source_colors) {
578  // Simply write the CPU blended color to the pass.
579  DlPaint paint;
580  auto dest = destination_color.Blend(color, blend_mode);
581  paint.setColor(DlColor::RGBA(dest.red, dest.green, dest.blue, dest.alpha));
582  paint.setBlendMode(DlBlendMode::kSrcOver);
583  builder.DrawRect(SkRect::MakeXYWH(25, 25, 100, 100), paint);
584  builder.Translate(100, 0);
585  }
586  builder.Restore();
587  builder.Restore();
588 
589  //----------------------------------------------------------------------------
590  /// 3. Image blending (bottom images).
591  ///
592  /// Compare these results with the images in the Flutter blend mode
593  /// documentation: https://api.flutter.dev/flutter/dart-ui/BlendMode.html
594  ///
595 
596  builder.Translate(0, 250);
597 
598  // Draw grid behind the images.
599  {
600  DlPaint paint;
601  paint.setColor(DlColor::RGBA(41 / 255.0, 41 / 255.0, 41 / 255.0, 1));
602  builder.DrawRect(SkRect::MakeLTRB(0, 0, 800, 400), paint);
603  }
604 
605  DlPaint square_paint;
606  square_paint.setColor(DlColor::RGBA(15 / 255.0, 15 / 255.0, 15 / 255.0, 1));
607  for (int y = 0; y < 400 / 8; y++) {
608  for (int x = 0; x < 800 / 16; x++) {
609  builder.DrawRect(SkRect::MakeXYWH(x * 16 + (y % 2) * 8, y * 8, 8, 8),
610  square_paint);
611  }
612  }
613 
614  // Uploaded image source (left image).
615  DlPaint paint;
616  paint.setBlendMode(DlBlendMode::kSrcOver);
617  builder.Save();
618  builder.SaveLayer(nullptr, &paint);
619  {
620  builder.DrawImage(dst_image, SkPoint{0, 0}, DlImageSampling::kMipmapLinear,
621  &paint);
622 
623  paint.setColor(DlColor::kWhite().withAlpha(src_alpha * 255));
624  paint.setBlendMode(static_cast<DlBlendMode>(blend_mode));
625  builder.DrawImage(src_image, SkPoint{0, 0}, DlImageSampling::kMipmapLinear,
626  &paint);
627  }
628  builder.Restore();
629  builder.Restore();
630 
631  // Rendered image source (right image).
632  builder.Save();
633 
634  DlPaint save_paint;
635  builder.SaveLayer(nullptr, &save_paint);
636  {
637  builder.DrawImage(dst_image, SkPoint{400, 0},
638  DlImageSampling::kMipmapLinear, nullptr);
639 
640  DlPaint save_paint;
641  save_paint.setColor(DlColor::kWhite().withAlpha(src_alpha * 255));
642  save_paint.setBlendMode(static_cast<DlBlendMode>(blend_mode));
643  builder.SaveLayer(nullptr, &save_paint);
644  {
645  builder.DrawImage(src_image, SkPoint{400, 0},
646  DlImageSampling::kMipmapLinear, nullptr);
647  }
648  builder.Restore();
649  }
650  builder.Restore();
651  builder.Restore();
652 
653  return builder.Build();
654 }
655 
656 #define BLEND_MODE_TEST(blend_mode) \
657  TEST_P(AiksTest, BlendMode##blend_mode) { \
658  auto src_image = \
659  DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png")); \
660  auto dst_image = \
661  DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png")); \
662  auto callback = [&]() -> sk_sp<DisplayList> { \
663  return BlendModeTest(GetContentScale(), BlendMode::k##blend_mode, \
664  src_image, dst_image, /*src_alpha=*/1.0); \
665  }; \
666  OpenPlaygroundHere(callback); \
667  }
669 
670 #define BLEND_MODE_SRC_ALPHA_TEST(blend_mode) \
671  TEST_P(AiksTest, BlendModeSrcAlpha##blend_mode) { \
672  auto src_image = \
673  DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png")); \
674  auto dst_image = \
675  DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png")); \
676  auto callback = [&]() -> sk_sp<DisplayList> { \
677  return BlendModeTest(GetContentScale(), BlendMode::k##blend_mode, \
678  src_image, dst_image, /*src_alpha=*/0.5); \
679  }; \
680  OpenPlaygroundHere(callback); \
681  }
683 
684 TEST_P(AiksTest, CanDrawPaintMultipleTimesInteractive) {
685  auto modes = GetBlendModeSelection();
686 
687  auto callback = [&]() -> sk_sp<DisplayList> {
688  static Color background = Color::MediumTurquoise();
689  static Color foreground = Color::Color::OrangeRed().WithAlpha(0.5);
690  static int current_blend_index = 3;
691 
692  if (AiksTest::ImGuiBegin("Controls", nullptr,
693  ImGuiWindowFlags_AlwaysAutoResize)) {
694  ImGui::ColorEdit4("Background", reinterpret_cast<float*>(&background));
695  ImGui::ColorEdit4("Foreground", reinterpret_cast<float*>(&foreground));
696  ImGui::ListBox("Blend mode", &current_blend_index,
697  modes.blend_mode_names.data(),
698  modes.blend_mode_names.size());
699  ImGui::End();
700  }
701 
702  DisplayListBuilder builder;
703  builder.Scale(0.2, 0.2);
704  DlPaint paint;
705  paint.setColor(DlColor(background.ToARGB()));
706  builder.DrawPaint(paint);
707 
708  paint.setColor(DlColor(foreground.ToARGB()));
709  paint.setBlendMode(static_cast<DlBlendMode>(current_blend_index));
710  builder.DrawPaint(paint);
711  return builder.Build();
712  };
713  ASSERT_TRUE(OpenPlaygroundHere(callback));
714 }
715 
716 TEST_P(AiksTest, ForegroundPipelineBlendAppliesTransformCorrectly) {
717  auto texture = CreateTextureForFixture("airplane.jpg",
718  /*enable_mipmapping=*/true);
719 
720  DisplayListBuilder builder;
721  builder.Rotate(30);
722 
723  DlPaint image_paint;
724  image_paint.setColorFilter(DlColorFilter::MakeBlend(
725  DlColor::RGBA(255.0f / 255.0f, 165.0f / 255.0f, 0.0f / 255.0f, 1.0f),
726  DlBlendMode::kSrcIn));
727 
728  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint{200, 200},
729  DlImageSampling::kMipmapLinear, &image_paint);
730 
731  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
732 }
733 
734 TEST_P(AiksTest, ForegroundAdvancedBlendAppliesTransformCorrectly) {
735  auto texture = CreateTextureForFixture("airplane.jpg",
736  /*enable_mipmapping=*/true);
737 
738  DisplayListBuilder builder;
739  builder.Rotate(30);
740 
741  DlPaint image_paint;
742  image_paint.setColorFilter(DlColorFilter::MakeBlend(
743  DlColor::RGBA(255.0f / 255.0f, 165.0f / 255.0f, 0.0f / 255.0f, 1.0f),
744  DlBlendMode::kColorDodge));
745 
746  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint{200, 200},
747  DlImageSampling::kMipmapLinear, &image_paint);
748 
749  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
750 }
751 
752 TEST_P(AiksTest, FramebufferAdvancedBlendCoverage) {
753  auto texture = CreateTextureForFixture("airplane.jpg",
754  /*enable_mipmapping=*/true);
755 
756  // Draw with an advanced blend that can use FramebufferBlendContents and
757  // verify that the scale transform is correctly applied to the image.
758  DisplayListBuilder builder;
759 
760  DlPaint paint;
761  paint.setColor(
762  DlColor::RGBA(169.0f / 255.0f, 169.0f / 255.0f, 169.0f / 255.0f, 1.0f));
763  builder.DrawPaint(paint);
764  builder.Scale(0.4, 0.4);
765 
766  DlPaint image_paint;
767  image_paint.setBlendMode(DlBlendMode::kMultiply);
768 
769  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint{20, 20},
770  DlImageSampling::kMipmapLinear, &image_paint);
771 
772  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
773 }
774 
775 TEST_P(AiksTest, ColorWheel) {
776  // Compare with https://fiddle.skia.org/c/@BlendModes
777 
779 
780  auto draw_color_wheel = [](DisplayListBuilder& builder) -> void {
781  /// color_wheel_sampler: r=0 -> fuchsia, r=2pi/3 -> yellow, r=4pi/3 ->
782  /// cyan domain: r >= 0 (because modulo used is non euclidean)
783  auto color_wheel_sampler = [](Radians r) {
784  Scalar x = r.radians / k2Pi + 1;
785 
786  // https://www.desmos.com/calculator/6nhjelyoaj
787  auto color_cycle = [](Scalar x) {
788  Scalar cycle = std::fmod(x, 6.0f);
789  return std::max(0.0f, std::min(1.0f, 2 - std::abs(2 - cycle)));
790  };
791  return Color(color_cycle(6 * x + 1), //
792  color_cycle(6 * x - 1), //
793  color_cycle(6 * x - 3), //
794  1);
795  };
796 
797  DlPaint paint;
798  paint.setBlendMode(DlBlendMode::kSrcOver);
799 
800  // Draw a fancy color wheel for the backdrop.
801  // https://www.desmos.com/calculator/xw7kafthwd
802  const int max_dist = 900;
803  for (int i = 0; i <= 900; i++) {
804  Radians r(kPhi / k2Pi * i);
805  Scalar distance = r.radians / std::powf(4.12, 0.0026 * r.radians);
806  Scalar normalized_distance = static_cast<Scalar>(i) / max_dist;
807 
808  auto color = color_wheel_sampler(r).WithAlpha(1.0f - normalized_distance);
809  paint.setColor(
810  DlColor::RGBA(color.red, color.green, color.blue, color.alpha));
811  SkPoint position = SkPoint::Make(distance * std::sin(r.radians),
812  -distance * std::cos(r.radians));
813 
814  builder.DrawCircle(position, 9 + normalized_distance * 3, paint);
815  }
816  };
817 
818  auto callback = [&]() -> sk_sp<DisplayList> {
819  // UI state.
820  static bool cache_the_wheel = true;
821  static int current_blend_index = 3;
822  static float dst_alpha = 1;
823  static float src_alpha = 1;
824  static DlColor color0 = DlColor::kRed();
825  static DlColor color1 = DlColor::kGreen();
826  static DlColor color2 = DlColor::kBlue();
827 
828  if (AiksTest::ImGuiBegin("Controls", nullptr,
829  ImGuiWindowFlags_AlwaysAutoResize)) {
830  ImGui::Checkbox("Cache the wheel", &cache_the_wheel);
831  ImGui::ListBox("Blending mode", &current_blend_index,
832  blend_modes.blend_mode_names.data(),
833  blend_modes.blend_mode_names.size());
834  ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1);
835  ImGui::ColorEdit4("Color A", reinterpret_cast<float*>(&color0));
836  ImGui::ColorEdit4("Color B", reinterpret_cast<float*>(&color1));
837  ImGui::ColorEdit4("Color C", reinterpret_cast<float*>(&color2));
838  ImGui::SliderFloat("Destination alpha", &dst_alpha, 0, 1);
839  ImGui::End();
840  }
841 
842  DisplayListBuilder builder;
843 
844  DlPaint paint;
845  paint.setColor(DlColor::kWhite().withAlpha(dst_alpha * 255));
846  paint.setBlendMode(DlBlendMode::kSrc);
847  builder.SaveLayer(nullptr, &paint);
848  {
849  DlPaint paint;
850  paint.setColor(DlColor::kWhite());
851  builder.DrawPaint(paint);
852 
853  builder.SaveLayer(nullptr, nullptr);
854  builder.Scale(GetContentScale().x, GetContentScale().y);
855  builder.Translate(500, 400);
856  builder.Scale(3, 3);
857  draw_color_wheel(builder);
858  builder.Restore();
859  }
860  builder.Restore();
861 
862  builder.Scale(GetContentScale().x, GetContentScale().y);
863  builder.Translate(500, 400);
864  builder.Scale(3, 3);
865 
866  // Draw 3 circles to a subpass and blend it in.
867  DlPaint save_paint;
868  save_paint.setColor(DlColor::kWhite().withAlpha(src_alpha * 255));
869  save_paint.setBlendMode(static_cast<DlBlendMode>(
870  blend_modes.blend_mode_values[current_blend_index]));
871  builder.SaveLayer(nullptr, &save_paint);
872  {
873  DlPaint paint;
874  paint.setBlendMode(DlBlendMode::kPlus);
875  const Scalar x = std::sin(k2Pi / 3);
876  const Scalar y = -std::cos(k2Pi / 3);
877  paint.setColor(color0);
878  builder.DrawCircle(SkPoint::Make(-x * 45, y * 45), 65, paint);
879  paint.setColor(color1);
880  builder.DrawCircle(SkPoint::Make(0, -45), 65, paint);
881  paint.setColor(color2);
882  builder.DrawCircle(SkPoint::Make(x * 45, y * 45), 65, paint);
883  }
884  builder.Restore();
885 
886  return builder.Build();
887  };
888 
889  ASSERT_TRUE(OpenPlaygroundHere(callback));
890 }
891 
892 TEST_P(AiksTest, DestructiveBlendColorFilterFloodsClip) {
893  DisplayListBuilder builder;
894 
895  DlPaint paint;
896  paint.setColor(DlColor::kBlue());
897  builder.DrawPaint(paint);
898 
899  DlPaint save_paint;
900  save_paint.setColorFilter(
901  DlColorFilter::MakeBlend(DlColor::kRed(), DlBlendMode::kSrc));
902  builder.SaveLayer(nullptr, &save_paint);
903  builder.Restore();
904 
905  // Should be solid red as the destructive color filter floods the clip.
906  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
907 }
908 
909 TEST_P(AiksTest, AdvancedBlendColorFilterWithDestinationOpacity) {
910  DisplayListBuilder builder;
911 
912  builder.DrawPaint(DlPaint(DlColor::kWhite()));
913 
914  DlPaint save_paint;
915  save_paint.setOpacity(0.3);
916  save_paint.setColorFilter(DlColorFilter::MakeBlend(DlColor::kTransparent(),
917  DlBlendMode::kSaturation));
918  builder.SaveLayer(nullptr, &save_paint);
919  builder.DrawRect(SkRect::MakeXYWH(100, 100, 300, 300),
920  DlPaint(DlColor::kMaroon()));
921  builder.DrawRect(SkRect::MakeXYWH(200, 200, 300, 300),
922  DlPaint(DlColor::kBlue()));
923  builder.Restore();
924 
925  // Should be solid red as the destructive color filter floods the clip.
926  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
927 }
928 
929 } // namespace testing
930 } // namespace impeller
#define BLEND_MODE_TUPLE(blend_mode)
#define BLEND_MODE_SRC_ALPHA_TEST(blend_mode)
#define BLEND_MODE_TEST(blend_mode)
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)
static constexpr BlendMode kLastAdvancedBlendMode
Definition: entity.h:23
#define IMPELLER_FOR_EACH_BLEND_MODE(V)
Definition: color.h:19
int32_t x
TEST_P(AiksTest, DrawAtlasNoColor)
static sk_sp< DisplayList > BlendModeTest(Vector2 content_scale, BlendMode blend_mode, const sk_sp< DlImageImpeller > &src_image, const sk_sp< DlImageImpeller > &dst_image, Scalar src_alpha)
static BlendModeSelection GetBlendModeSelection()
constexpr float k2Pi
Definition: constants.h:29
float Scalar
Definition: scalar.h:18
BlendMode
Definition: color.h:58
constexpr float kPhi
Definition: constants.h:53
Scalar blue
Definition: color.h:137
static constexpr Color LimeGreen()
Definition: color.h:601
Scalar alpha
Definition: color.h:142
constexpr uint32_t ToARGB() const
Convert to ARGB 32 bit color.
Definition: color.h:258
static constexpr Color Black()
Definition: color.h:265
static constexpr Color CornflowerBlue()
Definition: color.h:341
static constexpr Color MediumTurquoise()
Definition: color.h:645
static constexpr Color White()
Definition: color.h:263
constexpr Color WithAlpha(Scalar new_alpha) const
Definition: color.h:277
static constexpr Color OrangeRed()
Definition: color.h:693
Scalar red
Definition: color.h:127
Scalar green
Definition: color.h:132
Color Blend(Color source, BlendMode blend_mode) const
Blends an unpremultiplied destination color into a given unpremultiplied source color to form a new u...
Definition: color.cc:157
Scalar radians
Definition: scalar.h:44
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:622
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
std::vector< const char * > blend_mode_names