From f892f483b2888651c3de51bc018082df80342eb4 Mon Sep 17 00:00:00 2001 From: Radzivon Bartoshyk Date: Tue, 10 Mar 2026 19:19:44 +0000 Subject: [PATCH 1/5] Filtering NaN inputs in filter 1d --- src/imageops/filter_1d.rs | 2 +- src/imageops/sample.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/imageops/filter_1d.rs b/src/imageops/filter_1d.rs index db023080e3..289bbdc666 100644 --- a/src/imageops/filter_1d.rs +++ b/src/imageops/filter_1d.rs @@ -194,7 +194,7 @@ impl ToStorage for u32 { impl ToStorage for f32 { #[inline(always)] fn to_(self) -> u16 { - (self.min(u16::MAX as f32) + 0.5) as u16 + (self + 0.5) as u16 } } diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index 8d13605cf4..1ef65f80d3 100644 --- a/src/imageops/sample.rs +++ b/src/imageops/sample.rs @@ -1236,6 +1236,22 @@ pub(crate) fn gaussian_blur_dyn_image( image: &DynamicImage, parameters: GaussianBlurParameters, ) -> DynamicImage { + assert!( + parameters.x_axis_kernel_size > 0, + "X axis kernel size must not be zero" + ); + assert!( + parameters.x_axis_sigma.is_normal(), + "Kernel sigma do not allow infinities, zeros, NaNs or subnormals or negatives" + ); + assert!( + parameters.y_axis_kernel_size > 0, + "Y axis kernel size must not be zero" + ); + assert!( + parameters.y_axis_sigma.is_normal(), + "Kernel sigma do not allow infinities, zeros, NaNs or subnormals or negatives" + ); let x_axis_kernel = get_gaussian_kernel_1d( parameters.x_axis_kernel_size as usize, parameters.x_axis_sigma, @@ -1422,6 +1438,22 @@ fn gaussian_blur_indirect_impl( where I::Pixel: 'static, { + assert!( + parameters.x_axis_kernel_size > 0, + "X axis kernel size must not be zero" + ); + assert!( + parameters.x_axis_sigma.is_normal(), + "Kernel sigma do not allow infinities, zeros, NaNs or subnormals or negatives" + ); + assert!( + parameters.y_axis_kernel_size > 0, + "Y axis kernel size must not be zero" + ); + assert!( + parameters.y_axis_sigma.is_normal(), + "Kernel sigma do not allow infinities, zeros, NaNs or subnormals or negatives" + ); let mut transient = vec![0f32; image.width() as usize * image.height() as usize * CN]; let transient_chunks = transient.as_chunks_mut::().0.iter_mut(); for (pixel, dst) in image.pixels().zip(transient_chunks) { From 81caf13145336757cd6900f0d3a2c43073c5d373 Mon Sep 17 00:00:00 2001 From: Radzivon Bartoshyk Date: Tue, 10 Mar 2026 19:28:49 +0000 Subject: [PATCH 2/5] Filtering NaN inputs in filter 1d --- src/imageops/sample.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index 1ef65f80d3..eb8a5a4f4d 100644 --- a/src/imageops/sample.rs +++ b/src/imageops/sample.rs @@ -1241,7 +1241,7 @@ pub(crate) fn gaussian_blur_dyn_image( "X axis kernel size must not be zero" ); assert!( - parameters.x_axis_sigma.is_normal(), + parameters.x_axis_sigma.is_normal() && parameters.x_axis_sigma > 0., "Kernel sigma do not allow infinities, zeros, NaNs or subnormals or negatives" ); assert!( @@ -1249,7 +1249,7 @@ pub(crate) fn gaussian_blur_dyn_image( "Y axis kernel size must not be zero" ); assert!( - parameters.y_axis_sigma.is_normal(), + parameters.y_axis_sigma.is_normal() && parameters.y_axis_sigma > 0., "Kernel sigma do not allow infinities, zeros, NaNs or subnormals or negatives" ); let x_axis_kernel = get_gaussian_kernel_1d( @@ -1443,7 +1443,7 @@ where "X axis kernel size must not be zero" ); assert!( - parameters.x_axis_sigma.is_normal(), + parameters.x_axis_sigma.is_normal() && parameters.x_axis_sigma > 0., "Kernel sigma do not allow infinities, zeros, NaNs or subnormals or negatives" ); assert!( @@ -1451,7 +1451,7 @@ where "Y axis kernel size must not be zero" ); assert!( - parameters.y_axis_sigma.is_normal(), + parameters.y_axis_sigma.is_normal() && parameters.y_axis_sigma > 0., "Kernel sigma do not allow infinities, zeros, NaNs or subnormals or negatives" ); let mut transient = vec![0f32; image.width() as usize * image.height() as usize * CN]; From 4f158d8db404b225bae13aa48f78cce3af67bcb8 Mon Sep 17 00:00:00 2001 From: Radzivon Bartoshyk Date: Tue, 10 Mar 2026 21:57:45 +0000 Subject: [PATCH 3/5] Filtering NaN inputs in filter 1d --- src/imageops/sample.rs | 110 +++++++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 48 deletions(-) diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index eb8a5a4f4d..76baf0d53f 100644 --- a/src/imageops/sample.rs +++ b/src/imageops/sample.rs @@ -1108,6 +1108,9 @@ impl GaussianBlurParameters { }; /// Creates a new parameters set from radius only. + /// + /// # Panics + /// Panics any radius is negative, subnormal, NaN or infinity. pub fn new_from_radius(radius: f32) -> GaussianBlurParameters { // Previous implementation was allowing passing 0 so we'll allow here also. assert!(radius >= 0.0); @@ -1124,13 +1127,12 @@ impl GaussianBlurParameters { /// /// Kernel size will be rounded to nearest odd, and used with fraction /// to compute accurate required sigma. + /// + /// # Panics + /// Panics any kernel size is zero, negative, infinite, NaN, subnormal or not odd. pub fn new_from_kernel_size(kernel_size: f32) -> GaussianBlurParameters { assert!( - kernel_size > 0., - "Kernel size do not allow infinities, zeros, NaNs or subnormals or negatives" - ); - assert!( - kernel_size.is_normal(), + kernel_size > 0. && kernel_size.is_normal(), "Kernel size do not allow infinities, zeros, NaNs or subnormals or negatives" ); let i_kernel_size = GaussianBlurParameters::round_to_nearest_odd(kernel_size); @@ -1148,24 +1150,19 @@ impl GaussianBlurParameters { /// /// Kernel size will be rounded to nearest odd, and used with fraction /// to compute accurate required sigma. + /// + /// # Panics + /// Panics any kernel size is zero, negative, infinite, NaN, subnormal or not odd. pub fn new_anisotropic_kernel_size( x_axis_kernel_size: f32, y_axis_kernel_size: f32, ) -> GaussianBlurParameters { assert!( - x_axis_kernel_size > 0., - "Kernel size do not allow infinities, zeros, NaNs or subnormals or negatives" - ); - assert!( - y_axis_kernel_size.is_normal(), - "Kernel size do not allow infinities, zeros, NaNs or subnormals or negatives" - ); - assert!( - y_axis_kernel_size > 0., + x_axis_kernel_size > 0. && x_axis_kernel_size.is_normal(), "Kernel size do not allow infinities, zeros, NaNs or subnormals or negatives" ); assert!( - y_axis_kernel_size.is_normal(), + y_axis_kernel_size > 0. && y_axis_kernel_size.is_normal(), "Kernel size do not allow infinities, zeros, NaNs or subnormals or negatives" ); let x_kernel_size = GaussianBlurParameters::round_to_nearest_odd(x_axis_kernel_size); @@ -1183,9 +1180,12 @@ impl GaussianBlurParameters { } /// Creates a new parameters set from sigma only + /// + /// # Panics + /// Panics if sigma is zero, negative, infinite, NaN, or subnormal. pub fn new_from_sigma(sigma: f32) -> GaussianBlurParameters { assert!( - sigma.is_normal(), + sigma.is_normal() && sigma > 0., "Sigma cannot be NaN, Infinities, subnormal or zero" ); assert!(sigma > 0.0, "Sigma must be positive"); @@ -1198,6 +1198,52 @@ impl GaussianBlurParameters { } } + /// Creates a new [`GaussianBlurParameters`] with independent kernel sizes and sigmas + /// for each axis, allowing non-uniform (anisotropic) blurring. + /// + /// # Parameters + /// - `x_axis_kernel_size` — width of the horizontal kernel in pixels, must be > 0 + /// - `x_axis_sigma` — standard deviation for the horizontal pass; controls blur strength + /// - `y_axis_kernel_size` — height of the vertical kernel in pixels, must be > 0 + /// - `y_axis_sigma` — standard deviation for the vertical pass + /// + /// For uniform (isotropic) blur, pass equal values for both axes. + /// Larger sigma = more blur; kernel size should typically be `~2*ceil(3*sigma)+1` + /// to fully cover the Gaussian curve. + /// + /// # Panics + /// Panics if any kernel size is 0, not odd, or if any sigma is zero, negative, infinite, + /// NaN, or subnormal. + pub fn new( + x_axis_kernel_size: u32, + x_axis_sigma: f32, + y_axis_kernel_size: u32, + y_axis_sigma: f32, + ) -> GaussianBlurParameters { + assert!( + x_axis_kernel_size > 0 || !x_axis_kernel_size.is_multiple_of(2), + "Kernel size must be more than 0 and must be odd" + ); + assert!( + y_axis_kernel_size > 0 || !y_axis_kernel_size.is_multiple_of(2), + "Kernel size must be more than 0 and must be odd" + ); + assert!( + x_axis_sigma.is_normal() && x_axis_sigma > 0., + "Kernel sigma do not allow infinities, zeros, NaNs or subnormals or negatives" + ); + assert!( + y_axis_sigma.is_normal() && y_axis_sigma > 0., + "Kernel sigma do not allow infinities, zeros, NaNs or subnormals or negatives" + ); + GaussianBlurParameters { + x_axis_kernel_size, + x_axis_sigma, + y_axis_kernel_size, + y_axis_sigma, + } + } + #[inline] fn round_to_nearest_odd(x: f32) -> u32 { let n = x.round() as u32; @@ -1236,22 +1282,6 @@ pub(crate) fn gaussian_blur_dyn_image( image: &DynamicImage, parameters: GaussianBlurParameters, ) -> DynamicImage { - assert!( - parameters.x_axis_kernel_size > 0, - "X axis kernel size must not be zero" - ); - assert!( - parameters.x_axis_sigma.is_normal() && parameters.x_axis_sigma > 0., - "Kernel sigma do not allow infinities, zeros, NaNs or subnormals or negatives" - ); - assert!( - parameters.y_axis_kernel_size > 0, - "Y axis kernel size must not be zero" - ); - assert!( - parameters.y_axis_sigma.is_normal() && parameters.y_axis_sigma > 0., - "Kernel sigma do not allow infinities, zeros, NaNs or subnormals or negatives" - ); let x_axis_kernel = get_gaussian_kernel_1d( parameters.x_axis_kernel_size as usize, parameters.x_axis_sigma, @@ -1438,22 +1468,6 @@ fn gaussian_blur_indirect_impl( where I::Pixel: 'static, { - assert!( - parameters.x_axis_kernel_size > 0, - "X axis kernel size must not be zero" - ); - assert!( - parameters.x_axis_sigma.is_normal() && parameters.x_axis_sigma > 0., - "Kernel sigma do not allow infinities, zeros, NaNs or subnormals or negatives" - ); - assert!( - parameters.y_axis_kernel_size > 0, - "Y axis kernel size must not be zero" - ); - assert!( - parameters.y_axis_sigma.is_normal() && parameters.y_axis_sigma > 0., - "Kernel sigma do not allow infinities, zeros, NaNs or subnormals or negatives" - ); let mut transient = vec![0f32; image.width() as usize * image.height() as usize * CN]; let transient_chunks = transient.as_chunks_mut::().0.iter_mut(); for (pixel, dst) in image.pixels().zip(transient_chunks) { From dab6e4c4ce0f5b7049e3758bde0e2325b1686eff Mon Sep 17 00:00:00 2001 From: Radzivon Bartoshyk Date: Tue, 10 Mar 2026 21:59:05 +0000 Subject: [PATCH 4/5] Filtering NaN inputs in filter 1d --- src/imageops/sample.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index 76baf0d53f..6ac18ed5e0 100644 --- a/src/imageops/sample.rs +++ b/src/imageops/sample.rs @@ -1207,10 +1207,6 @@ impl GaussianBlurParameters { /// - `y_axis_kernel_size` — height of the vertical kernel in pixels, must be > 0 /// - `y_axis_sigma` — standard deviation for the vertical pass /// - /// For uniform (isotropic) blur, pass equal values for both axes. - /// Larger sigma = more blur; kernel size should typically be `~2*ceil(3*sigma)+1` - /// to fully cover the Gaussian curve. - /// /// # Panics /// Panics if any kernel size is 0, not odd, or if any sigma is zero, negative, infinite, /// NaN, or subnormal. From 097721c3e8d0e33d8a307cd7f1649df4d1c6cd8c Mon Sep 17 00:00:00 2001 From: Radzivon Bartoshyk Date: Tue, 10 Mar 2026 22:17:33 +0000 Subject: [PATCH 5/5] Fix comments --- src/imageops/sample.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index 6ac18ed5e0..b07dd0955c 100644 --- a/src/imageops/sample.rs +++ b/src/imageops/sample.rs @@ -1129,7 +1129,7 @@ impl GaussianBlurParameters { /// to compute accurate required sigma. /// /// # Panics - /// Panics any kernel size is zero, negative, infinite, NaN, subnormal or not odd. + /// Panics any kernel size is zero, negative, infinite, NaN, subnormal. pub fn new_from_kernel_size(kernel_size: f32) -> GaussianBlurParameters { assert!( kernel_size > 0. && kernel_size.is_normal(), @@ -1152,7 +1152,7 @@ impl GaussianBlurParameters { /// to compute accurate required sigma. /// /// # Panics - /// Panics any kernel size is zero, negative, infinite, NaN, subnormal or not odd. + /// Panics any kernel size is zero, negative, infinite, NaN, subnormal. pub fn new_anisotropic_kernel_size( x_axis_kernel_size: f32, y_axis_kernel_size: f32,