const K_GABORISH: [f64; 5] = [
-0.09495815671340026, -0.041031725066768575, 0.013710004822696948, 0.006510206083837737, -0.0014789063378272242, ];
fn compute_weights(mul: f64) -> (f32, f32, f32, f32, f32, f32) {
let sum = 1.0
+ mul
* 4.0
* (K_GABORISH[0] + K_GABORISH[1] + K_GABORISH[2] + K_GABORISH[4] + 2.0 * K_GABORISH[3]);
let sum = if sum < 1e-5 { 1e-5 } else { sum };
let normalize = 1.0 / sum;
let normalize_mul = mul * normalize;
(
normalize as f32, (normalize_mul * K_GABORISH[0]) as f32, (normalize_mul * K_GABORISH[1]) as f32, (normalize_mul * K_GABORISH[2]) as f32, (normalize_mul * K_GABORISH[3]) as f32, (normalize_mul * K_GABORISH[4]) as f32, )
}
fn apply_channel(data: &mut [f32], scratch: &mut [f32], width: usize, height: usize, mul: f64) {
let (wc, wr, wd, w_big_r, wl, w_big_d) = compute_weights(mul);
jxl_simd::gaborish_5x5_channel(
data, scratch, width, height, wc, wr, wd, w_big_r, wl, w_big_d,
);
}
pub fn gaborish_inverse(
xyb_x: &mut [f32],
xyb_y: &mut [f32],
xyb_b: &mut [f32],
width: usize,
height: usize,
) {
#[cfg(feature = "parallel")]
{
let (((), ()), ()) = rayon::join(
|| {
rayon::join(
|| {
let mut scratch = jxl_simd::vec_f32_dirty(width * height);
apply_channel(xyb_x, &mut scratch, width, height, 1.0);
},
|| {
let mut scratch = jxl_simd::vec_f32_dirty(width * height);
apply_channel(xyb_y, &mut scratch, width, height, 1.0);
},
)
},
|| {
let mut scratch = jxl_simd::vec_f32_dirty(width * height);
apply_channel(xyb_b, &mut scratch, width, height, 1.0);
},
);
}
#[cfg(not(feature = "parallel"))]
{
let mut scratch = jxl_simd::vec_f32_dirty(width * height);
apply_channel(xyb_x, &mut scratch, width, height, 1.0);
apply_channel(xyb_y, &mut scratch, width, height, 1.0);
apply_channel(xyb_b, &mut scratch, width, height, 1.0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kernel_normalization() {
let (wc, wr, wd, w_big_r, wl, w_big_d) = compute_weights(1.0);
let sum = wc + 4.0 * wr + 4.0 * wd + 4.0 * w_big_r + 8.0 * wl + 4.0 * w_big_d;
assert!(
(sum - 1.0).abs() < 1e-6,
"Kernel weights should sum to 1.0, got {}",
sum
);
}
#[test]
fn test_uniform_image_preserved() {
let width = 16;
let height = 16;
let value = 0.5f32;
let mut data = vec![value; width * height];
let mut scratch = vec![0.0f32; width * height];
apply_channel(&mut data, &mut scratch, width, height, 1.0);
for (i, &v) in data.iter().enumerate() {
assert!(
(v - value).abs() < 1e-5,
"Pixel {} changed from {} to {} on uniform image",
i,
value,
v
);
}
}
#[test]
fn test_sharpening_effect() {
let width = 8;
let height = 8;
let mut data = vec![0.0f32; width * height];
data[4 * width + 4] = 1.0;
let original_center = data[4 * width + 4];
let mut scratch = vec![0.0f32; width * height];
apply_channel(&mut data, &mut scratch, width, height, 1.0);
let new_center = data[4 * width + 4];
assert!(
new_center > original_center,
"Sharpening should increase isolated bright pixel: {} -> {}",
original_center,
new_center
);
let neighbor = data[4 * width + 3];
assert!(
neighbor < 0.0,
"Sharpening should create negative ringing at neighbors: got {}",
neighbor
);
}
}