const COS_Q15: [i64; 9] = [
32768, 32138, 30274, 27246, 23170, 18205, 12540, 6393, 0, ];
fn cos_q15_periodic(angle: usize) -> i64 {
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],
}
}
const PASS1_SHIFT: u32 = 11;
const PASS2_SHIFT: u32 = 21;
fn idct_1d(input: &[i64; 8], shift: u32) -> [i64; 8] {
let mut out = [0i64; 8];
let round = if shift == 0 { 0 } else { 1i64 << (shift - 1) };
for (n, slot) in out.iter_mut().enumerate() {
let mut acc: i64 = input[0] * COS_Q15[4];
for (k, &coeff) in input.iter().enumerate().skip(1) {
let phase = ((2 * n + 1) * k) % 32;
acc += coeff * cos_q15_periodic(phase);
}
*slot = (acc + round) >> shift;
}
out
}
#[must_use]
pub fn idct_8x8(coeffs: &[i32; 64]) -> [i32; 64] {
let mut intermediate = [0i64; 64];
for row in 0..8 {
let row_in: [i64; 8] = std::array::from_fn(|c| i64::from(coeffs[row * 8 + c]));
let row_out = idct_1d(&row_in, PASS1_SHIFT);
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: [i64; 8] = std::array::from_fn(|r| intermediate[r * 8 + col]);
let col_out = idct_1d(&col_in, PASS2_SHIFT);
for (r, &v) in col_out.iter().enumerate() {
output[r * 8 + col] = v as i32;
}
}
output
}
#[must_use]
pub fn clip_to_u8(val: i32) -> u8 {
val.clamp(0, 255) as u8
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dc_only_is_flat_and_dc_over_8() {
let mut coeffs = [0i32; 64];
coeffs[0] = 1024;
let out = idct_8x8(&coeffs);
for (i, &v) in out.iter().enumerate() {
assert!(
(v - 128).abs() <= 1,
"DC-only sample[{i}] = {v}, expected ~128"
);
}
}
#[test]
fn dc_only_various_values() {
for dc in [8i32, 64, 256, 512, 800, 2040] {
let mut coeffs = [0i32; 64];
coeffs[0] = dc;
let out = idct_8x8(&coeffs);
let expected = dc / 8;
for &v in &out {
assert!(
(v - expected).abs() <= 1,
"DC {dc}: sample {v}, expected ~{expected}"
);
}
}
}
#[test]
fn zero_input_is_zero() {
let out = idct_8x8(&[0i32; 64]);
assert!(out.iter().all(|&v| v == 0));
}
#[test]
fn negative_dc_is_flat() {
let mut coeffs = [0i32; 64];
coeffs[0] = -512;
let out = idct_8x8(&coeffs);
for &v in &out {
assert!((v - (-64)).abs() <= 1, "neg DC sample {v}, expected ~-64");
}
}
#[test]
fn cos_q15_periodic_quarter_periods() {
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));
}
#[test]
fn clip_to_u8_clamps() {
assert_eq!(clip_to_u8(-5), 0);
assert_eq!(clip_to_u8(300), 255);
assert_eq!(clip_to_u8(200), 200);
}
#[test]
fn single_ac_coefficient_has_zero_mean() {
let mut coeffs = [0i32; 64];
coeffs[1] = 1024; let out = idct_8x8(&coeffs);
let sum: i64 = out.iter().map(|&v| i64::from(v)).sum();
assert!(sum.abs() <= 16, "AC basis sum {sum} should be near zero");
}
}