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

Commit 786202e

Browse files
authored
[Impeller] Add FilterContents::GetSourceCoverage to enable filtered saveLayer clipping. (#47183)
The new method allows a caller to ask for the coverage of the input pixels required to produce an output that spans a given area. This allows the code to clip a saveLayer that has an image filter applied to it.
1 parent dbcf074 commit 786202e

24 files changed

+443
-1
lines changed

impeller/aiks/aiks_unittests.cc

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2760,6 +2760,117 @@ TEST_P(AiksTest, TranslucentSaveLayerWithColorAndImageFilterDrawsCorrectly) {
27602760
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
27612761
}
27622762

2763+
TEST_P(AiksTest, ImageFilteredSaveLayerWithUnboundedContents) {
2764+
Canvas canvas;
2765+
canvas.Scale(GetContentScale());
2766+
2767+
auto test = [&canvas](const std::shared_ptr<ImageFilter>& filter) {
2768+
auto DrawLine = [&canvas](const Point& p0, const Point& p1,
2769+
const Paint& p) {
2770+
auto path = PathBuilder{}
2771+
.AddLine(p0, p1)
2772+
.SetConvexity(Convexity::kConvex)
2773+
.TakePath();
2774+
Paint paint = p;
2775+
paint.style = Paint::Style::kStroke;
2776+
canvas.DrawPath(path, paint);
2777+
};
2778+
// Registration marks for the edge of the SaveLayer
2779+
DrawLine(Point(75, 100), Point(225, 100), {.color = Color::White()});
2780+
DrawLine(Point(75, 200), Point(225, 200), {.color = Color::White()});
2781+
DrawLine(Point(100, 75), Point(100, 225), {.color = Color::White()});
2782+
DrawLine(Point(200, 75), Point(200, 225), {.color = Color::White()});
2783+
2784+
canvas.SaveLayer({.image_filter = filter},
2785+
Rect::MakeLTRB(100, 100, 200, 200));
2786+
{
2787+
// DrawPaint to verify correct behavior when the contents are unbounded.
2788+
canvas.DrawPaint({.color = Color::Yellow()});
2789+
2790+
// Contrasting rectangle to see interior blurring
2791+
canvas.DrawRect(Rect::MakeLTRB(125, 125, 175, 175),
2792+
{.color = Color::Blue()});
2793+
}
2794+
canvas.Restore();
2795+
};
2796+
2797+
test(ImageFilter::MakeBlur(Sigma{10.0}, Sigma{10.0},
2798+
FilterContents::BlurStyle::kNormal,
2799+
Entity::TileMode::kDecal));
2800+
2801+
canvas.Translate({200.0, 0.0});
2802+
2803+
test(ImageFilter::MakeDilate(Radius{10.0}, Radius{10.0}));
2804+
2805+
canvas.Translate({200.0, 0.0});
2806+
2807+
test(ImageFilter::MakeErode(Radius{10.0}, Radius{10.0}));
2808+
2809+
canvas.Translate({-400.0, 200.0});
2810+
2811+
auto rotate_filter =
2812+
ImageFilter::MakeMatrix(Matrix::MakeTranslation({150, 150}) *
2813+
Matrix::MakeRotationZ(Degrees{10.0}) *
2814+
Matrix::MakeTranslation({-150, -150}),
2815+
SamplerDescriptor{});
2816+
test(rotate_filter);
2817+
2818+
canvas.Translate({200.0, 0.0});
2819+
2820+
auto rgb_swap_filter = ImageFilter::MakeFromColorFilter(
2821+
*ColorFilter::MakeMatrix({.array = {
2822+
0, 1, 0, 0, 0, //
2823+
0, 0, 1, 0, 0, //
2824+
1, 0, 0, 0, 0, //
2825+
0, 0, 0, 1, 0 //
2826+
}}));
2827+
test(rgb_swap_filter);
2828+
2829+
canvas.Translate({200.0, 0.0});
2830+
2831+
test(ImageFilter::MakeCompose(*rotate_filter, *rgb_swap_filter));
2832+
2833+
canvas.Translate({-400.0, 200.0});
2834+
2835+
test(ImageFilter::MakeLocalMatrix(Matrix::MakeTranslation({25.0, 25.0}),
2836+
*rotate_filter));
2837+
2838+
canvas.Translate({200.0, 0.0});
2839+
2840+
test(ImageFilter::MakeLocalMatrix(Matrix::MakeTranslation({25.0, 25.0}),
2841+
*rgb_swap_filter));
2842+
2843+
canvas.Translate({200.0, 0.0});
2844+
2845+
test(ImageFilter::MakeLocalMatrix(
2846+
Matrix::MakeTranslation({25.0, 25.0}),
2847+
*ImageFilter::MakeCompose(*rotate_filter, *rgb_swap_filter)));
2848+
2849+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2850+
}
2851+
2852+
TEST_P(AiksTest, ImageFilteredUnboundedSaveLayerWithUnboundedContents) {
2853+
Canvas canvas;
2854+
canvas.Scale(GetContentScale());
2855+
2856+
auto blur_filter = ImageFilter::MakeBlur(Sigma{10.0}, Sigma{10.0},
2857+
FilterContents::BlurStyle::kNormal,
2858+
Entity::TileMode::kDecal);
2859+
2860+
canvas.SaveLayer({.image_filter = blur_filter}, std::nullopt);
2861+
{
2862+
// DrawPaint to verify correct behavior when the contents are unbounded.
2863+
canvas.DrawPaint({.color = Color::Yellow()});
2864+
2865+
// Contrasting rectangle to see interior blurring
2866+
canvas.DrawRect(Rect::MakeLTRB(125, 125, 175, 175),
2867+
{.color = Color::Blue()});
2868+
}
2869+
canvas.Restore();
2870+
2871+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2872+
}
2873+
27632874
TEST_P(AiksTest, TranslucentSaveLayerImageDrawsCorrectly) {
27642875
Canvas canvas;
27652876

impeller/entity/contents/filters/border_mask_blur_filter_contents.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,15 @@ std::optional<Rect> BorderMaskBlurFilterContents::GetFilterCoverage(
169169
Size(extent.x, extent.y));
170170
}
171171

172+
std::optional<Rect> BorderMaskBlurFilterContents::GetFilterSourceCoverage(
173+
const Matrix& effect_transform,
174+
const Rect& output_limit) const {
175+
auto transformed_blur_vector =
176+
effect_transform.TransformDirection(Vector2(Radius{sigma_x_}.radius, 0))
177+
.Abs() +
178+
effect_transform.TransformDirection(Vector2(0, Radius{sigma_y_}.radius))
179+
.Abs();
180+
return output_limit.Expand(transformed_blur_vector);
181+
}
182+
172183
} // namespace impeller

impeller/entity/contents/filters/border_mask_blur_filter_contents.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ class BorderMaskBlurFilterContents final : public FilterContents {
2727
const Entity& entity,
2828
const Matrix& effect_transform) const override;
2929

30+
// |FilterContents|
31+
std::optional<Rect> GetFilterSourceCoverage(
32+
const Matrix& effect_transform,
33+
const Rect& output_limit) const override;
34+
3035
private:
3136
// |FilterContents|
3237
std::optional<Entity> RenderFilter(

impeller/entity/contents/filters/color_filter_contents.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,10 @@ std::optional<Scalar> ColorFilterContents::GetAlpha() const {
9999
return alpha_;
100100
}
101101

102+
std::optional<Rect> ColorFilterContents::GetFilterSourceCoverage(
103+
const Matrix& effect_transform,
104+
const Rect& output_limit) const {
105+
return output_limit;
106+
}
107+
102108
} // namespace impeller

impeller/entity/contents/filters/color_filter_contents.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ class ColorFilterContents : public FilterContents {
4444

4545
std::optional<Scalar> GetAlpha() const;
4646

47+
// |FilterContents|
48+
std::optional<Rect> GetFilterSourceCoverage(
49+
const Matrix& effect_transform,
50+
const Rect& output_limit) const override;
51+
4752
private:
4853
AbsorbOpacity absorb_opacity_ = AbsorbOpacity::kNo;
4954
std::optional<Scalar> alpha_;

impeller/entity/contents/filters/directional_gaussian_blur_filter_contents.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,17 @@ std::optional<Entity> DirectionalGaussianBlurFilterContents::RenderFilter(
273273
entity.GetBlendMode(), entity.GetClipDepth());
274274
}
275275

276+
std::optional<Rect>
277+
DirectionalGaussianBlurFilterContents::GetFilterSourceCoverage(
278+
const Matrix& effect_transform,
279+
const Rect& output_limit) const {
280+
auto transform = effect_transform.Basis();
281+
auto transformed_blur_vector =
282+
transform.TransformDirection(blur_direction_ * Radius{blur_sigma_}.radius)
283+
.Abs();
284+
return output_limit.Expand(transformed_blur_vector);
285+
}
286+
276287
std::optional<Rect> DirectionalGaussianBlurFilterContents::GetFilterCoverage(
277288
const FilterInput::Vector& inputs,
278289
const Entity& entity,

impeller/entity/contents/filters/directional_gaussian_blur_filter_contents.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ class DirectionalGaussianBlurFilterContents final : public FilterContents {
6969
/// beginning of the chain.
7070
void SetIsSecondPass(bool is_second_pass);
7171

72+
// |FilterContents|
73+
std::optional<Rect> GetFilterSourceCoverage(
74+
const Matrix& effect_transform,
75+
const Rect& output_limit) const override;
76+
7277
// |FilterContents|
7378
std::optional<Rect> GetFilterCoverage(
7479
const FilterInput::Vector& inputs,

impeller/entity/contents/filters/filter_contents.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,28 @@ std::optional<Rect> FilterContents::GetFilterCoverage(
229229
return result;
230230
}
231231

232+
std::optional<Rect> FilterContents::GetSourceCoverage(
233+
const Matrix& effect_transform,
234+
const Rect& output_limit) const {
235+
auto filter_input_coverage =
236+
GetFilterSourceCoverage(effect_transform_, output_limit);
237+
238+
if (!filter_input_coverage.has_value()) {
239+
return std::nullopt;
240+
}
241+
242+
std::optional<Rect> inputs_coverage;
243+
for (auto input : inputs_) {
244+
auto input_coverage = input->GetSourceCoverage(
245+
effect_transform, filter_input_coverage.value());
246+
if (!input_coverage.has_value()) {
247+
return std::nullopt;
248+
}
249+
inputs_coverage = Union(inputs_coverage, input_coverage.value());
250+
}
251+
return inputs_coverage;
252+
}
253+
232254
std::optional<Entity> FilterContents::GetEntity(
233255
const ContentContext& renderer,
234256
const Entity& entity,

impeller/entity/contents/filters/filter_contents.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,20 @@ class FilterContents : public Contents {
132132
// |Contents|
133133
const FilterContents* AsFilter() const override;
134134

135+
/// @brief Determines the coverage of source pixels that will be needed
136+
/// to apply this filter under the given transform and produce
137+
/// results anywhere within the indicated coverage limit.
138+
///
139+
/// This is useful for subpass rendering scenarios where a filter
140+
/// will be applied to the output of the subpass and we need to
141+
/// determine how large of a render target to allocate in order
142+
/// to collect all pixels that might affect the supplied output
143+
/// coverage limit. While we might clip the rendering of the subpass,
144+
/// we want to avoid clipping out any pixels that contribute to
145+
/// the output limit via the filtering operation.
146+
std::optional<Rect> GetSourceCoverage(const Matrix& effect_transform,
147+
const Rect& output_limit) const;
148+
135149
virtual Matrix GetLocalTransform(const Matrix& parent_transform) const;
136150

137151
Matrix GetTransform(const Matrix& parent_transform) const;
@@ -169,6 +183,10 @@ class FilterContents : public Contents {
169183
const Entity& entity,
170184
const Matrix& effect_transform) const;
171185

186+
virtual std::optional<Rect> GetFilterSourceCoverage(
187+
const Matrix& effect_transform,
188+
const Rect& output_limit) const = 0;
189+
172190
/// @brief Converts zero or more filter inputs into a render instruction.
173191
virtual std::optional<Entity> RenderFilter(
174192
const FilterInput::Vector& inputs,

impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ std::optional<Rect> FilterContentsFilterInput::GetCoverage(
4343
return filter_->GetCoverage(entity);
4444
}
4545

46+
std::optional<Rect> FilterContentsFilterInput::GetSourceCoverage(
47+
const Matrix& effect_transform,
48+
const Rect& output_limit) const {
49+
return filter_->GetSourceCoverage(effect_transform, output_limit);
50+
}
51+
4652
Matrix FilterContentsFilterInput::GetLocalTransform(
4753
const Entity& entity) const {
4854
return filter_->GetLocalTransform(entity.GetTransformation());

0 commit comments

Comments
 (0)