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 all 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_blur_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<ImageFilter> 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<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, GaussianBlurAtPeripheryVertical) {
Canvas canvas;

Expand Down
110 changes: 94 additions & 16 deletions impeller/aiks/canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<SolidRRectBlurContents>();
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<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
30 changes: 30 additions & 0 deletions testing/impeller_golden_tests_output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down