use crate::stackblur::{HorizontalStackBlurPass, StackBlurWorkingPass, VerticalStackBlurPass};
use crate::unsafe_slice::UnsafeSlice;
use crate::{AnisotropicRadius, BlurError, BlurImageMut, FastBlurChannels, ThreadingPolicy};
const LARGE_RADIUS_CUTOFF: u32 = 135;
fn stack_blur_worker_horizontal(
slice: &UnsafeSlice<u16>,
stride: u32,
width: u32,
height: u32,
radius: u32,
channels: FastBlurChannels,
thread: usize,
thread_count: usize,
) {
fn pass<const N: usize>(
slice: &UnsafeSlice<u16>,
stride: u32,
width: u32,
height: u32,
radius: u32,
thread: usize,
thread_count: usize,
) {
if LARGE_RADIUS_CUTOFF > radius {
let executor = HorizontalStackBlurPass::<u16, i32, f32, N>::default();
executor.pass(slice, stride, width, height, radius, thread, thread_count);
} else {
let executor = HorizontalStackBlurPass::<u16, i64, f64, N>::default();
executor.pass(slice, stride, width, height, radius, thread, thread_count);
}
}
match channels {
FastBlurChannels::Plane => {
pass::<1>(slice, stride, width, height, radius, thread, thread_count);
}
FastBlurChannels::Channels3 => {
pass::<3>(slice, stride, width, height, radius, thread, thread_count);
}
FastBlurChannels::Channels4 => {
pass::<4>(slice, stride, width, height, radius, thread, thread_count);
}
}
}
#[allow(clippy::too_many_arguments)]
fn stack_blur_worker_vertical(
slice: &UnsafeSlice<u16>,
stride: u32,
width: u32,
height: u32,
radius: u32,
channels: FastBlurChannels,
thread: usize,
thread_count: usize,
) {
fn pass<const N: usize>(
slice: &UnsafeSlice<u16>,
stride: u32,
width: u32,
height: u32,
radius: u32,
thread: usize,
thread_count: usize,
) {
if LARGE_RADIUS_CUTOFF > radius {
let executor = VerticalStackBlurPass::<u16, i32, f32, N>::default();
executor.pass(slice, stride, width, height, radius, thread, thread_count);
} else {
let executor = VerticalStackBlurPass::<u16, i64, f64, N>::default();
executor.pass(slice, stride, width, height, radius, thread, thread_count);
}
}
match channels {
FastBlurChannels::Plane => {
pass::<1>(slice, stride, width, height, radius, thread, thread_count);
}
FastBlurChannels::Channels3 => {
pass::<3>(slice, stride, width, height, radius, thread, thread_count);
}
FastBlurChannels::Channels4 => {
pass::<4>(slice, stride, width, height, radius, thread, thread_count);
}
}
}
pub fn stack_blur_u16(
image: &mut BlurImageMut<u16>,
radius: AnisotropicRadius,
threading_policy: ThreadingPolicy,
) -> Result<(), BlurError> {
let radius = radius.max(1);
let stride = image.row_stride();
let width = image.width;
let height = image.height;
let channels = image.channels;
let radius = radius.clamp(1, 2000);
let thread_count = threading_policy.thread_count(width, height) as u32;
if thread_count == 1 {
let slice = UnsafeSlice::new(image.data.borrow_mut());
stack_blur_worker_horizontal(&slice, stride, width, height, radius.x_axis, channels, 0, 1);
stack_blur_worker_vertical(&slice, stride, width, height, radius.y_axis, channels, 0, 1);
return Ok(());
}
let pool = novtb::ThreadPool::new(thread_count as usize);
let slice = UnsafeSlice::new(image.data.borrow_mut());
pool.parallel_for(|thread_index| {
stack_blur_worker_horizontal(
&slice,
stride,
width,
height,
radius.x_axis,
channels,
thread_index,
thread_count as usize,
);
});
pool.parallel_for(|thread_index| {
stack_blur_worker_vertical(
&slice,
stride,
width,
height,
radius.y_axis,
channels,
thread_index,
thread_count as usize,
);
});
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stack_blur_u16_q_k5() {
let width: usize = 148;
let height: usize = 148;
let mut dst = vec![43231u16; width * height * 3];
let mut dst_image = BlurImageMut::borrow(
&mut dst,
width as u32,
height as u32,
FastBlurChannels::Channels3,
);
stack_blur_u16(
&mut dst_image,
AnisotropicRadius::new(5),
ThreadingPolicy::Single,
)
.unwrap();
for (i, &cn) in dst.iter().enumerate() {
let diff = (cn as i32 - 43231i32).abs();
assert!(
diff <= 20,
"Diff expected to be less than 20 but it was {diff} at {i}"
);
}
}
}