diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index 22f71de36d258..91e80b6015968 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -145,6 +145,7 @@ ../../../flutter/impeller/display_list/aiks_dl_atlas_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_basic_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_blend_unittests.cc +../../../flutter/impeller/display_list/aiks_dl_blur_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_clip_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_gradient_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_opacity_unittests.cc diff --git a/display_list/dl_color.h b/display_list/dl_color.h index c7fd49b0857ac..be13257740a56 100644 --- a/display_list/dl_color.h +++ b/display_list/dl_color.h @@ -58,6 +58,12 @@ struct DlColor { static constexpr DlColor kCornflowerBlue() {return DlColor(0xFF6495ED);}; static constexpr DlColor kCrimson() {return DlColor(0xFFFF5733);}; static constexpr DlColor kAqua() {return DlColor(0xFF00FFFF);}; + static constexpr DlColor kOrange() {return DlColor(0xFFFFA500);}; + static constexpr DlColor kPurple() {return DlColor(0xFF800080);}; + static constexpr DlColor kLimeGreen() {return DlColor(0xFF32CD32);}; + static constexpr DlColor kGreenYellow() {return DlColor(0xFFADFF2F);}; + static constexpr DlColor kDarkMagenta() {return DlColor(0xFF8B008B);}; + static constexpr DlColor kOrangeRed() {return DlColor(0xFFFF4500);}; // clang-format on constexpr bool isOpaque() const { return getAlpha() == 0xFF; } diff --git a/impeller/aiks/aiks_blur_unittests.cc b/impeller/aiks/aiks_blur_unittests.cc index 95d7b1d10f568..640d8281ec5dd 100644 --- a/impeller/aiks/aiks_blur_unittests.cc +++ b/impeller/aiks/aiks_blur_unittests.cc @@ -5,7 +5,6 @@ #include "flutter/impeller/aiks/aiks_unittests.h" #include "impeller/aiks/canvas.h" -#include "impeller/entity/contents/filters/gaussian_blur_filter_contents.h" #include "impeller/entity/render_target_cache.h" #include "impeller/geometry/path_builder.h" #include "impeller/playground/widgets.h" @@ -20,210 +19,6 @@ namespace impeller { namespace testing { -TEST_P(AiksTest, CanRenderMaskBlurHugeSigma) { - Canvas canvas; - canvas.DrawCircle({400, 400}, 300, - {.color = Color::Green(), - .mask_blur_descriptor = Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kNormal, - .sigma = Sigma(99999), - }}); - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, CanRenderForegroundBlendWithMaskBlur) { - // This case triggers the ForegroundPorterDuffBlend path. The color filter - // should apply to the color only, and respect the alpha mask. - Canvas canvas; - canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400)); - canvas.DrawCircle({400, 400}, 200, - { - .color = Color::White(), - .color_filter = ColorFilter::MakeBlend( - BlendMode::kSource, Color::Green()), - .mask_blur_descriptor = - Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kNormal, - .sigma = Radius(20), - }, - }); - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, CanRenderForegroundAdvancedBlendWithMaskBlur) { - // This case triggers the ForegroundAdvancedBlend path. The color filter - // should apply to the color only, and respect the alpha mask. - Canvas canvas; - canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400)); - canvas.DrawCircle({400, 400}, 200, - { - .color = Color::Grey(), - .color_filter = ColorFilter::MakeBlend( - BlendMode::kColor, Color::Green()), - .mask_blur_descriptor = - Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kNormal, - .sigma = Radius(20), - }, - }); - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, CanRenderBackdropBlurInteractive) { - auto callback = [&](AiksContext& renderer) -> std::optional { - static PlaygroundPoint point_a(Point(50, 50), 30, Color::White()); - static PlaygroundPoint point_b(Point(300, 200), 30, Color::White()); - auto [a, b] = DrawPlaygroundLine(point_a, point_b); - - Canvas canvas; - canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()}); - canvas.DrawCircle({300, 200}, 100, {.color = Color::GreenYellow()}); - canvas.DrawCircle({140, 170}, 75, {.color = Color::DarkMagenta()}); - canvas.DrawCircle({180, 120}, 100, {.color = Color::OrangeRed()}); - canvas.ClipRRect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), {20, 20}); - canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt, - ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0), - FilterContents::BlurStyle::kNormal, - Entity::TileMode::kClamp)); - canvas.Restore(); - - return canvas.EndRecordingAsPicture(); - }; - - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(AiksTest, CanRenderBackdropBlur) { - Canvas canvas; - canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()}); - canvas.DrawCircle({300, 200}, 100, {.color = Color::GreenYellow()}); - canvas.DrawCircle({140, 170}, 75, {.color = Color::DarkMagenta()}); - canvas.DrawCircle({180, 120}, 100, {.color = Color::OrangeRed()}); - canvas.ClipRRect(Rect::MakeLTRB(75, 50, 375, 275), {20, 20}); - canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt, - ImageFilter::MakeBlur(Sigma(30.0), Sigma(30.0), - FilterContents::BlurStyle::kNormal, - Entity::TileMode::kClamp)); - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, CanRenderBackdropBlurHugeSigma) { - Canvas canvas; - canvas.DrawCircle({400, 400}, 300, {.color = Color::Green()}); - canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt, - ImageFilter::MakeBlur(Sigma(999999), Sigma(999999), - FilterContents::BlurStyle::kNormal, - Entity::TileMode::kClamp)); - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, CanRenderClippedBlur) { - Canvas canvas; - canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400)); - canvas.DrawCircle( - {400, 400}, 200, - { - .color = Color::Green(), - .image_filter = ImageFilter::MakeBlur( - Sigma(20.0), Sigma(20.0), FilterContents::BlurStyle::kNormal, - Entity::TileMode::kDecal), - }); - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, ClippedBlurFilterRendersCorrectlyInteractive) { - auto callback = [&](AiksContext& renderer) -> std::optional { - static PlaygroundPoint playground_point(Point(400, 400), 20, - Color::Green()); - auto point = DrawPlaygroundPoint(playground_point); - - Canvas canvas; - canvas.Translate(point - Point(400, 400)); - Paint paint; - paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kNormal, - .sigma = Radius{120 * 3}, - }; - paint.color = Color::Red(); - PathBuilder builder{}; - builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800)); - canvas.DrawPath(builder.TakePath(), paint); - return canvas.EndRecordingAsPicture(); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(AiksTest, ClippedBlurFilterRendersCorrectly) { - Canvas canvas; - canvas.Translate(Point(0, -400)); - Paint paint; - paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kNormal, - .sigma = Radius{120 * 3}, - }; - paint.color = Color::Red(); - PathBuilder builder{}; - builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800)); - canvas.DrawPath(builder.TakePath(), paint); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, ClearBlendWithBlur) { - 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; - clear.mask_blur_descriptor = Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kNormal, - .sigma = Sigma(20), - }; - - canvas.DrawCircle(Point::MakeXY(300.0, 300.0), 200.0, clear); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, BlurHasNoEdge) { - Scalar sigma = 47.6; - auto callback = [&](AiksContext& renderer) -> std::optional { - if (AiksTest::ImGuiBegin("Controls", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::SliderFloat("Sigma", &sigma, 0, 50); - ImGui::End(); - } - Canvas canvas; - canvas.Scale(GetContentScale()); - canvas.DrawPaint({}); - Paint blur = { - .color = Color::Green(), - .mask_blur_descriptor = - Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kNormal, - .sigma = Sigma(sigma), - }, - }; - canvas.DrawRect(Rect::MakeXYWH(300, 300, 200, 200), blur); - return canvas.EndRecordingAsPicture(); - }; - - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - TEST_P(AiksTest, BlurredRectangleWithShader) { Canvas canvas; canvas.Scale(GetContentScale()); @@ -274,228 +69,6 @@ TEST_P(AiksTest, BlurredRectangleWithShader) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -TEST_P(AiksTest, MaskBlurWithZeroSigmaIsSkipped) { - Canvas canvas; - - Paint paint = { - .color = Color::Blue(), - .mask_blur_descriptor = - Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kNormal, - .sigma = Sigma(0), - }, - }; - - canvas.DrawCircle({300, 300}, 200, paint); - canvas.DrawRect(Rect::MakeLTRB(100, 300, 500, 600), paint); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -struct MaskBlurTestConfig { - FilterContents::BlurStyle style = FilterContents::BlurStyle::kNormal; - Scalar sigma = 1.0f; - Scalar alpha = 1.0f; - std::shared_ptr image_filter; - bool invert_colors = false; - BlendMode blend_mode = BlendMode::kSourceOver; -}; - -static Picture MaskBlurVariantTest(const AiksTest& test_context, - const MaskBlurTestConfig& config) { - Canvas canvas; - canvas.Scale(test_context.GetContentScale()); - canvas.Scale(Vector2{0.8f, 0.8f}); - Paint paint; - paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kNormal, - .sigma = Sigma{1}, - }; - - canvas.DrawPaint({.color = Color::AntiqueWhite()}); - - paint.mask_blur_descriptor->style = config.style; - paint.mask_blur_descriptor->sigma = Sigma{config.sigma}; - paint.image_filter = config.image_filter; - paint.invert_colors = config.invert_colors; - paint.blend_mode = config.blend_mode; - - const Scalar x = 50; - const Scalar radius = 20.0f; - const Scalar y_spacing = 100.0f; - - Scalar y = 50; - paint.color = Color::Crimson().WithAlpha(config.alpha); - canvas.DrawRect(Rect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, // - radius, 60.0f - radius), - paint); - - y += y_spacing; - paint.color = Color::Blue().WithAlpha(config.alpha); - canvas.DrawCircle({x + 25, y + 25}, radius, paint); - - y += y_spacing; - paint.color = Color::Green().WithAlpha(config.alpha); - canvas.DrawOval(Rect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, // - radius, 60.0f - radius), - paint); - - y += y_spacing; - paint.color = Color::Purple().WithAlpha(config.alpha); - canvas.DrawRRect(Rect::MakeXYWH(x, y, 60.0f, 60.0f), // - {radius, radius}, // - paint); - - y += y_spacing; - paint.color = Color::Orange().WithAlpha(config.alpha); - canvas.DrawRRect(Rect::MakeXYWH(x, y, 60.0f, 60.0f), // - {radius, 5.0f}, paint); - - y += y_spacing; - paint.color = Color::Maroon().WithAlpha(config.alpha); - canvas.DrawPath(PathBuilder{} - .MoveTo({x + 0, y + 60}) - .LineTo({x + 30, y + 0}) - .LineTo({x + 60, y + 60}) - .Close() - .TakePath(), - paint); - - y += y_spacing; - paint.color = Color::Maroon().WithAlpha(config.alpha); - canvas.DrawPath(PathBuilder{} - .AddArc(Rect::MakeXYWH(x + 5, y, 50, 50), - Radians{kPi / 2}, Radians{kPi}) - .AddArc(Rect::MakeXYWH(x + 25, y, 50, 50), - Radians{kPi / 2}, Radians{kPi}) - .Close() - .TakePath(), - paint); - - return canvas.EndRecordingAsPicture(); -} - -static const std::map kPaintVariations = { - // 1. Normal style, translucent, zero sigma. - {"NormalTranslucentZeroSigma", - {.style = FilterContents::BlurStyle::kNormal, - .sigma = 0.0f, - .alpha = 0.5f}}, - // 2. Normal style, translucent. - {"NormalTranslucent", - {.style = FilterContents::BlurStyle::kNormal, - .sigma = 8.0f, - .alpha = 0.5f}}, - // 3. Solid style, translucent. - {"SolidTranslucent", - {.style = FilterContents::BlurStyle::kSolid, - .sigma = 8.0f, - .alpha = 0.5f}}, - // 4. Solid style, opaque. - {"SolidOpaque", - {.style = FilterContents::BlurStyle::kSolid, .sigma = 8.0f}}, - // 5. Solid style, translucent, color & image filtered. - {"SolidTranslucentWithFilters", - {.style = FilterContents::BlurStyle::kSolid, - .sigma = 8.0f, - .alpha = 0.5f, - .image_filter = ImageFilter::MakeBlur(Sigma{3}, - Sigma{3}, - FilterContents::BlurStyle::kNormal, - Entity::TileMode::kClamp), - .invert_colors = true}}, - // 6. Solid style, translucent, exclusion blended. - {"SolidTranslucentExclusionBlend", - {.style = FilterContents::BlurStyle::kSolid, - .sigma = 8.0f, - .alpha = 0.5f, - .blend_mode = BlendMode::kExclusion}}, - // 7. Inner style, translucent. - {"InnerTranslucent", - {.style = FilterContents::BlurStyle::kInner, - .sigma = 8.0f, - .alpha = 0.5f}}, - // 8. Inner style, translucent, blurred. - {"InnerTranslucentWithBlurImageFilter", - {.style = FilterContents::BlurStyle::kInner, - .sigma = 8.0f, - .alpha = 0.5f, - .image_filter = ImageFilter::MakeBlur(Sigma{3}, - Sigma{3}, - FilterContents::BlurStyle::kNormal, - Entity::TileMode::kClamp)}}, - // 9. Outer style, translucent. - {"OuterTranslucent", - {.style = FilterContents::BlurStyle::kOuter, - .sigma = 8.0f, - .alpha = 0.5f}}, - // 10. Outer style, opaque, image filtered. - {"OuterOpaqueWithBlurImageFilter", - {.style = FilterContents::BlurStyle::kOuter, - .sigma = 8.0f, - .image_filter = ImageFilter::MakeBlur(Sigma{3}, - Sigma{3}, - FilterContents::BlurStyle::kNormal, - Entity::TileMode::kClamp)}}, -}; - -#define MASK_BLUR_VARIANT_TEST(config) \ - TEST_P(AiksTest, MaskBlurVariantTest##config) { \ - ASSERT_TRUE(OpenPlaygroundHere( \ - MaskBlurVariantTest(*this, kPaintVariations.at(#config)))); \ - } - -MASK_BLUR_VARIANT_TEST(NormalTranslucentZeroSigma) -MASK_BLUR_VARIANT_TEST(NormalTranslucent) -MASK_BLUR_VARIANT_TEST(SolidTranslucent) -MASK_BLUR_VARIANT_TEST(SolidOpaque) -MASK_BLUR_VARIANT_TEST(SolidTranslucentWithFilters) -MASK_BLUR_VARIANT_TEST(SolidTranslucentExclusionBlend) -MASK_BLUR_VARIANT_TEST(InnerTranslucent) -MASK_BLUR_VARIANT_TEST(InnerTranslucentWithBlurImageFilter) -MASK_BLUR_VARIANT_TEST(OuterTranslucent) -MASK_BLUR_VARIANT_TEST(OuterOpaqueWithBlurImageFilter) - -#undef MASK_BLUR_VARIANT_TEST - -TEST_P(AiksTest, GaussianBlurAtPeripheryVertical) { - Canvas canvas; - - canvas.Scale(GetContentScale()); - canvas.DrawRRect(Rect::MakeLTRB(0, 0, GetWindowSize().width, 100), - Size(10, 10), Paint{.color = Color::LimeGreen()}); - canvas.DrawRRect(Rect::MakeLTRB(0, 110, GetWindowSize().width, 210), - Size(10, 10), Paint{.color = Color::Magenta()}); - canvas.ClipRect(Rect::MakeLTRB(100, 0, 200, GetWindowSize().height)); - canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt, - ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0), - FilterContents::BlurStyle::kNormal, - Entity::TileMode::kClamp)); - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, GaussianBlurAtPeripheryHorizontal) { - Canvas canvas; - - canvas.Scale(GetContentScale()); - std::shared_ptr boston = CreateTextureForFixture("boston.jpg"); - canvas.DrawImageRect( - std::make_shared(boston), - Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height), - Rect::MakeLTRB(0, 0, GetWindowSize().width, 100), Paint{}); - canvas.DrawRRect(Rect::MakeLTRB(0, 110, GetWindowSize().width, 210), - Size(10, 10), Paint{.color = Color::Magenta()}); - canvas.ClipRect(Rect::MakeLTRB(0, 50, GetWindowSize().width, 150)); - canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt, - ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0), - FilterContents::BlurStyle::kNormal, - Entity::TileMode::kClamp)); - canvas.Restore(); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - #define FLT_FORWARD(mock, real, method) \ EXPECT_CALL(*mock, method()) \ .WillRepeatedly(::testing::Return(real->method())); @@ -548,162 +121,6 @@ TEST_P(AiksTest, GaussianBlurWithoutDecalSupport) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -TEST_P(AiksTest, GaussianBlurOneDimension) { - Canvas canvas; - - canvas.Scale(GetContentScale()); - canvas.Scale({0.5, 0.5, 1.0}); - std::shared_ptr boston = CreateTextureForFixture("boston.jpg"); - canvas.DrawImage(std::make_shared(boston), Point(100, 100), Paint{}); - canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt, - ImageFilter::MakeBlur(Sigma(50.0), Sigma(0.0), - FilterContents::BlurStyle::kNormal, - Entity::TileMode::kClamp)); - canvas.Restore(); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -// Smoketest to catch issues with the coverage hint. -// Draws a rotated blurred image within a rectangle clip. The center of the clip -// rectangle is the center of the rotated image. The entire area of the clip -// rectangle should be filled with opaque colors output by the blur. -TEST_P(AiksTest, GaussianBlurRotatedAndClipped) { - Canvas canvas; - std::shared_ptr boston = CreateTextureForFixture("boston.jpg"); - Rect bounds = - Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height); - Vector2 image_center = Vector2(bounds.GetSize() / 2); - Paint paint = {.image_filter = - ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0), - FilterContents::BlurStyle::kNormal, - Entity::TileMode::kDecal)}; - Vector2 clip_size = {150, 75}; - Vector2 center = Vector2(1024, 768) / 2; - canvas.Scale(GetContentScale()); - canvas.ClipRect( - Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size)); - canvas.Translate({center.x, center.y, 0}); - canvas.Scale({0.6, 0.6, 1}); - canvas.Rotate(Degrees(25)); - - canvas.DrawImageRect(std::make_shared(boston), /*source=*/bounds, - /*dest=*/bounds.Shift(-image_center), paint); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, GaussianBlurScaledAndClipped) { - Canvas canvas; - std::shared_ptr boston = CreateTextureForFixture("boston.jpg"); - Rect bounds = - Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height); - Vector2 image_center = Vector2(bounds.GetSize() / 2); - Paint paint = {.image_filter = - ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0), - FilterContents::BlurStyle::kNormal, - Entity::TileMode::kDecal)}; - Vector2 clip_size = {150, 75}; - Vector2 center = Vector2(1024, 768) / 2; - canvas.Scale(GetContentScale()); - canvas.ClipRect( - Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size)); - canvas.Translate({center.x, center.y, 0}); - canvas.Scale({0.6, 0.6, 1}); - - canvas.DrawImageRect(std::make_shared(boston), /*source=*/bounds, - /*dest=*/bounds.Shift(-image_center), paint); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, GaussianBlurRotatedAndClippedInteractive) { - std::shared_ptr boston = CreateTextureForFixture("boston.jpg"); - - auto callback = [&](AiksContext& renderer) -> std::optional { - const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; - const Entity::TileMode tile_modes[] = { - Entity::TileMode::kClamp, Entity::TileMode::kRepeat, - Entity::TileMode::kMirror, Entity::TileMode::kDecal}; - - static float rotation = 0; - static float scale = 0.6; - static int selected_tile_mode = 3; - - if (AiksTest::ImGuiBegin("Controls", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180); - ImGui::SliderFloat("Scale", &scale, 0, 2.0); - ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names, - sizeof(tile_mode_names) / sizeof(char*)); - ImGui::End(); - } - - Canvas canvas; - Rect bounds = - Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height); - Vector2 image_center = Vector2(bounds.GetSize() / 2); - Paint paint = {.image_filter = - ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0), - FilterContents::BlurStyle::kNormal, - tile_modes[selected_tile_mode])}; - static PlaygroundPoint point_a(Point(362, 309), 20, Color::Red()); - static PlaygroundPoint point_b(Point(662, 459), 20, Color::Red()); - auto [handle_a, handle_b] = DrawPlaygroundLine(point_a, point_b); - Vector2 center = Vector2(1024, 768) / 2; - canvas.Scale(GetContentScale()); - canvas.ClipRect( - Rect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y)); - canvas.Translate({center.x, center.y, 0}); - canvas.Scale({scale, scale, 1}); - canvas.Rotate(Degrees(rotation)); - - canvas.DrawImageRect(std::make_shared(boston), /*source=*/bounds, - /*dest=*/bounds.Shift(-image_center), paint); - return canvas.EndRecordingAsPicture(); - }; - - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(AiksTest, GaussianBlurRotatedNonUniform) { - auto callback = [&](AiksContext& renderer) -> std::optional { - const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; - const Entity::TileMode tile_modes[] = { - Entity::TileMode::kClamp, Entity::TileMode::kRepeat, - Entity::TileMode::kMirror, Entity::TileMode::kDecal}; - - static float rotation = 45; - static float scale = 0.6; - static int selected_tile_mode = 3; - - if (AiksTest::ImGuiBegin("Controls", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180); - ImGui::SliderFloat("Scale", &scale, 0, 2.0); - ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names, - sizeof(tile_mode_names) / sizeof(char*)); - ImGui::End(); - } - - Canvas canvas; - Paint paint = {.color = Color::Green(), - .image_filter = - ImageFilter::MakeBlur(Sigma(50.0), Sigma(0.0), - FilterContents::BlurStyle::kNormal, - tile_modes[selected_tile_mode])}; - Vector2 center = Vector2(1024, 768) / 2; - canvas.Scale(GetContentScale()); - canvas.Translate({center.x, center.y, 0}); - canvas.Scale({scale, scale, 1}); - canvas.Rotate(Degrees(rotation)); - - canvas.DrawRRect(Rect::MakeXYWH(-100, -100, 200, 200), Size(10, 10), paint); - return canvas.EndRecordingAsPicture(); - }; - - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - // This addresses a bug where tiny blurs could result in mip maps that beyond // the limits for the textures used for blurring. // See also: b/323402168 @@ -760,321 +177,5 @@ TEST_P(AiksTest, GaussianBlurBackdropTinyMipMap) { } } -TEST_P(AiksTest, GaussianBlurAnimatedBackdrop) { - // This test is for checking out how stable rendering is when content is - // translated underneath a blur. Animating under a blur can cause - // *shimmering* to happen as a result of pixel alignment. - // See also: https://github.com/flutter/flutter/issues/140193 - auto boston = std::make_shared( - CreateTextureForFixture("boston.jpg", /*enable_mipmapping=*/true)); - ASSERT_TRUE(boston); - int64_t count = 0; - Scalar sigma = 20.0; - Scalar freq = 0.1; - Scalar amp = 50.0; - auto callback = [&](AiksContext& renderer) -> std::optional { - if (AiksTest::ImGuiBegin("Controls", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::SliderFloat("Sigma", &sigma, 0, 200); - ImGui::SliderFloat("Frequency", &freq, 0.01, 2.0); - ImGui::SliderFloat("Amplitude", &, 1, 100); - ImGui::End(); - } - - Canvas canvas; - canvas.Scale(GetContentScale()); - Scalar y = amp * sin(freq * 2.0 * M_PI * count / 60); - canvas.DrawImage(boston, - Point(1024 / 2 - boston->GetSize().width / 2, - (768 / 2 - boston->GetSize().height / 2) + y), - {}); - static PlaygroundPoint point_a(Point(100, 100), 20, Color::Red()); - static PlaygroundPoint point_b(Point(900, 700), 20, Color::Red()); - auto [handle_a, handle_b] = DrawPlaygroundLine(point_a, point_b); - canvas.ClipRect( - Rect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y)); - canvas.ClipRect(Rect::MakeLTRB(100, 100, 900, 700)); - canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt, - ImageFilter::MakeBlur(Sigma(sigma), Sigma(sigma), - FilterContents::BlurStyle::kNormal, - Entity::TileMode::kClamp)); - count += 1; - return canvas.EndRecordingAsPicture(); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(AiksTest, GaussianBlurStyleInnerGradient) { - Canvas canvas; - canvas.Scale(GetContentScale()); - - canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)}); - - std::vector colors = {Color{0.9568, 0.2627, 0.2118, 1.0}, - Color{0.7568, 0.2627, 0.2118, 1.0}}; - std::vector stops = {0.0, 1.0}; - - Paint paint; - paint.color_source = ColorSource::MakeLinearGradient( - {0, 0}, {200, 200}, std::move(colors), std::move(stops), - Entity::TileMode::kMirror, {}); - paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kInner, - .sigma = Sigma(30), - }; - canvas.DrawPath(PathBuilder() - .MoveTo({200, 200}) - .LineTo({300, 400}) - .LineTo({100, 400}) - .Close() - .TakePath(), - paint); - - // Draw another thing to make sure the clip area is reset. - Paint red; - red.color = Color::Red(); - canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, GaussianBlurStyleSolidGradient) { - Canvas canvas; - canvas.Scale(GetContentScale()); - - canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)}); - - std::vector colors = {Color{0.9568, 0.2627, 0.2118, 1.0}, - Color{0.7568, 0.2627, 0.2118, 1.0}}; - std::vector stops = {0.0, 1.0}; - - Paint paint; - paint.color_source = ColorSource::MakeLinearGradient( - {0, 0}, {200, 200}, std::move(colors), std::move(stops), - Entity::TileMode::kMirror, {}); - paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kSolid, - .sigma = Sigma(30), - }; - canvas.DrawPath(PathBuilder() - .MoveTo({200, 200}) - .LineTo({300, 400}) - .LineTo({100, 400}) - .Close() - .TakePath(), - paint); - - // Draw another thing to make sure the clip area is reset. - Paint red; - red.color = Color::Red(); - canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, GaussianBlurStyleOuterGradient) { - Canvas canvas; - canvas.Scale(GetContentScale()); - - canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)}); - - std::vector colors = {Color{0.9568, 0.2627, 0.2118, 1.0}, - Color{0.7568, 0.2627, 0.2118, 1.0}}; - std::vector stops = {0.0, 1.0}; - - Paint paint; - paint.color_source = ColorSource::MakeLinearGradient( - {0, 0}, {200, 200}, std::move(colors), std::move(stops), - Entity::TileMode::kMirror, {}); - paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kOuter, - .sigma = Sigma(30), - }; - canvas.DrawPath(PathBuilder() - .MoveTo({200, 200}) - .LineTo({300, 400}) - .LineTo({100, 400}) - .Close() - .TakePath(), - paint); - - // Draw another thing to make sure the clip area is reset. - Paint red; - red.color = Color::Red(); - canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, GaussianBlurStyleInner) { - Canvas canvas; - canvas.Scale(GetContentScale()); - - canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)}); - - Paint paint; - paint.color = Color::Green(); - paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kInner, - .sigma = Sigma(30), - }; - canvas.DrawPath(PathBuilder() - .MoveTo({200, 200}) - .LineTo({300, 400}) - .LineTo({100, 400}) - .Close() - .TakePath(), - paint); - - // Draw another thing to make sure the clip area is reset. - Paint red; - red.color = Color::Red(); - canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, GaussianBlurStyleOuter) { - Canvas canvas; - canvas.Scale(GetContentScale()); - - canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)}); - - Paint paint; - paint.color = Color::Green(); - paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kOuter, - .sigma = Sigma(30), - }; - canvas.DrawPath(PathBuilder() - .MoveTo({200, 200}) - .LineTo({300, 400}) - .LineTo({100, 400}) - .Close() - .TakePath(), - paint); - - // Draw another thing to make sure the clip area is reset. - Paint red; - red.color = Color::Red(); - canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, GaussianBlurStyleSolid) { - Canvas canvas; - canvas.Scale(GetContentScale()); - - canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)}); - - Paint paint; - paint.color = Color::Green(); - paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kSolid, - .sigma = Sigma(30), - }; - canvas.DrawPath(PathBuilder() - .MoveTo({200, 200}) - .LineTo({300, 400}) - .LineTo({100, 400}) - .Close() - .TakePath(), - paint); - - // Draw another thing to make sure the clip area is reset. - Paint red; - red.color = Color::Red(); - canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, MaskBlurTexture) { - Scalar sigma = 30; - auto callback = [&](AiksContext& renderer) -> std::optional { - if (AiksTest::ImGuiBegin("Controls", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::SliderFloat("Sigma", &sigma, 0, 500); - ImGui::End(); - } - Canvas canvas; - canvas.Scale(GetContentScale()); - Paint paint; - paint.color = Color::Green(); - paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kNormal, - .sigma = Sigma(sigma), - }; - std::shared_ptr boston = CreateTextureForFixture("boston.jpg"); - canvas.DrawImage(std::make_shared(boston), {200, 200}, paint); - Paint red; - red.color = Color::Red(); - canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red); - return canvas.EndRecordingAsPicture(); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(AiksTest, GuassianBlurUpdatesMipmapContents) { - // This makes sure if mip maps are recycled across invocations of blurs the - // contents get updated each frame correctly. If they aren't updated the color - // inside the blur and outside the blur will be different. - // - // If there is some change to render target caching this could display a false - // positive in the future. Also, if the LOD that is rendered is 1 it could - // present a false positive. - int32_t count = 0; - auto callback = [&](AiksContext& renderer) -> std::optional { - Canvas canvas; - if (count++ == 0) { - canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()}); - } else { - canvas.DrawCircle({100, 100}, 50, {.color = Color::Chartreuse()}); - } - canvas.ClipRRect(Rect::MakeLTRB(75, 50, 375, 275), {20, 20}); - canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt, - ImageFilter::MakeBlur(Sigma(30.0), Sigma(30.0), - FilterContents::BlurStyle::kNormal, - Entity::TileMode::kClamp)); - canvas.Restore(); - return canvas.EndRecordingAsPicture(); - }; - - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(AiksTest, MaskBlurDoesntStretchContents) { - Scalar sigma = 70; - auto callback = [&](AiksContext& renderer) -> std::optional { - if (AiksTest::ImGuiBegin("Controls", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::SliderFloat("Sigma", &sigma, 0, 500); - ImGui::End(); - } - Canvas canvas; - canvas.Scale(GetContentScale()); - canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)}); - - std::shared_ptr boston = CreateTextureForFixture("boston.jpg"); - ColorSource image_source = ColorSource::MakeImage( - boston, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, {}); - - canvas.Transform(Matrix::MakeTranslation({100, 100, 0}) * - Matrix::MakeScale({0.5, 0.5, 1.0})); - Paint paint = { - .color_source = image_source, - .mask_blur_descriptor = - Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kNormal, - .sigma = Sigma(sigma), - }, - }; - canvas.DrawRect( - Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height), - paint); - - return canvas.EndRecordingAsPicture(); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - } // namespace testing } // namespace impeller diff --git a/impeller/display_list/BUILD.gn b/impeller/display_list/BUILD.gn index 712e1bb723974..e60ec4e9f37de 100644 --- a/impeller/display_list/BUILD.gn +++ b/impeller/display_list/BUILD.gn @@ -55,6 +55,7 @@ template("display_list_unittests_component") { "aiks_dl_atlas_unittests.cc", "aiks_dl_basic_unittests.cc", "aiks_dl_blend_unittests.cc", + "aiks_dl_blur_unittests.cc", "aiks_dl_clip_unittests.cc", "aiks_dl_gradient_unittests.cc", "aiks_dl_opacity_unittests.cc", diff --git a/impeller/display_list/aiks_dl_blur_unittests.cc b/impeller/display_list/aiks_dl_blur_unittests.cc new file mode 100644 index 0000000000000..0319bba01ddba --- /dev/null +++ b/impeller/display_list/aiks_dl_blur_unittests.cc @@ -0,0 +1,977 @@ +// 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 "display_list/display_list.h" +#include "display_list/dl_blend_mode.h" +#include "display_list/dl_builder.h" +#include "display_list/dl_color.h" +#include "display_list/dl_paint.h" +#include "display_list/dl_sampling_options.h" +#include "display_list/dl_tile_mode.h" +#include "display_list/effects/dl_color_filter.h" +#include "display_list/effects/dl_color_source.h" +#include "display_list/effects/dl_image_filter.h" +#include "display_list/effects/dl_mask_filter.h" +#include "flutter/impeller/aiks/aiks_unittests.h" + +#include "impeller/display_list/dl_image_impeller.h" +#include "impeller/playground/widgets.h" +#include "include/core/SkRRect.h" +#include "include/core/SkRect.h" +#include "third_party/imgui/imgui.h" + +//////////////////////////////////////////////////////////////////////////////// +// This is for tests of Canvas that are interested the results of rendering +// blurs. +//////////////////////////////////////////////////////////////////////////////// + +namespace impeller { +namespace testing { + +using namespace flutter; + +TEST_P(AiksTest, CanRenderMaskBlurHugeSigma) { + DisplayListBuilder builder; + + DlPaint paint; + paint.setColor(DlColor::kGreen()); + paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 99999)); + builder.DrawCircle({400, 400}, 300, paint); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanRenderForegroundBlendWithMaskBlur) { + // This case triggers the ForegroundPorterDuffBlend path. The color filter + // should apply to the color only, and respect the alpha mask. + DisplayListBuilder builder; + builder.ClipRect(SkRect::MakeXYWH(100, 150, 400, 400)); + + DlPaint paint; + paint.setColor(DlColor::kWhite()); + + Sigma sigma = Radius(20); + paint.setMaskFilter( + DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma.sigma)); + paint.setColorFilter( + DlBlendColorFilter::Make(DlColor::kGreen(), DlBlendMode::kSrc)); + builder.DrawCircle({400, 400}, 200, paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanRenderForegroundAdvancedBlendWithMaskBlur) { + // This case triggers the ForegroundAdvancedBlend path. The color filter + // should apply to the color only, and respect the alpha mask. + DisplayListBuilder builder; + builder.ClipRect(SkRect::MakeXYWH(100, 150, 400, 400)); + + DlPaint paint; + paint.setColor( + DlColor::RGBA(128.0f / 255.0f, 128.0f / 255.0f, 128.0f / 255.0f, 1.0f)); + + Sigma sigma = Radius(20); + paint.setMaskFilter( + DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma.sigma)); + paint.setColorFilter( + DlBlendColorFilter::Make(DlColor::kGreen(), DlBlendMode::kColor)); + builder.DrawCircle({400, 400}, 200, paint); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanRenderBackdropBlurInteractive) { + auto callback = [&]() -> sk_sp { + static PlaygroundPoint point_a(Point(50, 50), 30, Color::White()); + static PlaygroundPoint point_b(Point(300, 200), 30, Color::White()); + auto [a, b] = DrawPlaygroundLine(point_a, point_b); + + DisplayListBuilder builder; + DlPaint paint; + paint.setColor(DlColor::kCornflowerBlue()); + builder.DrawCircle({100, 100}, 50, paint); + + paint.setColor(DlColor::kGreenYellow()); + builder.DrawCircle({300, 200}, 100, paint); + + paint.setColor(DlColor::kDarkMagenta()); + builder.DrawCircle({140, 170}, 75, paint); + + paint.setColor(DlColor::kOrangeRed()); + builder.DrawCircle({180, 120}, 100, paint); + + SkRRect rrect = + SkRRect::MakeRectXY(SkRect::MakeLTRB(a.x, a.y, b.x, b.y), 20, 20); + builder.ClipRRect(rrect); + + DlPaint save_paint; + save_paint.setBlendMode(DlBlendMode::kSrc); + + auto backdrop_filter = DlBlurImageFilter::Make(20, 20, DlTileMode::kClamp); + builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get()); + builder.Restore(); + + return builder.Build(); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(AiksTest, CanRenderBackdropBlur) { + DisplayListBuilder builder; + + DlPaint paint; + paint.setColor(DlColor::kCornflowerBlue()); + builder.DrawCircle({100, 100}, 50, paint); + + paint.setColor(DlColor::kGreenYellow()); + builder.DrawCircle({300, 200}, 100, paint); + + paint.setColor(DlColor::kDarkMagenta()); + builder.DrawCircle({140, 170}, 75, paint); + + paint.setColor(DlColor::kOrangeRed()); + builder.DrawCircle({180, 120}, 100, paint); + + SkRRect rrect = + SkRRect::MakeRectXY(SkRect::MakeLTRB(75, 50, 375, 275), 20, 20); + builder.ClipRRect(rrect); + + DlPaint save_paint; + save_paint.setBlendMode(DlBlendMode::kSrc); + auto backdrop_filter = DlBlurImageFilter::Make(30, 30, DlTileMode::kClamp); + builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get()); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanRenderBackdropBlurHugeSigma) { + DisplayListBuilder builder; + + DlPaint paint; + paint.setColor(DlColor::kGreen()); + builder.DrawCircle({400, 400}, 300, paint); + + DlPaint save_paint; + save_paint.setBlendMode(DlBlendMode::kSrc); + + auto backdrop_filter = + DlBlurImageFilter::Make(999999, 999999, DlTileMode::kClamp); + builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get()); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanRenderClippedBlur) { + DisplayListBuilder builder; + builder.ClipRect(SkRect::MakeXYWH(100, 150, 400, 400)); + + DlPaint paint; + paint.setColor(DlColor::kGreen()); + paint.setImageFilter(DlBlurImageFilter::Make(20, 20, DlTileMode::kDecal)); + builder.DrawCircle({400, 400}, 200, paint); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, ClippedBlurFilterRendersCorrectlyInteractive) { + auto callback = [&]() -> sk_sp { + static PlaygroundPoint playground_point(Point(400, 400), 20, + Color::Green()); + auto point = DrawPlaygroundPoint(playground_point); + + DisplayListBuilder builder; + auto location = point - Point(400, 400); + builder.Translate(location.x, location.y); + + DlPaint paint; + Sigma sigma = Radius{120 * 3}; + paint.setMaskFilter( + DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma.sigma)); + paint.setColor(DlColor::kRed()); + + SkPath path = SkPath::Rect(SkRect::MakeLTRB(0, 0, 800, 800)); + path.addCircle(0, 0, 0.5); + builder.DrawPath(path, paint); + return builder.Build(); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(AiksTest, ClippedBlurFilterRendersCorrectly) { + DisplayListBuilder builder; + builder.Translate(0, -400); + DlPaint paint; + + Sigma sigma = Radius{120 * 3}; + paint.setMaskFilter( + DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma.sigma)); + paint.setColor(DlColor::kRed()); + + SkPath path = SkPath::Rect(SkRect::MakeLTRB(0, 0, 800, 800)); + path.addCircle(0, 0, 0.5); + builder.DrawPath(path, paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, ClearBlendWithBlur) { + DisplayListBuilder builder; + DlPaint paint; + paint.setColor(DlColor::kBlue()); + builder.DrawRect(SkRect::MakeXYWH(0, 0, 600.0, 600.0), paint); + + DlPaint clear; + clear.setBlendMode(DlBlendMode::kClear); + clear.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 20)); + + builder.DrawCircle({300.0, 300.0}, 200.0, clear); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, BlurHasNoEdge) { + Scalar sigma = 47.6; + auto callback = [&]() -> sk_sp { + if (AiksTest::ImGuiBegin("Controls", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::SliderFloat("Sigma", &sigma, 0, 50); + ImGui::End(); + } + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + builder.DrawPaint({}); + + DlPaint paint; + paint.setColor(DlColor::kGreen()); + paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma)); + + builder.DrawRect(SkRect::MakeXYWH(300, 300, 200, 200), paint); + return builder.Build(); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(AiksTest, MaskBlurWithZeroSigmaIsSkipped) { + DisplayListBuilder builder; + + DlPaint paint; + paint.setColor(DlColor::kBlue()); + paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 0)); + + builder.DrawCircle({300, 300}, 200, paint); + builder.DrawRect(SkRect::MakeLTRB(100, 300, 500, 600), paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +struct MaskBlurTestConfig { + DlBlurStyle style = DlBlurStyle::kNormal; + Scalar sigma = 1.0f; + Scalar alpha = 1.0f; + std::shared_ptr image_filter; + bool invert_colors = false; + DlBlendMode blend_mode = DlBlendMode::kSrcOver; +}; + +static sk_sp MaskBlurVariantTest( + const AiksTest& test_context, + const MaskBlurTestConfig& config) { + DisplayListBuilder builder; + builder.Scale(test_context.GetContentScale().x, + test_context.GetContentScale().y); + builder.Scale(0.8f, 0.8f); + + DlPaint draw_paint; + draw_paint.setColor( + DlColor::RGBA(Color::AntiqueWhite().red, Color::AntiqueWhite().green, + Color::AntiqueWhite().blue, Color::AntiqueWhite().alpha)); + builder.DrawPaint(draw_paint); + + DlPaint paint; + paint.setMaskFilter(DlBlurMaskFilter::Make(config.style, config.sigma)); + paint.setInvertColors(config.invert_colors); + paint.setImageFilter(config.image_filter); + paint.setBlendMode(config.blend_mode); + + const Scalar x = 50; + const Scalar radius = 20.0f; + const Scalar y_spacing = 100.0f; + Scalar alpha = config.alpha * 255; + + Scalar y = 50; + paint.setColor(DlColor::kCrimson().withAlpha(alpha)); + builder.DrawRect(SkRect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, // + radius, 60.0f - radius), + paint); + + y += y_spacing; + paint.setColor(DlColor::kBlue().withAlpha(alpha)); + builder.DrawCircle({x + 25, y + 25}, radius, paint); + + y += y_spacing; + paint.setColor(DlColor::kGreen().withAlpha(alpha)); + builder.DrawOval(SkRect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, // + radius, 60.0f - radius), + paint); + + y += y_spacing; + paint.setColor(DlColor::kPurple().withAlpha(alpha)); + SkRRect rrect = + SkRRect::MakeRectXY(SkRect::MakeXYWH(x, y, 60.0f, 60.0f), radius, radius); + builder.DrawRRect(rrect, paint); + + y += y_spacing; + paint.setColor(DlColor::kOrange().withAlpha(alpha)); + + rrect = + SkRRect::MakeRectXY(SkRect::MakeXYWH(x, y, 60.0f, 60.0f), radius, 5.0); + builder.DrawRRect(rrect, paint); + + y += y_spacing; + paint.setColor(DlColor::kMaroon().withAlpha(alpha)); + + { + SkPath path; + path.moveTo(x + 0, y + 60); + path.lineTo(x + 30, y + 0); + path.lineTo(x + 60, y + 60); + path.close(); + + builder.DrawPath(path, paint); + } + + y += y_spacing; + paint.setColor(DlColor::kMaroon().withAlpha(alpha)); + { + SkPath path; + path.addArc(SkRect::MakeXYWH(x + 5, y, 50, 50), 90, 180); + path.addArc(SkRect::MakeXYWH(x + 25, y, 50, 50), 90, 180); + path.close(); + builder.DrawPath(path, paint); + } + + return builder.Build(); +} + +static const std::map kPaintVariations = { + // 1. Normal style, translucent, zero sigma. + {"NormalTranslucentZeroSigma", + {.style = DlBlurStyle::kNormal, .sigma = 0.0f, .alpha = 0.5f}}, + // 2. Normal style, translucent. + {"NormalTranslucent", + {.style = DlBlurStyle::kNormal, .sigma = 8.0f, .alpha = 0.5f}}, + // 3. Solid style, translucent. + {"SolidTranslucent", + {.style = DlBlurStyle::kSolid, .sigma = 8.0f, .alpha = 0.5f}}, + // 4. Solid style, opaque. + {"SolidOpaque", {.style = DlBlurStyle::kSolid, .sigma = 8.0f}}, + // 5. Solid style, translucent, color & image filtered. + {"SolidTranslucentWithFilters", + {.style = DlBlurStyle::kSolid, + .sigma = 8.0f, + .alpha = 0.5f, + .image_filter = DlBlurImageFilter::Make(3, 3, DlTileMode::kClamp), + .invert_colors = true}}, + // 6. Solid style, translucent, exclusion blended. + {"SolidTranslucentExclusionBlend", + {.style = DlBlurStyle::kSolid, + .sigma = 8.0f, + .alpha = 0.5f, + .blend_mode = DlBlendMode::kExclusion}}, + // 7. Inner style, translucent. + {"InnerTranslucent", + {.style = DlBlurStyle::kInner, .sigma = 8.0f, .alpha = 0.5f}}, + // 8. Inner style, translucent, blurred. + {"InnerTranslucentWithBlurImageFilter", + {.style = DlBlurStyle::kInner, + .sigma = 8.0f, + .alpha = 0.5f, + .image_filter = DlBlurImageFilter::Make(3, 3, DlTileMode::kClamp)}}, + // 9. Outer style, translucent. + {"OuterTranslucent", + {.style = DlBlurStyle::kOuter, .sigma = 8.0f, .alpha = 0.5f}}, + // 10. Outer style, opaque, image filtered. + {"OuterOpaqueWithBlurImageFilter", + {.style = DlBlurStyle::kOuter, + .sigma = 8.0f, + .image_filter = DlBlurImageFilter::Make(3, 3, DlTileMode::kClamp)}}, +}; + +#define MASK_BLUR_VARIANT_TEST(config) \ + TEST_P(AiksTest, MaskBlurVariantTest##config) { \ + ASSERT_TRUE(OpenPlaygroundHere( \ + MaskBlurVariantTest(*this, kPaintVariations.at(#config)))); \ + } + +MASK_BLUR_VARIANT_TEST(NormalTranslucentZeroSigma) +MASK_BLUR_VARIANT_TEST(NormalTranslucent) +MASK_BLUR_VARIANT_TEST(SolidTranslucent) +MASK_BLUR_VARIANT_TEST(SolidOpaque) +MASK_BLUR_VARIANT_TEST(SolidTranslucentWithFilters) +MASK_BLUR_VARIANT_TEST(SolidTranslucentExclusionBlend) +MASK_BLUR_VARIANT_TEST(InnerTranslucent) +MASK_BLUR_VARIANT_TEST(InnerTranslucentWithBlurImageFilter) +MASK_BLUR_VARIANT_TEST(OuterTranslucent) +MASK_BLUR_VARIANT_TEST(OuterOpaqueWithBlurImageFilter) + +#undef MASK_BLUR_VARIANT_TEST + +TEST_P(AiksTest, GaussianBlurStyleInner) { + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + + DlPaint paint; + paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1)); + builder.DrawPaint(paint); + + paint.setColor(DlColor::kGreen()); + paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kInner, 30)); + + SkPath path; + path.moveTo(200, 200); + path.lineTo(300, 400); + path.lineTo(100, 400); + path.close(); + + builder.DrawPath(path, paint); + + // Draw another thing to make sure the clip area is reset. + DlPaint red; + red.setColor(DlColor::kRed()); + builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), red); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, GaussianBlurStyleOuter) { + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + + DlPaint paint; + paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0)); + builder.DrawPaint(paint); + + paint.setColor(DlColor::kGreen()); + paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kOuter, 30)); + + SkPath path; + path.moveTo(200, 200); + path.lineTo(300, 400); + path.lineTo(100, 400); + path.close(); + + builder.DrawPath(path, paint); + + // Draw another thing to make sure the clip area is reset. + DlPaint red; + red.setColor(DlColor::kRed()); + builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), red); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, GaussianBlurStyleSolid) { + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + + DlPaint paint; + paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0)); + builder.DrawPaint(paint); + + paint.setColor(DlColor::kGreen()); + paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kSolid, 30)); + + SkPath path; + path.moveTo(200, 200); + path.lineTo(300, 400); + path.lineTo(100, 400); + path.close(); + + builder.DrawPath(path, paint); + + // Draw another thing to make sure the clip area is reset. + DlPaint red; + red.setColor(DlColor::kRed()); + builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), red); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, MaskBlurTexture) { + Scalar sigma = 30; + auto callback = [&]() -> sk_sp { + if (AiksTest::ImGuiBegin("Controls", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::SliderFloat("Sigma", &sigma, 0, 500); + ImGui::End(); + } + + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + + DlPaint paint; + paint.setColor(DlColor::kGreen()); + paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma)); + + builder.DrawImage( + DlImageImpeller::Make(CreateTextureForFixture("boston.jpg")), + {200, 200}, DlImageSampling::kNearestNeighbor, &paint); + + DlPaint red; + red.setColor(DlColor::kRed()); + builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), red); + + return builder.Build(); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(AiksTest, MaskBlurDoesntStretchContents) { + Scalar sigma = 70; + auto callback = [&]() -> sk_sp { + if (AiksTest::ImGuiBegin("Controls", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::SliderFloat("Sigma", &sigma, 0, 500); + ImGui::End(); + } + + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + + DlPaint paint; + paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0)); + builder.DrawPaint(paint); + + std::shared_ptr boston = CreateTextureForFixture("boston.jpg"); + + builder.Transform(SkMatrix::Translate(100, 100) * + SkMatrix::Scale(0.5, 0.5)); + + paint.setColorSource(std::make_shared( + DlImageImpeller::Make(boston), DlTileMode::kRepeat, DlTileMode::kRepeat, + DlImageSampling::kMipmapLinear)); + paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma)); + + builder.DrawRect(SkRect::MakeXYWH(0, 0, boston->GetSize().width, + boston->GetSize().height), + paint); + + return builder.Build(); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(AiksTest, GaussianBlurAtPeripheryVertical) { + DisplayListBuilder builder; + + DlPaint paint; + builder.Scale(GetContentScale().x, GetContentScale().y); + + paint.setColor(DlColor::kLimeGreen()); + SkRRect rrect = SkRRect::MakeRectXY( + SkRect::MakeLTRB(0, 0, GetWindowSize().width, 100), 10, 10); + builder.DrawRRect(rrect, paint); + + paint.setColor(DlColor::kMagenta()); + rrect = SkRRect::MakeRectXY( + SkRect::MakeLTRB(0, 110, GetWindowSize().width, 210), 10, 10); + builder.DrawRRect(rrect, paint); + builder.ClipRect(SkRect::MakeLTRB(100, 0, 200, GetWindowSize().height)); + + DlPaint save_paint; + save_paint.setBlendMode(DlBlendMode::kSrc); + + auto backdrop_filter = DlBlurImageFilter::Make(20, 20, DlTileMode::kClamp); + + builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get()); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, GaussianBlurAtPeripheryHorizontal) { + DisplayListBuilder builder; + + builder.Scale(GetContentScale().x, GetContentScale().y); + std::shared_ptr boston = CreateTextureForFixture("boston.jpg"); + builder.DrawImageRect( + DlImageImpeller::Make(boston), + SkRect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height), + SkRect::MakeLTRB(0, 0, GetWindowSize().width, 100), + DlImageSampling::kNearestNeighbor); + + DlPaint paint; + paint.setColor(DlColor::kMagenta()); + + SkRRect rrect = SkRRect::MakeRectXY( + SkRect::MakeLTRB(0, 110, GetWindowSize().width, 210), 10, 10); + builder.DrawRRect(rrect, paint); + builder.ClipRect(SkRect::MakeLTRB(0, 50, GetWindowSize().width, 150)); + + DlPaint save_paint; + save_paint.setBlendMode(DlBlendMode::kSrc); + + auto backdrop_filter = DlBlurImageFilter::Make(20, 20, DlTileMode::kClamp); + builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get()); + + builder.Restore(); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, GaussianBlurAnimatedBackdrop) { + // This test is for checking out how stable rendering is when content is + // translated underneath a blur. Animating under a blur can cause + // *shimmering* to happen as a result of pixel alignment. + // See also: https://github.com/flutter/flutter/issues/140193 + auto boston = + CreateTextureForFixture("boston.jpg", /*enable_mipmapping=*/true); + ASSERT_TRUE(boston); + int64_t count = 0; + Scalar sigma = 20.0; + Scalar freq = 0.1; + Scalar amp = 50.0; + auto callback = [&]() -> sk_sp { + if (AiksTest::ImGuiBegin("Controls", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::SliderFloat("Sigma", &sigma, 0, 200); + ImGui::SliderFloat("Frequency", &freq, 0.01, 2.0); + ImGui::SliderFloat("Amplitude", &, 1, 100); + ImGui::End(); + } + + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + Scalar y = amp * sin(freq * 2.0 * M_PI * count / 60); + builder.DrawImage( + DlImageImpeller::Make(boston), + SkPoint::Make(1024 / 2 - boston->GetSize().width / 2, + (768 / 2 - boston->GetSize().height / 2) + y), + DlImageSampling::kMipmapLinear); + static PlaygroundPoint point_a(Point(100, 100), 20, Color::Red()); + static PlaygroundPoint point_b(Point(900, 700), 20, Color::Red()); + auto [handle_a, handle_b] = DrawPlaygroundLine(point_a, point_b); + + builder.ClipRect( + SkRect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y)); + builder.ClipRect(SkRect::MakeLTRB(100, 100, 900, 700)); + + DlPaint paint; + paint.setBlendMode(DlBlendMode::kSrc); + + auto backdrop_filter = + DlBlurImageFilter::Make(sigma, sigma, DlTileMode::kClamp); + builder.SaveLayer(nullptr, &paint, backdrop_filter.get()); + count += 1; + return builder.Build(); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(AiksTest, GaussianBlurStyleInnerGradient) { + DisplayListBuilder builder; + + builder.Scale(GetContentScale().x, GetContentScale().y); + + DlPaint paint; + paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0)); + builder.DrawPaint(paint); + + std::vector colors = {DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0), + DlColor::RGBA(0.7568, 0.2627, 0.2118, 1.0)}; + std::vector stops = {0.0, 1.0}; + + paint = DlPaint{}; + paint.setColorSource(DlColorSource::MakeLinear( + /*start_point=*/{0, 0}, + /*end_point=*/{200, 200}, + /*stop_count=*/colors.size(), + /*colors=*/colors.data(), + /*stops=*/stops.data(), + /*tile_mode=*/DlTileMode::kMirror)); + paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kInner, 30)); + + SkPath path; + path.moveTo(200, 200); + path.lineTo(300, 400); + path.lineTo(100, 400); + path.close(); + builder.DrawPath(path, paint); + + // Draw another thing to make sure the clip area is reset. + DlPaint red; + red.setColor(DlColor::kRed()); + builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), red); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, GaussianBlurStyleSolidGradient) { + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + + DlPaint paint; + paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0)); + builder.DrawPaint(paint); + + std::vector colors = {DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0), + DlColor::RGBA(0.7568, 0.2627, 0.2118, 1.0)}; + std::vector stops = {0.0, 1.0}; + + paint = DlPaint{}; + paint.setColorSource(DlColorSource::MakeLinear( + /*start_point=*/{0, 0}, + /*end_point=*/{200, 200}, + /*stop_count=*/colors.size(), + /*colors=*/colors.data(), + /*stops=*/stops.data(), + /*tile_mode=*/DlTileMode::kMirror)); + paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kSolid, 30)); + + SkPath path; + path.moveTo(200, 200); + path.lineTo(300, 400); + path.lineTo(100, 400); + path.close(); + builder.DrawPath(path, paint); + + // Draw another thing to make sure the clip area is reset. + DlPaint red; + red.setColor(DlColor::kRed()); + builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), red); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, GaussianBlurStyleOuterGradient) { + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + + DlPaint paint; + paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0)); + builder.DrawPaint(paint); + + std::vector colors = {DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0), + DlColor::RGBA(0.7568, 0.2627, 0.2118, 1.0)}; + std::vector stops = {0.0, 1.0}; + + paint = DlPaint{}; + paint.setColorSource(DlColorSource::MakeLinear( + /*start_point=*/{0, 0}, + /*end_point=*/{200, 200}, + /*stop_count=*/colors.size(), + /*colors=*/colors.data(), + /*stops=*/stops.data(), + /*tile_mode=*/DlTileMode::kMirror)); + paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kOuter, 30)); + + SkPath path; + path.moveTo(200, 200); + path.lineTo(300, 400); + path.lineTo(100, 400); + path.close(); + builder.DrawPath(path, paint); + + // Draw another thing to make sure the clip area is reset. + DlPaint red; + red.setColor(DlColor::kRed()); + builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), red); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, GaussianBlurScaledAndClipped) { + DisplayListBuilder builder; + std::shared_ptr boston = CreateTextureForFixture("boston.jpg"); + Rect bounds = + Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height); + Vector2 image_center = Vector2(bounds.GetSize() / 2); + + DlPaint paint; + paint.setImageFilter(DlBlurImageFilter::Make(20, 20, DlTileMode::kDecal)); + + Vector2 clip_size = {150, 75}; + Vector2 center = Vector2(1024, 768) / 2; + builder.Scale(GetContentScale().x, GetContentScale().y); + + auto rect = + Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size); + builder.ClipRect(SkRect::MakeLTRB(rect.GetLeft(), rect.GetTop(), + rect.GetRight(), rect.GetBottom())); + builder.Translate(center.x, center.y); + builder.Scale(0.6, 0.6); + + SkRect sk_bounds = SkRect::MakeLTRB(bounds.GetLeft(), bounds.GetTop(), + bounds.GetRight(), bounds.GetBottom()); + Rect dest = bounds.Shift(-image_center); + SkRect sk_dst = SkRect::MakeLTRB(dest.GetLeft(), dest.GetTop(), + dest.GetRight(), dest.GetBottom()); + builder.DrawImageRect(DlImageImpeller::Make(boston), /*src=*/sk_bounds, + /*dst=*/sk_dst, DlImageSampling::kNearestNeighbor, + &paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, GaussianBlurRotatedAndClippedInteractive) { + std::shared_ptr boston = CreateTextureForFixture("boston.jpg"); + + auto callback = [&]() -> sk_sp { + const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; + const DlTileMode tile_modes[] = {DlTileMode::kClamp, DlTileMode::kRepeat, + DlTileMode::kMirror, DlTileMode::kDecal}; + + static float rotation = 0; + static float scale = 0.6; + static int selected_tile_mode = 3; + + if (AiksTest::ImGuiBegin("Controls", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180); + ImGui::SliderFloat("Scale", &scale, 0, 2.0); + ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names, + sizeof(tile_mode_names) / sizeof(char*)); + ImGui::End(); + } + + DisplayListBuilder builder; + Rect bounds = + Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height); + Vector2 image_center = Vector2(bounds.GetSize() / 2); + DlPaint paint; + paint.setImageFilter( + DlBlurImageFilter::Make(20, 20, tile_modes[selected_tile_mode])); + + static PlaygroundPoint point_a(Point(362, 309), 20, Color::Red()); + static PlaygroundPoint point_b(Point(662, 459), 20, Color::Red()); + auto [handle_a, handle_b] = DrawPlaygroundLine(point_a, point_b); + Vector2 center = Vector2(1024, 768) / 2; + + builder.Scale(GetContentScale().x, GetContentScale().y); + builder.ClipRect( + SkRect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y)); + builder.Translate(center.x, center.y); + builder.Scale(scale, scale); + builder.Rotate(rotation); + + SkRect sk_bounds = SkRect::MakeLTRB(bounds.GetLeft(), bounds.GetTop(), + bounds.GetRight(), bounds.GetBottom()); + Rect dest = bounds.Shift(-image_center); + SkRect sk_dst = SkRect::MakeLTRB(dest.GetLeft(), dest.GetTop(), + dest.GetRight(), dest.GetBottom()); + builder.DrawImageRect(DlImageImpeller::Make(boston), /*src=*/sk_bounds, + /*dst=*/sk_dst, DlImageSampling::kNearestNeighbor, + &paint); + return builder.Build(); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(AiksTest, GaussianBlurOneDimension) { + DisplayListBuilder builder; + + builder.Scale(GetContentScale().x, GetContentScale().y); + builder.Scale(0.5, 0.5); + + std::shared_ptr boston = CreateTextureForFixture("boston.jpg"); + builder.DrawImage(DlImageImpeller::Make(boston), {100, 100}, {}); + + DlPaint paint; + paint.setBlendMode(DlBlendMode::kSrc); + + auto backdrop_filter = DlBlurImageFilter::Make(50, 0, DlTileMode::kClamp); + builder.SaveLayer(nullptr, &paint, backdrop_filter.get()); + builder.Restore(); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +// Smoketest to catch issues with the coverage hint. +// Draws a rotated blurred image within a rectangle clip. The center of the clip +// rectangle is the center of the rotated image. The entire area of the clip +// rectangle should be filled with opaque colors output by the blur. +TEST_P(AiksTest, GaussianBlurRotatedAndClipped) { + DisplayListBuilder builder; + + std::shared_ptr boston = CreateTextureForFixture("boston.jpg"); + Rect bounds = + Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height); + + DlPaint paint; + paint.setImageFilter(DlBlurImageFilter::Make(20, 20, DlTileMode::kDecal)); + + Vector2 image_center = Vector2(bounds.GetSize() / 2); + Vector2 clip_size = {150, 75}; + Vector2 center = Vector2(1024, 768) / 2; + builder.Scale(GetContentScale().x, GetContentScale().y); + + auto clip_bounds = + Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size); + builder.ClipRect(SkRect::MakeLTRB(clip_bounds.GetLeft(), clip_bounds.GetTop(), + clip_bounds.GetRight(), + clip_bounds.GetBottom())); + builder.Translate(center.x, center.y); + builder.Scale(0.6, 0.6); + builder.Rotate(25); + + auto dst_rect = bounds.Shift(-image_center); + builder.DrawImageRect( + DlImageImpeller::Make(boston), /*src=*/ + SkRect::MakeLTRB(bounds.GetLeft(), bounds.GetTop(), bounds.GetRight(), + bounds.GetBottom()), + /*dst=*/ + SkRect::MakeLTRB(dst_rect.GetLeft(), dst_rect.GetTop(), + dst_rect.GetRight(), dst_rect.GetBottom()), + DlImageSampling::kMipmapLinear, &paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, GaussianBlurRotatedNonUniform) { + auto callback = [&]() -> sk_sp { + const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; + const DlTileMode tile_modes[] = {DlTileMode::kClamp, DlTileMode::kRepeat, + DlTileMode::kMirror, DlTileMode::kDecal}; + + static float rotation = 45; + static float scale = 0.6; + static int selected_tile_mode = 3; + + if (AiksTest::ImGuiBegin("Controls", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180); + ImGui::SliderFloat("Scale", &scale, 0, 2.0); + ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names, + sizeof(tile_mode_names) / sizeof(char*)); + ImGui::End(); + } + + DisplayListBuilder builder; + + DlPaint paint; + paint.setColor(DlColor::kGreen()); + paint.setImageFilter( + DlBlurImageFilter::Make(50, 0, tile_modes[selected_tile_mode])); + + Vector2 center = Vector2(1024, 768) / 2; + builder.Scale(GetContentScale().x, GetContentScale().y); + builder.Translate(center.x, center.y); + builder.Scale(scale, scale); + builder.Rotate(rotation); + + SkRRect rrect = + SkRRect::MakeRectXY(SkRect::MakeXYWH(-100, -100, 200, 200), 10, 10); + builder.DrawRRect(rrect, paint); + return builder.Build(); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +} // namespace testing +} // namespace impeller diff --git a/testing/impeller_golden_tests_output.txt b/testing/impeller_golden_tests_output.txt index 8feb287d4003c..568b9d445f3a5 100644 --- a/testing/impeller_golden_tests_output.txt +++ b/testing/impeller_golden_tests_output.txt @@ -696,9 +696,6 @@ impeller_Play_AiksTest_GaussianBlurWithoutDecalSupport_Metal.png impeller_Play_AiksTest_GradientStrokesRenderCorrectly_Metal.png impeller_Play_AiksTest_GradientStrokesRenderCorrectly_OpenGLES.png impeller_Play_AiksTest_GradientStrokesRenderCorrectly_Vulkan.png -impeller_Play_AiksTest_GuassianBlurUpdatesMipmapContents_Metal.png -impeller_Play_AiksTest_GuassianBlurUpdatesMipmapContents_OpenGLES.png -impeller_Play_AiksTest_GuassianBlurUpdatesMipmapContents_Vulkan.png impeller_Play_AiksTest_ImageColorSourceEffectTransform_Metal.png impeller_Play_AiksTest_ImageColorSourceEffectTransform_OpenGLES.png impeller_Play_AiksTest_ImageColorSourceEffectTransform_Vulkan.png