From 36f6113ea093e7d949be8507ccabd91052d670b8 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 29 Mar 2024 11:22:53 -0700 Subject: [PATCH 1/2] [Impeller] split out aiks blend tests --- impeller/aiks/BUILD.gn | 1 + impeller/aiks/aiks_blend_unittests.cc | 525 ++++++++++++++++++++++++++ impeller/aiks/aiks_unittests.cc | 507 +------------------------ 3 files changed, 527 insertions(+), 506 deletions(-) create mode 100644 impeller/aiks/aiks_blend_unittests.cc diff --git a/impeller/aiks/BUILD.gn b/impeller/aiks/BUILD.gn index 10b0a57c79f5e..0fc2354a1dd2f 100644 --- a/impeller/aiks/BUILD.gn +++ b/impeller/aiks/BUILD.gn @@ -85,6 +85,7 @@ impeller_component("context_spy") { template("aiks_unittests_component") { target_name = invoker.target_name predefined_sources = [ + "aiks_blend_unittests.cc", "aiks_blur_unittests.cc", "aiks_gradient_unittests.cc", "aiks_path_unittests.cc", diff --git a/impeller/aiks/aiks_blend_unittests.cc b/impeller/aiks/aiks_blend_unittests.cc new file mode 100644 index 0000000000000..b94bd5be6e453 --- /dev/null +++ b/impeller/aiks/aiks_blend_unittests.cc @@ -0,0 +1,525 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/impeller/aiks/aiks_unittests.h" + +#include "flutter/testing/testing.h" +#include "impeller/aiks/canvas.h" + +//////////////////////////////////////////////////////////////////////////////// +// This is for tests of Canvas that are interested the results of rendering +// blends. +//////////////////////////////////////////////////////////////////////////////// + +namespace impeller { +namespace testing { + +TEST_P(AiksTest, CanRenderAdvancedBlendColorFilterWithSaveLayer) { + Canvas canvas; + + Rect layer_rect = Rect::MakeXYWH(0, 0, 500, 500); + canvas.ClipRect(layer_rect); + + canvas.SaveLayer( + { + .color_filter = ColorFilter::MakeBlend(BlendMode::kDifference, + Color(0, 1, 0, 0.5)), + }, + layer_rect); + + Paint paint; + canvas.DrawPaint({.color = Color::Black()}); + canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), + {.color = Color::White()}); + canvas.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, BlendModeShouldCoverWholeScreen) { + Canvas canvas; + Paint paint; + + paint.color = Color::Red(); + canvas.DrawPaint(paint); + + paint.blend_mode = BlendMode::kSourceOver; + canvas.SaveLayer(paint); + + paint.color = Color::White(); + canvas.DrawRect(Rect::MakeXYWH(100, 100, 400, 400), paint); + + paint.blend_mode = BlendMode::kSource; + canvas.SaveLayer(paint); + + paint.color = Color::Blue(); + canvas.DrawRect(Rect::MakeXYWH(200, 200, 200, 200), paint); + + canvas.Restore(); + canvas.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanDrawPaintWithAdvancedBlend) { + Canvas canvas; + canvas.Scale(Vector2(0.2, 0.2)); + canvas.DrawPaint({.color = Color::MediumTurquoise()}); + canvas.DrawPaint({.color = Color::Color::OrangeRed().WithAlpha(0.5), + .blend_mode = BlendMode::kHue}); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, DrawPaintWithAdvancedBlendOverFilter) { + Paint filtered = { + .color = Color::Black(), + .mask_blur_descriptor = + Paint::MaskBlurDescriptor{ + .style = FilterContents::BlurStyle::kNormal, + .sigma = Sigma(60), + }, + }; + + Canvas canvas; + canvas.DrawPaint({.color = Color::White()}); + canvas.DrawCircle({300, 300}, 200, filtered); + canvas.DrawPaint({.color = Color::Green(), .blend_mode = BlendMode::kScreen}); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, DrawAdvancedBlendPartlyOffscreen) { + std::vector colors = {Color{0.9568, 0.2627, 0.2118, 1.0}, + Color{0.1294, 0.5882, 0.9529, 1.0}}; + std::vector stops = {0.0, 1.0}; + + Paint paint = { + .color_source = ColorSource::MakeLinearGradient( + {0, 0}, {100, 100}, std::move(colors), std::move(stops), + Entity::TileMode::kRepeat, Matrix::MakeScale(Vector3(0.3, 0.3, 0.3))), + .blend_mode = BlendMode::kLighten, + }; + + Canvas canvas; + canvas.DrawPaint({.color = Color::Blue()}); + canvas.Scale(Vector2(2, 2)); + canvas.ClipRect(Rect::MakeLTRB(0, 0, 200, 200)); + canvas.DrawCircle({100, 100}, 100, paint); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, PaintBlendModeIsRespected) { + Paint paint; + Canvas canvas; + // Default is kSourceOver. + paint.color = Color(1, 0, 0, 0.5); + canvas.DrawCircle(Point(150, 200), 100, paint); + paint.color = Color(0, 1, 0, 0.5); + canvas.DrawCircle(Point(250, 200), 100, paint); + + paint.blend_mode = BlendMode::kPlus; + paint.color = Color::Red(); + canvas.DrawCircle(Point(450, 250), 100, paint); + paint.color = Color::Green(); + canvas.DrawCircle(Point(550, 250), 100, paint); + paint.color = Color::Blue(); + canvas.DrawCircle(Point(500, 150), 100, paint); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +// Bug: https://github.com/flutter/flutter/issues/142549 +TEST_P(AiksTest, BlendModePlusAlphaWideGamut) { + if (GetParam() != PlaygroundBackend::kMetal) { + GTEST_SKIP_("This backend doesn't yet support wide gamut."); + } + EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(), + PixelFormat::kR16G16B16A16Float); + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + + Canvas canvas; + canvas.Scale(GetContentScale()); + canvas.DrawPaint({.color = Color(0.9, 1.0, 0.9, 1.0)}); + canvas.SaveLayer({}); + Paint paint; + paint.blend_mode = BlendMode::kPlus; + paint.color = Color::Red(); + canvas.DrawRect(Rect::MakeXYWH(100, 100, 400, 400), paint); + paint.color = Color::White(); + canvas.DrawImageRect( + std::make_shared(texture), Rect::MakeSize(texture->GetSize()), + Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100), paint); + canvas.Restore(); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +// Bug: https://github.com/flutter/flutter/issues/142549 +TEST_P(AiksTest, BlendModePlusAlphaColorFilterWideGamut) { + if (GetParam() != PlaygroundBackend::kMetal) { + GTEST_SKIP_("This backend doesn't yet support wide gamut."); + } + EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(), + PixelFormat::kR16G16B16A16Float); + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + + Canvas canvas; + canvas.Scale(GetContentScale()); + canvas.DrawPaint({.color = Color(0.1, 0.2, 0.1, 1.0)}); + canvas.SaveLayer({ + .color_filter = + ColorFilter::MakeBlend(BlendMode::kPlus, Color(Vector4{1, 0, 0, 1})), + }); + Paint paint; + paint.color = Color::Red(); + canvas.DrawRect(Rect::MakeXYWH(100, 100, 400, 400), paint); + paint.color = Color::White(); + canvas.DrawImageRect( + std::make_shared(texture), Rect::MakeSize(texture->GetSize()), + Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100), paint); + canvas.Restore(); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +#define BLEND_MODE_TUPLE(blend_mode) {#blend_mode, BlendMode::k##blend_mode}, + +struct BlendModeSelection { + std::vector blend_mode_names; + std::vector blend_mode_values; +}; + +static BlendModeSelection GetBlendModeSelection() { + std::vector blend_mode_names; + std::vector blend_mode_values; + { + const std::vector> blends = { + IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TUPLE)}; + assert(blends.size() == + static_cast(Entity::kLastAdvancedBlendMode) + 1); + for (const auto& [name, mode] : blends) { + blend_mode_names.push_back(name); + blend_mode_values.push_back(mode); + } + } + + return {blend_mode_names, blend_mode_values}; +} + +TEST_P(AiksTest, ColorWheel) { + // Compare with https://fiddle.skia.org/c/@BlendModes + + BlendModeSelection blend_modes = GetBlendModeSelection(); + + auto draw_color_wheel = [](Canvas& canvas) { + /// color_wheel_sampler: r=0 -> fuchsia, r=2pi/3 -> yellow, r=4pi/3 -> + /// cyan domain: r >= 0 (because modulo used is non euclidean) + auto color_wheel_sampler = [](Radians r) { + Scalar x = r.radians / k2Pi + 1; + + // https://www.desmos.com/calculator/6nhjelyoaj + auto color_cycle = [](Scalar x) { + Scalar cycle = std::fmod(x, 6.0f); + return std::max(0.0f, std::min(1.0f, 2 - std::abs(2 - cycle))); + }; + return Color(color_cycle(6 * x + 1), // + color_cycle(6 * x - 1), // + color_cycle(6 * x - 3), // + 1); + }; + + Paint paint; + paint.blend_mode = BlendMode::kSourceOver; + + // Draw a fancy color wheel for the backdrop. + // https://www.desmos.com/calculator/xw7kafthwd + const int max_dist = 900; + for (int i = 0; i <= 900; i++) { + Radians r(kPhi / k2Pi * i); + Scalar distance = r.radians / std::powf(4.12, 0.0026 * r.radians); + Scalar normalized_distance = static_cast(i) / max_dist; + + paint.color = + color_wheel_sampler(r).WithAlpha(1.0f - normalized_distance); + Point position(distance * std::sin(r.radians), + -distance * std::cos(r.radians)); + + canvas.DrawCircle(position, 9 + normalized_distance * 3, paint); + } + }; + + std::shared_ptr color_wheel_image; + Matrix color_wheel_transform; + + auto callback = [&](AiksContext& renderer) -> std::optional { + // UI state. + static bool cache_the_wheel = true; + static int current_blend_index = 3; + static float dst_alpha = 1; + static float src_alpha = 1; + static Color color0 = Color::Red(); + static Color color1 = Color::Green(); + static Color color2 = Color::Blue(); + + if (AiksTest::ImGuiBegin("Controls", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Checkbox("Cache the wheel", &cache_the_wheel); + ImGui::ListBox("Blending mode", ¤t_blend_index, + blend_modes.blend_mode_names.data(), + blend_modes.blend_mode_names.size()); + ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1); + ImGui::ColorEdit4("Color A", reinterpret_cast(&color0)); + ImGui::ColorEdit4("Color B", reinterpret_cast(&color1)); + ImGui::ColorEdit4("Color C", reinterpret_cast(&color2)); + ImGui::SliderFloat("Destination alpha", &dst_alpha, 0, 1); + ImGui::End(); + } + + static Point content_scale; + Point new_content_scale = GetContentScale(); + + if (!cache_the_wheel || new_content_scale != content_scale) { + content_scale = new_content_scale; + + // Render the color wheel to an image. + + Canvas canvas; + canvas.Scale(content_scale); + + canvas.Translate(Vector2(500, 400)); + canvas.Scale(Vector2(3, 3)); + + draw_color_wheel(canvas); + auto color_wheel_picture = canvas.EndRecordingAsPicture(); + auto snapshot = color_wheel_picture.Snapshot(renderer); + if (!snapshot.has_value() || !snapshot->texture) { + return std::nullopt; + } + color_wheel_image = std::make_shared(snapshot->texture); + color_wheel_transform = snapshot->transform; + } + + Canvas canvas; + + // Blit the color wheel backdrop to the screen with managed alpha. + canvas.SaveLayer({.color = Color::White().WithAlpha(dst_alpha), + .blend_mode = BlendMode::kSource}); + { + canvas.DrawPaint({.color = Color::White()}); + + canvas.Save(); + canvas.Transform(color_wheel_transform); + canvas.DrawImage(color_wheel_image, Point(), Paint()); + canvas.Restore(); + } + canvas.Restore(); + + canvas.Scale(content_scale); + canvas.Translate(Vector2(500, 400)); + canvas.Scale(Vector2(3, 3)); + + // Draw 3 circles to a subpass and blend it in. + canvas.SaveLayer( + {.color = Color::White().WithAlpha(src_alpha), + .blend_mode = blend_modes.blend_mode_values[current_blend_index]}); + { + Paint paint; + paint.blend_mode = BlendMode::kPlus; + const Scalar x = std::sin(k2Pi / 3); + const Scalar y = -std::cos(k2Pi / 3); + paint.color = color0; + canvas.DrawCircle(Point(-x, y) * 45, 65, paint); + paint.color = color1; + canvas.DrawCircle(Point(0, -1) * 45, 65, paint); + paint.color = color2; + canvas.DrawCircle(Point(x, y) * 45, 65, paint); + } + canvas.Restore(); + + return canvas.EndRecordingAsPicture(); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(AiksTest, ForegroundBlendSubpassCollapseOptimization) { + Canvas canvas; + + canvas.SaveLayer({ + .color_filter = + ColorFilter::MakeBlend(BlendMode::kColorDodge, Color::Red()), + }); + + canvas.Translate({500, 300, 0}); + canvas.Rotate(Radians(2 * kPi / 3)); + canvas.DrawRect(Rect::MakeXYWH(100, 100, 200, 200), {.color = Color::Blue()}); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, ClearBlend) { + Canvas canvas; + Paint white; + white.color = Color::Blue(); + canvas.DrawRect(Rect::MakeXYWH(0, 0, 600.0, 600.0), white); + + Paint clear; + clear.blend_mode = BlendMode::kClear; + + canvas.DrawCircle(Point::MakeXY(300.0, 300.0), 200.0, clear); +} + +static Picture BlendModeTest(Vector2 content_scale, + BlendMode blend_mode, + const std::shared_ptr& src_image, + const std::shared_ptr& dst_image) { + Color destination_color = Color::CornflowerBlue().WithAlpha(0.75); + auto source_colors = std::vector({Color::White().WithAlpha(0.75), + Color::LimeGreen().WithAlpha(0.75), + Color::Black().WithAlpha(0.75)}); + + Canvas canvas; + canvas.DrawPaint({.color = Color::Black()}); + // TODO(bdero): Why does this cause the left image to double scale on high DPI + // displays. + // canvas.Scale(content_scale); + + //---------------------------------------------------------------------------- + /// 1. Save layer blending (top squares). + /// + + canvas.Save(); + for (const auto& color : source_colors) { + canvas.Save(); + { + canvas.ClipRect(Rect::MakeXYWH(25, 25, 100, 100)); + // Perform the blend in a SaveLayer so that the initial backdrop color is + // fully transparent black. SourceOver blend the result onto the parent + // pass. + canvas.SaveLayer({}); + { + canvas.DrawPaint({.color = destination_color}); + // Draw the source color in an offscreen pass and blend it to the parent + // pass. + canvas.SaveLayer({.blend_mode = blend_mode}); + { // + canvas.DrawRect(Rect::MakeXYWH(25, 25, 100, 100), {.color = color}); + } + canvas.Restore(); + } + canvas.Restore(); + } + canvas.Restore(); + canvas.Translate(Vector2(100, 0)); + } + canvas.RestoreToCount(0); + + //---------------------------------------------------------------------------- + /// 2. CPU blend modes (bottom squares). + /// + + canvas.Save(); + canvas.Translate({0, 100}); + // Perform the blend in a SaveLayer so that the initial backdrop color is + // fully transparent black. SourceOver blend the result onto the parent pass. + canvas.SaveLayer({}); + for (const auto& color : source_colors) { + // Simply write the CPU blended color to the pass. + canvas.DrawRect(Rect::MakeXYWH(25, 25, 100, 100), + {.color = destination_color.Blend(color, blend_mode), + .blend_mode = BlendMode::kSourceOver}); + canvas.Translate(Vector2(100, 0)); + } + canvas.Restore(); + canvas.Restore(); + + //---------------------------------------------------------------------------- + /// 3. Image blending (bottom images). + /// + /// Compare these results with the images in the Flutter blend mode + /// documentation: https://api.flutter.dev/flutter/dart-ui/BlendMode.html + /// + + canvas.Translate({0, 250}); + + // Draw grid behind the images. + canvas.DrawRect(Rect::MakeLTRB(0, 0, 800, 400), + {.color = Color::MakeRGBA8(41, 41, 41, 255)}); + Paint square_paint = {.color = Color::MakeRGBA8(15, 15, 15, 255)}; + for (int y = 0; y < 400 / 8; y++) { + for (int x = 0; x < 800 / 16; x++) { + canvas.DrawRect(Rect::MakeXYWH(x * 16 + (y % 2) * 8, y * 8, 8, 8), + square_paint); + } + } + + // Uploaded image source (left image). + canvas.Save(); + canvas.SaveLayer({.blend_mode = BlendMode::kSourceOver}); + { + canvas.DrawImage(dst_image, {0, 0}, {.blend_mode = BlendMode::kSourceOver}); + canvas.DrawImage(src_image, {0, 0}, {.blend_mode = blend_mode}); + } + canvas.Restore(); + canvas.Restore(); + + // Rendered image source (right image). + canvas.Save(); + canvas.SaveLayer({.blend_mode = BlendMode::kSourceOver}); + { + canvas.DrawImage(dst_image, {400, 0}, + {.blend_mode = BlendMode::kSourceOver}); + canvas.SaveLayer({.blend_mode = blend_mode}); + { + canvas.DrawImage(src_image, {400, 0}, + {.blend_mode = BlendMode::kSourceOver}); + } + canvas.Restore(); + } + canvas.Restore(); + canvas.Restore(); + + return canvas.EndRecordingAsPicture(); +} + +#define BLEND_MODE_TEST(blend_mode) \ + TEST_P(AiksTest, BlendMode##blend_mode) { \ + auto src_image = std::make_shared( \ + CreateTextureForFixture("blend_mode_src.png")); \ + auto dst_image = std::make_shared( \ + CreateTextureForFixture("blend_mode_dst.png")); \ + OpenPlaygroundHere(BlendModeTest( \ + GetContentScale(), BlendMode::k##blend_mode, src_image, dst_image)); \ + } +IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TEST) + +TEST_P(AiksTest, CanDrawPaintMultipleTimesInteractive) { + auto modes = GetBlendModeSelection(); + + auto callback = [&](AiksContext& renderer) -> std::optional { + static Color background = Color::MediumTurquoise(); + static Color foreground = Color::Color::OrangeRed().WithAlpha(0.5); + static int current_blend_index = 3; + + if (AiksTest::ImGuiBegin("Controls", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::ColorEdit4("Background", reinterpret_cast(&background)); + ImGui::ColorEdit4("Foreground", reinterpret_cast(&foreground)); + ImGui::ListBox("Blend mode", ¤t_blend_index, + modes.blend_mode_names.data(), + modes.blend_mode_names.size()); + ImGui::End(); + } + + Canvas canvas; + canvas.Scale(Vector2(0.2, 0.2)); + canvas.DrawPaint({.color = background}); + canvas.DrawPaint( + {.color = foreground, + .blend_mode = static_cast(current_blend_index)}); + return canvas.EndRecordingAsPicture(); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index ee375d67628a9..96354f78d14c8 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -130,28 +130,6 @@ TEST_P(AiksTest, CanRenderColorFilterWithInvertColorsDrawPaint) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -TEST_P(AiksTest, CanRenderAdvancedBlendColorFilterWithSaveLayer) { - Canvas canvas; - - Rect layer_rect = Rect::MakeXYWH(0, 0, 500, 500); - canvas.ClipRect(layer_rect); - - canvas.SaveLayer( - { - .color_filter = ColorFilter::MakeBlend(BlendMode::kDifference, - Color(0, 1, 0, 0.5)), - }, - layer_rect); - - Paint paint; - canvas.DrawPaint({.color = Color::Black()}); - canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), - {.color = Color::White()}); - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - namespace { bool GenerateMipmap(const std::shared_ptr& context, std::shared_ptr texture, @@ -517,31 +495,6 @@ TEST_P(AiksTest, CanEmptyPictureConvertToImage) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -TEST_P(AiksTest, BlendModeShouldCoverWholeScreen) { - Canvas canvas; - Paint paint; - - paint.color = Color::Red(); - canvas.DrawPaint(paint); - - paint.blend_mode = BlendMode::kSourceOver; - canvas.SaveLayer(paint); - - paint.color = Color::White(); - canvas.DrawRect(Rect::MakeXYWH(100, 100, 400, 400), paint); - - paint.blend_mode = BlendMode::kSource; - canvas.SaveLayer(paint); - - paint.color = Color::Blue(); - canvas.DrawRect(Rect::MakeXYWH(200, 200, 200, 200), paint); - - canvas.Restore(); - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - TEST_P(AiksTest, CanRenderGroupOpacity) { Canvas canvas; @@ -984,124 +937,6 @@ TEST_P(AiksTest, CanDrawPaintMultipleTimes) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -TEST_P(AiksTest, CanDrawPaintWithAdvancedBlend) { - Canvas canvas; - canvas.Scale(Vector2(0.2, 0.2)); - canvas.DrawPaint({.color = Color::MediumTurquoise()}); - canvas.DrawPaint({.color = Color::Color::OrangeRed().WithAlpha(0.5), - .blend_mode = BlendMode::kHue}); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, DrawPaintWithAdvancedBlendOverFilter) { - Paint filtered = { - .color = Color::Black(), - .mask_blur_descriptor = - Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kNormal, - .sigma = Sigma(60), - }, - }; - - Canvas canvas; - canvas.DrawPaint({.color = Color::White()}); - canvas.DrawCircle({300, 300}, 200, filtered); - canvas.DrawPaint({.color = Color::Green(), .blend_mode = BlendMode::kScreen}); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, DrawAdvancedBlendPartlyOffscreen) { - std::vector colors = {Color{0.9568, 0.2627, 0.2118, 1.0}, - Color{0.1294, 0.5882, 0.9529, 1.0}}; - std::vector stops = {0.0, 1.0}; - - Paint paint = { - .color_source = ColorSource::MakeLinearGradient( - {0, 0}, {100, 100}, std::move(colors), std::move(stops), - Entity::TileMode::kRepeat, Matrix::MakeScale(Vector3(0.3, 0.3, 0.3))), - .blend_mode = BlendMode::kLighten, - }; - - Canvas canvas; - canvas.DrawPaint({.color = Color::Blue()}); - canvas.Scale(Vector2(2, 2)); - canvas.ClipRect(Rect::MakeLTRB(0, 0, 200, 200)); - canvas.DrawCircle({100, 100}, 100, paint); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -#define BLEND_MODE_TUPLE(blend_mode) {#blend_mode, BlendMode::k##blend_mode}, - -struct BlendModeSelection { - std::vector blend_mode_names; - std::vector blend_mode_values; -}; - -static BlendModeSelection GetBlendModeSelection() { - std::vector blend_mode_names; - std::vector blend_mode_values; - { - const std::vector> blends = { - IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TUPLE)}; - assert(blends.size() == - static_cast(Entity::kLastAdvancedBlendMode) + 1); - for (const auto& [name, mode] : blends) { - blend_mode_names.push_back(name); - blend_mode_values.push_back(mode); - } - } - - return {blend_mode_names, blend_mode_values}; -} - -TEST_P(AiksTest, CanDrawPaintMultipleTimesInteractive) { - auto modes = GetBlendModeSelection(); - - auto callback = [&](AiksContext& renderer) -> std::optional { - static Color background = Color::MediumTurquoise(); - static Color foreground = Color::Color::OrangeRed().WithAlpha(0.5); - static int current_blend_index = 3; - - if (AiksTest::ImGuiBegin("Controls", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::ColorEdit4("Background", reinterpret_cast(&background)); - ImGui::ColorEdit4("Foreground", reinterpret_cast(&foreground)); - ImGui::ListBox("Blend mode", ¤t_blend_index, - modes.blend_mode_names.data(), - modes.blend_mode_names.size()); - ImGui::End(); - } - - Canvas canvas; - canvas.Scale(Vector2(0.2, 0.2)); - canvas.DrawPaint({.color = background}); - canvas.DrawPaint( - {.color = foreground, - .blend_mode = static_cast(current_blend_index)}); - return canvas.EndRecordingAsPicture(); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(AiksTest, PaintBlendModeIsRespected) { - Paint paint; - Canvas canvas; - // Default is kSourceOver. - paint.color = Color(1, 0, 0, 0.5); - canvas.DrawCircle(Point(150, 200), 100, paint); - paint.color = Color(0, 1, 0, 0.5); - canvas.DrawCircle(Point(250, 200), 100, paint); - - paint.blend_mode = BlendMode::kPlus; - paint.color = Color::Red(); - canvas.DrawCircle(Point(450, 250), 100, paint); - paint.color = Color::Green(); - canvas.DrawCircle(Point(550, 250), 100, paint); - paint.color = Color::Blue(); - canvas.DrawCircle(Point(500, 150), 100, paint); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - // This makes sure the WideGamut named tests use 16bit float pixel format. TEST_P(AiksTest, F16WideGamut) { if (GetParam() != PlaygroundBackend::kMetal) { @@ -1118,196 +953,6 @@ TEST_P(AiksTest, NotF16) { GetContext()->GetCapabilities()->GetDefaultColorFormat())); } -// Bug: https://github.com/flutter/flutter/issues/142549 -TEST_P(AiksTest, BlendModePlusAlphaWideGamut) { - if (GetParam() != PlaygroundBackend::kMetal) { - GTEST_SKIP_("This backend doesn't yet support wide gamut."); - } - EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(), - PixelFormat::kR16G16B16A16Float); - auto texture = CreateTextureForFixture("airplane.jpg", - /*enable_mipmapping=*/true); - - Canvas canvas; - canvas.Scale(GetContentScale()); - canvas.DrawPaint({.color = Color(0.9, 1.0, 0.9, 1.0)}); - canvas.SaveLayer({}); - Paint paint; - paint.blend_mode = BlendMode::kPlus; - paint.color = Color::Red(); - canvas.DrawRect(Rect::MakeXYWH(100, 100, 400, 400), paint); - paint.color = Color::White(); - canvas.DrawImageRect( - std::make_shared(texture), Rect::MakeSize(texture->GetSize()), - Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100), paint); - canvas.Restore(); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -// Bug: https://github.com/flutter/flutter/issues/142549 -TEST_P(AiksTest, BlendModePlusAlphaColorFilterWideGamut) { - if (GetParam() != PlaygroundBackend::kMetal) { - GTEST_SKIP_("This backend doesn't yet support wide gamut."); - } - EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(), - PixelFormat::kR16G16B16A16Float); - auto texture = CreateTextureForFixture("airplane.jpg", - /*enable_mipmapping=*/true); - - Canvas canvas; - canvas.Scale(GetContentScale()); - canvas.DrawPaint({.color = Color(0.1, 0.2, 0.1, 1.0)}); - canvas.SaveLayer({ - .color_filter = - ColorFilter::MakeBlend(BlendMode::kPlus, Color(Vector4{1, 0, 0, 1})), - }); - Paint paint; - paint.color = Color::Red(); - canvas.DrawRect(Rect::MakeXYWH(100, 100, 400, 400), paint); - paint.color = Color::White(); - canvas.DrawImageRect( - std::make_shared(texture), Rect::MakeSize(texture->GetSize()), - Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100), paint); - canvas.Restore(); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, ColorWheel) { - // Compare with https://fiddle.skia.org/c/@BlendModes - - BlendModeSelection blend_modes = GetBlendModeSelection(); - - auto draw_color_wheel = [](Canvas& canvas) { - /// color_wheel_sampler: r=0 -> fuchsia, r=2pi/3 -> yellow, r=4pi/3 -> - /// cyan domain: r >= 0 (because modulo used is non euclidean) - auto color_wheel_sampler = [](Radians r) { - Scalar x = r.radians / k2Pi + 1; - - // https://www.desmos.com/calculator/6nhjelyoaj - auto color_cycle = [](Scalar x) { - Scalar cycle = std::fmod(x, 6.0f); - return std::max(0.0f, std::min(1.0f, 2 - std::abs(2 - cycle))); - }; - return Color(color_cycle(6 * x + 1), // - color_cycle(6 * x - 1), // - color_cycle(6 * x - 3), // - 1); - }; - - Paint paint; - paint.blend_mode = BlendMode::kSourceOver; - - // Draw a fancy color wheel for the backdrop. - // https://www.desmos.com/calculator/xw7kafthwd - const int max_dist = 900; - for (int i = 0; i <= 900; i++) { - Radians r(kPhi / k2Pi * i); - Scalar distance = r.radians / std::powf(4.12, 0.0026 * r.radians); - Scalar normalized_distance = static_cast(i) / max_dist; - - paint.color = - color_wheel_sampler(r).WithAlpha(1.0f - normalized_distance); - Point position(distance * std::sin(r.radians), - -distance * std::cos(r.radians)); - - canvas.DrawCircle(position, 9 + normalized_distance * 3, paint); - } - }; - - std::shared_ptr color_wheel_image; - Matrix color_wheel_transform; - - auto callback = [&](AiksContext& renderer) -> std::optional { - // UI state. - static bool cache_the_wheel = true; - static int current_blend_index = 3; - static float dst_alpha = 1; - static float src_alpha = 1; - static Color color0 = Color::Red(); - static Color color1 = Color::Green(); - static Color color2 = Color::Blue(); - - if (AiksTest::ImGuiBegin("Controls", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Checkbox("Cache the wheel", &cache_the_wheel); - ImGui::ListBox("Blending mode", ¤t_blend_index, - blend_modes.blend_mode_names.data(), - blend_modes.blend_mode_names.size()); - ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1); - ImGui::ColorEdit4("Color A", reinterpret_cast(&color0)); - ImGui::ColorEdit4("Color B", reinterpret_cast(&color1)); - ImGui::ColorEdit4("Color C", reinterpret_cast(&color2)); - ImGui::SliderFloat("Destination alpha", &dst_alpha, 0, 1); - ImGui::End(); - } - - static Point content_scale; - Point new_content_scale = GetContentScale(); - - if (!cache_the_wheel || new_content_scale != content_scale) { - content_scale = new_content_scale; - - // Render the color wheel to an image. - - Canvas canvas; - canvas.Scale(content_scale); - - canvas.Translate(Vector2(500, 400)); - canvas.Scale(Vector2(3, 3)); - - draw_color_wheel(canvas); - auto color_wheel_picture = canvas.EndRecordingAsPicture(); - auto snapshot = color_wheel_picture.Snapshot(renderer); - if (!snapshot.has_value() || !snapshot->texture) { - return std::nullopt; - } - color_wheel_image = std::make_shared(snapshot->texture); - color_wheel_transform = snapshot->transform; - } - - Canvas canvas; - - // Blit the color wheel backdrop to the screen with managed alpha. - canvas.SaveLayer({.color = Color::White().WithAlpha(dst_alpha), - .blend_mode = BlendMode::kSource}); - { - canvas.DrawPaint({.color = Color::White()}); - - canvas.Save(); - canvas.Transform(color_wheel_transform); - canvas.DrawImage(color_wheel_image, Point(), Paint()); - canvas.Restore(); - } - canvas.Restore(); - - canvas.Scale(content_scale); - canvas.Translate(Vector2(500, 400)); - canvas.Scale(Vector2(3, 3)); - - // Draw 3 circles to a subpass and blend it in. - canvas.SaveLayer( - {.color = Color::White().WithAlpha(src_alpha), - .blend_mode = blend_modes.blend_mode_values[current_blend_index]}); - { - Paint paint; - paint.blend_mode = BlendMode::kPlus; - const Scalar x = std::sin(k2Pi / 3); - const Scalar y = -std::cos(k2Pi / 3); - paint.color = color0; - canvas.DrawCircle(Point(-x, y) * 45, 65, paint); - paint.color = color1; - canvas.DrawCircle(Point(0, -1) * 45, 65, paint); - paint.color = color2; - canvas.DrawCircle(Point(x, y) * 45, 65, paint); - } - canvas.Restore(); - - return canvas.EndRecordingAsPicture(); - }; - - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - TEST_P(AiksTest, TransformMultipliesCorrectly) { Canvas canvas; ASSERT_MATRIX_NEAR(canvas.GetCurrentTransform(), Matrix()); @@ -2236,21 +1881,6 @@ TEST_P(AiksTest, CollapsedDrawPaintInSubpassBackdropFilter) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -TEST_P(AiksTest, ForegroundBlendSubpassCollapseOptimization) { - Canvas canvas; - - canvas.SaveLayer({ - .color_filter = - ColorFilter::MakeBlend(BlendMode::kColorDodge, Color::Red()), - }); - - canvas.Translate({500, 300, 0}); - canvas.Rotate(Radians(2 * kPi / 3)); - canvas.DrawRect(Rect::MakeXYWH(100, 100, 200, 200), {.color = Color::Blue()}); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - TEST_P(AiksTest, ColorMatrixFilterSubpassCollapseOptimization) { Canvas canvas; @@ -2300,130 +1930,6 @@ TEST_P(AiksTest, SrgbToLinearFilterSubpassCollapseOptimization) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -static Picture BlendModeTest(Vector2 content_scale, - BlendMode blend_mode, - const std::shared_ptr& src_image, - const std::shared_ptr& dst_image) { - Color destination_color = Color::CornflowerBlue().WithAlpha(0.75); - auto source_colors = std::vector({Color::White().WithAlpha(0.75), - Color::LimeGreen().WithAlpha(0.75), - Color::Black().WithAlpha(0.75)}); - - Canvas canvas; - canvas.DrawPaint({.color = Color::Black()}); - // TODO(bdero): Why does this cause the left image to double scale on high DPI - // displays. - // canvas.Scale(content_scale); - - //---------------------------------------------------------------------------- - /// 1. Save layer blending (top squares). - /// - - canvas.Save(); - for (const auto& color : source_colors) { - canvas.Save(); - { - canvas.ClipRect(Rect::MakeXYWH(25, 25, 100, 100)); - // Perform the blend in a SaveLayer so that the initial backdrop color is - // fully transparent black. SourceOver blend the result onto the parent - // pass. - canvas.SaveLayer({}); - { - canvas.DrawPaint({.color = destination_color}); - // Draw the source color in an offscreen pass and blend it to the parent - // pass. - canvas.SaveLayer({.blend_mode = blend_mode}); - { // - canvas.DrawRect(Rect::MakeXYWH(25, 25, 100, 100), {.color = color}); - } - canvas.Restore(); - } - canvas.Restore(); - } - canvas.Restore(); - canvas.Translate(Vector2(100, 0)); - } - canvas.RestoreToCount(0); - - //---------------------------------------------------------------------------- - /// 2. CPU blend modes (bottom squares). - /// - - canvas.Save(); - canvas.Translate({0, 100}); - // Perform the blend in a SaveLayer so that the initial backdrop color is - // fully transparent black. SourceOver blend the result onto the parent pass. - canvas.SaveLayer({}); - for (const auto& color : source_colors) { - // Simply write the CPU blended color to the pass. - canvas.DrawRect(Rect::MakeXYWH(25, 25, 100, 100), - {.color = destination_color.Blend(color, blend_mode), - .blend_mode = BlendMode::kSourceOver}); - canvas.Translate(Vector2(100, 0)); - } - canvas.Restore(); - canvas.Restore(); - - //---------------------------------------------------------------------------- - /// 3. Image blending (bottom images). - /// - /// Compare these results with the images in the Flutter blend mode - /// documentation: https://api.flutter.dev/flutter/dart-ui/BlendMode.html - /// - - canvas.Translate({0, 250}); - - // Draw grid behind the images. - canvas.DrawRect(Rect::MakeLTRB(0, 0, 800, 400), - {.color = Color::MakeRGBA8(41, 41, 41, 255)}); - Paint square_paint = {.color = Color::MakeRGBA8(15, 15, 15, 255)}; - for (int y = 0; y < 400 / 8; y++) { - for (int x = 0; x < 800 / 16; x++) { - canvas.DrawRect(Rect::MakeXYWH(x * 16 + (y % 2) * 8, y * 8, 8, 8), - square_paint); - } - } - - // Uploaded image source (left image). - canvas.Save(); - canvas.SaveLayer({.blend_mode = BlendMode::kSourceOver}); - { - canvas.DrawImage(dst_image, {0, 0}, {.blend_mode = BlendMode::kSourceOver}); - canvas.DrawImage(src_image, {0, 0}, {.blend_mode = blend_mode}); - } - canvas.Restore(); - canvas.Restore(); - - // Rendered image source (right image). - canvas.Save(); - canvas.SaveLayer({.blend_mode = BlendMode::kSourceOver}); - { - canvas.DrawImage(dst_image, {400, 0}, - {.blend_mode = BlendMode::kSourceOver}); - canvas.SaveLayer({.blend_mode = blend_mode}); - { - canvas.DrawImage(src_image, {400, 0}, - {.blend_mode = BlendMode::kSourceOver}); - } - canvas.Restore(); - } - canvas.Restore(); - canvas.Restore(); - - return canvas.EndRecordingAsPicture(); -} - -#define BLEND_MODE_TEST(blend_mode) \ - TEST_P(AiksTest, BlendMode##blend_mode) { \ - auto src_image = std::make_shared( \ - CreateTextureForFixture("blend_mode_src.png")); \ - auto dst_image = std::make_shared( \ - CreateTextureForFixture("blend_mode_dst.png")); \ - OpenPlaygroundHere(BlendModeTest( \ - GetContentScale(), BlendMode::k##blend_mode, src_image, dst_image)); \ - } -IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TEST) - TEST_P(AiksTest, TranslucentSaveLayerDrawsCorrectly) { Canvas canvas; @@ -3227,18 +2733,6 @@ TEST_P(AiksTest, VerticesGeometryUVPositionDataWithTranslate) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -TEST_P(AiksTest, ClearBlend) { - Canvas canvas; - Paint white; - white.color = Color::Blue(); - canvas.DrawRect(Rect::MakeXYWH(0, 0, 600.0, 600.0), white); - - Paint clear; - clear.blend_mode = BlendMode::kClear; - - canvas.DrawCircle(Point::MakeXY(300.0, 300.0), 200.0, clear); -} - TEST_P(AiksTest, MatrixImageFilterMagnify) { Canvas canvas; canvas.Scale(GetContentScale()); @@ -3587,6 +3081,7 @@ TEST_P(AiksTest, MipmapGenerationWorksCorrectly) { // █ the subdivisions of AiksTest to avoid having one massive file. // █ // █ Subdivisions: +// █ - aiks_blend_unittests.cc // █ - aiks_blur_unittests.cc // █ - aiks_gradient_unittests.cc // █ - aiks_path_unittests.cc From 70021ad413fcf2d5def0deab9a441b585b1ca840 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 29 Mar 2024 11:48:35 -0700 Subject: [PATCH 2/2] license --- ci/licenses_golden/excluded_files | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index a3b47fc20bb3f..94cf7dc8283b0 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -124,6 +124,7 @@ ../../../flutter/impeller/.clang-format ../../../flutter/impeller/.gitignore ../../../flutter/impeller/README.md +../../../flutter/impeller/aiks/aiks_blend_unittests.cc ../../../flutter/impeller/aiks/aiks_blur_unittests.cc ../../../flutter/impeller/aiks/aiks_gradient_unittests.cc ../../../flutter/impeller/aiks/aiks_path_unittests.cc