diff --git a/src/imageops/filter_1d.rs b/src/imageops/filter_1d.rs index 8fb2a66fdc..2081faa562 100644 --- a/src/imageops/filter_1d.rs +++ b/src/imageops/filter_1d.rs @@ -929,6 +929,26 @@ pub(crate) fn filter_1d_rgba( filter_1d::(image, destination, image_size, row_kernel, column_kernel) } +pub(crate) fn filter_1d_la_f32( + image: &[f32], + destination: &mut [f32], + image_size: FilterImageSize, + row_kernel: &[f32], + column_kernel: &[f32], +) -> Result<(), ImageError> { + filter_1d::(image, destination, image_size, row_kernel, column_kernel) +} + +pub(crate) fn filter_1d_plane_f32( + image: &[f32], + destination: &mut [f32], + image_size: FilterImageSize, + row_kernel: &[f32], + column_kernel: &[f32], +) -> Result<(), ImageError> { + filter_1d::(image, destination, image_size, row_kernel, column_kernel) +} + pub(crate) fn filter_1d_rgb_f32( image: &[f32], destination: &mut [f32], diff --git a/src/imageops/mod.rs b/src/imageops/mod.rs index 82b46133e5..587ca6bed1 100644 --- a/src/imageops/mod.rs +++ b/src/imageops/mod.rs @@ -17,9 +17,10 @@ pub use self::affine::{ /// Image sampling pub use self::sample::{ - blur, filter3x3, gaussian_blur_dyn_image, interpolate_bilinear, interpolate_nearest, resize, + blur, filter3x3, interpolate_bilinear, interpolate_nearest, resize, sample_bilinear, sample_nearest, thumbnail, unsharpen, }; +pub(crate) use self::sample::gaussian_blur_dyn_image; /// Color operations pub use self::colorops::{ diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index 5d3949b4d0..573cf90524 100644 --- a/src/imageops/sample.rs +++ b/src/imageops/sample.rs @@ -10,9 +10,9 @@ use std::ops::Mul; use crate::buffer_::{Gray16Image, GrayAlpha16Image, Rgb16Image, Rgba16Image}; use crate::image::{GenericImage, GenericImageView}; use crate::imageops::filter_1d::{ - filter_1d_la, filter_1d_la_u16, filter_1d_plane, filter_1d_plane_u16, filter_1d_rgb, - filter_1d_rgb_f32, filter_1d_rgb_u16, filter_1d_rgba, filter_1d_rgba_f32, filter_1d_rgba_u16, - FilterImageSize, + filter_1d_la, filter_1d_la_f32, filter_1d_la_u16, filter_1d_plane, filter_1d_plane_f32, + filter_1d_plane_u16, filter_1d_rgb, filter_1d_rgb_f32, filter_1d_rgb_u16, filter_1d_rgba, + filter_1d_rgba_f32, filter_1d_rgba_u16, FilterImageSize, }; use crate::traits::{Enlargeable, Pixel, Primitive}; use crate::utils::clamp; @@ -1002,58 +1002,53 @@ pub fn blur( where I::Pixel: 'static, { - let sigma = if sigma <= 0.0 { 1.0 } else { sigma }; + // let sigma = if sigma <= 0.0 { 1.0 } else { sigma }; + // + // let mut method = Filter { + // kernel: Box::new(|x| gaussian(x, sigma)), + // support: 2.0 * sigma, + // }; + // + // let (width, height) = image.dimensions(); + // let is_empty = width == 0 || height == 0; + // + // if is_empty { + // return ImageBuffer::new(width, height); + // } + // + // // Keep width and height the same for horizontal and + // // vertical sampling. + // // Note: tmp is not necessarily actually Rgba + // let tmp: Rgba32FImage = vertical_sample(image, height, &mut method); + // horizontal_sample(&tmp, width, &mut method) + gaussian_blur_indirect(image, sigma) +} - let mut method = Filter { - kernel: Box::new(|x| gaussian(x, sigma)), - support: 2.0 * sigma, - }; +fn get_gaussian_kernel_1d(width: usize, sigma: f32) -> Vec { + let mut sum_norm: f32 = 0f32; + let mut kernel = vec![0f32; width]; + let scale = 1f32 / (f32::sqrt(2f32 * f32::consts::PI) * sigma); + let mean = (width / 2) as f32; - let (width, height) = image.dimensions(); - let is_empty = width == 0 || height == 0; + for (x, weight) in kernel.iter_mut().enumerate() { + let new_weight = f32::exp(-0.5f32 * f32::powf((x as f32 - mean) / sigma, 2.0f32)) * scale; + *weight = new_weight; + sum_norm += new_weight; + } - if is_empty { - return ImageBuffer::new(width, height); + if sum_norm != 0f32 { + let sum_scale = 1f32 / sum_norm; + for weight in kernel.iter_mut() { + *weight = weight.mul(sum_scale); + } } - // Keep width and height the same for horizontal and - // vertical sampling. - // Note: tmp is not necessarily actually Rgba - let tmp: Rgba32FImage = vertical_sample(image, height, &mut method); - horizontal_sample(&tmp, width, &mut method) + kernel } /// In previous implementation sigma means radius, which is not the same one -pub fn gaussian_blur_dyn_image(image: &DynamicImage, radius: f32) -> DynamicImage { - fn get_sigma_size(kernel_size: usize) -> f32 { - 0.3f32 * ((kernel_size as f32 - 1.) * 0.5f32 - 1f32) + 0.8f32 - } - - fn get_gaussian_kernel_1d(width: usize, sigma: f32) -> Vec { - let mut sum_norm: f32 = 0f32; - let mut kernel = vec![0f32; width]; - let scale = 1f32 / (f32::sqrt(2f32 * f32::consts::PI) * sigma); - let mean = (width / 2) as f32; - - for (x, weight) in kernel.iter_mut().enumerate() { - let new_weight = - f32::exp(-0.5f32 * f32::powf((x as f32 - mean) / sigma, 2.0f32)) * scale; - *weight = new_weight; - sum_norm += new_weight; - } - - if sum_norm != 0f32 { - let sum_scale = 1f32 / sum_norm; - for weight in kernel.iter_mut() { - *weight = weight.mul(sum_scale); - } - } - - kernel - } - - let kernel_size = radius.max(1.) as usize * 2 + 1; - let sigma = get_sigma_size(kernel_size); +pub(crate) fn gaussian_blur_dyn_image(image: &DynamicImage, sigma: f32) -> DynamicImage { + let kernel_size = sigma.max(1.) as usize * 2 + 1; let gaussian_kernel = get_gaussian_kernel_1d(kernel_size, sigma); let filter_image_size = FilterImageSize { @@ -1205,6 +1200,149 @@ pub fn gaussian_blur_dyn_image(image: &DynamicImage, radius: f32) -> DynamicImag } } +pub(crate) fn gaussian_blur_indirect( + image: &I, + sigma: f32, +) -> ImageBuffer::Subpixel>> +where + I::Pixel: 'static, +{ + match I::Pixel::CHANNEL_COUNT { + 1 => gaussian_blur_indirect_impl::(image, sigma), + 2 => gaussian_blur_indirect_impl::(image, sigma), + 3 => gaussian_blur_indirect_impl::(image, sigma), + 4 => gaussian_blur_indirect_impl::(image, sigma), + _ => unimplemented!(), + } +} + +fn gaussian_blur_indirect_impl( + image: &I, + sigma: f32, +) -> ImageBuffer::Subpixel>> +where + I::Pixel: 'static, +{ + let mut transient = vec![0f32; image.width() as usize * image.height() as usize * CN]; + for (pixel, dst) in image.pixels().zip(transient.chunks_exact_mut(CN)) { + let px = pixel.2.channels(); + match CN { + 1 => { + dst[0] = NumCast::from(px[0]).unwrap(); + } + 2 => { + dst[0] = NumCast::from(px[0]).unwrap(); + dst[1] = NumCast::from(px[1]).unwrap(); + } + 3 => { + dst[0] = NumCast::from(px[0]).unwrap(); + dst[1] = NumCast::from(px[1]).unwrap(); + dst[2] = NumCast::from(px[2]).unwrap(); + } + 4 => { + dst[0] = NumCast::from(px[0]).unwrap(); + dst[1] = NumCast::from(px[1]).unwrap(); + dst[2] = NumCast::from(px[2]).unwrap(); + dst[3] = NumCast::from(px[3]).unwrap(); + } + _ => unreachable!(), + } + } + + let mut transient_dst = vec![0f32; image.width() as usize * image.height() as usize * CN]; + + let kernel_size = sigma.max(1.) as usize * 2 + 1; + let gaussian_kernel = get_gaussian_kernel_1d(kernel_size, sigma); + + let filter_image_size = FilterImageSize { + width: image.width() as usize, + height: image.height() as usize, + }; + + match CN { + 1 => { + filter_1d_plane_f32( + &transient, + &mut transient_dst, + filter_image_size, + &gaussian_kernel, + &gaussian_kernel, + ) + .unwrap(); + } + 2 => { + filter_1d_la_f32( + &transient, + &mut transient_dst, + filter_image_size, + &gaussian_kernel, + &gaussian_kernel, + ) + .unwrap(); + } + 3 => { + filter_1d_rgb_f32( + &transient, + &mut transient_dst, + filter_image_size, + &gaussian_kernel, + &gaussian_kernel, + ) + .unwrap(); + } + 4 => { + filter_1d_rgba_f32( + &transient, + &mut transient_dst, + filter_image_size, + &gaussian_kernel, + &gaussian_kernel, + ) + .unwrap(); + } + _ => unreachable!(), + } + + let mut out = ImageBuffer::new(image.width(), image.height()); + for (dst, src) in out.pixels_mut().zip(transient_dst.chunks_exact_mut(CN)) { + match CN { + 1 => { + let v0 = NumCast::from(FloatNearest(src[0])).unwrap(); + #[allow(deprecated)] + let t = Pixel::from_channels(v0, v0, v0, v0); + *dst = t; + } + 2 => { + let v0 = NumCast::from(FloatNearest(src[0])).unwrap(); + let v1 = NumCast::from(FloatNearest(src[1])).unwrap(); + #[allow(deprecated)] + let t = Pixel::from_channels(v0, v1, v0, v0); + *dst = t; + } + 3 => { + let v0 = NumCast::from(FloatNearest(src[0])).unwrap(); + let v1 = NumCast::from(FloatNearest(src[1])).unwrap(); + let v2 = NumCast::from(FloatNearest(src[2])).unwrap(); + #[allow(deprecated)] + let t = Pixel::from_channels(v0, v1, v2, v0); + *dst = t; + } + 4 => { + let v0 = NumCast::from(FloatNearest(src[0])).unwrap(); + let v1 = NumCast::from(FloatNearest(src[1])).unwrap(); + let v2 = NumCast::from(FloatNearest(src[2])).unwrap(); + let v3 = NumCast::from(FloatNearest(src[3])).unwrap(); + #[allow(deprecated)] + let t = Pixel::from_channels(v0, v1, v2, v3); + *dst = t; + } + _ => unreachable!(), + } + } + + out +} + /// Performs an unsharpen mask on the supplied image. /// ```sigma``` is the amount to blur the image by. /// ```threshold``` is the threshold for minimal brightness change that will be sharpened.