pub(super) const COS_Q15: [i32; 9] = [
32768, 32138, 30274, 27246, 23170, 18205, 12540, 6393, 0, ];
fn idct_1d(input: &[i32; 8]) -> [i32; 8] {
let mut out = [0i32; 8];
for (n, slot) in out.iter_mut().enumerate() {
let mut acc: i64 = i64::from(input[0]) * i64::from(COS_Q15[4]);
for k in 1..8 {
let phase_index = ((2 * n + 1) * k) % 32;
let cos_val = cos_q15_periodic(phase_index);
acc += i64::from(input[k]) * i64::from(cos_val);
}
*slot = ((acc + (1 << 14)) >> 15) as i32;
}
out
}
pub(super) fn cos_q15_periodic(angle: usize) -> i32 {
let angle = angle % 32;
match angle {
0..=8 => COS_Q15[angle],
9..=15 => -COS_Q15[16 - angle],
16..=23 => -COS_Q15[angle - 16],
_ => COS_Q15[32 - angle],
}
}
#[must_use]
pub fn idct_8x8(coeffs: &[i32; 64]) -> [i32; 64] {
let mut intermediate = [0i32; 64];
for row in 0..8 {
let row_in: [i32; 8] = std::array::from_fn(|c| coeffs[row * 8 + c]);
let row_out = idct_1d(&row_in);
for (c, &v) in row_out.iter().enumerate() {
intermediate[row * 8 + c] = v;
}
}
let mut output = [0i32; 64];
for col in 0..8 {
let col_in: [i32; 8] = std::array::from_fn(|r| intermediate[r * 8 + col]);
let col_out = idct_1d(&col_in);
for (r, &v) in col_out.iter().enumerate() {
output[r * 8 + col] = v;
}
}
output
}
#[must_use]
pub fn finalize_idct_output(idct_block: &[i32; 64]) -> [u16; 64] {
let mut out = [0u16; 64];
for (i, &v) in idct_block.iter().enumerate() {
let centered = v.saturating_add(512);
out[i] = centered.clamp(0, 1023) as u16;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn idct_of_dc_only_is_constant_block() {
let mut coeffs = [0i32; 64];
coeffs[0] = 1000;
let out = idct_8x8(&coeffs);
let first = out[0];
for (i, &v) in out.iter().enumerate() {
assert!(
(v - first).abs() <= 2,
"DC-only IDCT should be uniform; sample[{i}]={v} vs first={first}"
);
}
}
#[test]
fn idct_of_zero_is_zero() {
let out = idct_8x8(&[0i32; 64]);
assert!(out.iter().all(|&v| v == 0));
}
#[test]
fn finalize_clips_to_10bit_range() {
let huge_pos = [i32::MAX / 4; 64];
let out_pos = finalize_idct_output(&huge_pos);
assert!(out_pos.iter().all(|&v| v == 1023));
let huge_neg = [i32::MIN / 4; 64];
let out_neg = finalize_idct_output(&huge_neg);
assert!(out_neg.iter().all(|&v| v == 0));
}
#[test]
fn finalize_centers_zero_at_512() {
let zero_block = [0i32; 64];
let out = finalize_idct_output(&zero_block);
assert!(out.iter().all(|&v| v == 512));
}
#[test]
fn idct_then_finalize_dc_only_produces_uniform_offset_from_midgrey() {
let mut pos_dc = [0i32; 64];
pos_dc[0] = 5000;
let after_idct = idct_8x8(&pos_dc);
let out = finalize_idct_output(&after_idct);
assert!(
out.iter().all(|&v| v > 512),
"positive DC should lift every sample above 512"
);
let mut neg_dc = [0i32; 64];
neg_dc[0] = -5000;
let out = finalize_idct_output(&idct_8x8(&neg_dc));
assert!(
out.iter().all(|&v| v < 512),
"negative DC should push every sample below 512"
);
}
#[test]
fn cos_q15_periodic_handles_full_period() {
assert_eq!(cos_q15_periodic(0), 32768);
assert_eq!(cos_q15_periodic(8), 0);
assert_eq!(cos_q15_periodic(16), -32768);
assert_eq!(cos_q15_periodic(24), 0);
assert_eq!(cos_q15_periodic(32), cos_q15_periodic(0));
}
}