From 0e629157728357cabae171891d9e99854a1f7e01 Mon Sep 17 00:00:00 2001 From: Stefano Di Martino Date: Sat, 13 Jun 2026 23:59:58 +0200 Subject: [PATCH] Fix panic in feComposite arithmetic with oversized filter region The source pixmap of a filtered group is clamped to `max_bbox` in `render_group`, but the filter `region` in `apply_inner` was derived directly from the unclamped filter rect. When the filter region was larger than the clamped buffer, `feComposite` with the `arithmetic` operator panicked on a size mismatch, since it requires its inputs and destination to have identical dimensions: assertion failed: src1.height == src2.height && src1.height == dest.height All intermediate filter images are expected to share the source's dimensions, so clamp the region to the source bounds. This keeps every buffer the same size and makes the affected SVG render correctly instead of crashing (or being silently cleared). This also fixes the `huge-region` test, whose reference image previously captured the buggy behaviour where the filtered element disappeared; it now renders the blurred shape correctly. Fixes #1021. Fixes #1007. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 3 +++ crates/resvg/src/filter/mod.rs | 10 ++++++++++ crates/resvg/tests/integration/render.rs | 1 + .../operator=arithmetic-with-huge-region.png | Bin 0 -> 403 bytes .../operator=arithmetic-with-huge-region.svg | 13 +++++++++++++ .../tests/tests/filters/filter/huge-region.png | Bin 346 -> 1608 bytes 6 files changed, 27 insertions(+) create mode 100644 crates/resvg/tests/tests/filters/feComposite/operator=arithmetic-with-huge-region.png create mode 100644 crates/resvg/tests/tests/filters/feComposite/operator=arithmetic-with-huge-region.svg 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 0000000000000000000000000000000000000000..8216dd4ed78a7fbeb63e063f51321ab2d082d31c GIT binary patch literal 403 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&7G|JGckpC4Af*-H6XFV_(STm}e+C91de6Xc zfPrB@1H%LchJK)$hm&520x7nVAirP+0egr1f|M_x3xI|SdAc};RNQ)dZX<6~fC$5Z z=@y+wSa}NNwX?0#FHGv2b*QCh-46m#Xdtn+BF$xOC~4v@Qayy?t$66gsd+7oa15r4+((AlBO6==XY|(NpJ6ySB$U g*rq+EcJHB|-0v>md~0Ooa2*u1p00i_>zopr0JSWNd;kCd literal 0 HcmV?d00001 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 4fa749a9e49f5a032a899f85bf01c602ca52c295..ce69e3f40d31765897f571bb27e497e58000d3fe 100644 GIT binary patch literal 1608 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4rZW;-{LxVAg4CKC&U%VW%v&wKwuRELj#xr zB!G-Gu*jrbhK2?ZcU34uUnoOE0*E~`jiEc1p&^uEk|#rl3&XT1hW-QwAea)z(3i~6 z7RS)w&M-5Qp(m1IYA8cjC_`(IWnUaar=Rh}IK!SOgZ>DE_6XhnV4bc2?RG!?-Z;(f zaGmZro$g@O{wS5+2$kMojfqj}eG#e?f>pbN)jC5}dc##Z!wrE(F))~PN9cD%8FYv0 z_l9WoMQL?MS@uL~c1P%RhMV?=ns$X6b%*QsM(TEln)Zb20a14pSXZQ8cO;0>2~uFx z4J1Pidm;^cLiKyX^t!@yJAo?0^t(b#d%_I6!i{>ujk`jOfKuH+6be*e1Vr6HsZhhN z2p|K*?E#XZKm|Z9&~Uw;2oM{l5~4;IBn3f0S&#w<0=lH=br3Mf8EQ*{{DK`A85o(F zn3!2u+1T0GI5{~uxVU-v_yq+7goT8KL_{PcB&DU~!xPhU^7HfZ3knKKtE*eu+dF%D`}?NOp1*9>nzd`T@7;go*zr@R zFW&~O+FJHg@`1$MipTGZ3);Ko;(HZ)+SqcFWl*4ZD4S^t+4;l_9RAu3A0x}6I-!OXRFDY z<4p%8(q4Z2wv+K6pZ{hDxeZd>YzZsQM!x^K@@K^>b9v~sn)BZu zXPyk(I`fH7w=(y!MhUTr^TSik>n*;XtEx4MlKd#K!EDon))Q-#jBWPF`Dw>3OLbG} z>Aw6Sg-^BodA3yD>xVVbn|73XHvf!?-R$S0J8{m;cdz)Dvu+M`^%6Y4QKGw5z@zu< zm2C@5Z%$$GtkAt?$fv|65*%`OQE2xp1%trT`bQt_KkZlVbx4PQIj`scmW5x%uj|O2 z;9v>QPOGvwbL7M7(-swf^ExgV3$DN7A>E)`HB+u(dz>tXU(4D9!Y{s6i_K?fnBDT~ z{geOGFa1w{Dqr>@0+<{a5+>~bs#7NF1PaYo9dHN_GKwGUC{=FSQb+qFpY7p0h1J%@U=xzcAm;yCLzg5akKF1AcwR-ax+TyV7Ik~afR>#7eW4p zMJ^9Sz9*+91_?|(apdetm#(H6Iy)t`y(emPa-BI-S!|^KX#a+3|H9U6p5xGUxmoZZ zgOY^ZN|tGV?nyplY5h>bU(fPzo}7STartIGqg!+Ke#;XpE1s4Rzp8kfLjT=Hdn5M) zXEt43{cr2+&)Zd^^K?fv1lt+O|a*e^RPJLBZu2rYHXnx21E+v~Pg z72mpg`}fu3L2Bn0ojvO$RrU3neR{>+d8#`@_LfB?m2VC?BKR*eE`II3dEZve|NT)S zwZk-pWbDwn$)X_xAy2n$|I+wQY%Q@W#1S1HrrJ*Qgfr+pWG#1 U?k``p9Fzn-UHx3vIVCg!0EQ>1;{X5v literal 346 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&7G|JGckpC4ASD{$6XFV_F@X>x!xEqANY#`SnJY({=WH`;V?Kzq?2P6hfY^ KelF{r5}E)hI;`0M