use std::f64::consts::PI;
pub const ZIGZAG_ORDER: [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,
];
pub const INVERSE_ZIGZAG: [usize; 64] = compute_inverse_zigzag();
const fn compute_inverse_zigzag() -> [usize; 64] {
let mut inv = [0usize; 64];
let mut i = 0;
while i < 64 {
inv[ZIGZAG_ORDER[i]] = i;
i += 1;
}
inv
}
const APV_BASE_QUANT: u16 = 16;
pub fn forward_dct_8x8(block: &mut [f64; 64], dc_offset: f64) {
for v in block.iter_mut() {
*v -= dc_offset;
}
for row in 0..8 {
let start = row * 8;
let end = start + 8;
dct_1d_f64(&mut block[start..end]);
}
let mut col_buf = [0.0f64; 8];
for col in 0..8 {
for row in 0..8 {
col_buf[row] = block[row * 8 + col];
}
dct_1d_f64(&mut col_buf);
for row in 0..8 {
block[row * 8 + col] = col_buf[row];
}
}
}
pub fn inverse_dct_8x8(block: &mut [f64; 64], dc_offset: f64) {
for row in 0..8 {
let start = row * 8;
let end = start + 8;
idct_1d_f64(&mut block[start..end]);
}
let mut col_buf = [0.0f64; 8];
for col in 0..8 {
for row in 0..8 {
col_buf[row] = block[row * 8 + col];
}
idct_1d_f64(&mut col_buf);
for row in 0..8 {
block[row * 8 + col] = col_buf[row];
}
}
for v in block.iter_mut() {
*v += dc_offset;
}
}
fn dct_1d_f64(x: &mut [f64]) {
let n = x.len() as f64;
let mut out = [0.0f64; 8];
for k in 0..8 {
let ck = if k == 0 { 1.0 / 2.0_f64.sqrt() } else { 1.0 };
let mut sum = 0.0f64;
for (i, &xi) in x.iter().enumerate() {
sum += xi * ((PI * k as f64 * (2.0 * i as f64 + 1.0)) / (2.0 * n)).cos();
}
out[k] = (2.0 / n).sqrt() * ck * sum;
}
x[..8].copy_from_slice(&out);
}
fn idct_1d_f64(x: &mut [f64]) {
let n = x.len() as f64;
let mut out = [0.0f64; 8];
for i in 0..8 {
let mut sum = 0.0f64;
for (k, &xk) in x.iter().enumerate() {
let ck = if k == 0 { 1.0 / 2.0_f64.sqrt() } else { 1.0 };
sum += ck * xk * ((PI * k as f64 * (2.0 * i as f64 + 1.0)) / (2.0 * n)).cos();
}
out[i] = (2.0 / n).sqrt() * sum;
}
x[..8].copy_from_slice(&out);
}
fn qp_to_step(qp: u8) -> f64 {
let base = APV_BASE_QUANT as f64;
base * 2.0_f64.powf((qp as f64 - 4.0) / 6.0)
}
#[must_use]
pub fn generate_quant_matrix(qp: u8) -> [f64; 64] {
let step = qp_to_step(qp);
let mut matrix = [0.0f64; 64];
for (i, val) in matrix.iter_mut().enumerate() {
let row = i / 8;
let col = i % 8;
let freq_scale = 1.0 + 0.05 * (row + col) as f64;
*val = (step * freq_scale).max(1.0);
}
matrix
}
pub fn quantize_block(coeffs: &[f64; 64], quant_matrix: &[f64; 64]) -> [i32; 64] {
let mut quantized = [0i32; 64];
for i in 0..64 {
quantized[i] = (coeffs[i] / quant_matrix[i]).round() as i32;
}
quantized
}
pub fn dequantize_block(quantized: &[i32; 64], quant_matrix: &[f64; 64]) -> [f64; 64] {
let mut coeffs = [0.0f64; 64];
for i in 0..64 {
coeffs[i] = quantized[i] as f64 * quant_matrix[i];
}
coeffs
}
pub fn zigzag_scan(block: &[i32; 64]) -> [i32; 64] {
let mut scanned = [0i32; 64];
for (i, &pos) in ZIGZAG_ORDER.iter().enumerate() {
scanned[i] = block[pos];
}
scanned
}
pub fn inverse_zigzag_scan(scanned: &[i32; 64]) -> [i32; 64] {
let mut block = [0i32; 64];
for (i, &pos) in ZIGZAG_ORDER.iter().enumerate() {
block[pos] = scanned[i];
}
block
}
#[cfg(test)]
mod tests {
use super::*;
const ROUNDTRIP_TOLERANCE: f64 = 1e-8;
#[test]
fn test_forward_dct_flat_block() {
let val = 200.0;
let dc_offset = 128.0;
let mut block = [val; 64];
forward_dct_8x8(&mut block, dc_offset);
let expected_dc = (val - dc_offset) * 8.0;
assert!(
(block[0] - expected_dc).abs() < 1e-6,
"DC coefficient: expected {expected_dc}, got {}",
block[0]
);
for (i, &c) in block.iter().enumerate().skip(1) {
assert!(
c.abs() < 1e-8,
"AC coefficient [{i}] should be zero, got {c}"
);
}
}
#[test]
fn test_forward_inverse_roundtrip() {
let dc_offset = 128.0;
let mut original = [0.0f64; 64];
for i in 0..64 {
original[i] = 50.0 + (i as f64 * 3.0) % 200.0;
}
let mut block = original;
forward_dct_8x8(&mut block, dc_offset);
inverse_dct_8x8(&mut block, dc_offset);
for i in 0..64 {
assert!(
(block[i] - original[i]).abs() < ROUNDTRIP_TOLERANCE,
"Mismatch at [{i}]: expected {}, got {} (diff {})",
original[i],
block[i],
(block[i] - original[i]).abs()
);
}
}
#[test]
fn test_forward_inverse_all_zeros() {
let dc_offset = 128.0;
let mut block = [128.0f64; 64]; forward_dct_8x8(&mut block, dc_offset);
for &c in &block {
assert!(c.abs() < 1e-10);
}
inverse_dct_8x8(&mut block, dc_offset);
for &v in &block {
assert!((v - 128.0).abs() < 1e-10);
}
}
#[test]
fn test_forward_inverse_checkerboard() {
let dc_offset = 128.0;
let mut original = [0.0f64; 64];
for i in 0..64 {
let row = i / 8;
let col = i % 8;
original[i] = if (row + col) % 2 == 0 { 200.0 } else { 50.0 };
}
let mut block = original;
forward_dct_8x8(&mut block, dc_offset);
inverse_dct_8x8(&mut block, dc_offset);
for i in 0..64 {
assert!(
(block[i] - original[i]).abs() < ROUNDTRIP_TOLERANCE,
"Checkerboard mismatch at [{i}]"
);
}
}
#[test]
fn test_quantize_dequantize_roundtrip() {
let dc_offset = 128.0;
let mut block = [0.0f64; 64];
for i in 0..64 {
block[i] = 100.0 + (i as f64 * 2.5) % 150.0;
}
forward_dct_8x8(&mut block, dc_offset);
let quant_matrix = generate_quant_matrix(0); let quantized = quantize_block(&block, &quant_matrix);
let dequantized = dequantize_block(&quantized, &quant_matrix);
inverse_dct_8x8(&mut block, dc_offset);
let mut recon = dequantized;
inverse_dct_8x8(&mut recon, dc_offset);
let original_block: Vec<f64> = (0..64).map(|i| 100.0 + (i as f64 * 2.5) % 150.0).collect();
for i in 0..64 {
let diff = (recon[i] - original_block[i]).abs();
assert!(
diff < 20.0,
"Quant roundtrip too lossy at [{i}]: diff={diff}"
);
}
}
#[test]
fn test_quant_matrix_qp_scaling() {
let q0 = generate_quant_matrix(0);
let q30 = generate_quant_matrix(30);
let q63 = generate_quant_matrix(63);
for i in 0..64 {
assert!(
q30[i] > q0[i],
"QP=30 step should be larger than QP=0 at [{i}]"
);
assert!(
q63[i] > q30[i],
"QP=63 step should be larger than QP=30 at [{i}]"
);
}
}
#[test]
fn test_quant_matrix_frequency_scaling() {
let matrix = generate_quant_matrix(22);
assert!(
matrix[0] < matrix[63],
"DC step {} should be less than HF step {}",
matrix[0],
matrix[63]
);
}
#[test]
fn test_zigzag_scan_identity() {
let mut block = [0i32; 64];
for i in 0..64 {
block[i] = i as i32;
}
let scanned = zigzag_scan(&block);
let restored = inverse_zigzag_scan(&scanned);
assert_eq!(block, restored);
}
#[test]
fn test_zigzag_order_first_elements() {
assert_eq!(ZIGZAG_ORDER[0], 0); assert_eq!(ZIGZAG_ORDER[1], 1); assert_eq!(ZIGZAG_ORDER[2], 8); assert_eq!(ZIGZAG_ORDER[3], 16); assert_eq!(ZIGZAG_ORDER[4], 9); assert_eq!(ZIGZAG_ORDER[5], 2); }
#[test]
fn test_zigzag_scan_zeros_at_end() {
let mut block = [0i32; 64];
block[0] = 100; block[1] = 10; block[8] = 5;
let scanned = zigzag_scan(&block);
assert_eq!(scanned[0], 100); assert_eq!(scanned[1], 10); assert_eq!(scanned[2], 5); for &v in scanned.iter().skip(3) {
assert_eq!(v, 0);
}
}
#[test]
fn test_inverse_zigzag_table() {
for i in 0..64 {
assert_eq!(INVERSE_ZIGZAG[ZIGZAG_ORDER[i]], i);
}
}
#[test]
fn test_quantize_block_zero_input() {
let coeffs = [0.0f64; 64];
let quant_matrix = generate_quant_matrix(22);
let quantized = quantize_block(&coeffs, &quant_matrix);
for &v in &quantized {
assert_eq!(v, 0);
}
}
#[test]
fn test_dequantize_block_zero_input() {
let quantized = [0i32; 64];
let quant_matrix = generate_quant_matrix(22);
let dequantized = dequantize_block(&quantized, &quant_matrix);
for &v in &dequantized {
assert!((v - 0.0).abs() < f64::EPSILON);
}
}
#[test]
fn test_dct_energy_conservation() {
let dc_offset = 128.0;
let mut block = [0.0f64; 64];
for i in 0..64 {
block[i] = 50.0 + (i as f64 * 7.0) % 200.0;
}
let spatial_energy: f64 = block.iter().map(|&v| (v - dc_offset).powi(2)).sum();
forward_dct_8x8(&mut block, dc_offset);
let freq_energy: f64 = block.iter().map(|&v| v.powi(2)).sum();
let ratio = spatial_energy / freq_energy;
assert!(
(ratio - 1.0).abs() < 1e-8,
"Energy ratio: {ratio} (should be ~1.0)"
);
}
#[test]
fn test_qp_step_monotonic() {
let mut prev = qp_to_step(0);
for qp in 1..=63 {
let current = qp_to_step(qp);
assert!(
current > prev,
"QP step should increase: QP={qp}, prev={prev}, current={current}"
);
prev = current;
}
}
#[test]
fn test_qp_step_doubling() {
let s0 = qp_to_step(10);
let s6 = qp_to_step(16);
let ratio = s6 / s0;
assert!(
(ratio - 2.0).abs() < 0.01,
"6-step QP ratio should be ~2.0, got {ratio}"
);
}
#[test]
fn test_forward_dct_gradient() {
let dc_offset = 128.0;
let mut block = [0.0f64; 64];
for i in 0..64 {
let col = (i % 8) as f64;
block[i] = 100.0 + col * 20.0;
}
let original = block;
forward_dct_8x8(&mut block, dc_offset);
assert!(block[0].abs() > 1.0);
inverse_dct_8x8(&mut block, dc_offset);
for i in 0..64 {
assert!(
(block[i] - original[i]).abs() < ROUNDTRIP_TOLERANCE,
"Gradient mismatch at [{i}]"
);
}
}
}