use crate::theora::tables::ZIGZAG_SCAN;
pub type Block8x8 = [i16; 64];
pub fn fdct8x8(input: &[i16; 64], output: &mut Block8x8) {
let mut temp = [0.0f64; 64];
for row in 0..8 {
let base = row * 8;
for k in 0..8 {
let mut sum = 0.0;
for n in 0..8 {
sum += f64::from(input[base + n])
* ((2 * n + 1) as f64 * k as f64 * core::f64::consts::PI / 16.0).cos();
}
let ck = if k == 0 { 1.0 / (2.0f64).sqrt() } else { 1.0 };
temp[base + k] = sum * ck * 0.5;
}
}
let mut temp2 = [0.0f64; 64];
for col in 0..8 {
for k in 0..8 {
let mut sum = 0.0;
for n in 0..8 {
sum += temp[n * 8 + col]
* ((2 * n + 1) as f64 * k as f64 * core::f64::consts::PI / 16.0).cos();
}
let ck = if k == 0 { 1.0 / (2.0f64).sqrt() } else { 1.0 };
temp2[k * 8 + col] = sum * ck * 0.5;
}
}
for i in 0..64 {
output[i] = temp2[ZIGZAG_SCAN[i]].round() as i16;
}
}
pub fn idct8x8(input: &Block8x8, output: &mut [i16; 64]) {
let mut freq = [0.0f64; 64];
for i in 0..64 {
freq[ZIGZAG_SCAN[i]] = f64::from(input[i]);
}
let mut temp = [0.0f64; 64];
for row in 0..8 {
let base = row * 8;
for n in 0..8 {
let mut sum = 0.0;
for k in 0..8 {
let ck = if k == 0 { 1.0 / (2.0f64).sqrt() } else { 1.0 };
sum += ck
* freq[base + k]
* ((2 * n + 1) as f64 * k as f64 * core::f64::consts::PI / 16.0).cos();
}
temp[base + n] = sum * 0.5;
}
}
for col in 0..8 {
for n in 0..8 {
let mut sum = 0.0;
for k in 0..8 {
let ck = if k == 0 { 1.0 / (2.0f64).sqrt() } else { 1.0 };
sum += ck
* temp[k * 8 + col]
* ((2 * n + 1) as f64 * k as f64 * core::f64::consts::PI / 16.0).cos();
}
output[n * 8 + col] = (sum * 0.5).round().clamp(-32768.0, 32767.0) as i16;
}
}
}
pub fn quantize_block(input: &Block8x8, output: &mut Block8x8, quant_matrix: &[u16; 64]) {
for i in 0..64 {
let coeff = i32::from(input[i]);
let quant = i32::from(quant_matrix[i]);
if quant == 0 {
output[i] = 0;
continue;
}
let quantized = if coeff >= 0 {
(coeff + quant / 2) / quant
} else {
(coeff - quant / 2) / quant
};
output[i] = quantized as i16;
}
}
pub fn dequantize_block(input: &Block8x8, output: &mut Block8x8, quant_matrix: &[u16; 64]) {
for i in 0..64 {
let coeff = i32::from(input[i]);
let quant = i32::from(quant_matrix[i]);
output[i] = (coeff * quant) as i16;
}
}
pub fn build_quant_matrix(base: &[u16; 64], quality: u8, output: &mut [u16; 64]) {
let q = quality.min(63);
let scale = if q < 50 {
5000 / (u32::from(q) + 50)
} else {
200 - u32::from(q) * 2
};
for i in 0..64 {
let val = (u32::from(base[i]) * scale + 50) / 100;
output[i] = val.clamp(1, 255) as u16;
}
}
pub fn add_residual(prediction: &[u8; 64], residual: &[i16; 64], output: &mut [u8; 64]) {
for i in 0..64 {
let val = i32::from(prediction[i]) + i32::from(residual[i]);
output[i] = val.clamp(0, 255) as u8;
}
}
pub fn subtract_prediction(block: &[u8; 64], prediction: &[u8; 64], residual: &mut [i16; 64]) {
for i in 0..64 {
residual[i] = i16::from(block[i]) - i16::from(prediction[i]);
}
}
pub fn copy_block(plane: &[u8], stride: usize, x: usize, y: usize, output: &mut [u8; 64]) {
for i in 0..8 {
let src_offset = (y + i) * stride + x;
let dst_offset = i * 8;
if src_offset + 8 <= plane.len() {
output[dst_offset..dst_offset + 8].copy_from_slice(&plane[src_offset..src_offset + 8]);
}
}
}
pub fn paste_block(block: &[u8; 64], plane: &mut [u8], stride: usize, x: usize, y: usize) {
for i in 0..8 {
let src_offset = i * 8;
let dst_offset = (y + i) * stride + x;
if dst_offset + 8 <= plane.len() {
plane[dst_offset..dst_offset + 8].copy_from_slice(&block[src_offset..src_offset + 8]);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dct_idct_roundtrip() {
let mut input = [0i16; 64];
for i in 0..64 {
input[i] = ((i * 13) % 256) as i16 - 128;
}
let mut freq = [0i16; 64];
fdct8x8(&input, &mut freq);
let mut output = [0i16; 64];
idct8x8(&freq, &mut output);
for i in 0..64 {
let diff = (input[i] - output[i]).abs();
assert!(
diff <= 2,
"Position {i}: input={}, output={}",
input[i],
output[i]
);
}
}
#[test]
fn test_quantization() {
let input = [
100i16, 50, 25, 12, 6, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
];
let quant_matrix = [16u16; 64];
let mut quantized = [0i16; 64];
quantize_block(&input, &mut quantized, &quant_matrix);
let mut dequantized = [0i16; 64];
dequantize_block(&quantized, &mut dequantized, &quant_matrix);
assert!((input[0] - dequantized[0]).abs() <= 16);
}
#[test]
fn test_residual_operations() {
let block = [128u8; 64];
let prediction = [120u8; 64];
let mut residual = [0i16; 64];
subtract_prediction(&block, &prediction, &mut residual);
assert_eq!(residual[0], 8);
let mut reconstructed = [0u8; 64];
add_residual(&prediction, &residual, &mut reconstructed);
assert_eq!(reconstructed[0], 128);
}
}