#![allow(dead_code)]
#[inline(always)]
const fn round_shift(val: i32, bit: u32) -> i32 {
debug_assert!(bit > 0, "round_shift with bit=0 would add 0 bias");
(val + (1 << (bit - 1))) >> bit
}
#[inline(always)]
fn wht4_butterfly(a: i32, b: i32, c: i32, d: i32) -> (i32, i32, i32, i32) {
let t0 = a + b;
let t1 = c + d;
let t2 = t0 + t1;
let t3 = t0 - t1;
let t4 = a - b;
let t5 = c - d;
let t6 = t4 + t5;
let t7 = t4 - t5;
(t2, t3, t7, t6)
}
pub fn fwht4x4(block: &[i32; 16]) -> [i32; 16] {
let mut tmp = [0i32; 16];
for r in 0..4 {
let i = r * 4;
let (o0, o1, o2, o3) = wht4_butterfly(block[i], block[i + 1], block[i + 2], block[i + 3]);
tmp[i] = o0;
tmp[i + 1] = o1;
tmp[i + 2] = o2;
tmp[i + 3] = o3;
}
let mut out = [0i32; 16];
for c in 0..4 {
let (o0, o1, o2, o3) = wht4_butterfly(tmp[c], tmp[4 + c], tmp[8 + c], tmp[12 + c]);
out[c] = o0;
out[4 + c] = o1;
out[8 + c] = o2;
out[12 + c] = o3;
}
out
}
pub fn iwht4x4(coeffs: &[i32; 16]) -> [i32; 16] {
let mut tmp = [0i32; 16];
for r in 0..4 {
let i = r * 4;
let (o0, o1, o2, o3) = wht4_butterfly(coeffs[i], coeffs[i + 1], coeffs[i + 2], coeffs[i + 3]);
tmp[i] = o0;
tmp[i + 1] = o1;
tmp[i + 2] = o2;
tmp[i + 3] = o3;
}
let mut out = [0i32; 16];
for c in 0..4 {
let (o0, o1, o2, o3) = wht4_butterfly(tmp[c], tmp[4 + c], tmp[8 + c], tmp[12 + c]);
out[c] = o0 >> 4;
out[4 + c] = o1 >> 4;
out[8 + c] = o2 >> 4;
out[12 + c] = o3 >> 4;
}
out
}
const COSPI_8: i32 = 3784;
const COSPI_16: i32 = 2896;
const COSPI_24: i32 = 1567;
pub fn fdct4(input: [i32; 4]) -> [i32; 4] {
let a = input[0] + input[3];
let b = input[1] + input[2];
let c = input[1] - input[2];
let d = input[0] - input[3];
[
round_shift(COSPI_16 * (a + b), 12),
round_shift(COSPI_8 * d + COSPI_24 * c, 12),
round_shift(COSPI_16 * (a - b), 12),
round_shift(-COSPI_24 * d + COSPI_8 * c, 12),
]
}
pub fn idct4(input: [i32; 4]) -> [i32; 4] {
let a = round_shift(COSPI_16 * (input[0] + input[2]), 13);
let b = round_shift(COSPI_16 * (input[0] - input[2]), 13);
let c = round_shift(COSPI_8 * input[1] - COSPI_24 * input[3], 13);
let d = round_shift(COSPI_24 * input[1] + COSPI_8 * input[3], 13);
[a + c, b + d, b - d, a - c]
}
pub fn fdct4x4(block: &[i32; 16]) -> [i32; 16] {
let mut tmp = [0i32; 16];
for r in 0..4 {
let i = r * 4;
let row = [block[i], block[i + 1], block[i + 2], block[i + 3]];
let out_row = fdct4(row);
tmp[i] = out_row[0];
tmp[i + 1] = out_row[1];
tmp[i + 2] = out_row[2];
tmp[i + 3] = out_row[3];
}
let mut out = [0i32; 16];
for c in 0..4 {
let col = [tmp[c], tmp[4 + c], tmp[8 + c], tmp[12 + c]];
let out_col = fdct4(col);
out[c] = out_col[0];
out[4 + c] = out_col[1];
out[8 + c] = out_col[2];
out[12 + c] = out_col[3];
}
out
}
pub fn idct4x4(coeffs: &[i32; 16]) -> [i32; 16] {
let mut tmp = [0i32; 16];
for r in 0..4 {
let i = r * 4;
let row = [coeffs[i], coeffs[i + 1], coeffs[i + 2], coeffs[i + 3]];
let out_row = idct4(row);
tmp[i] = out_row[0];
tmp[i + 1] = out_row[1];
tmp[i + 2] = out_row[2];
tmp[i + 3] = out_row[3];
}
let mut out = [0i32; 16];
for c in 0..4 {
let col = [tmp[c], tmp[4 + c], tmp[8 + c], tmp[12 + c]];
let out_col = idct4(col);
out[c] = out_col[0];
out[4 + c] = out_col[1];
out[8 + c] = out_col[2];
out[12 + c] = out_col[3];
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wht_round_trip_zero() {
let block = [0i32; 16];
let coeffs = fwht4x4(&block);
let recon = iwht4x4(&coeffs);
assert_eq!(recon, block, "all-zero block should round-trip exactly");
}
#[test]
fn wht_round_trip_exact() {
let block: [i32; 16] = [
10, -5, 3, 7,
-2, 11, -8, 4,
6, 0, -3, 9,
-1, 14, 2, -6,
];
let coeffs = fwht4x4(&block);
let recon = iwht4x4(&coeffs);
for (i, (&orig, &got)) in block.iter().zip(recon.iter()).enumerate() {
assert_eq!(orig, got, "WHT round-trip mismatch at index {i}: orig={orig} got={got}");
}
}
#[test]
fn wht_dc_only_block() {
let val = 4i32;
let block = [val; 16];
let coeffs = fwht4x4(&block);
assert_ne!(coeffs[0], 0, "DC coefficient must be non-zero for a flat block");
for (i, &c) in coeffs.iter().enumerate().skip(1) {
assert_eq!(c, 0, "AC coefficient at index {i} should be zero for a flat block");
}
}
#[test]
fn wht_round_trip_random_residuals() {
let block: [i32; 16] = [
-128, 127, 64, -64,
32, -32, 0, 100,
-100, 50, -50, 25,
-25, 12, -12, 1,
];
let coeffs = fwht4x4(&block);
let recon = iwht4x4(&coeffs);
for (i, (&orig, &got)) in block.iter().zip(recon.iter()).enumerate() {
assert_eq!(orig, got, "WHT round-trip mismatch at index {i}");
}
}
#[test]
fn dct_round_trip_zero() {
let block = [0i32; 16];
let coeffs = fdct4x4(&block);
let recon = idct4x4(&coeffs);
for (i, &v) in recon.iter().enumerate() {
assert_eq!(v, 0, "DCT round-trip of zero block failed at index {i}");
}
}
#[test]
fn dct_round_trip_within_tolerance() {
let block: [i32; 16] = [
10, -5, 3, 7,
-2, 11, -8, 4,
6, 0, -3, 9,
-1, 14, 2, -6,
];
let coeffs = fdct4x4(&block);
let recon = idct4x4(&coeffs);
for (i, (&orig, &got)) in block.iter().zip(recon.iter()).enumerate() {
let err = (orig - got).abs();
assert!(
err <= 1,
"DCT round-trip error at index {i}: orig={orig} got={got} err={err}"
);
}
}
#[test]
fn dct_fdct4_1d_dc() {
let input = [8i32; 4];
let out = fdct4(input);
assert_ne!(out[0], 0, "DC must be non-zero");
assert_eq!(out[1], 0, "AC1 should be zero for flat input");
assert_eq!(out[2], 0, "AC2 should be zero for flat input");
assert_eq!(out[3], 0, "AC3 should be zero for flat input");
}
#[test]
fn dct_idct4_1d_inverse() {
let inputs: &[[i32; 4]] = &[
[0, 0, 0, 0],
[10, -5, 3, 7],
[127, -128, 64, -64],
[1, 1, 1, 1],
];
for &inp in inputs {
let fwd = fdct4(inp);
let inv = idct4(fwd);
for (j, (&orig, &got)) in inp.iter().zip(inv.iter()).enumerate() {
let err = (orig - got).abs();
assert!(
err <= 1,
"1D DCT round-trip error at index {j}: orig={orig} got={got}"
);
}
}
}
#[test]
fn wht_coefficients_bounded() {
let block: [i32; 16] = [255; 16];
let coeffs = fwht4x4(&block);
for &c in &coeffs {
assert!(c.abs() <= 255 * 16, "WHT coefficient out of expected range: {c}");
}
assert_eq!(coeffs[0], 255 * 16, "DC coefficient of flat block must equal 255*16");
for (i, &c) in coeffs.iter().enumerate().skip(1) {
assert_eq!(c, 0, "AC coefficient {i} must be zero for flat block");
}
}
}