diff --git a/CHANGELOG.md b/CHANGELOG.md index cb2e52f17..914bf03ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ This changelog also contains important changes in dependencies. This release has an MSRV of 1.87.0 for `usvg` and `resvg` and the C API. +### Fixed +- Panic in `feComposite` with the `arithmetic` operator when the filter region is larger than the clamped layer. (#1021, #1007) + ## [0.47.0] 2026-02-05 This release has an MSRV of 1.87.0 for `usvg` and `resvg` and the C API. diff --git a/crates/resvg/src/filter/mod.rs b/crates/resvg/src/filter/mod.rs index 29ff3a7a4..1a087b9b5 100644 --- a/crates/resvg/src/filter/mod.rs +++ b/crates/resvg/src/filter/mod.rs @@ -365,6 +365,16 @@ fn apply_inner( .map(|r| r.to_int_rect()) .ok_or(Error::InvalidRegion)?; + // The source pixmap is clamped to `max_bbox` in `render_group`, so the filter + // region (derived directly from the unclamped filter rect) can be larger than + // the buffer we actually render into. All intermediate filter images share the + // source's dimensions, so clamp the region to the source bounds to keep them + // consistent. Otherwise `feComposite` with the `arithmetic` operator, which + // requires equally sized inputs, would panic on a size mismatch. + let source_rect = + IntRect::from_xywh(0, 0, source.width(), source.height()).ok_or(Error::InvalidRegion)?; + let region = crate::geom::fit_to_rect(region, source_rect).ok_or(Error::InvalidRegion)?; + let mut results: Vec = Vec::new(); for primitive in filter.primitives() { diff --git a/crates/resvg/tests/integration/render.rs b/crates/resvg/tests/integration/render.rs index dbeeac0b1..afee8b9c9 100644 --- a/crates/resvg/tests/integration/render.rs +++ b/crates/resvg/tests/integration/render.rs @@ -81,6 +81,7 @@ use crate::render; #[test] fn filters_feComposite_invalid_operator() { assert_eq!(render("tests/filters/feComposite/invalid-operator"), 0); } #[test] fn filters_feComposite_operator_eq_arithmetic_and_invalid_k1_4() { assert_eq!(render("tests/filters/feComposite/operator=arithmetic-and-invalid-k1-4"), 0); } #[test] fn filters_feComposite_operator_eq_arithmetic_on_sRGB() { assert_eq!(render("tests/filters/feComposite/operator=arithmetic-on-sRGB"), 0); } +#[test] fn filters_feComposite_operator_eq_arithmetic_with_huge_region() { assert_eq!(render("tests/filters/feComposite/operator=arithmetic-with-huge-region"), 0); } #[test] fn filters_feComposite_operator_eq_arithmetic_with_large_k1_4() { assert_eq!(render("tests/filters/feComposite/operator=arithmetic-with-large-k1-4"), 0); } #[test] fn filters_feComposite_operator_eq_arithmetic_with_opacity_on_sRGB() { assert_eq!(render("tests/filters/feComposite/operator=arithmetic-with-opacity-on-sRGB"), 0); } #[test] fn filters_feComposite_operator_eq_arithmetic_with_opacity() { assert_eq!(render("tests/filters/feComposite/operator=arithmetic-with-opacity"), 0); } diff --git a/crates/resvg/tests/tests/filters/feComposite/operator=arithmetic-with-huge-region.png b/crates/resvg/tests/tests/filters/feComposite/operator=arithmetic-with-huge-region.png new file mode 100644 index 000000000..8216dd4ed Binary files /dev/null and b/crates/resvg/tests/tests/filters/feComposite/operator=arithmetic-with-huge-region.png differ diff --git a/crates/resvg/tests/tests/filters/feComposite/operator=arithmetic-with-huge-region.svg b/crates/resvg/tests/tests/filters/feComposite/operator=arithmetic-with-huge-region.svg new file mode 100644 index 000000000..665e27317 --- /dev/null +++ b/crates/resvg/tests/tests/filters/feComposite/operator=arithmetic-with-huge-region.svg @@ -0,0 +1,13 @@ + + operator=arithmetic-with-huge-region + A filter region larger than the clamped layer must not crash feComposite arithmetic. + + + + + + + + + + diff --git a/crates/resvg/tests/tests/filters/filter/huge-region.png b/crates/resvg/tests/tests/filters/filter/huge-region.png index 4fa749a9e..ce69e3f40 100644 Binary files a/crates/resvg/tests/tests/filters/filter/huge-region.png and b/crates/resvg/tests/tests/filters/filter/huge-region.png differ