use crate::channels_configuration::FastBlurChannels;
use crate::gaussian::gaussian_hint::IeeeBinaryConvolutionMode;
use crate::gaussian::gaussian_kernel::gaussian_kernel_1d;
use crate::gaussian::gaussian_util::{kernel_size as get_kernel_size, kernel_size_d};
use crate::{
BlurError, BlurImage, BlurImageMut, ConvolutionMode, EdgeMode2D, Scalar, ThreadingPolicy,
filter_1d_approx, filter_1d_exact, gaussian_kernel_1d_f64, sigma_size, sigma_size_d,
};
#[cfg(feature = "nightly_f16")]
use core::f16;
#[derive(Copy, Clone, Debug)]
pub struct GaussianBlurParams {
pub x_kernel: u32,
pub x_sigma: f64,
pub y_kernel: u32,
pub y_sigma: f64,
}
#[inline]
fn round_to_nearest_odd(x: f64) -> i64 {
let n = x.round() as i64;
if n % 2 != 0 {
n
} else {
let lower = n - 1;
let upper = n + 1;
let dist_lower = (x - lower as f64).abs();
let dist_upper = (x - upper as f64).abs();
if dist_lower <= dist_upper {
lower
} else {
upper
}
}
}
impl GaussianBlurParams {
pub fn new(kernel: u32, sigma: f64) -> GaussianBlurParams {
GaussianBlurParams {
x_kernel: kernel,
x_sigma: sigma,
y_kernel: kernel,
y_sigma: sigma,
}
}
pub fn new_from_sigma(sigma: f64) -> GaussianBlurParams {
assert!(sigma > 0.);
let kernel_size = kernel_size_d(sigma);
Self::new(kernel_size, sigma)
}
pub fn new_from_kernel(kernel: f64) -> GaussianBlurParams {
assert!(kernel > 0.);
let sigma = sigma_size_d(kernel);
Self::new(round_to_nearest_odd(kernel) as u32, sigma)
}
pub fn new_asymmetric_from_kernels(x_kernel: f64, y_kernel: f64) -> GaussianBlurParams {
assert!(x_kernel > 0.);
assert!(y_kernel > 0.);
let x_sigma = sigma_size_d(x_kernel);
let y_sigma = sigma_size_d(y_kernel);
Self::new_asymmetric(
round_to_nearest_odd(x_kernel) as u32,
x_sigma,
round_to_nearest_odd(y_kernel) as u32,
y_sigma,
)
}
pub fn new_asymmetric(
x_kernel: u32,
x_sigma: f64,
y_kernel: u32,
y_sigma: f64,
) -> GaussianBlurParams {
GaussianBlurParams {
x_kernel,
x_sigma,
y_kernel,
y_sigma,
}
}
pub fn new_asymmetric_from_sigma(x_sigma: f64, y_sigma: f64) -> GaussianBlurParams {
GaussianBlurParams {
x_kernel: kernel_size_d(x_sigma),
x_sigma,
y_kernel: kernel_size_d(y_sigma),
y_sigma,
}
}
fn make_f32_kernel(&self, kernel_size: u32, sigma: f32) -> Vec<f32> {
assert!(
kernel_size != 0 || sigma > 0.0,
"Either sigma or kernel size must be set"
);
if kernel_size != 0 {
assert_ne!(kernel_size % 2, 0, "Kernel size must be odd");
}
let sigma = if sigma <= 0. {
sigma_size(kernel_size as f32)
} else {
sigma
};
let kernel_size = if kernel_size == 0 {
get_kernel_size(sigma)
} else {
kernel_size
};
gaussian_kernel_1d(kernel_size, sigma)
}
fn make_f64_kernel(&self, kernel_size: u32, sigma: f64) -> Vec<f64> {
assert!(
kernel_size != 0 || sigma > 0.0,
"Either sigma or kernel size must be set"
);
if kernel_size != 0 {
assert_ne!(kernel_size % 2, 0, "Kernel size must be odd");
}
let sigma = if sigma <= 0. {
sigma_size_d(kernel_size as f64)
} else {
sigma
};
let kernel_size = if kernel_size == 0 {
kernel_size_d(sigma)
} else {
kernel_size
};
gaussian_kernel_1d_f64(kernel_size, sigma)
}
fn make_f32_kernels(&self) -> (Vec<f32>, Vec<f32>) {
let vx_kernel = self.make_f32_kernel(self.x_kernel, self.x_sigma as f32);
let vy_kernel = self.make_f32_kernel(self.y_kernel, self.y_sigma as f32);
(vx_kernel, vy_kernel)
}
fn make_f64_kernels(&self) -> (Vec<f64>, Vec<f64>) {
let vx_kernel = self.make_f64_kernel(self.x_kernel, self.x_sigma);
let vy_kernel = self.make_f64_kernel(self.y_kernel, self.y_sigma);
(vx_kernel, vy_kernel)
}
fn validate(&self) -> Result<(), BlurError> {
if self.x_sigma < 0. || self.y_sigma < 0. {
return Err(BlurError::NegativeOrZeroSigma);
}
if self.x_kernel > 0 && self.x_kernel.is_multiple_of(2) {
return Err(BlurError::OddKernel(self.x_kernel as usize));
}
if self.y_kernel > 0 && self.y_kernel.is_multiple_of(2) {
return Err(BlurError::OddKernel(self.y_kernel as usize));
}
if self.x_sigma == 0. && self.x_kernel == 0 {
return Err(BlurError::InvalidArguments);
}
if self.y_sigma == 0. && self.y_kernel == 0 {
return Err(BlurError::InvalidArguments);
}
Ok(())
}
}
pub fn gaussian_blur(
src: &BlurImage<u8>,
dst: &mut BlurImageMut<u8>,
params: GaussianBlurParams,
edge_modes: EdgeMode2D,
threading_policy: ThreadingPolicy,
hint: ConvolutionMode,
) -> Result<(), BlurError> {
src.check_layout()?;
dst.check_layout(Some(src))?;
src.size_matches_mut(dst)?;
params.validate()?;
let (x_kernel, y_kernel) = params.make_f32_kernels();
match hint {
ConvolutionMode::Exact => {
let _dispatcher = match src.channels {
FastBlurChannels::Plane => filter_1d_exact::<u8, f32, 1>,
FastBlurChannels::Channels3 => filter_1d_exact::<u8, f32, 3>,
FastBlurChannels::Channels4 => filter_1d_exact::<u8, f32, 4>,
};
_dispatcher(
src,
dst,
&x_kernel,
&y_kernel,
edge_modes,
Scalar::default(),
threading_policy,
)?;
}
ConvolutionMode::FixedPoint => {
let _dispatcher = match src.channels {
FastBlurChannels::Plane => filter_1d_approx::<u8, f32, i32, 1>,
FastBlurChannels::Channels3 => filter_1d_approx::<u8, f32, i32, 3>,
FastBlurChannels::Channels4 => filter_1d_approx::<u8, f32, i32, 4>,
};
_dispatcher(
src,
dst,
&x_kernel,
&y_kernel,
edge_modes,
Scalar::default(),
threading_policy,
)?;
}
}
Ok(())
}
pub fn gaussian_blur_u16(
src: &BlurImage<u16>,
dst: &mut BlurImageMut<u16>,
params: GaussianBlurParams,
edge_modes: EdgeMode2D,
threading_policy: ThreadingPolicy,
hint: ConvolutionMode,
) -> Result<(), BlurError> {
src.check_layout()?;
dst.check_layout(Some(src))?;
src.size_matches_mut(dst)?;
params.validate()?;
let (x_kernel, y_kernel) = params.make_f32_kernels();
match hint {
ConvolutionMode::Exact => {
let _dispatcher = match src.channels {
FastBlurChannels::Plane => filter_1d_exact::<u16, f32, 1>,
FastBlurChannels::Channels3 => filter_1d_exact::<u16, f32, 3>,
FastBlurChannels::Channels4 => filter_1d_exact::<u16, f32, 4>,
};
_dispatcher(
src,
dst,
&x_kernel,
&y_kernel,
edge_modes,
Scalar::default(),
threading_policy,
)
}
ConvolutionMode::FixedPoint => {
use crate::filter1d::filter_1d_approx;
let _dispatcher = match src.channels {
FastBlurChannels::Plane => filter_1d_approx::<u16, f32, u32, 1>,
FastBlurChannels::Channels3 => filter_1d_approx::<u16, f32, u32, 3>,
FastBlurChannels::Channels4 => filter_1d_approx::<u16, f32, u32, 4>,
};
_dispatcher(
src,
dst,
&x_kernel,
&y_kernel,
edge_modes,
Scalar::default(),
threading_policy,
)
}
}
}
pub fn gaussian_blur_f32(
src: &BlurImage<f32>,
dst: &mut BlurImageMut<f32>,
params: GaussianBlurParams,
edge_modes: EdgeMode2D,
threading_policy: ThreadingPolicy,
convolution_mode: IeeeBinaryConvolutionMode,
) -> Result<(), BlurError> {
src.check_layout()?;
dst.check_layout(Some(src))?;
src.size_matches_mut(dst)?;
params.validate()?;
match convolution_mode {
IeeeBinaryConvolutionMode::Normal => {
let (x_kernel, y_kernel) = params.make_f32_kernels();
let _dispatcher = match src.channels {
FastBlurChannels::Plane => filter_1d_exact::<f32, f32, 1>,
FastBlurChannels::Channels3 => filter_1d_exact::<f32, f32, 3>,
FastBlurChannels::Channels4 => filter_1d_exact::<f32, f32, 4>,
};
_dispatcher(
src,
dst,
&x_kernel,
&y_kernel,
edge_modes,
Scalar::default(),
threading_policy,
)
}
IeeeBinaryConvolutionMode::Zealous => {
let (x_kernel, y_kernel) = params.make_f64_kernels();
let _dispatcher = match src.channels {
FastBlurChannels::Plane => filter_1d_exact::<f32, f64, 1>,
FastBlurChannels::Channels3 => filter_1d_exact::<f32, f64, 3>,
FastBlurChannels::Channels4 => filter_1d_exact::<f32, f64, 4>,
};
_dispatcher(
src,
dst,
&x_kernel,
&y_kernel,
edge_modes,
Scalar::default(),
threading_policy,
)
}
}
}
#[cfg(feature = "nightly_f16")]
#[cfg_attr(docsrs, doc(cfg(feature = "nightly_f16")))]
pub fn gaussian_blur_f16(
src: &BlurImage<f16>,
dst: &mut BlurImageMut<f16>,
params: GaussianBlurParams,
edge_modes: EdgeMode2D,
threading_policy: ThreadingPolicy,
) -> Result<(), BlurError> {
src.check_layout()?;
dst.check_layout(Some(src))?;
src.size_matches_mut(dst)?;
params.validate()?;
let (x_kernel, y_kernel) = params.make_f32_kernels();
let _dispatcher = match src.channels {
FastBlurChannels::Plane => filter_1d_exact::<f16, f32, 1>,
FastBlurChannels::Channels3 => filter_1d_exact::<f16, f32, 3>,
FastBlurChannels::Channels4 => filter_1d_exact::<f16, f32, 4>,
};
_dispatcher(
src,
dst,
&x_kernel,
&y_kernel,
edge_modes,
Scalar::default(),
threading_policy,
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::EdgeMode;
use crate::{gaussian_kernel_1d_f64, sigma_size_d};
macro_rules! compare_u8_stat {
($dst: expr) => {
for (i, cn) in $dst.data.borrow_mut().chunks_exact(3).enumerate() {
let diff0 = (cn[0] as i32 - 126).abs();
assert!(
diff0 <= 3,
"Diff expected to be less than 3, but it was {diff0} at {i} in channel 0"
);
let diff1 = (cn[1] as i32 - 66).abs();
assert!(
diff1 <= 3,
"Diff expected to be less than 3, but it was {diff1} at {i} in channel 1"
);
let diff2 = (cn[2] as i32 - 77).abs();
assert!(
diff2 <= 3,
"Diff expected to be less than 3, but it was {diff2} at {i} in channel 2"
);
}
};
}
#[test]
fn test_gauss_u8_q_k5() {
let width: usize = 148;
let height: usize = 148;
let mut src = vec![126; width * height * 3];
for dst in src.chunks_exact_mut(3) {
dst[0] = 126;
dst[1] = 66;
dst[2] = 77;
}
let src_image = BlurImage::borrow(
&src,
width as u32,
height as u32,
FastBlurChannels::Channels3,
);
let mut dst = BlurImageMut::default();
gaussian_blur(
&src_image,
&mut dst,
GaussianBlurParams::new_from_kernel(5.),
EdgeMode2D::new(EdgeMode::Clamp),
ThreadingPolicy::Single,
ConvolutionMode::FixedPoint,
)
.unwrap();
compare_u8_stat!(dst);
}
#[test]
fn test_gauss_u8_q_k3() {
let width: usize = 148;
let height: usize = 148;
let mut src = vec![126; width * height * 3];
for dst in src.chunks_exact_mut(3) {
dst[0] = 126;
dst[1] = 66;
dst[2] = 77;
}
let src_image = BlurImage::borrow(
&src,
width as u32,
height as u32,
FastBlurChannels::Channels3,
);
let mut dst = BlurImageMut::default();
gaussian_blur(
&src_image,
&mut dst,
GaussianBlurParams::new_from_kernel(3.),
EdgeMode2D::new(EdgeMode::Clamp),
ThreadingPolicy::Single,
ConvolutionMode::FixedPoint,
)
.unwrap();
println!("{}", dst.data.borrow_mut()[0]);
compare_u8_stat!(dst);
}
#[test]
fn test_gauss_u8_q_k7() {
let width: usize = 148;
let height: usize = 148;
let mut src = vec![126; width * height * 3];
for dst in src.chunks_exact_mut(3) {
dst[0] = 126;
dst[1] = 66;
dst[2] = 77;
}
let src_image = BlurImage::borrow(
&src,
width as u32,
height as u32,
FastBlurChannels::Channels3,
);
let mut dst = BlurImageMut::default();
gaussian_blur(
&src_image,
&mut dst,
GaussianBlurParams::new_from_kernel(7.),
EdgeMode2D::new(EdgeMode::Clamp),
ThreadingPolicy::Single,
ConvolutionMode::FixedPoint,
)
.unwrap();
compare_u8_stat!(dst);
}
#[test]
fn test_gauss_u8_fp_k5() {
let width: usize = 148;
let height: usize = 148;
let mut src = vec![126; width * height * 3];
for dst in src.chunks_exact_mut(3) {
dst[0] = 126;
dst[1] = 66;
dst[2] = 77;
}
let src_image = BlurImage::borrow(
&src,
width as u32,
height as u32,
FastBlurChannels::Channels3,
);
let mut dst = BlurImageMut::default();
gaussian_blur(
&src_image,
&mut dst,
GaussianBlurParams::new_from_kernel(5.),
EdgeMode2D::new(EdgeMode::Clamp),
ThreadingPolicy::Single,
ConvolutionMode::Exact,
)
.unwrap();
compare_u8_stat!(dst);
}
#[test]
fn test_gauss_u8_q_k31() {
let width: usize = 148;
let height: usize = 148;
let mut src = vec![126; width * height * 3];
for dst in src.chunks_exact_mut(3) {
dst[0] = 126;
dst[1] = 66;
dst[2] = 77;
}
let src_image = BlurImage::borrow(
&src,
width as u32,
height as u32,
FastBlurChannels::Channels3,
);
let mut dst = BlurImageMut::default();
gaussian_blur(
&src_image,
&mut dst,
GaussianBlurParams::new_from_kernel(31.),
EdgeMode2D::new(EdgeMode::Clamp),
ThreadingPolicy::Single,
ConvolutionMode::FixedPoint,
)
.unwrap();
compare_u8_stat!(dst);
}
#[test]
fn test_gauss_u8_fp_k31() {
let width: usize = 148;
let height: usize = 148;
let mut src = vec![126; width * height * 3];
for dst in src.chunks_exact_mut(3) {
dst[0] = 126;
dst[1] = 66;
dst[2] = 77;
}
let src_image = BlurImage::borrow(
&src,
width as u32,
height as u32,
FastBlurChannels::Channels3,
);
let mut dst = BlurImageMut::default();
gaussian_blur(
&src_image,
&mut dst,
GaussianBlurParams::new_from_kernel(31.),
EdgeMode2D::new(EdgeMode::Clamp),
ThreadingPolicy::Single,
ConvolutionMode::Exact,
)
.unwrap();
compare_u8_stat!(dst);
}
macro_rules! compare_u16_stat {
($dst: expr) => {
for (i, cn) in $dst.data.borrow_mut().chunks_exact(3).enumerate() {
let diff0 = (cn[0] as i32 - 17234i32).abs();
assert!(
diff0 <= 16,
"Diff expected to be less than 16, but it was {diff0} at {i} in channel 0"
);
let diff1 = (cn[1] as i32 - 5322).abs();
assert!(
diff1 <= 16,
"Diff expected to be less than 16, but it was {diff1} at {i} in channel 1"
);
let diff2 = (cn[2] as i32 - 7652).abs();
assert!(
diff2 <= 16,
"Diff expected to be less than 16, but it was {diff2} at {i} in channel 2"
);
}
};
}
#[test]
fn test_gauss_u16_q_k31() {
let width: usize = 148;
let height: usize = 148;
let mut src = vec![17234u16; width * height * 3];
for dst in src.chunks_exact_mut(3) {
dst[0] = 17234u16;
dst[1] = 5322;
dst[2] = 7652;
}
let src_image = BlurImage::borrow(
&src,
width as u32,
height as u32,
FastBlurChannels::Channels3,
);
let mut dst = BlurImageMut::default();
gaussian_blur_u16(
&src_image,
&mut dst,
GaussianBlurParams::new_from_kernel(31.),
EdgeMode2D::new(EdgeMode::Clamp),
ThreadingPolicy::Single,
ConvolutionMode::FixedPoint,
)
.unwrap();
compare_u16_stat!(dst);
}
#[test]
fn test_gauss_u16_fp_k31() {
let width: usize = 148;
let height: usize = 148;
let mut src = vec![17234u16; width * height * 3];
for dst in src.chunks_exact_mut(3) {
dst[0] = 17234u16;
dst[1] = 5322;
dst[2] = 7652;
}
let src_image = BlurImage::borrow(
&src,
width as u32,
height as u32,
FastBlurChannels::Channels3,
);
let mut dst = BlurImageMut::default();
gaussian_blur_u16(
&src_image,
&mut dst,
GaussianBlurParams::new_from_kernel(31.),
EdgeMode2D::new(EdgeMode::Clamp),
ThreadingPolicy::Single,
ConvolutionMode::Exact,
)
.unwrap();
compare_u16_stat!(dst);
}
macro_rules! compare_f32_stat {
($dst: expr) => {
for (i, cn) in $dst.data.borrow_mut().chunks_exact(3).enumerate() {
let diff0 = (cn[0] as f32 - 0.532).abs();
assert!(
diff0 <= 1e-4,
"Diff expected to be less than 1e-4, but it was {diff0} at {i} in channel 0"
);
let diff1 = (cn[1] as f32 - 0.123).abs();
assert!(
diff1 <= 1e-4,
"Diff expected to be less than 1e-4, but it was {diff1} at {i} in channel 1"
);
let diff2 = (cn[2] as f32 - 0.654).abs();
assert!(
diff2 <= 1e-4,
"Diff expected to be less than 1e-4, but it was {diff2} at {i} in channel 2"
);
}
};
}
#[test]
fn test_gauss_f32_k31() {
let width: usize = 148;
let height: usize = 148;
let mut src = vec![0.532; width * height * 3];
for dst in src.chunks_exact_mut(3) {
dst[0] = 0.532;
dst[1] = 0.123;
dst[2] = 0.654;
}
let src_image = BlurImage::borrow(
&src,
width as u32,
height as u32,
FastBlurChannels::Channels3,
);
let mut dst = BlurImageMut::default();
gaussian_blur_f32(
&src_image,
&mut dst,
GaussianBlurParams::new_from_kernel(31.),
EdgeMode2D::new(EdgeMode::Clamp),
ThreadingPolicy::Single,
IeeeBinaryConvolutionMode::Normal,
)
.unwrap();
compare_f32_stat!(dst);
dst.data.borrow_mut().fill(0.);
gaussian_blur_f32(
&src_image,
&mut dst,
GaussianBlurParams::new_from_kernel(31.),
EdgeMode2D::new(EdgeMode::Clamp),
ThreadingPolicy::Single,
IeeeBinaryConvolutionMode::Zealous,
)
.unwrap();
compare_f32_stat!(dst);
}
#[test]
fn test_gauss_f32_f64_k31() {
let width: usize = 148;
let height: usize = 148;
let mut src = vec![0.532; width * height * 3];
for dst in src.chunks_exact_mut(3) {
dst[0] = 0.532;
dst[1] = 0.123;
dst[2] = 0.654;
}
let src_image = BlurImage::borrow(
&src,
width as u32,
height as u32,
FastBlurChannels::Channels3,
);
let kernel = gaussian_kernel_1d_f64(31, sigma_size_d(2.5));
let mut dst = BlurImageMut::default();
filter_1d_exact::<f32, f64, 3>(
&src_image,
&mut dst,
&kernel,
&kernel,
EdgeMode2D::new(EdgeMode::Clamp),
Scalar::default(),
ThreadingPolicy::Adaptive,
)
.unwrap();
compare_f32_stat!(dst);
}
}