use crate::blur::gaussian_blur;
use crate::consts::{
ADD_HF_RANGE, ADD_MF_RANGE, BMUL_LF_TO_VALS, MAXCLAMP_HF, MAXCLAMP_UHF, MUL_Y_HF, MUL_Y_UHF,
REMOVE_HF_RANGE, REMOVE_MF_RANGE, REMOVE_UHF_RANGE, SIGMA_HF, SIGMA_LF, SIGMA_UHF, SUPPRESS_S,
SUPPRESS_XY, XMUL_LF_TO_VALS, YMUL_LF_TO_VALS, Y_TO_B_MUL_LF_TO_VALS,
};
use crate::image::{BufferPool, Image3F, ImageF};
#[derive(Debug, Clone)]
pub struct PsychoImage {
pub uhf: [ImageF; 2],
pub hf: [ImageF; 2],
pub mf: Image3F,
pub lf: Image3F,
}
impl PsychoImage {
#[must_use]
pub fn new(width: usize, height: usize) -> Self {
Self {
uhf: [ImageF::new(width, height), ImageF::new(width, height)],
hf: [ImageF::new(width, height), ImageF::new(width, height)],
mf: Image3F::new(width, height),
lf: Image3F::new(width, height),
}
}
#[must_use]
pub fn width(&self) -> usize {
self.lf.width()
}
#[must_use]
pub fn height(&self) -> usize {
self.lf.height()
}
}
#[inline]
fn remove_range_around_zero(x: f32, range: f32) -> f32 {
if x > range {
x - range
} else if x < -range {
x + range
} else {
0.0
}
}
#[inline]
fn amplify_range_around_zero(x: f32, range: f32) -> f32 {
if x > range {
x + range
} else if x < -range {
x - range
} else {
x * 2.0
}
}
#[inline]
fn maximum_clamp(v: f32, max_val: f32) -> f32 {
const MUL: f32 = 0.724_216_146;
if v >= max_val {
(v - max_val).mul_add(MUL, max_val)
} else if v <= -max_val {
(v + max_val).mul_add(MUL, -max_val)
} else {
v
}
}
fn subtract(a: &ImageF, b: &ImageF, out: &mut ImageF) {
for y in 0..a.height() {
let row_a = a.row(y);
let row_b = b.row(y);
let row_out = out.row_mut(y);
for x in 0..a.width() {
row_out[x] = row_a[x] - row_b[x];
}
}
}
fn xyb_low_freq_to_vals(lf: &mut Image3F) {
let width = lf.width();
let height = lf.height();
let (p0, p1, p2) = lf.planes_mut();
for y in 0..height {
let row_x = p0.row_mut(y);
let row_y = p1.row_mut(y);
let row_b = p2.row_mut(y);
for x in 0..width {
let vx = row_x[x];
let vy = row_y[x];
let vb = row_b[x];
let b = (Y_TO_B_MUL_LF_TO_VALS as f32).mul_add(vy, vb);
row_b[x] = b * BMUL_LF_TO_VALS as f32;
row_x[x] = vx * XMUL_LF_TO_VALS as f32;
row_y[x] = vy * YMUL_LF_TO_VALS as f32;
}
}
}
fn suppress_x_by_y(in_y: &ImageF, inout_x: &mut ImageF) {
let width = in_y.width();
let height = in_y.height();
use wide::f32x8;
let s = SUPPRESS_S as f32;
let one_minus_s = 1.0 - s;
let yw = SUPPRESS_XY as f32;
let s_simd = f32x8::splat(s);
let one_minus_s_simd = f32x8::splat(one_minus_s);
let yw_simd = f32x8::splat(yw);
for y in 0..height {
let row_y = in_y.row(y);
let row_x = inout_x.row_mut(y);
for (i, chunk) in row_x.chunks_exact_mut(8).enumerate() {
let x = i * 8;
let vy = f32x8::from(<[f32; 8]>::try_from(&row_y[x..x + 8]).unwrap());
let vx = f32x8::from(<[f32; 8]>::try_from(&*chunk).unwrap());
let denom = vy * vy + yw_simd;
let scaler = (yw_simd / denom) * one_minus_s_simd + s_simd;
let result = scaler * vx;
chunk.copy_from_slice(&<[f32; 8]>::from(result));
}
let simd_width = width / 8 * 8;
for x in simd_width..width {
let vy = row_y[x];
let vx = row_x[x];
let scaler = (yw / vy.mul_add(vy, yw)).mul_add(one_minus_s, s);
row_x[x] = scaler * vx;
}
}
}
fn separate_lf_and_mf(xyb: &Image3F, lf: &mut Image3F, mf: &mut Image3F, pool: &BufferPool) {
let sigma = SIGMA_LF as f32;
for i in 0..3 {
let blurred = gaussian_blur(xyb.plane(i), sigma, pool);
lf.plane_mut(i).copy_from(&blurred);
subtract(xyb.plane(i), &blurred, mf.plane_mut(i));
blurred.recycle(pool);
}
xyb_low_freq_to_vals(lf);
}
fn separate_mf_and_hf(mf: &mut Image3F, hf: &mut [ImageF; 2], pool: &BufferPool) {
let width = mf.width();
let height = mf.height();
let sigma = SIGMA_HF as f32;
for i in 0..2 {
hf[i].copy_from(mf.plane(i));
let blurred = gaussian_blur(mf.plane(i), sigma, pool);
mf.plane_mut(i).copy_from(&blurred);
blurred.recycle(pool);
let range = if i == 0 {
REMOVE_MF_RANGE
} else {
ADD_MF_RANGE
};
for y in 0..height {
let row_mf = mf.plane_row_mut(i, y);
let row_hf = hf[i].row_mut(y);
for x in 0..width {
let hf_val = row_hf[x] - row_mf[x];
row_hf[x] = hf_val;
if i == 0 {
row_mf[x] = remove_range_around_zero(row_mf[x], range as f32);
} else {
row_mf[x] = amplify_range_around_zero(row_mf[x], range as f32);
}
}
}
}
let blurred_b = gaussian_blur(mf.plane(2), sigma, pool);
mf.plane_mut(2).copy_from(&blurred_b);
blurred_b.recycle(pool);
let (hf_x, hf_y) = hf.split_at_mut(1);
suppress_x_by_y(&hf_y[0], &mut hf_x[0]);
}
fn separate_hf_and_uhf(hf: &mut [ImageF; 2], uhf: &mut [ImageF; 2], pool: &BufferPool) {
let width = hf[0].width();
let height = hf[0].height();
let sigma = SIGMA_UHF as f32;
for i in 0..2 {
uhf[i].copy_from(&hf[i]);
let blurred = gaussian_blur(&hf[i], sigma, pool);
hf[i].copy_from(&blurred);
blurred.recycle(pool);
for y in 0..height {
let row_hf = hf[i].row_mut(y);
let row_uhf = uhf[i].row_mut(y);
for x in 0..width {
let hf_val = row_hf[x];
if i == 0 {
let uhf_val = row_uhf[x] - hf_val;
row_hf[x] = remove_range_around_zero(hf_val, REMOVE_HF_RANGE as f32);
row_uhf[x] = remove_range_around_zero(uhf_val, REMOVE_UHF_RANGE as f32);
} else {
let hf_clamped = maximum_clamp(hf_val, MAXCLAMP_HF as f32);
let uhf_val = row_uhf[x] - hf_clamped; let uhf_clamped = maximum_clamp(uhf_val, MAXCLAMP_UHF as f32);
row_uhf[x] = uhf_clamped * MUL_Y_UHF as f32;
row_hf[x] = amplify_range_around_zero(
hf_clamped * MUL_Y_HF as f32,
ADD_HF_RANGE as f32,
);
}
}
}
}
}
pub fn separate_frequencies(xyb: &Image3F, pool: &BufferPool) -> PsychoImage {
let width = xyb.width();
let height = xyb.height();
let mut ps = PsychoImage::new(width, height);
separate_lf_and_mf(xyb, &mut ps.lf, &mut ps.mf, pool);
separate_mf_and_hf(&mut ps.mf, &mut ps.hf, pool);
separate_hf_and_uhf(&mut ps.hf, &mut ps.uhf, pool);
ps
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_remove_range_around_zero() {
assert!((remove_range_around_zero(0.5, 0.1) - 0.4).abs() < 0.001);
assert!((remove_range_around_zero(-0.5, 0.1) - (-0.4)).abs() < 0.001);
assert!((remove_range_around_zero(0.05, 0.1) - 0.0).abs() < 0.001);
}
#[test]
fn test_amplify_range_around_zero() {
assert!((amplify_range_around_zero(0.5, 0.1) - 0.6).abs() < 0.001);
assert!((amplify_range_around_zero(-0.5, 0.1) - (-0.6)).abs() < 0.001);
assert!((amplify_range_around_zero(0.05, 0.1) - 0.1).abs() < 0.001);
}
#[test]
fn test_maximum_clamp() {
assert!((maximum_clamp(5.0, 10.0) - 5.0).abs() < 0.001);
assert!(maximum_clamp(15.0, 10.0) < 15.0);
assert!(maximum_clamp(15.0, 10.0) > 10.0);
}
#[test]
fn test_psycho_image_creation() {
let ps = PsychoImage::new(100, 50);
assert_eq!(ps.width(), 100);
assert_eq!(ps.height(), 50);
}
#[test]
fn test_frequency_separation() {
let pool = BufferPool::new();
let mut xyb = Image3F::new(32, 32);
for y in 0..32 {
for x in 0..32 {
let val = (x + y) as f32 / 64.0;
xyb.plane_mut(0).set(x, y, val * 0.1);
xyb.plane_mut(1).set(x, y, val);
xyb.plane_mut(2).set(x, y, val * 0.5);
}
}
let ps = separate_frequencies(&xyb, &pool);
assert_eq!(ps.width(), 32);
assert_eq!(ps.height(), 32);
}
}