diff --git a/impeller/aiks/aiks_blur_unittests.cc b/impeller/aiks/aiks_blur_unittests.cc index d6ad5ebe41884..59fef60d69cc8 100644 --- a/impeller/aiks/aiks_blur_unittests.cc +++ b/impeller/aiks/aiks_blur_unittests.cc @@ -281,6 +281,172 @@ TEST_P(AiksTest, MaskBlurWithZeroSigmaIsSkipped) { 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; diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index 2fbfe2b2394d5..5a0a86faebbf2 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -308,41 +308,119 @@ void Canvas::DrawPaint(const Paint& paint) { } bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, - Size corner_radius, + Size corner_radii, const Paint& paint) { if (paint.color_source.GetType() != ColorSource::Type::kColor || paint.style != Paint::Style::kFill) { return false; } - if (!paint.mask_blur_descriptor.has_value() || - paint.mask_blur_descriptor->style != FilterContents::BlurStyle::kNormal) { + if (!paint.mask_blur_descriptor.has_value()) { return false; } + // A blur sigma that is not positive enough should not result in a blur. if (paint.mask_blur_descriptor->sigma.sigma <= kEhCloseEnough) { return false; } - Paint new_paint = paint; - // For symmetrically mask blurred solid RRects, absorb the mask blur and use // a faster SDF approximation. - auto contents = std::make_shared(); - contents->SetColor(new_paint.color); - contents->SetSigma(new_paint.mask_blur_descriptor->sigma); - contents->SetRRect(rect, corner_radius); + Color rrect_color = + paint.HasColorFilter() + // Absorb the color filter, if any. + ? paint.GetColorFilter()->GetCPUColorFilterProc()(paint.color) + : paint.color; + + Paint rrect_paint = {.mask_blur_descriptor = paint.mask_blur_descriptor}; + + // In some cases, we need to render the mask blur to a separate layer. + // + // 1. If the blur style is normal, we'll be drawing using one draw call and + // no clips. And so we can just wrap the RRect contents with the + // ImageFilter, which will get applied to the result as per usual. + // + // 2. If the blur style is solid, we combine the non-blurred RRect with the + // blurred RRect via two separate draw calls, and so we need to defer any + // fancy blending, translucency, or image filtering until after these two + // draws have been combined in a separate layer. + // + // 3. If the blur style is outer or inner, we apply the blur style via a + // clip. The ImageFilter needs to be applied to the mask blurred result. + // And so if there's an ImageFilter, we need to defer applying it until + // after the clipped RRect blur has been drawn to a separate texture. + // However, since there's only one draw call that produces color, we + // don't need to worry about the blend mode or translucency (unlike with + // BlurStyle::kSolid). + // + if ((paint.mask_blur_descriptor->style != + FilterContents::BlurStyle::kNormal && + paint.image_filter) || + (paint.mask_blur_descriptor->style == FilterContents::BlurStyle::kSolid && + (!rrect_color.IsOpaque() || + paint.blend_mode != BlendMode::kSourceOver))) { + // Defer the alpha, blend mode, and image filter to a separate layer. + SaveLayer({.color = Color::White().WithAlpha(rrect_color.alpha), + .blend_mode = paint.blend_mode, + .image_filter = paint.image_filter}); + rrect_paint.color = rrect_color.WithAlpha(1); + } else { + rrect_paint.color = rrect_color; + rrect_paint.blend_mode = paint.blend_mode; + rrect_paint.image_filter = paint.image_filter; + Save(); + } - new_paint.mask_blur_descriptor = std::nullopt; + auto draw_blurred_rrect = [this, &rect, &corner_radii, &rrect_paint]() { + auto contents = std::make_shared(); - Entity entity; - entity.SetTransform(GetCurrentTransform()); - entity.SetClipDepth(GetClipDepth()); - entity.SetBlendMode(new_paint.blend_mode); - entity.SetContents(new_paint.WithFilters(std::move(contents))); + contents->SetColor(rrect_paint.color); + contents->SetSigma(rrect_paint.mask_blur_descriptor->sigma); + contents->SetRRect(rect, corner_radii); - AddEntityToCurrentPass(std::move(entity)); + Entity blurred_rrect_entity; + blurred_rrect_entity.SetTransform(GetCurrentTransform()); + blurred_rrect_entity.SetClipDepth(GetClipDepth()); + blurred_rrect_entity.SetBlendMode(rrect_paint.blend_mode); + + rrect_paint.mask_blur_descriptor = std::nullopt; + blurred_rrect_entity.SetContents( + rrect_paint.WithFilters(std::move(contents))); + AddEntityToCurrentPass(std::move(blurred_rrect_entity)); + }; + + switch (rrect_paint.mask_blur_descriptor->style) { + case FilterContents::BlurStyle::kNormal: { + draw_blurred_rrect(); + break; + } + case FilterContents::BlurStyle::kSolid: { + // First, draw the blurred RRect. + draw_blurred_rrect(); + // Then, draw the non-blurred RRect on top. + Entity entity; + entity.SetTransform(GetCurrentTransform()); + entity.SetClipDepth(GetClipDepth()); + entity.SetBlendMode(rrect_paint.blend_mode); + entity.SetContents(CreateContentsForGeometryWithFilters( + rrect_paint, Geometry::MakeRoundRect(rect, corner_radii))); + AddEntityToCurrentPass(std::move(entity)); + break; + } + case FilterContents::BlurStyle::kOuter: { + ClipRRect(rect, corner_radii, Entity::ClipOperation::kDifference); + draw_blurred_rrect(); + break; + } + case FilterContents::BlurStyle::kInner: { + ClipRRect(rect, corner_radii, Entity::ClipOperation::kIntersect); + draw_blurred_rrect(); + break; + } + } + + Restore(); return true; } diff --git a/impeller/aiks/canvas.h b/impeller/aiks/canvas.h index b4ba0838bec8d..867c5b8535069 100644 --- a/impeller/aiks/canvas.h +++ b/impeller/aiks/canvas.h @@ -207,7 +207,7 @@ class Canvas { void RestoreClip(); bool AttemptDrawBlurredRRect(const Rect& rect, - Size corner_radius, + Size corner_radii, const Paint& paint); Canvas(const Canvas&) = delete; diff --git a/testing/impeller_golden_tests_output.txt b/testing/impeller_golden_tests_output.txt index 26f73d04bad0a..0a6c28e5bb62d 100644 --- a/testing/impeller_golden_tests_output.txt +++ b/testing/impeller_golden_tests_output.txt @@ -516,6 +516,36 @@ impeller_Play_AiksTest_ImageFilteredUnboundedSaveLayerWithUnboundedContents_Vulk impeller_Play_AiksTest_LinearToSrgbFilterSubpassCollapseOptimization_Metal.png impeller_Play_AiksTest_LinearToSrgbFilterSubpassCollapseOptimization_OpenGLES.png impeller_Play_AiksTest_LinearToSrgbFilterSubpassCollapseOptimization_Vulkan.png +impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucentWithBlurImageFilter_Metal.png +impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucentWithBlurImageFilter_OpenGLES.png +impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucentWithBlurImageFilter_Vulkan.png +impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucent_Metal.png +impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucent_OpenGLES.png +impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucent_Vulkan.png +impeller_Play_AiksTest_MaskBlurVariantTestNormalTranslucentZeroSigma_Metal.png +impeller_Play_AiksTest_MaskBlurVariantTestNormalTranslucentZeroSigma_OpenGLES.png +impeller_Play_AiksTest_MaskBlurVariantTestNormalTranslucentZeroSigma_Vulkan.png +impeller_Play_AiksTest_MaskBlurVariantTestNormalTranslucent_Metal.png +impeller_Play_AiksTest_MaskBlurVariantTestNormalTranslucent_OpenGLES.png +impeller_Play_AiksTest_MaskBlurVariantTestNormalTranslucent_Vulkan.png +impeller_Play_AiksTest_MaskBlurVariantTestOuterOpaqueWithBlurImageFilter_Metal.png +impeller_Play_AiksTest_MaskBlurVariantTestOuterOpaqueWithBlurImageFilter_OpenGLES.png +impeller_Play_AiksTest_MaskBlurVariantTestOuterOpaqueWithBlurImageFilter_Vulkan.png +impeller_Play_AiksTest_MaskBlurVariantTestOuterTranslucent_Metal.png +impeller_Play_AiksTest_MaskBlurVariantTestOuterTranslucent_OpenGLES.png +impeller_Play_AiksTest_MaskBlurVariantTestOuterTranslucent_Vulkan.png +impeller_Play_AiksTest_MaskBlurVariantTestSolidOpaque_Metal.png +impeller_Play_AiksTest_MaskBlurVariantTestSolidOpaque_OpenGLES.png +impeller_Play_AiksTest_MaskBlurVariantTestSolidOpaque_Vulkan.png +impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucentExclusionBlend_Metal.png +impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucentExclusionBlend_OpenGLES.png +impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucentExclusionBlend_Vulkan.png +impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucentWithFilters_Metal.png +impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucentWithFilters_OpenGLES.png +impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucentWithFilters_Vulkan.png +impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucent_Metal.png +impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucent_OpenGLES.png +impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucent_Vulkan.png impeller_Play_AiksTest_MaskBlurWithZeroSigmaIsSkipped_Metal.png impeller_Play_AiksTest_MaskBlurWithZeroSigmaIsSkipped_OpenGLES.png impeller_Play_AiksTest_MaskBlurWithZeroSigmaIsSkipped_Vulkan.png