From 926a500dffd880ee2a6e2eb617a9ada98549fe6b Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Sun, 22 Mar 2026 20:26:30 +0100 Subject: [PATCH] Fix `unsharpen` API and f32 behavior --- benches/imageops.rs | 2 +- src/imageops/sample.rs | 26 ++++++++++++++++---------- src/images/dynimage.rs | 11 +++++++---- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/benches/imageops.rs b/benches/imageops.rs index 3f58892747..d83317d723 100644 --- a/benches/imageops.rs +++ b/benches/imageops.rs @@ -69,7 +69,7 @@ pub fn bench_imageops(c: &mut Criterion) { }); c.bench_function("unsharpen", |b| { - b.iter(|| imageops::unsharpen(&src, 2.0, 0)); + b.iter(|| imageops::unsharpen(&src, 2.0, 0.0)); }); } diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index 6797f7b41c..8cb7bff0d2 100644 --- a/src/imageops/sample.rs +++ b/src/imageops/sample.rs @@ -1546,16 +1546,19 @@ fn gaussian_blur_indirect_impl( /// Performs an unsharpen mask on the supplied image. /// -/// # Arguments: +/// # Arguments /// -/// * `sigma` - is the amount to blur the image by. -/// * `threshold` - is the threshold for minimal brightness change that will be sharpened. +/// - `sigma` - The amount to blur the image by. +/// - `threshold` - The threshold between 0 and 1 that removes sharpening from +/// areas with little contrast. +/// +/// # Notes /// /// This method typically assumes that the input is scene-linear light. /// If it is not, color distortion may occur. /// /// See [Digital unsharp masking](https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking) for more information. -pub fn unsharpen(image: &I, sigma: f32, threshold: i32) -> ImageBuffer> +pub fn unsharpen(image: &I, sigma: f32, threshold: f32) -> ImageBuffer> where I: GenericImageView, P: Pixel, @@ -1563,8 +1566,11 @@ where { let mut tmp = blur_advanced(image, GaussianBlurParameters::new_from_sigma(sigma)); - let max = S::DEFAULT_MAX_VALUE; - let max: i32 = NumCast::from(max).unwrap(); + let max: f32 = NumCast::from(S::DEFAULT_MAX_VALUE).unwrap(); + let min: f32 = NumCast::from(S::DEFAULT_MIN_VALUE).unwrap(); + + let threshold = threshold * (max - min); + let (width, height) = image.dimensions(); for y in 0..height { @@ -1573,13 +1579,13 @@ where let b = tmp.get_pixel_mut(x, y); let p = a.map2(b, |c, d| { - let ic: i32 = NumCast::from(c).unwrap(); - let id: i32 = NumCast::from(d).unwrap(); + let c_f: f32 = NumCast::from(c).unwrap(); + let d_f: f32 = NumCast::from(d).unwrap(); - let diff = ic - id; + let diff = c_f - d_f; if diff.abs() > threshold { - let e = clamp(ic + diff, 0, max); // FIXME what does this do for f32? clamp 0-1 integers?? + let e = clamp(c_f + diff, min, max); NumCast::from(e).unwrap() } else { diff --git a/src/images/dynimage.rs b/src/images/dynimage.rs index d6a69d01c8..eee9e749d0 100644 --- a/src/images/dynimage.rs +++ b/src/images/dynimage.rs @@ -1019,8 +1019,11 @@ impl DynamicImage { /// /// # Arguments /// - /// * `sigma` - value controls image flattening level. - /// * `threshold` - is a control of how much to sharpen. + /// - `sigma` - The amount to blur the image by. + /// - `threshold` - The threshold between 0 and 1 that removes sharpening from + /// areas with little contrast. + /// + /// # Notes /// /// This method typically assumes that the input is scene-linear light. If it is not, color /// distortion may occur. It operates on pixel channel values directly without taking into @@ -1029,7 +1032,7 @@ impl DynamicImage { /// See [Digital unsharp masking](https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking) /// for more information #[must_use] - pub fn unsharpen(&self, sigma: f32, threshold: i32) -> DynamicImage { + pub fn unsharpen(&self, sigma: f32, threshold: f32) -> DynamicImage { dynamic_map!(*self, ref p => imageops::unsharpen(p, sigma, threshold)) } @@ -2219,7 +2222,7 @@ mod test { ) }, &|img| img.fast_blur(1.0), - &|img| img.unsharpen(1.0, 3), + &|img| img.unsharpen(1.0, 0.02), &|img| img.filter3x3(&[0.0, -1.0, 0.0, -1.0, 5.0, -1.0, 0.0, -1.0, 0.0]), &|img| img.adjust_contrast(0.5), &|img| img.brighten(10),