Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit e21a452

Browse files
committed
[Impeller] Add mask blur style support to the RRect blur fast path.
1 parent c4b9afa commit e21a452

File tree

3 files changed

+234
-17
lines changed

3 files changed

+234
-17
lines changed

impeller/aiks/aiks_unittests.cc

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,6 +1614,143 @@ TEST_P(AiksTest, SolidColorCirclesOvalsRRectsMaskBlurCorrectly) {
16141614
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
16151615
}
16161616

1617+
TEST_P(AiksTest,
1618+
SolidColorCirclesOvalsRRectsMaskBlurCorrectlyBlurStyleVariations) {
1619+
Canvas canvas;
1620+
canvas.Scale(GetContentScale());
1621+
canvas.Scale(Vector2{0.8f, 0.8f});
1622+
Paint paint;
1623+
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
1624+
.style = FilterContents::BlurStyle::kNormal,
1625+
.sigma = Sigma{1},
1626+
};
1627+
1628+
canvas.DrawPaint({.color = Color::AntiqueWhite()});
1629+
1630+
struct MaskBlurTestConfig {
1631+
FilterContents::BlurStyle style = FilterContents::BlurStyle::kNormal;
1632+
Scalar sigma = 1.0f;
1633+
Scalar alpha = 1.0f;
1634+
std::shared_ptr<ImageFilter> image_filter;
1635+
bool invert_colors = false;
1636+
BlendMode blend_mode = BlendMode::kSourceOver;
1637+
};
1638+
1639+
std::vector<MaskBlurTestConfig> paint_variations = {
1640+
// 1. Normal style, translucent, zero sigma.
1641+
{.style = FilterContents::BlurStyle::kNormal,
1642+
.sigma = 0.0f,
1643+
.alpha = 0.5f},
1644+
// 2. Normal style, translucent.
1645+
{.style = FilterContents::BlurStyle::kNormal,
1646+
.sigma = 8.0f,
1647+
.alpha = 0.5f},
1648+
// 3. Solid style, translucent.
1649+
{.style = FilterContents::BlurStyle::kSolid,
1650+
.sigma = 8.0f,
1651+
.alpha = 0.5f},
1652+
// 4. Solid style, opaque.
1653+
{.style = FilterContents::BlurStyle::kSolid, .sigma = 8.0f},
1654+
// 5. Solid style, translucent, color & image filtered.
1655+
{.style = FilterContents::BlurStyle::kSolid,
1656+
.sigma = 8.0f,
1657+
.alpha = 0.5f,
1658+
.image_filter = ImageFilter::MakeBlur(Sigma{3}, Sigma{3},
1659+
FilterContents::BlurStyle::kNormal,
1660+
Entity::TileMode::kClamp),
1661+
.invert_colors = true},
1662+
// 6. Solid style, translucent, exclusion blended.
1663+
{.style = FilterContents::BlurStyle::kSolid,
1664+
.sigma = 8.0f,
1665+
.alpha = 0.5f,
1666+
.blend_mode = BlendMode::kExclusion},
1667+
// 7. Inner style, translucent.
1668+
{.style = FilterContents::BlurStyle::kInner,
1669+
.sigma = 8.0f,
1670+
.alpha = 0.5f},
1671+
// 8. Inner style, translucent, blurred.
1672+
{.style = FilterContents::BlurStyle::kInner,
1673+
.sigma = 8.0f,
1674+
.alpha = 0.5f,
1675+
.image_filter = ImageFilter::MakeBlur(Sigma{3}, Sigma{3},
1676+
FilterContents::BlurStyle::kNormal,
1677+
Entity::TileMode::kClamp)},
1678+
// 9. Outer style, translucent.
1679+
{.style = FilterContents::BlurStyle::kOuter,
1680+
.sigma = 8.0f,
1681+
.alpha = 0.5f},
1682+
// 10. Outer style, opaque, image filtered.
1683+
{.style = FilterContents::BlurStyle::kOuter,
1684+
.sigma = 8.0f,
1685+
.image_filter = ImageFilter::MakeBlur(Sigma{3}, Sigma{3},
1686+
FilterContents::BlurStyle::kNormal,
1687+
Entity::TileMode::kClamp)},
1688+
};
1689+
1690+
for (size_t i = 0; i < paint_variations.size(); i++) {
1691+
const MaskBlurTestConfig& config = paint_variations[i];
1692+
paint.mask_blur_descriptor->style = config.style;
1693+
paint.mask_blur_descriptor->sigma = Sigma{config.sigma};
1694+
paint.image_filter = config.image_filter;
1695+
paint.invert_colors = config.invert_colors;
1696+
paint.blend_mode = config.blend_mode;
1697+
1698+
const Scalar x = 50 + i * 80;
1699+
const Scalar radius = 20.0f;
1700+
const Scalar y_spacing = 100.0f;
1701+
1702+
Scalar y = 50;
1703+
paint.color = Color::Crimson().WithAlpha(config.alpha);
1704+
canvas.DrawRect(Rect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
1705+
radius, 60.0f - radius),
1706+
paint);
1707+
1708+
y += y_spacing;
1709+
paint.color = Color::Blue().WithAlpha(config.alpha);
1710+
canvas.DrawCircle({x + 25, y + 25}, radius, paint);
1711+
1712+
y += y_spacing;
1713+
paint.color = Color::Green().WithAlpha(config.alpha);
1714+
canvas.DrawOval(Rect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
1715+
radius, 60.0f - radius),
1716+
paint);
1717+
1718+
y += y_spacing;
1719+
paint.color = Color::Purple().WithAlpha(config.alpha);
1720+
canvas.DrawRRect(Rect::MakeXYWH(x, y, 60.0f, 60.0f), //
1721+
{radius, radius}, //
1722+
paint);
1723+
1724+
y += y_spacing;
1725+
paint.color = Color::Orange().WithAlpha(config.alpha);
1726+
canvas.DrawRRect(Rect::MakeXYWH(x, y, 60.0f, 60.0f), //
1727+
{radius, 5.0f}, paint);
1728+
1729+
y += y_spacing;
1730+
paint.color = Color::Maroon().WithAlpha(config.alpha);
1731+
canvas.DrawPath(PathBuilder{}
1732+
.MoveTo({x + 0, y + 60})
1733+
.LineTo({x + 30, y + 0})
1734+
.LineTo({x + 60, y + 60})
1735+
.Close()
1736+
.TakePath(),
1737+
paint);
1738+
1739+
y += y_spacing;
1740+
paint.color = Color::Maroon().WithAlpha(config.alpha);
1741+
canvas.DrawPath(PathBuilder{}
1742+
.AddArc(Rect::MakeXYWH(x + 5, y, 50, 50),
1743+
Radians{kPi / 2}, Radians{kPi})
1744+
.AddArc(Rect::MakeXYWH(x + 25, y, 50, 50),
1745+
Radians{kPi / 2}, Radians{kPi})
1746+
.Close()
1747+
.TakePath(),
1748+
paint);
1749+
}
1750+
1751+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1752+
}
1753+
16171754
TEST_P(AiksTest, FilledRoundRectPathsRenderCorrectly) {
16181755
Canvas canvas;
16191756
canvas.Scale(GetContentScale());

impeller/aiks/canvas.cc

Lines changed: 96 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -308,41 +308,121 @@ void Canvas::DrawPaint(const Paint& paint) {
308308
}
309309

310310
bool Canvas::AttemptDrawBlurredRRect(const Rect& rect,
311-
Size corner_radius,
311+
Size corner_radii,
312312
const Paint& paint) {
313313
if (paint.color_source.GetType() != ColorSource::Type::kColor ||
314314
paint.style != Paint::Style::kFill) {
315315
return false;
316316
}
317317

318-
if (!paint.mask_blur_descriptor.has_value() ||
319-
paint.mask_blur_descriptor->style != FilterContents::BlurStyle::kNormal) {
318+
if (!paint.mask_blur_descriptor.has_value()) {
320319
return false;
321320
}
321+
322322
// A blur sigma that is not positive enough should not result in a blur.
323323
if (paint.mask_blur_descriptor->sigma.sigma <= kEhCloseEnough) {
324324
return false;
325325
}
326326

327-
Paint new_paint = paint;
328-
329327
// For symmetrically mask blurred solid RRects, absorb the mask blur and use
330328
// a faster SDF approximation.
331329

332-
auto contents = std::make_shared<SolidRRectBlurContents>();
333-
contents->SetColor(new_paint.color);
334-
contents->SetSigma(new_paint.mask_blur_descriptor->sigma);
335-
contents->SetRRect(rect, corner_radius);
330+
Paint rrect_paint = paint;
331+
332+
// Absorb the color filter, if any.
333+
if (rrect_paint.HasColorFilter()) {
334+
rrect_paint.color =
335+
rrect_paint.GetColorFilter()->GetCPUColorFilterProc()(paint.color);
336+
rrect_paint.color_filter = nullptr;
337+
rrect_paint.invert_colors = false;
338+
}
339+
340+
// In some cases, we need to render the mask blur to a separate layer.
341+
//
342+
// 1. If the blur style is normal, we'll be drawing using one draw call and
343+
// no clips. And so we can just wrap the RRect contents with the
344+
// ImageFilter, which will get applied to the result as per usual.
345+
//
346+
// 2. If the blur style is solid, we combine the non-blurred RRect with the
347+
// blurred RRect via two separate draw calls, and so we need to defer any
348+
// fancy blending, translucency, or image filtering until after these two
349+
// draws have been combined in a separate layer.
350+
//
351+
// 3. If the blur style is outer or inner, we apply the blur style via a
352+
// clip. The ImageFilter needs to be applied to the mask blurred result.
353+
// And so if there's an ImageFilter, we need to defer applying it until
354+
// after the clipped RRect blur has been drawn to a separate texture.
355+
// However, since there's only one draw call that produces color, we
356+
// don't need to worry about the blend mode or translucency (unlike with
357+
// BlurStyle::kSolid).
358+
//
359+
if ((rrect_paint.mask_blur_descriptor->style !=
360+
FilterContents::BlurStyle::kNormal &&
361+
rrect_paint.image_filter) ||
362+
(rrect_paint.mask_blur_descriptor->style ==
363+
FilterContents::BlurStyle::kSolid &&
364+
(!rrect_paint.color.IsOpaque() ||
365+
rrect_paint.blend_mode != BlendMode::kSourceOver))) {
366+
// Defer the alpha, blend mode, and image filter to a separate layer.
367+
SaveLayer({.color = Color::White().WithAlpha(rrect_paint.color.alpha),
368+
.blend_mode = rrect_paint.blend_mode,
369+
.image_filter = rrect_paint.image_filter});
370+
rrect_paint.color = rrect_paint.color.WithAlpha(1);
371+
rrect_paint.blend_mode = BlendMode::kSourceOver;
372+
rrect_paint.image_filter = nullptr;
373+
} else {
374+
Save();
375+
}
336376

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

339-
Entity entity;
340-
entity.SetTransform(GetCurrentTransform());
341-
entity.SetClipDepth(GetClipDepth());
342-
entity.SetBlendMode(new_paint.blend_mode);
343-
entity.SetContents(new_paint.WithFilters(std::move(contents)));
380+
contents->SetColor(rrect_paint.color);
381+
contents->SetSigma(rrect_paint.mask_blur_descriptor->sigma);
382+
contents->SetRRect(rect, corner_radii);
344383

345-
AddEntityToCurrentPass(std::move(entity));
384+
Entity blurred_rrect_entity;
385+
blurred_rrect_entity.SetTransform(GetCurrentTransform());
386+
blurred_rrect_entity.SetClipDepth(GetClipDepth());
387+
blurred_rrect_entity.SetBlendMode(rrect_paint.blend_mode);
388+
389+
rrect_paint.mask_blur_descriptor = std::nullopt;
390+
blurred_rrect_entity.SetContents(
391+
rrect_paint.WithFilters(std::move(contents)));
392+
AddEntityToCurrentPass(std::move(blurred_rrect_entity));
393+
};
394+
395+
switch (rrect_paint.mask_blur_descriptor->style) {
396+
case FilterContents::BlurStyle::kNormal: {
397+
draw_blurred_rrect();
398+
break;
399+
}
400+
case FilterContents::BlurStyle::kSolid: {
401+
// First, draw the blurred RRect.
402+
draw_blurred_rrect();
403+
// Then, draw the non-blurred RRect on top.
404+
Entity entity;
405+
entity.SetTransform(GetCurrentTransform());
406+
entity.SetClipDepth(GetClipDepth());
407+
entity.SetBlendMode(rrect_paint.blend_mode);
408+
entity.SetContents(CreateContentsForGeometryWithFilters(
409+
rrect_paint, Geometry::MakeRoundRect(rect, corner_radii)));
410+
AddEntityToCurrentPass(std::move(entity));
411+
break;
412+
}
413+
case FilterContents::BlurStyle::kOuter: {
414+
ClipRRect(rect, corner_radii, Entity::ClipOperation::kDifference);
415+
draw_blurred_rrect();
416+
break;
417+
}
418+
case FilterContents::BlurStyle::kInner: {
419+
ClipRRect(rect, corner_radii, Entity::ClipOperation::kIntersect);
420+
draw_blurred_rrect();
421+
break;
422+
}
423+
}
424+
425+
Restore();
346426

347427
return true;
348428
}

impeller/aiks/canvas.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ class Canvas {
207207
void RestoreClip();
208208

209209
bool AttemptDrawBlurredRRect(const Rect& rect,
210-
Size corner_radius,
210+
Size corner_radii,
211211
const Paint& paint);
212212

213213
Canvas(const Canvas&) = delete;

0 commit comments

Comments
 (0)