use std::sync::OnceLock;
#[rustfmt::skip]
pub const ZIGZAG: [usize; 64] = [
0, 1, 8, 16, 9, 2, 3, 10,
17, 24, 32, 25, 18, 11, 4, 5,
12, 19, 26, 33, 40, 48, 41, 34,
27, 20, 13, 6, 7, 14, 21, 28,
35, 42, 49, 56, 57, 50, 43, 36,
29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46,
53, 60, 61, 54, 47, 55, 62, 63,
];
fn cos_table() -> &'static [[f32; 8]; 8] {
static TABLE: OnceLock<[[f32; 8]; 8]> = OnceLock::new();
TABLE.get_or_init(|| {
let mut t = [[0.0f32; 8]; 8];
for (u, row) in t.iter_mut().enumerate() {
let alpha = if u == 0 {
1.0 / std::f32::consts::SQRT_2
} else {
1.0
};
for (x, v) in row.iter_mut().enumerate() {
*v =
alpha * ((2.0 * x as f32 + 1.0) * u as f32 * std::f32::consts::PI / 16.0).cos();
}
}
t
})
}
pub fn dequantize(zz: &[i16], quant: &[u16; 64]) -> [f32; 64] {
debug_assert_eq!(zz.len(), 64);
let mut natural = [0.0f32; 64];
for i in 0..64 {
natural[ZIGZAG[i]] = (zz[i] as i32 * quant[i] as i32) as f32;
}
natural
}
pub fn idct_8x8(coef: &[f32; 64], out: &mut [u8; 64]) {
let cos = cos_table();
let mut tmp = [0.0f32; 64];
for r in 0..8 {
for x in 0..8 {
let mut acc = 0.0;
for c in 0..8 {
acc += cos[c][x] * coef[r * 8 + c];
}
tmp[r * 8 + x] = acc * 0.5;
}
}
for y in 0..8 {
for x in 0..8 {
let mut acc = 0.0;
for r in 0..8 {
acc += cos[r][y] * tmp[r * 8 + x];
}
let v = acc * 0.5 + 128.0;
out[y * 8 + x] = v.clamp(0.0, 255.0).round() as u8;
}
}
}
#[inline]
pub fn ycbcr_to_rgb(y: u8, cb: u8, cr: u8) -> (u8, u8, u8) {
let y = (y as i32) << 16;
let cb = cb as i32 - 128;
let cr = cr as i32 - 128;
let clamp = |v: i32| (v >> 16).clamp(0, 255) as u8;
let half = 1 << 15;
(
clamp(y + 91_881 * cr + half),
clamp(y - 22_554 * cb - 46_802 * cr + half),
clamp(y + 116_130 * cb + half),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dc_only_block_is_flat() {
let mut coef = [0.0f32; 64];
coef[0] = 240.0;
let mut out = [0u8; 64];
idct_8x8(&coef, &mut out);
assert!(out.iter().all(|&s| s == 158), "{out:?}");
}
#[test]
fn single_ac_coefficient_is_a_cosine_wave() {
let mut coef = [0.0f32; 64];
coef[1] = 100.0; coef[8] = -60.0; let mut fast = [0u8; 64];
idct_8x8(&coef, &mut fast);
let mut reference = [0u8; 64];
for y in 0..8 {
for x in 0..8 {
let mut acc = 0.0f32;
for u in 0..8usize {
for v in 0..8usize {
let au = if u == 0 { 1.0 / 2f32.sqrt() } else { 1.0 };
let av = if v == 0 { 1.0 / 2f32.sqrt() } else { 1.0 };
acc += 0.25
* au
* av
* coef[u * 8 + v]
* (((2 * x + 1) as f32 * v as f32 * std::f32::consts::PI) / 16.0).cos()
* (((2 * y + 1) as f32 * u as f32 * std::f32::consts::PI) / 16.0).cos();
}
}
reference[y * 8 + x] = (acc + 128.0).clamp(0.0, 255.0).round() as u8;
}
}
assert_eq!(fast, reference);
}
#[test]
fn zigzag_is_a_permutation() {
let mut seen = [false; 64];
for &i in &ZIGZAG {
assert!(!seen[i]);
seen[i] = true;
}
assert_eq!(ZIGZAG[0], 0);
assert_eq!(ZIGZAG[1], 1);
assert_eq!(ZIGZAG[2], 8);
assert_eq!(ZIGZAG[63], 63);
}
#[test]
fn ycbcr_known_points() {
assert_eq!(ycbcr_to_rgb(128, 128, 128), (128, 128, 128), "neutral gray");
assert_eq!(ycbcr_to_rgb(255, 128, 128), (255, 255, 255));
assert_eq!(ycbcr_to_rgb(0, 128, 128), (0, 0, 0));
let (r, g, b) = ycbcr_to_rgb(76, 85, 255);
assert!(r > 245 && g < 12 && b < 12, "({r},{g},{b})");
}
}