Skip to content

Commit

Permalink
Handle generic image
Browse files Browse the repository at this point in the history
  • Loading branch information
awxkee committed Dec 17, 2024
1 parent eceee18 commit 64022dc
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 48 deletions.
20 changes: 20 additions & 0 deletions src/imageops/filter_1d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,26 @@ pub(crate) fn filter_1d_rgba(
filter_1d::<u8, f32, i32, 4>(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::<f32, f32, f32, 2>(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::<f32, f32, f32, 1>(image, destination, image_size, row_kernel, column_kernel)
}

pub(crate) fn filter_1d_rgb_f32(
image: &[f32],
destination: &mut [f32],
Expand Down
3 changes: 2 additions & 1 deletion src/imageops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down
232 changes: 185 additions & 47 deletions src/imageops/sample.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1002,58 +1002,53 @@ pub fn blur<I: GenericImageView>(
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<f32> {
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<f32> {
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 {
Expand Down Expand Up @@ -1205,6 +1200,149 @@ pub fn gaussian_blur_dyn_image(image: &DynamicImage, radius: f32) -> DynamicImag
}
}

pub(crate) fn gaussian_blur_indirect<I: GenericImageView>(
image: &I,
sigma: f32,
) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>>
where
I::Pixel: 'static,
{
match I::Pixel::CHANNEL_COUNT {
1 => gaussian_blur_indirect_impl::<I, 1>(image, sigma),
2 => gaussian_blur_indirect_impl::<I, 2>(image, sigma),
3 => gaussian_blur_indirect_impl::<I, 3>(image, sigma),
4 => gaussian_blur_indirect_impl::<I, 4>(image, sigma),
_ => unimplemented!(),
}
}

fn gaussian_blur_indirect_impl<I: GenericImageView, const CN: usize>(
image: &I,
sigma: f32,
) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::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.
Expand Down

0 comments on commit 64022dc

Please sign in to comment.