Skip to content

Commit fa72c7a

Browse files
authored
fix gaussian blur getting clipped with negative scale (flutter#184037)
Fixes flutter#183884 GaussianBlurFilterContents::GetFilterSourceCoverage decides how much to expand the bounding rect to account for the applied blur. If given a matrix with a negative scale, `GetFilterSourceCoverage` would shrink the bounding rect instead of expanding it - giving the appearance of the "offset" described in flutter#183884 Added a unittest and golden test to verify the change. Repro app on MacOS with --enable-impeller at HEAD: <img width="799" height="626" alt="Screenshot 2026-03-23 at 2 28 06 PM" src="https://github.com/user-attachments/assets/ed26b264-a1b2-481e-aa54-9be6858b35f5" /> Repro app with this change with --enable-impeller: <img width="799" height="626" alt="Screenshot 2026-03-23 at 2 27 10 PM" src="https://github.com/user-attachments/assets/2f8edf40-e4da-4833-88ca-1ff6180c25ac" /> ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing.
1 parent 8ae690c commit fa72c7a

3 files changed

Lines changed: 42 additions & 1 deletion

File tree

engine/src/flutter/impeller/display_list/aiks_dl_blur_unittests.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1509,5 +1509,25 @@ TEST_P(AiksTest, CanRenderNestedBackdropBlur) {
15091509
ASSERT_TRUE(OpenPlaygroundHere(callback));
15101510
}
15111511

1512+
TEST_P(AiksTest, GaussianBlurFlipped) {
1513+
DisplayListBuilder builder;
1514+
builder.Scale(GetContentScale().x, GetContentScale().y);
1515+
1516+
builder.DrawRect(DlRect::MakeXYWH(0, 0, 350, 350),
1517+
DlPaint().setColor(DlColor::kWhite()));
1518+
1519+
builder.Save();
1520+
builder.Scale(-1, 1);
1521+
DlPaint paint;
1522+
paint.setImageFilter(DlBlurImageFilter::Make(10, 10, DlTileMode::kDecal));
1523+
builder.SaveLayer(DlRect::MakeLTRB(-150, 100, 150, 200), &paint);
1524+
builder.DrawRect(DlRect::MakeLTRB(-150, 0, 150, 300),
1525+
DlPaint().setColor(DlColor::kRed()));
1526+
builder.Restore();
1527+
builder.Restore();
1528+
1529+
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
1530+
}
1531+
15121532
} // namespace testing
15131533
} // namespace impeller

engine/src/flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,8 @@ std::optional<Rect> GaussianBlurFilterContents::GetFilterSourceCoverage(
817817
Vector2 blur_radius = {CalculateBlurRadius(scaled_sigma.x),
818818
CalculateBlurRadius(scaled_sigma.y)};
819819
Vector3 blur_radii =
820-
effect_transform.Basis() * Vector3{blur_radius.x, blur_radius.y, 0.0};
820+
(effect_transform.Basis() * Vector3{blur_radius.x, blur_radius.y, 0.0})
821+
.Abs();
821822
return output_limit.Expand(Point(blur_radii.x, blur_radii.y));
822823
}
823824

engine/src/flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,26 @@ TEST(GaussianBlurFilterContentsTest, FilterSourceCoverage) {
229229
}
230230
}
231231

232+
TEST(GaussianBlurFilterContentsTest, FilterSourceCoverageNegativeScale) {
233+
fml::StatusOr<Scalar> sigma_radius_1 =
234+
CalculateSigmaForBlurRadius(1.0, Matrix());
235+
ASSERT_TRUE(sigma_radius_1.ok());
236+
auto contents = std::make_unique<GaussianBlurFilterContents>(
237+
sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
238+
/*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
239+
/*mask_geometry=*/nullptr);
240+
241+
// Negative scale should still result in an expanded coverage rect.
242+
std::optional<Rect> coverage = contents->GetFilterSourceCoverage(
243+
/*effect_transform=*/Matrix::MakeScale({-2.0, 2.0, 1.0}),
244+
/*output_limit=*/Rect::MakeLTRB(100, 100, 200, 200));
245+
ASSERT_TRUE(coverage.has_value());
246+
if (coverage.has_value()) {
247+
EXPECT_RECT_NEAR(coverage.value(),
248+
Rect::MakeLTRB(100 - 2, 100 - 2, 200 + 2, 200 + 2));
249+
}
250+
}
251+
232252
TEST(GaussianBlurFilterContentsTest, CalculateSigmaValues) {
233253
EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1.0f), 1);
234254
EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(2.0f), 1);

0 commit comments

Comments
 (0)