use std::cmp::min;
use umath::FF32;
pub unsafe fn gaussian_blur<const CHANNELS: usize>(
data: &mut [[u8; CHANNELS]],
width: usize,
height: usize,
blur_radius: FF32,
) {
let boxes = create_box_gauss::<CHANNELS>(blur_radius);
let mut backbuf = data.to_owned();
for &box_size in boxes.iter() {
let radius = ((box_size - 1) / 2) as usize;
box_blur(&mut backbuf, data, width, height, radius, radius);
}
}
#[inline]
unsafe fn create_box_gauss<const N: usize>(sigma: FF32) -> [i32; N] {
if sigma > 0.0 {
let n_float = FF32::new(N as f32);
let w_ideal = (FF32::new(12.0) * sigma * sigma / n_float).sqrt() + 1.0;
let mut wl: i32 = w_ideal.floor() as i32;
if wl % 2 == 0 {
wl -= 1;
};
let wu = wl + 2;
let wl_float = FF32::new(wl as f32);
let m_ideal = (FF32::new(12.0) * sigma * sigma
- n_float * wl_float * wl_float
- FF32::new(4.0) * n_float * wl_float
- FF32::new(3.0) * n_float)
/ (FF32::new(-4.0) * wl_float - FF32::new(4.0));
let m: usize = m_ideal.round() as usize;
let mut sizes = [0; N];
for (i, pass) in sizes.iter_mut().enumerate() {
if i < m {
*pass = wl;
} else {
*pass = wu;
}
}
sizes
} else {
[1; N]
}
}
#[inline]
fn box_blur<const CHANNELS: usize>(
backbuf: &mut [[u8; CHANNELS]],
frontbuf: &mut [[u8; CHANNELS]],
width: usize,
height: usize,
blur_radius_horz: usize,
blur_radius_vert: usize,
) {
box_blur_horz(backbuf, frontbuf, width, height, blur_radius_horz);
box_blur_vert(frontbuf, backbuf, width, height, blur_radius_vert);
}
macro_rules! C {
($buf:ident[$n:expr]) => {
unsafe { *$buf.get_unchecked($n) }
};
($buf:ident[$n:expr] = $e:expr) => {
*unsafe { $buf.get_unchecked_mut($n) } = $e
};
($buf:ident[$a:expr][$b:expr]) => {
unsafe { *$buf.get_unchecked($a).get_unchecked($b) }
};
($buf:ident[$a:expr][$b:expr] = $c:expr) => {
*unsafe { $buf.get_unchecked_mut($a).get_unchecked_mut($b) } = unsafe { $c }
};
}
#[inline]
fn box_blur_vert<const CHANNELS: usize>(
backbuf: &[[u8; CHANNELS]],
frontbuf: &mut [[u8; CHANNELS]],
width: usize,
height: usize,
blur_radius: usize,
) {
if blur_radius == 0 {
frontbuf.copy_from_slice(backbuf);
return;
}
let iarr = 1.0 / (blur_radius + blur_radius + 1) as f32;
for i in 0..width {
let col_start = i;
let col_end = i + width * (height - 1);
let mut ti: usize = i;
let mut li: usize = ti;
let mut ri: usize = ti + blur_radius * width;
let fv: [u8; CHANNELS] = C!(backbuf[col_start]);
let lv: [u8; CHANNELS] = C!(backbuf[col_end]);
let mut vals: [isize; CHANNELS] = [0; CHANNELS];
for i in 0..CHANNELS {
vals[i] = (blur_radius as isize + 1) * isize::from(fv[i]);
}
let get_top = |i: usize| {
if i < col_start {
fv
} else {
C! { backbuf[i] }
}
};
let get_bottom = |i: usize| {
if i > col_end {
lv
} else {
C! { backbuf[i] }
}
};
for j in 0..min(blur_radius, height) {
let bb = C! { backbuf[ti + j * width] };
for i in 0..CHANNELS {
vals[i] += isize::from(bb[i]);
}
}
if blur_radius > height {
for i in 0..CHANNELS {
vals[i] += (blur_radius - height) as isize * isize::from(lv[i]);
}
}
for _ in 0..min(height, blur_radius + 1) {
let bb = get_bottom(ri);
ri += width;
for i in 0..CHANNELS {
vals[i] += isize::from(bb[i]) - isize::from(fv[i]);
}
for i in 0..CHANNELS {
C! { frontbuf[ti][i] = *round(FF32::new(vals[i] as f32) * iarr) as u8 };
}
ti += width;
}
if height > blur_radius {
for _ in (blur_radius + 1)..(height - blur_radius) {
let bb1 = C! { backbuf[ri] };
ri += width;
let bb2 = C! { backbuf[li] };
li += width;
for i in 0..CHANNELS {
vals[i] += isize::from(bb1[i]) - isize::from(bb2[i]);
}
for i in 0..CHANNELS {
C! { frontbuf[ti][i] = *round(FF32::new(vals[i] as f32) * iarr) as u8 };
}
ti += width;
}
for _ in 0..min(height - blur_radius - 1, blur_radius) {
let bb = get_top(li);
li += width;
for i in 0..CHANNELS {
vals[i] += isize::from(lv[i]) - isize::from(bb[i]);
}
for i in 0..CHANNELS {
C! { frontbuf[ti][i] = *round(FF32::new(vals[i] as f32) * iarr) as u8 };
}
ti += width;
}
}
}
}
#[inline]
fn box_blur_horz<const CHANNELS: usize>(
backbuf: &[[u8; CHANNELS]],
frontbuf: &mut [[u8; CHANNELS]],
width: usize,
height: usize,
blur_radius: usize,
) {
if blur_radius == 0 {
frontbuf.copy_from_slice(backbuf);
return;
}
let iarr = 1.0 / (blur_radius + blur_radius + 1) as f32;
for i in 0..height {
let row_start: usize = i * width;
let row_end: usize = i * width + width - 1;
let mut ti: usize = i * width;
let mut li: usize = ti;
let mut ri: usize = ti + blur_radius;
let fv: [u8; CHANNELS] = C! { backbuf[row_start] };
let lv: [u8; CHANNELS] = C! { backbuf[row_end] };
let mut vals: [isize; CHANNELS] = [0; CHANNELS];
for i in 0..CHANNELS {
vals[i] = (blur_radius as isize + 1) * isize::from(fv[i]);
}
let get_left = |i: usize| {
if i < row_start {
fv
} else {
C! { backbuf[i] }
}
};
let get_right = |i: usize| {
if i > row_end {
lv
} else {
C! { backbuf[i] }
}
};
for j in 0..min(blur_radius, width) {
let bb = C! { backbuf[ti + j] };
for i in 0..CHANNELS {
vals[i] += isize::from(bb[i]);
}
}
if blur_radius > width {
for i in 0..CHANNELS {
vals[i] += (blur_radius - height) as isize * isize::from(lv[i]);
}
}
for _ in 0..min(width, blur_radius + 1) {
let bb = get_right(ri);
ri += 1;
for i in 0..CHANNELS {
vals[i] += isize::from(bb[i]) - isize::from(fv[i]);
}
for i in 0..CHANNELS {
C! { frontbuf[ti][i] = *round(FF32::new(vals[i] as f32) * iarr) as u8 };
}
ti += 1;
}
if width > blur_radius {
for _ in (blur_radius + 1)..(width - blur_radius) {
let bb1 = C! { backbuf[ri] };
ri += 1;
let bb2 = C! { backbuf[li] };
li += 1;
for i in 0..CHANNELS {
vals[i] += isize::from(bb1[i]) - isize::from(bb2[i]);
}
for i in 0..CHANNELS {
C! { frontbuf[ti][i] = *round(FF32::new(vals[i] as f32) * iarr) as u8 };
}
ti += 1;
}
for _ in 0..min(width - blur_radius - 1, blur_radius) {
let bb = get_left(li);
li += 1;
for i in 0..CHANNELS {
vals[i] += isize::from(lv[i]) - isize::from(bb[i]);
}
for i in 0..CHANNELS {
C! { frontbuf[ti][i] = *round(FF32::new(vals[i] as f32) * iarr) as u8 };
}
ti += 1;
}
}
}
}
#[inline]
fn round(mut x: FF32) -> FF32 {
x += 12582912.0;
x -= 12582912.0;
x
}