Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions impeller/aiks/aiks_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1614,6 +1614,172 @@ TEST_P(AiksTest, SolidColorCirclesOvalsRRectsMaskBlurCorrectly) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

struct MaskBlurTestConfig {
FilterContents::BlurStyle style = FilterContents::BlurStyle::kNormal;
Scalar sigma = 1.0f;
Scalar alpha = 1.0f;
std::shared_ptr<ImageFilter> image_filter;
bool invert_colors = false;
BlendMode blend_mode = BlendMode::kSourceOver;
};

static Picture MaskBlurVariantTest(const AiksTest& test_context,
Copy link
Member

@gaaclarke gaaclarke Mar 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait, can you put these in aiks_blur_unittests, please? I wish I had a better way to force the sorting of this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

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<std::string, MaskBlurTestConfig> 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, FilledRoundRectPathsRenderCorrectly) {
Canvas canvas;
canvas.Scale(GetContentScale());
Expand Down
112 changes: 96 additions & 16 deletions impeller/aiks/canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -308,41 +308,121 @@ 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<SolidRRectBlurContents>();
contents->SetColor(new_paint.color);
contents->SetSigma(new_paint.mask_blur_descriptor->sigma);
contents->SetRRect(rect, corner_radius);
Paint rrect_paint = paint;

// Absorb the color filter, if any.
if (rrect_paint.HasColorFilter()) {
rrect_paint.color =
rrect_paint.GetColorFilter()->GetCPUColorFilterProc()(paint.color);
rrect_paint.color_filter = nullptr;
rrect_paint.invert_colors = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This seems tricky, having to clear out invert_colors. I could see someone missing this easily, it might be worth making a function AbsorbColorFilter

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be turned around. Rather than clone and eliminate properties, create a default paint and only copy in the attributes you want.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed this up to try and make the paint construction less confusing. Please TAL.

}

// 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 ((rrect_paint.mask_blur_descriptor->style !=
FilterContents::BlurStyle::kNormal &&
rrect_paint.image_filter) ||
(rrect_paint.mask_blur_descriptor->style ==
FilterContents::BlurStyle::kSolid &&
(!rrect_paint.color.IsOpaque() ||
rrect_paint.blend_mode != BlendMode::kSourceOver))) {
// Defer the alpha, blend mode, and image filter to a separate layer.
SaveLayer({.color = Color::White().WithAlpha(rrect_paint.color.alpha),
.blend_mode = rrect_paint.blend_mode,
.image_filter = rrect_paint.image_filter});
rrect_paint.color = rrect_paint.color.WithAlpha(1);
rrect_paint.blend_mode = BlendMode::kSourceOver;
rrect_paint.image_filter = nullptr;
} else {
Save();
}

new_paint.mask_blur_descriptor = std::nullopt;
auto draw_blurred_rrect = [this, &rect, &corner_radii, &rrect_paint]() {
auto contents = std::make_shared<SolidRRectBlurContents>();

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;
}
Expand Down
2 changes: 1 addition & 1 deletion impeller/aiks/canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
42 changes: 42 additions & 0 deletions testing/impeller_golden_tests_output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,48 @@ 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_MaskBlurVariantTestInnerTranslucent_Metal.png
impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucent_OpenGLES.png
impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucent_Vulkan.png
impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucentWithBlurImageFilter_Metal.png
impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucentWithBlurImageFilter_OpenGLES.png
impeller_Play_AiksTest_MaskBlurVariantTestInnerTranslucentWithBlurImageFilter_Vulkan.png
impeller_Play_AiksTest_MaskBlurVariantTestNormalTranslucent_Metal.png
impeller_Play_AiksTest_MaskBlurVariantTestNormalTranslucent_OpenGLES.png
impeller_Play_AiksTest_MaskBlurVariantTestNormalTranslucent_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_MaskBlurVariantTestOuterTranslucent_Metal.png
impeller_Play_AiksTest_MaskBlurVariantTestOuterTranslucent_OpenGLES.png
impeller_Play_AiksTest_MaskBlurVariantTestOuterTranslucent_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_MaskBlurVariantTestSolidTranslucent_Metal.png
impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucent_OpenGLES.png
impeller_Play_AiksTest_MaskBlurVariantTestSolidTranslucent_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
Expand Down