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 FDCT_SHIFT: u32 = 32;
fn fdct_1d(input: &[i64; 8]) -> [i64; 8] {
let mut out = [0i64; 8];
for (k, slot) in out.iter_mut().enumerate() {
let mut acc: i64 = 0;
for (n, &x) in input.iter().enumerate() {
let cos_val = if k == 0 {
COS_Q15[4]
} else {
cos_q15_periodic((2 * n + 1) * k)
};
acc += x * cos_val;
}
*slot = acc;
}
out
}
#[must_use]
pub fn fdct_8x8(block: &[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(block[row * 8 + c]));
let row_out = fdct_1d(&row_in);
for (h_freq, &v) in row_out.iter().enumerate() {
intermediate[row * 8 + h_freq] = v;
}
}
let round: i64 = 1 << (FDCT_SHIFT - 1);
let mut output = [0i32; 64];
for h_freq in 0..8usize {
for v_freq in 0..8usize {
let mut acc: i64 = 0;
for r in 0..8usize {
let cos_val = if v_freq == 0 {
COS_Q15[4]
} else {
cos_q15_periodic((2 * r + 1) * v_freq)
};
acc += intermediate[r * 8 + h_freq] * cos_val;
}
output[v_freq * 8 + h_freq] = ((acc + round) >> FDCT_SHIFT) as i32;
}
}
output
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mpeg2::idct::idct_8x8;
#[test]
fn dc_only_forward_gives_eight_times_mean() {
for v in [0i32, 16, 64, 128, 200, 255] {
let block = [v; 64];
let freq = fdct_8x8(&block);
assert!(
(freq[0] - 8 * v).abs() <= 1,
"DC for V={v}: got {}, expected ~{}",
freq[0],
8 * v
);
for (i, &c) in freq.iter().enumerate().skip(1) {
assert!(c.abs() <= 1, "AC[{i}] for flat block should be ~0, got {c}");
}
}
}
#[test]
fn zero_block_is_zero() {
let freq = fdct_8x8(&[0i32; 64]);
assert!(freq.iter().all(|&v| v == 0));
}
#[test]
fn fdct_idct_round_trip_flat() {
let block = [100i32; 64];
let freq = fdct_8x8(&block);
let spatial = idct_8x8(&freq);
for (i, &v) in spatial.iter().enumerate() {
assert!(
(v - 100).abs() <= 2,
"flat round-trip [{i}]: expected 100, got {v}"
);
}
}
#[test]
fn fdct_idct_round_trip_ramp() {
let block: [i32; 64] = std::array::from_fn(|i| (i as i32) - 32);
let freq = fdct_8x8(&block);
let spatial = idct_8x8(&freq);
for (i, &v) in spatial.iter().enumerate() {
assert!(
(v - block[i]).abs() <= 16,
"ramp round-trip [{i}]: expected {}, got {v}",
block[i]
);
}
}
#[test]
fn dc_energy_dominates_for_smooth_block() {
let block: [i32; 64] = std::array::from_fn(|i| {
let r = (i / 8) as i32;
let c = (i % 8) as i32;
r * 6 + c * 3 + 40
});
let freq = fdct_8x8(&block);
assert!(
freq[0].abs() >= freq[63].abs(),
"DC {} should dominate AC63 {}",
freq[0],
freq[63]
);
}
#[test]
fn negative_flat_block() {
let block = [-30i32; 64];
let freq = fdct_8x8(&block);
assert!((freq[0] - (8 * -30)).abs() <= 1, "neg DC {}", freq[0]);
}
}