const K_SIDE: f32 = 0.308_758_86;
const K_CENTER: f32 = 0.382_482_8;
const K5_OUTER: f32 = K_SIDE * K_SIDE;
const K5_INNER: f32 = 2.0 * K_SIDE * K_CENTER;
const K5_MID: f32 = 2.0 * K_SIDE * K_SIDE + K_CENTER * K_CENTER;
const K5_EDGE_CENTER: f32 = K5_MID + K5_INNER;
const K5_EDGE_NEAR: f32 = K5_OUTER + K5_INNER;
const K5_EDGE_FAR: f32 = K5_OUTER;
mod portable {
use super::{K5_EDGE_CENTER, K5_EDGE_FAR, K5_EDGE_NEAR, K5_INNER, K5_MID, K5_OUTER};
use imgref::*;
use std::mem::MaybeUninit;
fn blur_h5(src: &[f32], dst: &mut [MaybeUninit<f32>], width: usize, height: usize, src_stride: usize) {
debug_assert!(width >= 1);
let last = width - 1;
for y in 0..height {
let row = &src[y * src_stride..][..width];
let out = &mut dst[y * width..][..width];
let p0 = row[0];
let p1 = row[1.min(last)];
let p2 = row[2.min(last)];
out[0].write(K5_EDGE_CENTER * p0 + K5_EDGE_NEAR * p1 + K5_EDGE_FAR * p2);
if width >= 2 {
let pl = row[last];
let pl1 = row[last - 1];
let pl2 = row[last.saturating_sub(2)];
out[last].write(K5_EDGE_FAR * pl2 + K5_EDGE_NEAR * pl1 + K5_EDGE_CENTER * pl);
}
if width >= 3 {
let m2 = row[0];
let m1 = row[0];
let c = row[1];
let p1n = row[2.min(last)];
let p2n = row[3.min(last)];
out[1].write((m2 + p2n) * K5_OUTER + (m1 + p1n) * K5_INNER + c * K5_MID);
}
if width >= 4 {
let i = last - 1;
let m2 = row[i - 2];
let m1 = row[i - 1];
let c = row[i];
let p1n = row[i + 1]; let p2n = row[(i + 2).min(last)]; out[i].write((m2 + p2n) * K5_OUTER + (m1 + p1n) * K5_INNER + c * K5_MID);
}
if width >= 5 {
let inner_len = width - 4;
let r_m2 = &row[..inner_len];
let r_m1 = &row[1..=inner_len];
let r_c = &row[2..2 + inner_len];
let r_p1 = &row[3..3 + inner_len];
let r_p2 = &row[4..4 + inner_len];
let (_, out_rest) = out.split_at_mut(2);
let out_inner = &mut out_rest[..inner_len];
for j in 0..inner_len {
out_inner[j].write(
(r_m2[j] + r_p2[j]) * K5_OUTER
+ (r_m1[j] + r_p1[j]) * K5_INNER
+ r_c[j] * K5_MID,
);
}
}
}
}
fn blur_v5(src: &[f32], dst: &mut [MaybeUninit<f32>], width: usize, height: usize, dst_stride: usize) {
debug_assert!(height >= 1);
let last_y = height - 1;
let row = |y: usize| &src[y * width..][..width];
{
let r0 = row(0);
let r1 = row(1.min(last_y));
let r2 = row(2.min(last_y));
let out = &mut dst[..width];
for x in 0..width {
out[x].write(
K5_EDGE_CENTER * r0[x] + K5_EDGE_NEAR * r1[x] + K5_EDGE_FAR * r2[x],
);
}
}
if height >= 2 {
let rl = row(last_y);
let rl1 = row(last_y - 1);
let rl2 = row(last_y.saturating_sub(2));
let out = &mut dst[last_y * dst_stride..][..width];
for x in 0..width {
out[x].write(
K5_EDGE_FAR * rl2[x] + K5_EDGE_NEAR * rl1[x] + K5_EDGE_CENTER * rl[x],
);
}
}
if height >= 3 {
let rm = row(0);
let rc = row(1);
let rp1 = row(2.min(last_y));
let rp2 = row(3.min(last_y));
let out = &mut dst[dst_stride..][..width];
for x in 0..width {
out[x].write(
(rm[x] + rp2[x]) * K5_OUTER
+ (rm[x] + rp1[x]) * K5_INNER
+ rc[x] * K5_MID,
);
}
}
if height >= 4 {
let y = last_y - 1;
let rm2 = row(y - 2);
let rm1 = row(y - 1);
let rc = row(y);
let rp1 = row(y + 1); let rp2 = row((y + 2).min(last_y)); let out = &mut dst[y * dst_stride..][..width];
for x in 0..width {
out[x].write(
(rm2[x] + rp2[x]) * K5_OUTER
+ (rm1[x] + rp1[x]) * K5_INNER
+ rc[x] * K5_MID,
);
}
}
if height >= 5 {
for y in 2..height - 2 {
let rm2 = row(y - 2);
let rm1 = row(y - 1);
let rc = row(y);
let rp1 = row(y + 1);
let rp2 = row(y + 2);
let out = &mut dst[y * dst_stride..][..width];
for x in 0..width {
out[x].write(
(rm2[x] + rp2[x]) * K5_OUTER
+ (rm1[x] + rp1[x]) * K5_INNER
+ rc[x] * K5_MID,
);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn blur_h5_mul(
src1: &[f32],
src2: &[f32],
dst: &mut [MaybeUninit<f32>],
width: usize,
height: usize,
stride1: usize,
stride2: usize,
) {
debug_assert!(width >= 1);
let last = width - 1;
for y in 0..height {
let r1 = &src1[y * stride1..][..width];
let r2 = &src2[y * stride2..][..width];
let out = &mut dst[y * width..][..width];
let prod = |i: usize| r1[i] * r2[i];
let q0 = prod(0);
let q1 = prod(1.min(last));
let q2 = prod(2.min(last));
out[0].write(K5_EDGE_CENTER * q0 + K5_EDGE_NEAR * q1 + K5_EDGE_FAR * q2);
if width >= 2 {
let ql = prod(last);
let ql1 = prod(last - 1);
let ql2 = prod(last.saturating_sub(2));
out[last].write(K5_EDGE_FAR * ql2 + K5_EDGE_NEAR * ql1 + K5_EDGE_CENTER * ql);
}
if width >= 3 {
let m2 = prod(0);
let m1 = prod(0);
let c = prod(1);
let p1n = prod(2.min(last));
let p2n = prod(3.min(last));
out[1].write((m2 + p2n) * K5_OUTER + (m1 + p1n) * K5_INNER + c * K5_MID);
}
if width >= 4 {
let i = last - 1;
let m2 = prod(i - 2);
let m1 = prod(i - 1);
let c = prod(i);
let p1n = prod(i + 1);
let p2n = prod((i + 2).min(last));
out[i].write((m2 + p2n) * K5_OUTER + (m1 + p1n) * K5_INNER + c * K5_MID);
}
if width >= 5 {
let inner_len = width - 4;
let s1_m2 = &r1[..inner_len];
let s1_m1 = &r1[1..=inner_len];
let s1_c = &r1[2..2 + inner_len];
let s1_p1 = &r1[3..3 + inner_len];
let s1_p2 = &r1[4..4 + inner_len];
let s2_m2 = &r2[..inner_len];
let s2_m1 = &r2[1..=inner_len];
let s2_c = &r2[2..2 + inner_len];
let s2_p1 = &r2[3..3 + inner_len];
let s2_p2 = &r2[4..4 + inner_len];
let (_, out_rest) = out.split_at_mut(2);
let out_inner = &mut out_rest[..inner_len];
for j in 0..inner_len {
let pm2 = s1_m2[j] * s2_m2[j];
let pm1 = s1_m1[j] * s2_m1[j];
let pc = s1_c[j] * s2_c[j];
let pp1 = s1_p1[j] * s2_p1[j];
let pp2 = s1_p2[j] * s2_p2[j];
out_inner[j].write((pm2 + pp2) * K5_OUTER + (pm1 + pp1) * K5_INNER + pc * K5_MID);
}
}
}
}
unsafe fn assume_init_ref(slice: &[MaybeUninit<f32>]) -> &[f32] {
unsafe { std::slice::from_raw_parts(slice.as_ptr().cast::<f32>(), slice.len()) }
}
pub fn blur(src: ImgRef<'_, f32>, tmp: &mut [MaybeUninit<f32>]) -> ImgVec<f32> {
let width = src.width();
let height = src.height();
assert!(width > 0 && width < 1 << 24);
assert!(height > 0 && height < 1 << 24);
debug_assert!(src.pixels().all(|p| p.is_finite()));
let pixels = width * height;
assert!(tmp.len() >= pixels);
let tmp = &mut tmp[..pixels];
let mut dst_vec: Vec<f32> = Vec::with_capacity(pixels);
let dst_uninit: &mut [MaybeUninit<f32>] = &mut dst_vec.spare_capacity_mut()[..pixels];
blur_h5(src.buf(), tmp, width, height, src.stride());
let tmp_init: &[f32] = unsafe { assume_init_ref(tmp) };
blur_v5(tmp_init, dst_uninit, width, height, width);
unsafe { dst_vec.set_len(pixels); }
ImgVec::new(dst_vec, width, height)
}
pub fn blur_in_place(mut srcdst: ImgRefMut<'_, f32>, tmp: &mut [MaybeUninit<f32>]) {
let width = srcdst.width();
let height = srcdst.height();
let stride = srcdst.stride();
assert!(width > 0 && width < 1 << 24);
assert!(height > 0 && height < 1 << 24);
let pixels = width * height;
assert!(tmp.len() >= pixels);
let tmp = &mut tmp[..pixels];
blur_h5(srcdst.buf(), tmp, width, height, stride);
let tmp_init: &[f32] = unsafe { assume_init_ref(tmp) };
let dst_buf = srcdst.buf_mut();
let dst_uninit: &mut [MaybeUninit<f32>] = unsafe {
std::slice::from_raw_parts_mut(
dst_buf.as_mut_ptr().cast::<MaybeUninit<f32>>(),
dst_buf.len(),
)
};
blur_v5(tmp_init, dst_uninit, width, height, stride);
}
pub fn blur_mul(src1: ImgRef<'_, f32>, src2: ImgRef<'_, f32>, tmp: &mut [MaybeUninit<f32>]) -> Vec<f32> {
let width = src1.width();
let height = src1.height();
debug_assert_eq!(width, src2.width());
debug_assert_eq!(height, src2.height());
assert!(width > 0 && width < 1 << 24);
assert!(height > 0 && height < 1 << 24);
let pixels = width * height;
assert!(tmp.len() >= pixels);
let tmp = &mut tmp[..pixels];
let mut dst_vec: Vec<f32> = Vec::with_capacity(pixels);
let dst_uninit: &mut [MaybeUninit<f32>] = &mut dst_vec.spare_capacity_mut()[..pixels];
blur_h5_mul(
src1.buf(),
src2.buf(),
tmp,
width,
height,
src1.stride(),
src2.stride(),
);
let tmp_init: &[f32] = unsafe { assume_init_ref(tmp) };
blur_v5(tmp_init, dst_uninit, width, height, width);
unsafe { dst_vec.set_len(pixels); }
dst_vec
}
}
pub use self::portable::*;
#[cfg(test)]
use imgref::*;
#[test]
fn blur_zero() {
use std::mem::MaybeUninit;
let src = vec![0.25];
let mut src2 = src.clone();
let mut tmp = [MaybeUninit::uninit(); 1];
let dst = blur(ImgRef::new(&src[..], 1, 1), &mut tmp[..]);
blur_in_place(ImgRefMut::new(&mut src2[..], 1, 1), &mut tmp[..]);
assert_eq!(&src2, dst.buf());
assert!((0.25 - dst.buf()[0]).abs() < 0.00001);
}
#[test]
fn blur_one() {
blur_one_compare(Img::new(vec![
0.,0.,0.,0.,0.,
0.,0.,0.,0.,0.,
0.,0.,1.,0.,0.,
0.,0.,0.,0.,0.,
0.,0.,0.,0.,0.,
], 5, 5));
}
#[test]
fn blur_one_stride() {
let nan = 1./0.;
blur_one_compare(Img::new_stride(vec![
0.,0.,0.,0.,0., nan, -11.,
0.,0.,0.,0.,0., 333., nan,
0.,0.,1.,0.,0., nan, -11.,
0.,0.,0.,0.,0., 333., nan,
0.,0.,0.,0.,0., nan,
], 5, 5, 7));
}
#[cfg(test)]
fn blur_one_compare(src: ImgVec<f32>) {
use std::mem::MaybeUninit;
let mut src2 = src.clone();
let mut tmp = [MaybeUninit::uninit(); 5 * 5];
let dst = blur(src.as_ref(), &mut tmp[..]);
blur_in_place(src2.as_mut(), &mut tmp[..]);
assert_eq!(&src2.pixels().collect::<Vec<_>>(), dst.buf());
assert!((1. / 110. - dst.buf()[0]).abs() < 0.0001, "{dst:?}");
assert!((1. / 110. - dst.buf()[5 * 5 - 1]).abs() < 0.0001, "{dst:?}");
assert!((0.11354011 - dst.buf()[2 * 5 + 2]).abs() < 0.0001);
}
#[test]
fn blur_1x1() {
use std::mem::MaybeUninit;
let src = vec![1.];
let mut src2 = src.clone();
let mut tmp = [MaybeUninit::uninit(); 1];
let dst = blur(ImgRef::new(&src[..], 1, 1), &mut tmp[..]);
blur_in_place(ImgRefMut::new(&mut src2[..], 1, 1), &mut tmp[..]);
assert!((dst.buf()[0] - 1.).abs() < 0.00001);
assert!((src2[0] - 1.).abs() < 0.00001);
}
#[test]
fn blur_two() {
use std::mem::MaybeUninit;
let src = vec![
0., 1., 1., 1.,
1., 1., 1., 1.,
1., 1., 1., 1.,
1., 1., 1., 1.,
];
let mut src2 = src.clone();
let mut tmp = [MaybeUninit::uninit(); 4 * 4];
let dst = blur(ImgRef::new(&src[..], 4, 4), &mut tmp[..]);
blur_in_place(ImgRefMut::new(&mut src2[..], 4, 4), &mut tmp[..]);
assert_eq!(&src2, dst.buf());
assert!((1. - dst.buf()[3]).abs() < 0.0001, "{}", dst.buf()[3]);
assert!((1. - dst.buf()[3 * 4]).abs() < 0.0001, "{}", dst.buf()[3 * 4]);
assert!((1. - dst.buf()[4 * 4 - 1]).abs() < 0.0001, "{}", dst.buf()[4 * 4 - 1]);
let expected = 0.671_504_5_f32;
assert!(
(f64::from(expected) - f64::from(dst.buf()[0])).abs() < 0.0001,
"expected {expected}, got {}",
dst.buf()[0]
);
}
#[cfg(test)]
mod equiv_tests;