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(DlBlendColorFilter::Make(
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({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  SkMatrix matrix = SkMatrix::Scale(0.3, 0.3);
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({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({150, 200}, 100, paint);
180 
181  paint.setColor(DlColor::RGBA(0, 1, 0, 0.5));
182  builder.DrawCircle({250, 200}, 100, paint);
183 
184  paint.setBlendMode(DlBlendMode::kPlus);
185 
186  paint.setColor(DlColor::kRed());
187  builder.DrawCircle({450, 250}, 100, paint);
188 
189  paint.setColor(DlColor::kGreen());
190  builder.DrawCircle({550, 250}, 100, paint);
191 
192  paint.setColor(DlColor::kBlue());
193  builder.DrawCircle({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, {0, 0}, DlImageSampling::kMipmapLinear,
229  &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  DlBlendColorFilter::Make(DlColor::RGBA(0.9, 0.5, 0.0, 1.0),
237  DlBlendMode::kSrcIn);
238  srcPaint.setColorFilter(color_filter);
239  }
240  builder.DrawImage(src_image, {0, 0}, DlImageSampling::kMipmapLinear,
241  &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, {0, 0}, DlImageSampling::kMipmapLinear,
286  &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  DlBlendColorFilter::Make(DlColor::RGBA(0.9, 0.5, 0.0, 1.0),
294  DlBlendMode::kSrcIn);
295  srcPaint.setColorFilter(color_filter);
296  }
297  builder.DrawImage(src_image, {0, 0}, DlImageSampling::kMipmapLinear,
298  &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  ASSERT_TRUE(SetCapabilities(mock_capabilities).ok());
342 
343  bool has_color_filter = true;
344  auto callback = [&]() -> sk_sp<DisplayList> {
345  if (AiksTest::ImGuiBegin("Controls", nullptr,
346  ImGuiWindowFlags_AlwaysAutoResize)) {
347  ImGui::Checkbox("has color filter", &has_color_filter);
348  ImGui::End();
349  }
350 
351  DisplayListBuilder builder;
352  builder.Scale(GetContentScale().x, GetContentScale().y);
353 
354  auto src_image =
355  DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png"));
356  auto dst_image =
357  DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png"));
358 
359  std::vector<DlBlendMode> blend_modes = {
360  DlBlendMode::kScreen, DlBlendMode::kOverlay,
361  DlBlendMode::kDarken, DlBlendMode::kLighten,
362  DlBlendMode::kColorDodge, DlBlendMode::kColorBurn,
363  DlBlendMode::kHardLight, DlBlendMode::kSoftLight,
364  DlBlendMode::kDifference, DlBlendMode::kExclusion,
365  DlBlendMode::kMultiply, DlBlendMode::kHue,
366  DlBlendMode::kSaturation, DlBlendMode::kColor,
367  DlBlendMode::kLuminosity,
368  };
369 
370  for (uint32_t i = 0; i < blend_modes.size(); ++i) {
371  builder.Save();
372  builder.Translate((i % 5) * 200, (i / 5) * 200);
373  builder.Scale(0.4, 0.4);
374  {
375  DlPaint dstPaint;
376  builder.DrawImage(dst_image, {0, 0}, DlImageSampling::kMipmapLinear,
377  &dstPaint);
378  }
379  {
380  DlPaint srcPaint;
381  srcPaint.setBlendMode(blend_modes[i]);
382  if (has_color_filter) {
383  std::shared_ptr<const DlColorFilter> color_filter =
384  DlBlendColorFilter::Make(DlColor::RGBA(0.9, 0.5, 0.0, 1.0),
385  DlBlendMode::kMultiply);
386  srcPaint.setColorFilter(color_filter);
387  }
388  builder.DrawImage(src_image, {0, 0}, DlImageSampling::kMipmapLinear,
389  &srcPaint);
390  }
391  builder.Restore();
392  }
393  return builder.Build();
394  };
395  ASSERT_TRUE(OpenPlaygroundHere(callback));
396 }
397 
398 // Bug: https://github.com/flutter/flutter/issues/142549
399 TEST_P(AiksTest, BlendModePlusAlphaWideGamut) {
400  EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(),
402  auto texture = CreateTextureForFixture("airplane.jpg",
403  /*enable_mipmapping=*/true);
404 
405  DisplayListBuilder builder;
406  DlPaint paint;
407  builder.Scale(GetContentScale().x, GetContentScale().y);
408 
409  paint.setColor(DlColor::RGBA(0.9, 1, 0.9, 1.0));
410  builder.DrawPaint(paint);
411  builder.SaveLayer(nullptr);
412 
413  paint.setBlendMode(DlBlendMode::kPlus);
414  paint.setColor(DlColor::kRed());
415 
416  builder.DrawRect(SkRect::MakeXYWH(100, 100, 400, 400), paint);
417  paint.setColor(DlColor::kWhite());
418 
419  auto rect = Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100);
420  builder.DrawImageRect(
421  DlImageImpeller::Make(texture),
422  SkRect::MakeSize(
423  SkSize::Make(texture->GetSize().width, texture->GetSize().height)),
424  SkRect::MakeLTRB(rect.GetLeft(), rect.GetTop(), rect.GetRight(),
425  rect.GetBottom()),
426  DlImageSampling::kMipmapLinear, &paint);
427  builder.Restore();
428  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
429 }
430 
431 // Bug: https://github.com/flutter/flutter/issues/142549
432 TEST_P(AiksTest, BlendModePlusAlphaColorFilterWideGamut) {
433  EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(),
435  auto texture = CreateTextureForFixture("airplane.jpg",
436  /*enable_mipmapping=*/true);
437 
438  DisplayListBuilder builder;
439  builder.Scale(GetContentScale().x, GetContentScale().y);
440 
441  DlPaint paint;
442  paint.setColor(DlColor::RGBA(0.1, 0.2, 0.1, 1.0));
443  builder.DrawPaint(paint);
444 
445  DlPaint save_paint;
446  save_paint.setColorFilter(
447  DlBlendColorFilter::Make(DlColor::RGBA(1, 0, 0, 1), DlBlendMode::kPlus));
448  builder.SaveLayer(nullptr, &save_paint);
449 
450  paint.setColor(DlColor::kRed());
451  builder.DrawRect(SkRect::MakeXYWH(100, 100, 400, 400), paint);
452 
453  paint.setColor(DlColor::kWhite());
454 
455  auto rect = Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100);
456  builder.DrawImageRect(
457  DlImageImpeller::Make(texture),
458  SkRect::MakeSize(
459  SkSize::Make(texture->GetSize().width, texture->GetSize().height)),
460  SkRect::MakeLTRB(rect.GetLeft(), rect.GetTop(), rect.GetRight(),
461  rect.GetBottom()),
462  DlImageSampling::kMipmapLinear, &paint);
463  builder.Restore();
464 
465  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
466 }
467 
468 TEST_P(AiksTest, ForegroundBlendSubpassCollapseOptimization) {
469  DisplayListBuilder builder;
470 
471  DlPaint save_paint;
472  save_paint.setColorFilter(
473  DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kColorDodge));
474  builder.SaveLayer(nullptr, &save_paint);
475 
476  builder.Translate(500, 300);
477  builder.Rotate(120);
478 
479  DlPaint paint;
480  paint.setColor(DlColor::kBlue());
481  builder.DrawRect(SkRect::MakeXYWH(100, 100, 200, 200), paint);
482 
483  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
484 }
485 
486 TEST_P(AiksTest, ClearBlend) {
487  DisplayListBuilder builder;
488 
489  DlPaint blue;
490  blue.setColor(DlColor::kBlue());
491  builder.DrawRect(SkRect::MakeXYWH(0, 0, 600.0, 600.0), blue);
492 
493  DlPaint clear;
494  clear.setBlendMode(DlBlendMode::kClear);
495 
496  builder.DrawCircle({300.0, 300.0}, 200.0, clear);
497 
498  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
499 }
500 
501 static sk_sp<DisplayList> BlendModeTest(Vector2 content_scale,
502  BlendMode blend_mode,
503  const sk_sp<DlImageImpeller>& src_image,
504  const sk_sp<DlImageImpeller>& dst_image,
505  Scalar src_alpha) {
506  if (AiksTest::ImGuiBegin("Controls", nullptr,
507  ImGuiWindowFlags_AlwaysAutoResize)) {
508  ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1);
509  ImGui::End();
510  }
511 
512  Color destination_color = Color::CornflowerBlue().WithAlpha(0.75);
513  auto source_colors = std::vector<Color>({Color::White().WithAlpha(0.75),
514  Color::LimeGreen().WithAlpha(0.75),
515  Color::Black().WithAlpha(0.75)});
516 
517  DisplayListBuilder builder;
518  {
519  DlPaint paint;
520  paint.setColor(DlColor::kBlack());
521  builder.DrawPaint(paint);
522  }
523  // TODO(bdero): Why does this cause the left image to double scale on high DPI
524  // displays.
525  // builder.Scale(content_scale);
526 
527  //----------------------------------------------------------------------------
528  /// 1. Save layer blending (top squares).
529  ///
530 
531  builder.Save();
532  for (const auto& color : source_colors) {
533  builder.Save();
534  {
535  builder.ClipRect(SkRect::MakeXYWH(25, 25, 100, 100));
536  // Perform the blend in a SaveLayer so that the initial backdrop color is
537  // fully transparent black. SourceOver blend the result onto the parent
538  // pass.
539  builder.SaveLayer({});
540  {
541  DlPaint draw_paint;
542  draw_paint.setColor(
543  DlColor::RGBA(destination_color.red, destination_color.green,
544  destination_color.blue, destination_color.alpha));
545  builder.DrawPaint(draw_paint);
546 
547  // Draw the source color in an offscreen pass and blend it to the parent
548  // pass.
549  DlPaint save_paint;
550  save_paint.setBlendMode(static_cast<DlBlendMode>(blend_mode));
551  builder.SaveLayer(nullptr, &save_paint);
552  { //
553  DlPaint paint;
554  paint.setColor(
555  DlColor::RGBA(color.red, color.green, color.blue, color.alpha));
556  builder.DrawRect(SkRect::MakeXYWH(25, 25, 100, 100), paint);
557  }
558  builder.Restore();
559  }
560  builder.Restore();
561  }
562  builder.Restore();
563  builder.Translate(100, 0);
564  }
565  builder.RestoreToCount(0);
566 
567  //----------------------------------------------------------------------------
568  /// 2. CPU blend modes (bottom squares).
569  ///
570 
571  builder.Save();
572  builder.Translate(0, 100);
573  // Perform the blend in a SaveLayer so that the initial backdrop color is
574  // fully transparent black. SourceOver blend the result onto the parent pass.
575  builder.SaveLayer({});
576  for (const auto& color : source_colors) {
577  // Simply write the CPU blended color to the pass.
578  DlPaint paint;
579  auto dest = destination_color.Blend(color, blend_mode);
580  paint.setColor(DlColor::RGBA(dest.red, dest.green, dest.blue, dest.alpha));
581  paint.setBlendMode(DlBlendMode::kSrcOver);
582  builder.DrawRect(SkRect::MakeXYWH(25, 25, 100, 100), paint);
583  builder.Translate(100, 0);
584  }
585  builder.Restore();
586  builder.Restore();
587 
588  //----------------------------------------------------------------------------
589  /// 3. Image blending (bottom images).
590  ///
591  /// Compare these results with the images in the Flutter blend mode
592  /// documentation: https://api.flutter.dev/flutter/dart-ui/BlendMode.html
593  ///
594 
595  builder.Translate(0, 250);
596 
597  // Draw grid behind the images.
598  {
599  DlPaint paint;
600  paint.setColor(DlColor::RGBA(41 / 255.0, 41 / 255.0, 41 / 255.0, 1));
601  builder.DrawRect(SkRect::MakeLTRB(0, 0, 800, 400), paint);
602  }
603 
604  DlPaint square_paint;
605  square_paint.setColor(DlColor::RGBA(15 / 255.0, 15 / 255.0, 15 / 255.0, 1));
606  for (int y = 0; y < 400 / 8; y++) {
607  for (int x = 0; x < 800 / 16; x++) {
608  builder.DrawRect(SkRect::MakeXYWH(x * 16 + (y % 2) * 8, y * 8, 8, 8),
609  square_paint);
610  }
611  }
612 
613  // Uploaded image source (left image).
614  DlPaint paint;
615  paint.setBlendMode(DlBlendMode::kSrcOver);
616  builder.Save();
617  builder.SaveLayer(nullptr, &paint);
618  {
619  builder.DrawImage(dst_image, {0, 0}, DlImageSampling::kMipmapLinear,
620  &paint);
621 
622  paint.setColor(DlColor::kWhite().withAlpha(src_alpha * 255));
623  paint.setBlendMode(static_cast<DlBlendMode>(blend_mode));
624  builder.DrawImage(src_image, {0, 0}, DlImageSampling::kMipmapLinear,
625  &paint);
626  }
627  builder.Restore();
628  builder.Restore();
629 
630  // Rendered image source (right image).
631  builder.Save();
632 
633  DlPaint save_paint;
634  builder.SaveLayer(nullptr, &save_paint);
635  {
636  builder.DrawImage(dst_image, {400, 0}, DlImageSampling::kMipmapLinear,
637  nullptr);
638 
639  DlPaint save_paint;
640  save_paint.setColor(DlColor::kWhite().withAlpha(src_alpha * 255));
641  save_paint.setBlendMode(static_cast<DlBlendMode>(blend_mode));
642  builder.SaveLayer(nullptr, &save_paint);
643  {
644  builder.DrawImage(src_image, {400, 0}, DlImageSampling::kMipmapLinear,
645  nullptr);
646  }
647  builder.Restore();
648  }
649  builder.Restore();
650  builder.Restore();
651 
652  return builder.Build();
653 }
654 
655 #define BLEND_MODE_TEST(blend_mode) \
656  TEST_P(AiksTest, BlendMode##blend_mode) { \
657  auto src_image = \
658  DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png")); \
659  auto dst_image = \
660  DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png")); \
661  auto callback = [&]() -> sk_sp<DisplayList> { \
662  return BlendModeTest(GetContentScale(), BlendMode::k##blend_mode, \
663  src_image, dst_image, /*src_alpha=*/1.0); \
664  }; \
665  OpenPlaygroundHere(callback); \
666  }
668 
669 #define BLEND_MODE_SRC_ALPHA_TEST(blend_mode) \
670  TEST_P(AiksTest, BlendModeSrcAlpha##blend_mode) { \
671  auto src_image = \
672  DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png")); \
673  auto dst_image = \
674  DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png")); \
675  auto callback = [&]() -> sk_sp<DisplayList> { \
676  return BlendModeTest(GetContentScale(), BlendMode::k##blend_mode, \
677  src_image, dst_image, /*src_alpha=*/0.5); \
678  }; \
679  OpenPlaygroundHere(callback); \
680  }
682 
683 TEST_P(AiksTest, CanDrawPaintMultipleTimesInteractive) {
684  auto modes = GetBlendModeSelection();
685 
686  auto callback = [&]() -> sk_sp<DisplayList> {
687  static Color background = Color::MediumTurquoise();
688  static Color foreground = Color::Color::OrangeRed().WithAlpha(0.5);
689  static int current_blend_index = 3;
690 
691  if (AiksTest::ImGuiBegin("Controls", nullptr,
692  ImGuiWindowFlags_AlwaysAutoResize)) {
693  ImGui::ColorEdit4("Background", reinterpret_cast<float*>(&background));
694  ImGui::ColorEdit4("Foreground", reinterpret_cast<float*>(&foreground));
695  ImGui::ListBox("Blend mode", &current_blend_index,
696  modes.blend_mode_names.data(),
697  modes.blend_mode_names.size());
698  ImGui::End();
699  }
700 
701  DisplayListBuilder builder;
702  builder.Scale(0.2, 0.2);
703  DlPaint paint;
704  paint.setColor(DlColor(background.ToARGB()));
705  builder.DrawPaint(paint);
706 
707  paint.setColor(DlColor(foreground.ToARGB()));
708  paint.setBlendMode(static_cast<DlBlendMode>(current_blend_index));
709  builder.DrawPaint(paint);
710  return builder.Build();
711  };
712  ASSERT_TRUE(OpenPlaygroundHere(callback));
713 }
714 
715 TEST_P(AiksTest, ForegroundPipelineBlendAppliesTransformCorrectly) {
716  auto texture = CreateTextureForFixture("airplane.jpg",
717  /*enable_mipmapping=*/true);
718 
719  DisplayListBuilder builder;
720  builder.Rotate(30);
721 
722  DlPaint image_paint;
723  image_paint.setColorFilter(DlBlendColorFilter::Make(
724  DlColor::RGBA(255.0f / 255.0f, 165.0f / 255.0f, 0.0f / 255.0f, 1.0f),
725  DlBlendMode::kSrcIn));
726 
727  builder.DrawImage(DlImageImpeller::Make(texture), {200, 200},
728  DlImageSampling::kMipmapLinear, &image_paint);
729 
730  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
731 }
732 
733 TEST_P(AiksTest, ForegroundAdvancedBlendAppliesTransformCorrectly) {
734  auto texture = CreateTextureForFixture("airplane.jpg",
735  /*enable_mipmapping=*/true);
736 
737  DisplayListBuilder builder;
738  builder.Rotate(30);
739 
740  DlPaint image_paint;
741  image_paint.setColorFilter(DlBlendColorFilter::Make(
742  DlColor::RGBA(255.0f / 255.0f, 165.0f / 255.0f, 0.0f / 255.0f, 1.0f),
743  DlBlendMode::kColorDodge));
744 
745  builder.DrawImage(DlImageImpeller::Make(texture), {200, 200},
746  DlImageSampling::kMipmapLinear, &image_paint);
747 
748  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
749 }
750 
751 TEST_P(AiksTest, FramebufferAdvancedBlendCoverage) {
752  auto texture = CreateTextureForFixture("airplane.jpg",
753  /*enable_mipmapping=*/true);
754 
755  // Draw with an advanced blend that can use FramebufferBlendContents and
756  // verify that the scale transform is correctly applied to the image.
757  DisplayListBuilder builder;
758 
759  DlPaint paint;
760  paint.setColor(
761  DlColor::RGBA(169.0f / 255.0f, 169.0f / 255.0f, 169.0f / 255.0f, 1.0f));
762  builder.DrawPaint(paint);
763  builder.Scale(0.4, 0.4);
764 
765  DlPaint image_paint;
766  image_paint.setBlendMode(DlBlendMode::kMultiply);
767 
768  builder.DrawImage(DlImageImpeller::Make(texture), {20, 20},
769  DlImageSampling::kMipmapLinear, &image_paint);
770 
771  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
772 }
773 
774 TEST_P(AiksTest, ColorWheel) {
775  // Compare with https://fiddle.skia.org/c/@BlendModes
776 
778 
779  auto draw_color_wheel = [](DisplayListBuilder& builder) -> void {
780  /// color_wheel_sampler: r=0 -> fuchsia, r=2pi/3 -> yellow, r=4pi/3 ->
781  /// cyan domain: r >= 0 (because modulo used is non euclidean)
782  auto color_wheel_sampler = [](Radians r) {
783  Scalar x = r.radians / k2Pi + 1;
784 
785  // https://www.desmos.com/calculator/6nhjelyoaj
786  auto color_cycle = [](Scalar x) {
787  Scalar cycle = std::fmod(x, 6.0f);
788  return std::max(0.0f, std::min(1.0f, 2 - std::abs(2 - cycle)));
789  };
790  return Color(color_cycle(6 * x + 1), //
791  color_cycle(6 * x - 1), //
792  color_cycle(6 * x - 3), //
793  1);
794  };
795 
796  DlPaint paint;
797  paint.setBlendMode(DlBlendMode::kSrcOver);
798 
799  // Draw a fancy color wheel for the backdrop.
800  // https://www.desmos.com/calculator/xw7kafthwd
801  const int max_dist = 900;
802  for (int i = 0; i <= 900; i++) {
803  Radians r(kPhi / k2Pi * i);
804  Scalar distance = r.radians / std::powf(4.12, 0.0026 * r.radians);
805  Scalar normalized_distance = static_cast<Scalar>(i) / max_dist;
806 
807  auto color = color_wheel_sampler(r).WithAlpha(1.0f - normalized_distance);
808  paint.setColor(
809  DlColor::RGBA(color.red, color.green, color.blue, color.alpha));
810  SkPoint position = SkPoint::Make(distance * std::sin(r.radians),
811  -distance * std::cos(r.radians));
812 
813  builder.DrawCircle(position, 9 + normalized_distance * 3, paint);
814  }
815  };
816 
817  auto callback = [&]() -> sk_sp<DisplayList> {
818  // UI state.
819  static bool cache_the_wheel = true;
820  static int current_blend_index = 3;
821  static float dst_alpha = 1;
822  static float src_alpha = 1;
823  static DlColor color0 = DlColor::kRed();
824  static DlColor color1 = DlColor::kGreen();
825  static DlColor color2 = DlColor::kBlue();
826 
827  if (AiksTest::ImGuiBegin("Controls", nullptr,
828  ImGuiWindowFlags_AlwaysAutoResize)) {
829  ImGui::Checkbox("Cache the wheel", &cache_the_wheel);
830  ImGui::ListBox("Blending mode", &current_blend_index,
831  blend_modes.blend_mode_names.data(),
832  blend_modes.blend_mode_names.size());
833  ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1);
834  ImGui::ColorEdit4("Color A", reinterpret_cast<float*>(&color0));
835  ImGui::ColorEdit4("Color B", reinterpret_cast<float*>(&color1));
836  ImGui::ColorEdit4("Color C", reinterpret_cast<float*>(&color2));
837  ImGui::SliderFloat("Destination alpha", &dst_alpha, 0, 1);
838  ImGui::End();
839  }
840 
841  DisplayListBuilder builder;
842 
843  DlPaint paint;
844  paint.setColor(DlColor::kWhite().withAlpha(dst_alpha * 255));
845  paint.setBlendMode(DlBlendMode::kSrc);
846  builder.SaveLayer(nullptr, &paint);
847  {
848  DlPaint paint;
849  paint.setColor(DlColor::kWhite());
850  builder.DrawPaint(paint);
851 
852  builder.SaveLayer(nullptr, nullptr);
853  builder.Scale(GetContentScale().x, GetContentScale().y);
854  builder.Translate(500, 400);
855  builder.Scale(3, 3);
856  draw_color_wheel(builder);
857  builder.Restore();
858  }
859  builder.Restore();
860 
861  builder.Scale(GetContentScale().x, GetContentScale().y);
862  builder.Translate(500, 400);
863  builder.Scale(3, 3);
864 
865  // Draw 3 circles to a subpass and blend it in.
866  DlPaint save_paint;
867  save_paint.setColor(DlColor::kWhite().withAlpha(src_alpha * 255));
868  save_paint.setBlendMode(static_cast<DlBlendMode>(
869  blend_modes.blend_mode_values[current_blend_index]));
870  builder.SaveLayer(nullptr, &save_paint);
871  {
872  DlPaint paint;
873  paint.setBlendMode(DlBlendMode::kPlus);
874  const Scalar x = std::sin(k2Pi / 3);
875  const Scalar y = -std::cos(k2Pi / 3);
876  paint.setColor(color0);
877  builder.DrawCircle(SkPoint::Make(-x * 45, y * 45), 65, paint);
878  paint.setColor(color1);
879  builder.DrawCircle(SkPoint::Make(0, -45), 65, paint);
880  paint.setColor(color2);
881  builder.DrawCircle(SkPoint::Make(x * 45, y * 45), 65, paint);
882  }
883  builder.Restore();
884 
885  return builder.Build();
886  };
887 
888  ASSERT_TRUE(OpenPlaygroundHere(callback));
889 }
890 
891 } // namespace testing
892 } // namespace impeller
impeller::AiksPlayground
Definition: aiks_playground.h:16
impeller::testing::BlendModeTest
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)
Definition: aiks_dl_blend_unittests.cc:501
impeller::Scalar
float Scalar
Definition: scalar.h:18
impeller::PixelFormat::kB10G10R10A10XR
@ kB10G10R10A10XR
aiks_unittests.h
playground_test.h
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:58
impeller::Color
Definition: color.h:123
impeller::PlaygroundBackend::kMetal
@ kMetal
aiks_context.h
BLEND_MODE_TEST
#define BLEND_MODE_TEST(blend_mode)
Definition: aiks_dl_blend_unittests.cc:655
dl_dispatcher.h
impeller::Color::alpha
Scalar alpha
Definition: color.h:142
playground.h
impeller::Color::CornflowerBlue
static constexpr Color CornflowerBlue()
Definition: color.h:341
impeller::Color::ToARGB
constexpr uint32_t ToARGB() const
Convert to ARGB 32 bit color.
Definition: color.h:258
impeller::Radians::radians
Scalar radians
Definition: scalar.h:44
impeller::Color::green
Scalar green
Definition: color.h:132
impeller::DlImageImpeller::Make
static sk_sp< DlImageImpeller > Make(std::shared_ptr< Texture > texture, OwningContext owning_context=OwningContext::kIO)
Definition: dl_image_impeller.cc:23
impeller::kPhi
constexpr float kPhi
Definition: constants.h:53
impeller::saturated::distance
SI distance
Definition: saturated_math.h:57
impeller::k2Pi
constexpr float k2Pi
Definition: constants.h:29
impeller::Color::OrangeRed
static constexpr Color OrangeRed()
Definition: color.h:693
impeller::testing::GetBlendModeSelection
static BlendModeSelection GetBlendModeSelection()
Definition: aiks_dl_blend_unittests.cc:45
impeller::Color::WithAlpha
constexpr Color WithAlpha(Scalar new_alpha) const
Definition: color.h:277
impeller::Entity::kLastAdvancedBlendMode
static constexpr BlendMode kLastAdvancedBlendMode
Definition: entity.h:23
impeller::Color::red
Scalar red
Definition: color.h:127
impeller::Color::White
static constexpr Color White()
Definition: color.h:263
impeller::Radians
Definition: scalar.h:43
flutter
Definition: dl_golden_blur_unittests.cc:15
impeller::testing::BlendModeSelection
Definition: aiks_dl_blend_unittests.cc:40
BLEND_MODE_SRC_ALPHA_TEST
#define BLEND_MODE_SRC_ALPHA_TEST(blend_mode)
Definition: aiks_dl_blend_unittests.cc:669
IMPELLER_FOR_EACH_BLEND_MODE
#define IMPELLER_FOR_EACH_BLEND_MODE(V)
Definition: color.h:19
impeller::testing::TEST_P
TEST_P(AiksTest, DrawAtlasNoColor)
Definition: aiks_dl_atlas_unittests.cc:78
scalar.h
impeller::Color::MediumTurquoise
static constexpr Color MediumTurquoise()
Definition: color.h:645
BLEND_MODE_TUPLE
#define BLEND_MODE_TUPLE(blend_mode)
Definition: aiks_dl_blend_unittests.cc:38
impeller::TPoint< Scalar >
impeller::Color::Black
static constexpr Color Black()
Definition: color.h:265
impeller::testing::BlendModeSelection::blend_mode_values
std::vector< BlendMode > blend_mode_values
Definition: aiks_dl_blend_unittests.cc:42
impeller::AiksPlayground::ImGuiBegin
static bool ImGuiBegin(const char *name, bool *p_open, ImGuiWindowFlags flags)
Definition: aiks_playground.cc:30
impeller::Color::LimeGreen
static constexpr Color LimeGreen()
Definition: color.h:601
color
DlColor color
Definition: dl_golden_blur_unittests.cc:24
impeller::testing::BlendModeSelection::blend_mode_names
std::vector< const char * > blend_mode_names
Definition: aiks_dl_blend_unittests.cc:41
impeller::Color::blue
Scalar blue
Definition: color.h:137
impeller
Definition: allocation.cc:12
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:612
dl_image_impeller.h
impeller::Color::Blend
Color Blend(Color source, BlendMode blend_mode) const
Blends an unpremultiplied destination color into a given unpremultiplied source color to form a new u...
Definition: color.cc:157