diff --git a/CHANGELOG.md b/CHANGELOG.md index 30a71c972..867c8ad82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This changelog also contains important changes in dependencies. - `dominant-baseline` is now correctly inherited by nested `` elements, so a nested span stays on the same baseline as its siblings. (#864) +- Panics and incorrect clipping when a filter region is smaller than its input. (#1141) ## [0.48.1] 2026-08-02 diff --git a/crates/resvg/src/filter/mod.rs b/crates/resvg/src/filter/mod.rs index 30f8680c5..8b6fd138e 100644 --- a/crates/resvg/src/filter/mod.rs +++ b/crates/resvg/src/filter/mod.rs @@ -338,7 +338,7 @@ pub fn apply( source: &mut tiny_skia::Pixmap, ) { let result = apply_inner(filter, ts, source); - let result = result.and_then(|image| apply_to_canvas(image, source)); + let result = result.and_then(|(image, region)| apply_to_canvas(image, region, source)); // Clear on error. if result.is_err() { @@ -357,8 +357,8 @@ pub fn apply( fn apply_inner( filter: &usvg::filter::Filter, ts: usvg::Transform, - source: &mut tiny_skia::Pixmap, -) -> Result { + source: &tiny_skia::Pixmap, +) -> Result<(Image, IntRect), Error> { let region = filter .rect() .transform(ts) @@ -372,6 +372,17 @@ fn apply_inner( 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 cropped_source = if region != source_rect { + // TODO: Avoid copying? + Some(source.copy_region(region)?) + } else { + None + }; + let source = cropped_source.as_ref().unwrap_or(source); + let output_region = region; + let ts = ts.post_translate(-region.x() as f32, -region.y() as f32); + let region = region.translate_to(0, 0).ok_or(Error::InvalidRegion)?; + let mut results: Vec = Vec::new(); for primitive in filter.primitives() { @@ -514,7 +525,7 @@ fn apply_inner( } if let Some(res) = results.pop() { - Ok(res.image) + Ok((res.image, output_region)) } else { Err(Error::NoResults) } @@ -1097,13 +1108,17 @@ fn transform_light_source( source } -fn apply_to_canvas(input: Image, pixmap: &mut tiny_skia::Pixmap) -> Result<(), Error> { +fn apply_to_canvas( + input: Image, + region: IntRect, + pixmap: &mut tiny_skia::Pixmap, +) -> Result<(), Error> { let input = input.into_color_space(usvg::filter::ColorInterpolation::SRGB)?; pixmap.fill(tiny_skia::Color::TRANSPARENT); pixmap.draw_pixmap( - 0, - 0, + region.x(), + region.y(), input.as_ref().as_ref(), &tiny_skia::PixmapPaint::default(), tiny_skia::Transform::identity(), diff --git a/crates/resvg/tests/integration/render.rs b/crates/resvg/tests/integration/render.rs index e55675256..a556ff5dd 100644 --- a/crates/resvg/tests/integration/render.rs +++ b/crates/resvg/tests/integration/render.rs @@ -142,6 +142,7 @@ use crate::render; #[test] fn filters_feDiffuseLighting_surfaceScale_eq_0() { assert_eq!(render("tests/filters/feDiffuseLighting/surfaceScale=0"), 0); } #[test] fn filters_feDiffuseLighting_surfaceScale_eq_1_33() { assert_eq!(render("tests/filters/feDiffuseLighting/surfaceScale=1.33"), 0); } #[test] fn filters_feDiffuseLighting_surfaceScale_eq_5() { assert_eq!(render("tests/filters/feDiffuseLighting/surfaceScale=5"), 0); } +#[test] fn filters_feDiffuseLighting_tiny_element_with_spot_light() { assert_eq!(render("tests/filters/feDiffuseLighting/tiny-element-with-spot-light"), 0); } #[test] fn filters_feDisplacementMap_simple_case() { assert_eq!(render("tests/filters/feDisplacementMap/simple-case"), 0); } #[test] fn filters_feDistantLight_default_attributes() { assert_eq!(render("tests/filters/feDistantLight/default-attributes"), 0); } #[test] fn filters_feDistantLight_negative_azimuth_and_elevation() { assert_eq!(render("tests/filters/feDistantLight/negative-azimuth-and-elevation"), 0); } @@ -393,6 +394,7 @@ use crate::render; #[test] fn filters_filter_functions_one_invalid_url_in_list() { assert_eq!(render("tests/filters/filter-functions/one-invalid-url-in-list"), 0); } #[test] fn filters_filter_functions_two_drop_shadow_function() { assert_eq!(render("tests/filters/filter-functions/two-drop-shadow-function"), 0); } #[test] fn filters_filter_functions_two_exact_urls() { assert_eq!(render("tests/filters/filter-functions/two-exact-urls"), 0); } +#[test] fn filters_filter_functions_two_urls_with_different_regions() { assert_eq!(render("tests/filters/filter-functions/two-urls-with-different-regions"), 0); } #[test] fn filters_filter_functions_two_urls() { assert_eq!(render("tests/filters/filter-functions/two-urls"), 0); } #[test] fn filters_filter_functions_url_and_grayscale() { assert_eq!(render("tests/filters/filter-functions/url-and-grayscale"), 0); } #[test] fn filters_flood_color_hsla_color() { assert_eq!(render("tests/filters/flood-color/hsla-color"), 0); } diff --git a/crates/resvg/tests/tests/filters/feDiffuseLighting/tiny-element-with-spot-light.png b/crates/resvg/tests/tests/filters/feDiffuseLighting/tiny-element-with-spot-light.png new file mode 100644 index 000000000..08c03cd81 Binary files /dev/null and b/crates/resvg/tests/tests/filters/feDiffuseLighting/tiny-element-with-spot-light.png differ diff --git a/crates/resvg/tests/tests/filters/feDiffuseLighting/tiny-element-with-spot-light.svg b/crates/resvg/tests/tests/filters/feDiffuseLighting/tiny-element-with-spot-light.svg new file mode 100644 index 000000000..1ce197123 --- /dev/null +++ b/crates/resvg/tests/tests/filters/feDiffuseLighting/tiny-element-with-spot-light.svg @@ -0,0 +1,14 @@ + + Tiny element with a spot light + + + + + + + + + + + diff --git a/crates/resvg/tests/tests/filters/filter-functions/two-urls-with-different-regions.png b/crates/resvg/tests/tests/filters/filter-functions/two-urls-with-different-regions.png new file mode 100644 index 000000000..9a7732c65 Binary files /dev/null and b/crates/resvg/tests/tests/filters/filter-functions/two-urls-with-different-regions.png differ diff --git a/crates/resvg/tests/tests/filters/filter-functions/two-urls-with-different-regions.svg b/crates/resvg/tests/tests/filters/filter-functions/two-urls-with-different-regions.svg new file mode 100644 index 000000000..3ec6ab8ce --- /dev/null +++ b/crates/resvg/tests/tests/filters/filter-functions/two-urls-with-different-regions.svg @@ -0,0 +1,16 @@ + + Two urls with different filter regions + + + + + + + + + + + + + +