diff --git a/src/imageops/filter_1d.rs b/src/imageops/filter_1d.rs index 12ef5c8dd8..a2a537ebcb 100644 --- a/src/imageops/filter_1d.rs +++ b/src/imageops/filter_1d.rs @@ -47,11 +47,22 @@ struct KernelShape { height: usize, } +/// Size of a non-empty image. Width and height are guaranteed to be non-zero. #[derive(Debug, Clone, Copy)] pub(crate) struct FilterImageSize { pub(crate) width: usize, pub(crate) height: usize, } +impl FilterImageSize { + pub(crate) fn new(dimensions: (u32, u32)) -> Option { + let width = dimensions.0 as usize; + let height = dimensions.1 as usize; + if width == 0 || height == 0 { + return None; + } + Some(Self { width, height }) + } +} /// Pads an image row with *clamp* strategy /// diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index e5c828650d..c8e6a83862 100644 --- a/src/imageops/sample.rs +++ b/src/imageops/sample.rs @@ -1306,9 +1306,11 @@ pub(crate) fn gaussian_blur_dyn_image( parameters.y_axis_sigma, ); - let filter_image_size = FilterImageSize { - width: image.width() as usize, - height: image.height() as usize, + let Some(filter_image_size) = FilterImageSize::new(image.dimensions()) else { + // If the image is empty, return an empty image of the same dimensions and color space. + let mut target = DynamicImage::new(image.width(), image.height(), image.color()); + let _ = target.set_color_space(image.color_space()); + return target; }; let mut target = match image { @@ -1514,9 +1516,9 @@ fn gaussian_blur_indirect_impl( parameters.y_axis_sigma, ); - let filter_image_size = FilterImageSize { - width: image.width() as usize, - height: image.height() as usize, + let Some(filter_image_size) = FilterImageSize::new(image.dimensions()) else { + // if the input image is empty, just return an empty image + return image.buffer_like(); }; match CN { @@ -1626,7 +1628,7 @@ where #[cfg(test)] mod tests { use super::{resize, sample_bilinear, sample_nearest, FilterType}; - use crate::{GenericImageView, ImageBuffer, RgbImage}; + use crate::{DynamicImage, GenericImageView, ImageBuffer, RgbImage}; #[cfg(feature = "benchmarks")] use test; @@ -1898,4 +1900,25 @@ mod tests { let huge_u16 = ImageBuffer::from_pixel(1024, 1024, crate::Luma([65535_u16])); super::thumbnail(&huge_u16, 1, 1); } + + #[test] + fn sample_empty_image() { + // blurring an empty image should not panic + _ = DynamicImage::new(0, 0, crate::ColorType::Rgb8).blur(1.0); + _ = DynamicImage::new(10, 0, crate::ColorType::Rgb8).blur(1.0); + _ = DynamicImage::new(0, 10, crate::ColorType::Rgb8).blur(1.0); + + _ = crate::imageops::blur( + &ImageBuffer::from_pixel(0, 0, crate::Rgb([255_u8, 255_u8, 255_u8])), + 1.0, + ); + _ = crate::imageops::blur( + &ImageBuffer::from_pixel(10, 0, crate::Rgb([255_u8, 255_u8, 255_u8])), + 1.0, + ); + _ = crate::imageops::blur( + &ImageBuffer::from_pixel(0, 10, crate::Rgb([255_u8, 255_u8, 255_u8])), + 1.0, + ); + } } diff --git a/src/images/generic_image.rs b/src/images/generic_image.rs index 0cadc2dce4..4d5767901c 100644 --- a/src/images/generic_image.rs +++ b/src/images/generic_image.rs @@ -65,6 +65,17 @@ pub trait GenericImageView { { let (width, height) = self.dimensions(); + if width == 0 || height == 0 { + // Return an empty iterator for empty images + return Pixels { + image: self, + x: 0, + y: 0, + width: 0, + height: 0, + }; + } + Pixels { image: self, x: 0,