use crate::foundation::consts::{DCT_BLOCK_SIZE, JPEG_NATURAL_ORDER};
use crate::foundation::simd_types::Block8x8f;
const MAX_SAMPLE: f32 = 127.0;
const MAX_SAMPLE_THRESHOLD: f32 = 126.5;
#[inline]
fn catmull_rom_f32(value1: f32, value2: f32, value3: f32, value4: f32, t: f32, size: f32) -> f32 {
let tan1 = (value3 - value1) * size;
let tan2 = (value4 - value2) * size;
let t2 = t * t;
let t3 = t2 * t;
let f1 = 2.0 * t3 - 3.0 * t2 + 1.0; let f2 = -2.0 * t3 + 3.0 * t2; let f3 = t3 - 2.0 * t2 + t; let f4 = t3 - t2;
value2 * f1 + tan1 * f3 + value3 * f2 + tan2 * f4
}
pub fn preprocess_deringing_f32(data: &mut [f32; DCT_BLOCK_SIZE], dc_quant: u16) {
let mut sum: f32 = 0.0;
let mut max_sample_count: usize = 0;
for &sample in data.iter() {
sum += sample;
if sample >= MAX_SAMPLE_THRESHOLD {
max_sample_count += 1;
}
}
if max_sample_count == 0 || max_sample_count == DCT_BLOCK_SIZE {
return;
}
let dc_limit = 2.0 * dc_quant as f32;
let headroom = (MAX_SAMPLE * DCT_BLOCK_SIZE as f32 - sum) / max_sample_count as f32;
let max_overshoot: f32 = MAX_SAMPLE + 31.0_f32.min(dc_limit).min(headroom);
let mut n: usize = 0;
while n < DCT_BLOCK_SIZE {
if data[JPEG_NATURAL_ORDER[n] as usize] < MAX_SAMPLE_THRESHOLD {
n += 1;
continue;
}
let start = n;
while n + 1 < DCT_BLOCK_SIZE
&& data[JPEG_NATURAL_ORDER[n + 1] as usize] >= MAX_SAMPLE_THRESHOLD
{
n += 1;
}
let end = n + 1;
let f1 = data[JPEG_NATURAL_ORDER[start.saturating_sub(1)] as usize];
let f2 = data[JPEG_NATURAL_ORDER[start.saturating_sub(2)] as usize];
let l1 = data[JPEG_NATURAL_ORDER[end.min(DCT_BLOCK_SIZE - 1)] as usize];
let l2 = data[JPEG_NATURAL_ORDER[(end + 1).min(DCT_BLOCK_SIZE - 1)] as usize];
let mut fslope = (f1 - f2).max(MAX_SAMPLE - f1);
let mut lslope = (l1 - l2).max(MAX_SAMPLE - l1);
if start == 0 {
fslope = lslope;
}
if end == DCT_BLOCK_SIZE {
lslope = fslope;
}
let length = (end - start) as f32;
let step = 1.0 / (length + 1.0);
let mut position = step;
for i in start..end {
let interpolated = catmull_rom_f32(
MAX_SAMPLE - fslope,
MAX_SAMPLE,
MAX_SAMPLE,
MAX_SAMPLE - lslope,
position,
length,
);
data[JPEG_NATURAL_ORDER[i] as usize] = interpolated.ceil().min(max_overshoot);
position += step;
}
n += 1;
}
}
#[inline]
pub fn preprocess_deringing_block(block: &mut Block8x8f, dc_quant: u16) {
let mut data = [0.0f32; DCT_BLOCK_SIZE];
for (row_idx, row) in block.rows.iter().enumerate() {
let arr: [f32; 8] = *row;
data[row_idx * 8..row_idx * 8 + 8].copy_from_slice(&arr);
}
preprocess_deringing_f32(&mut data, dc_quant);
for (row_idx, row) in block.rows.iter_mut().enumerate() {
*row = data[row_idx * 8..row_idx * 8 + 8].try_into().unwrap();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_catmull_rom_midpoint() {
let result = catmull_rom_f32(100.0, 110.0, 120.0, 130.0, 0.5, 1.0);
assert!(
(result - 115.0).abs() < 1.0,
"Expected ~115, got {}",
result
);
}
#[test]
fn test_catmull_rom_endpoints() {
let result0 = catmull_rom_f32(100.0, 110.0, 120.0, 130.0, 0.0, 1.0);
assert!(
(result0 - 110.0).abs() < 0.1,
"Expected 110, got {}",
result0
);
let result1 = catmull_rom_f32(100.0, 110.0, 120.0, 130.0, 1.0, 1.0);
assert!(
(result1 - 120.0).abs() < 0.1,
"Expected 120, got {}",
result1
);
}
#[test]
fn test_deringing_no_max_pixels() {
let mut data = [64.0f32; DCT_BLOCK_SIZE];
let original = data;
preprocess_deringing_f32(&mut data, 16);
assert_eq!(
data, original,
"Block with no max pixels should be unchanged"
);
}
#[test]
fn test_deringing_all_max_pixels() {
let mut data = [MAX_SAMPLE; DCT_BLOCK_SIZE];
let original = data;
preprocess_deringing_f32(&mut data, 16);
assert_eq!(
data, original,
"Block with all max pixels should be unchanged"
);
}
#[test]
fn test_deringing_creates_overshoot() {
let mut data = [0.0f32; DCT_BLOCK_SIZE];
for i in 10..16 {
data[JPEG_NATURAL_ORDER[i] as usize] = MAX_SAMPLE;
}
data[JPEG_NATURAL_ORDER[8] as usize] = 80.0;
data[JPEG_NATURAL_ORDER[9] as usize] = 100.0;
data[JPEG_NATURAL_ORDER[16] as usize] = 100.0;
data[JPEG_NATURAL_ORDER[17] as usize] = 80.0;
preprocess_deringing_f32(&mut data, 16);
let mut has_overshoot = false;
for i in 10..16 {
if data[JPEG_NATURAL_ORDER[i] as usize] > MAX_SAMPLE {
has_overshoot = true;
break;
}
}
assert!(
has_overshoot,
"Deringing should create overshoot above MAX_SAMPLE"
);
for i in 10..16 {
assert!(
data[JPEG_NATURAL_ORDER[i] as usize] <= MAX_SAMPLE + 31.0,
"Overshoot should be limited to MAX_SAMPLE + 31"
);
}
}
#[test]
fn test_deringing_smooth_curve() {
let mut data = [0.0f32; DCT_BLOCK_SIZE];
data[JPEG_NATURAL_ORDER[0] as usize] = 20.0;
data[JPEG_NATURAL_ORDER[1] as usize] = 60.0;
data[JPEG_NATURAL_ORDER[2] as usize] = 100.0;
for i in 3..8 {
data[JPEG_NATURAL_ORDER[i] as usize] = MAX_SAMPLE;
}
data[JPEG_NATURAL_ORDER[8] as usize] = 100.0;
data[JPEG_NATURAL_ORDER[9] as usize] = 60.0;
data[JPEG_NATURAL_ORDER[10] as usize] = 20.0;
preprocess_deringing_f32(&mut data, 16);
let edge_val = data[JPEG_NATURAL_ORDER[3] as usize];
let mid_val = data[JPEG_NATURAL_ORDER[5] as usize];
assert!(
mid_val >= edge_val,
"Middle of curve should be >= edges: mid={}, edge={}",
mid_val,
edge_val
);
}
#[test]
fn test_deringing_respects_dc_quant_limit() {
let mut data = [MAX_SAMPLE; DCT_BLOCK_SIZE];
data[0] = 50.0;
let mut data_small_quant = data;
let mut data_large_quant = data;
preprocess_deringing_f32(&mut data_small_quant, 2); preprocess_deringing_f32(&mut data_large_quant, 32);
let max_small = data_small_quant.iter().copied().fold(f32::MIN, f32::max);
let max_large = data_large_quant.iter().copied().fold(f32::MIN, f32::max);
assert!(
max_small <= max_large,
"Smaller DC quant should allow less overshoot: small_max={}, large_max={}",
max_small,
max_large
);
}
#[test]
fn test_deringing_block() {
let mut block = Block8x8f::ZERO;
block.rows[0] = [MAX_SAMPLE; 8];
block.rows[1] = [100.0, 90.0, 80.0, 70.0, 60.0, 50.0, 40.0, 30.0];
preprocess_deringing_block(&mut block, 16);
let has_overshoot = block.rows[0].iter().any(|&v| v > MAX_SAMPLE);
assert!(has_overshoot, "Block deringing should create overshoot");
}
}