use crate::transform::{inverse_dct, round2};
use std::sync::OnceLock;
pub fn forward_dct_4(t: &mut [i64], _r: u32) {
let x0 = t[0];
let x1 = t[1];
let x2 = t[2];
let x3 = t[3];
let y0 = 2896i64 * x0 + 2896 * x1 + 2896 * x2 + 2896 * x3;
let y1 = 3784i64 * x0 + 1567 * x1 - 1567 * x2 - 3784 * x3;
let y2 = 2896i64 * x0 - 2896 * x1 - 2896 * x2 + 2896 * x3;
let y3 = 1567i64 * x0 - 3784 * x1 + 3784 * x2 - 1567 * x3;
t[0] = round2(y0, 12);
t[1] = round2(y1, 12);
t[2] = round2(y2, 12);
t[3] = round2(y3, 12);
}
pub fn forward_dct_4x4(input: &[i64]) -> [i64; 16] {
assert_eq!(
input.len(),
16,
"oxideav-av1 forward_dct_4x4 expects 4 * 4 = 16 spatial samples"
);
let mut work = [0i64; 16];
work.copy_from_slice(input);
let mut row_buf = [0i64; 4];
for i in 0..4 {
row_buf.copy_from_slice(&work[i * 4..i * 4 + 4]);
forward_dct_4(&mut row_buf, 16);
work[i * 4..i * 4 + 4].copy_from_slice(&row_buf);
}
let mut col_buf = [0i64; 4];
for j in 0..4 {
for i in 0..4 {
col_buf[i] = work[i * 4 + j];
}
forward_dct_4(&mut col_buf, 16);
for i in 0..4 {
work[i * 4 + j] = col_buf[i];
}
}
work
}
fn inverse_dct_matrix(n: u32) -> &'static [i64] {
debug_assert!((2..=6).contains(&n));
static M2: OnceLock<Vec<i64>> = OnceLock::new();
static M3: OnceLock<Vec<i64>> = OnceLock::new();
static M4: OnceLock<Vec<i64>> = OnceLock::new();
static M5: OnceLock<Vec<i64>> = OnceLock::new();
static M6: OnceLock<Vec<i64>> = OnceLock::new();
let cell = match n {
2 => &M2,
3 => &M3,
4 => &M4,
5 => &M5,
6 => &M6,
_ => unreachable!("forward DCT size n must be in 2..=6"),
};
cell.get_or_init(|| {
let nn = 1usize << n;
let mut m = vec![0i64; nn * nn];
let mut probe = vec![0i64; nn];
for k in 0..nn {
for v in probe.iter_mut() {
*v = 0;
}
probe[k] = 4096;
inverse_dct(&mut probe, n, 32);
for i in 0..nn {
m[i + nn * k] = probe[i];
}
}
m
})
}
fn forward_dct_n(t: &mut [i64], n: u32) {
assert!(
(2..=6).contains(&n),
"oxideav-av1 forward_dct_n requires n in 2..=6, got {n}"
);
let nn = 1usize << n;
assert!(
t.len() >= nn,
"oxideav-av1 forward_dct_n: buffer too short for length {nn}"
);
let m = inverse_dct_matrix(n);
let mut x = [0i64; 64];
x[..nn].copy_from_slice(&t[..nn]);
for k in 0..nn {
let mut acc: i64 = 0;
for i in 0..nn {
acc += m[i + nn * k] * x[i];
}
t[k] = round2(acc, 12);
}
}
pub fn forward_dct_8(t: &mut [i64], _r: u32) {
forward_dct_n(t, 3);
}
pub fn forward_dct_16(t: &mut [i64], _r: u32) {
forward_dct_n(t, 4);
}
pub fn forward_dct_32(t: &mut [i64], _r: u32) {
forward_dct_n(t, 5);
}
pub fn forward_dct_64(t: &mut [i64], _r: u32) {
forward_dct_n(t, 6);
}
fn forward_dct_nxn(input: &[i64], side: usize) -> Vec<i64> {
assert!(
matches!(side, 4 | 8 | 16 | 32 | 64),
"oxideav-av1 forward_dct_nxn requires side in {{4,8,16,32,64}}, got {side}"
);
assert_eq!(
input.len(),
side * side,
"oxideav-av1 forward_dct_nxn expects side * side = {} samples",
side * side
);
let n: u32 = match side {
4 => 2,
8 => 3,
16 => 4,
32 => 5,
64 => 6,
_ => unreachable!(),
};
let mut work = input.to_vec();
let mut row_buf = vec![0i64; side];
for i in 0..side {
row_buf.copy_from_slice(&work[i * side..(i + 1) * side]);
forward_dct_n(&mut row_buf, n);
work[i * side..(i + 1) * side].copy_from_slice(&row_buf);
}
let mut col_buf = vec![0i64; side];
for j in 0..side {
for i in 0..side {
col_buf[i] = work[i * side + j];
}
forward_dct_n(&mut col_buf, n);
for i in 0..side {
work[i * side + j] = col_buf[i];
}
}
work
}
pub fn forward_dct_8x8(input: &[i64]) -> [i64; 64] {
let v = forward_dct_nxn(input, 8);
let mut out = [0i64; 64];
out.copy_from_slice(&v);
out
}
pub fn forward_dct_16x16(input: &[i64]) -> [i64; 256] {
let v = forward_dct_nxn(input, 16);
let mut out = [0i64; 256];
out.copy_from_slice(&v);
out
}
pub fn forward_dct_32x32(input: &[i64]) -> Vec<i64> {
forward_dct_nxn(input, 32)
}
pub fn forward_dct_64x64(input: &[i64]) -> Vec<i64> {
forward_dct_nxn(input, 64)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transform::{butterfly_b, butterfly_h, inverse_dct_permute};
fn inverse_dct_4(t: &mut [i64; 4]) {
let mut tmp = t.to_vec();
inverse_dct_permute(&mut tmp, 2);
butterfly_b(&mut tmp, 0, 1, 32, 1, 16);
butterfly_b(&mut tmp, 2, 3, 48, 0, 16);
butterfly_h(&mut tmp, 0, 3, 0, 16);
butterfly_h(&mut tmp, 1, 2, 0, 16);
for (slot, v) in t.iter_mut().zip(tmp.iter().copied()) {
*slot = v;
}
}
#[test]
fn forward_dct_4_zero_input_yields_zero() {
let mut t = [0i64; 4];
forward_dct_4(&mut t, 16);
assert_eq!(t, [0; 4]);
}
#[test]
fn forward_dct_4_dc_only_yields_first_basis() {
let mut t = [1024i64; 4];
forward_dct_4(&mut t, 16);
assert_eq!(t, [2896, 0, 0, 0]);
}
#[test]
fn forward_dct_4_matches_matrix_transpose_on_unit_vectors() {
for k in 0..4 {
let mut t = [0i64; 4];
t[k] = 4096;
forward_dct_4(&mut t, 16);
let expected = match k {
0 => [2896i64, 3784, 2896, 1567],
1 => [2896, 1567, -2896, -3784],
2 => [2896, -1567, -2896, 3784],
3 => [2896, -3784, 2896, -1567],
_ => unreachable!(),
};
assert_eq!(t, expected, "M^T column {k} mismatch");
}
}
#[test]
fn forward_inverse_roundtrip_scales_dc_by_two() {
let mut x = [4096i64, 0, 0, 0];
inverse_dct_4(&mut x);
assert_eq!(x, [2896; 4]);
forward_dct_4(&mut x, 16);
assert_eq!(x, [8190, 0, 0, 0]);
}
#[test]
fn forward_inverse_roundtrip_zeroes_off_diagonal() {
let expected_diag: [i64; 4] = [8190, 8191, 8190, 8191];
for k in 0..4 {
let mut c = [0i64; 4];
c[k] = 4096;
inverse_dct_4(&mut c);
forward_dct_4(&mut c, 16);
for (j, &got) in c.iter().enumerate() {
if j == k {
assert_eq!(
got, expected_diag[k],
"basis k={k} diag j={j}: got {got}, want {}",
expected_diag[k]
);
} else {
assert_eq!(got, 0, "basis k={k} off-diag j={j}: got {got}, want 0");
}
}
}
}
#[test]
fn forward_dct_4_linearity_at_aligned_inputs() {
let mut a = [1024i64; 4];
let mut b = [2048i64; 4];
forward_dct_4(&mut a, 16);
forward_dct_4(&mut b, 16);
for i in 0..4 {
assert_eq!(b[i], 2 * a[i], "linearity row {i}");
}
}
#[test]
fn forward_dct_4x4_zero_input_yields_zero() {
let input = [0i64; 16];
let out = forward_dct_4x4(&input);
assert_eq!(out, [0; 16]);
}
#[test]
fn forward_dct_4x4_dc_only_concentrates_energy() {
let input = [1024i64; 16];
let out = forward_dct_4x4(&input);
let mut expected = [0i64; 16];
expected[0] = 8190;
assert_eq!(out, expected);
}
#[test]
fn forward_dct_4x4_top_left_impulse_spreads_into_basis() {
let mut input = [0i64; 16];
input[0] = 4096;
let out = forward_dct_4x4(&input);
let expected: [i64; 16] = [
2048, 2675, 2048, 1108, 2675, 3496, 2675, 1448, 2048, 2675, 2048, 1108, 1108, 1448, 1108, 599, ];
assert_eq!(out, expected);
}
fn run_forward_dct(c: &mut [i64], n: u32) {
match n {
3 => forward_dct_8(c, 32),
4 => forward_dct_16(c, 32),
5 => forward_dct_32(c, 32),
6 => forward_dct_64(c, 32),
_ => unreachable!(),
}
}
fn dc_roundtrip_via_inverse(n: u32, k: i64, max_per_cell_abs_error: i64) {
let nn = 1usize << n;
let mut c = vec![k; nn];
run_forward_dct(&mut c, n);
crate::transform::inverse_dct(&mut c, n, 32);
let pre = (nn as i64) * 2896 * k;
let d = crate::transform::round2(pre, 12);
let expected_cell = crate::transform::round2(d * 2896, 12);
for (i, &got) in c.iter().enumerate() {
let err = (got - expected_cell).abs();
assert!(
err <= max_per_cell_abs_error,
"n={n} k={k} cell {i}: got {got}, expected {expected_cell} (|err| {err} > bound {max_per_cell_abs_error})"
);
}
}
fn arbitrary_roundtrip_via_inverse(n: u32, input: &[i64], max_abs_error: i64) {
let nn = 1usize << n;
assert_eq!(input.len(), nn);
let mut buf = input.to_vec();
run_forward_dct(&mut buf, n);
crate::transform::inverse_dct(&mut buf, n, 32);
let scale = (nn as i64) / 2;
for (i, (&got, &orig)) in buf.iter().zip(input.iter()).enumerate() {
let expected = scale * orig;
let err = (got - expected).abs();
assert!(
err <= max_abs_error,
"n={n} cell {i}: orig={orig}, got={got}, expected≈{expected} (|err| {err} > bound {max_abs_error})"
);
}
}
#[test]
fn forward_dct_8_zero_input_yields_zero() {
let mut t = [0i64; 8];
forward_dct_8(&mut t, 32);
assert_eq!(t, [0; 8]);
}
#[test]
fn forward_dct_8_dc_concentrates_energy() {
let mut t = [1024i64; 8];
forward_dct_8(&mut t, 32);
let nominal = crate::transform::round2(8i64 * 2896 * 1024, 12);
assert!(
(t[0] - nominal).abs() <= 8,
"DC bin got {} expected≈{nominal}",
t[0]
);
for (i, v) in t.iter().enumerate().skip(1) {
assert!(v.abs() <= 16, "off-DC bin {i} = {v}, exceeds noise bound");
}
}
#[test]
fn forward_dct_8_dc_roundtrip_via_inverse() {
dc_roundtrip_via_inverse(3, 1024, 4);
}
#[test]
fn forward_dct_8x8_zero_input_yields_zero() {
let input = [0i64; 64];
let out = forward_dct_8x8(&input);
assert_eq!(out, [0; 64]);
}
#[test]
fn forward_dct_8x8_dc_concentrates_in_top_left() {
let input = [1024i64; 64];
let out = forward_dct_8x8(&input);
let dc_after_row = crate::transform::round2(8i64 * 2896 * 1024, 12);
let dc_nominal = crate::transform::round2(8i64 * 2896 * dc_after_row, 12);
assert!(
(out[0] - dc_nominal).abs() <= 64,
"8x8 DC cell {} vs nominal {dc_nominal}",
out[0]
);
for (i, &v) in out.iter().enumerate().skip(1) {
assert!(v.abs() <= 256, "8x8 off-DC cell {i} = {v} too large");
}
}
#[test]
fn forward_dct_16_zero_input_yields_zero() {
let mut t = [0i64; 16];
forward_dct_16(&mut t, 32);
assert_eq!(t, [0; 16]);
}
#[test]
fn forward_dct_16_dc_concentrates_energy() {
let mut t = [1024i64; 16];
forward_dct_16(&mut t, 32);
let nominal = crate::transform::round2(16i64 * 2896 * 1024, 12);
assert!(
(t[0] - nominal).abs() <= 16,
"DC bin {} expected≈{nominal}",
t[0]
);
for (i, v) in t.iter().enumerate().skip(1) {
assert!(v.abs() <= 32, "off-DC bin {i} = {v} exceeds noise bound");
}
}
#[test]
fn forward_dct_16_dc_roundtrip_via_inverse() {
dc_roundtrip_via_inverse(4, 1024, 16);
}
#[test]
fn forward_dct_16x16_zero_input_yields_zero() {
let input = [0i64; 256];
let out = forward_dct_16x16(&input);
assert_eq!(out, [0; 256]);
}
#[test]
fn forward_dct_16x16_dc_concentrates_in_top_left() {
let input = [1024i64; 256];
let out = forward_dct_16x16(&input);
let row_dc = crate::transform::round2(16i64 * 2896 * 1024, 12);
let nominal = crate::transform::round2(16i64 * 2896 * row_dc, 12);
assert!(
(out[0] - nominal).abs() <= 256,
"16x16 DC cell {} vs nominal {nominal}",
out[0]
);
for (i, &v) in out.iter().enumerate().skip(1) {
assert!(v.abs() <= 1024, "16x16 off-DC cell {i} = {v} too large");
}
}
#[test]
fn forward_dct_32_zero_input_yields_zero() {
let mut t = [0i64; 32];
forward_dct_32(&mut t, 32);
assert_eq!(t, [0; 32]);
}
#[test]
fn forward_dct_32_dc_concentrates_energy() {
let mut t = [1024i64; 32];
forward_dct_32(&mut t, 32);
let nominal = crate::transform::round2(32i64 * 2896 * 1024, 12);
assert!(
(t[0] - nominal).abs() <= 32,
"DC bin {} expected≈{nominal}",
t[0]
);
for (i, v) in t.iter().enumerate().skip(1) {
assert!(v.abs() <= 64, "off-DC bin {i} = {v} exceeds noise bound");
}
}
#[test]
fn forward_dct_32_dc_roundtrip_via_inverse() {
dc_roundtrip_via_inverse(5, 1024, 64);
}
#[test]
fn forward_dct_32x32_zero_input_yields_zero() {
let input = vec![0i64; 1024];
let out = forward_dct_32x32(&input);
assert_eq!(out.len(), 1024);
for (i, &v) in out.iter().enumerate() {
assert_eq!(v, 0, "32x32 zero-input bin {i} = {v}");
}
}
#[test]
fn forward_dct_32x32_dc_concentrates_in_top_left() {
let input = vec![1024i64; 1024];
let out = forward_dct_32x32(&input);
let row_dc = crate::transform::round2(32i64 * 2896 * 1024, 12);
let nominal = crate::transform::round2(32i64 * 2896 * row_dc, 12);
assert!(
(out[0] - nominal).abs() <= 1024,
"32x32 DC cell {} vs nominal {nominal}",
out[0]
);
for (i, &v) in out.iter().enumerate().skip(1) {
assert!(v.abs() <= 4096, "32x32 off-DC cell {i} = {v} too large");
}
}
#[test]
fn forward_dct_64_zero_input_yields_zero() {
let mut t = [0i64; 64];
forward_dct_64(&mut t, 32);
assert_eq!(t, [0; 64]);
}
#[test]
fn forward_dct_64_dc_concentrates_energy() {
let mut t = [1024i64; 64];
forward_dct_64(&mut t, 32);
let nominal = crate::transform::round2(64i64 * 2896 * 1024, 12);
assert!(
(t[0] - nominal).abs() <= 64,
"DC bin {} expected≈{nominal}",
t[0]
);
for (i, v) in t.iter().enumerate().skip(1) {
assert!(v.abs() <= 128, "off-DC bin {i} = {v} exceeds noise bound");
}
}
#[test]
fn forward_dct_64_dc_roundtrip_via_inverse() {
dc_roundtrip_via_inverse(6, 1024, 256);
}
#[test]
fn forward_dct_64x64_zero_input_yields_zero() {
let input = vec![0i64; 4096];
let out = forward_dct_64x64(&input);
assert_eq!(out.len(), 4096);
for (i, &v) in out.iter().enumerate() {
assert_eq!(v, 0, "64x64 zero-input bin {i} = {v}");
}
}
#[test]
fn forward_dct_64x64_dc_concentrates_in_top_left() {
let input = vec![1024i64; 4096];
let out = forward_dct_64x64(&input);
let row_dc = crate::transform::round2(64i64 * 2896 * 1024, 12);
let nominal = crate::transform::round2(64i64 * 2896 * row_dc, 12);
assert!(
(out[0] - nominal).abs() <= 4096,
"64x64 DC cell {} vs nominal {nominal}",
out[0]
);
for (i, &v) in out.iter().enumerate().skip(1) {
assert!(v.abs() <= 16384, "64x64 off-DC cell {i} = {v} too large");
}
}
#[test]
fn forward_dct_8_arbitrary_roundtrip_via_inverse() {
let mut input = [0i64; 8];
let mut s: u64 = 0x12345678;
for v in input.iter_mut() {
s = s
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
*v = ((s >> 32) & 0xFF) as i64 - 128;
}
arbitrary_roundtrip_via_inverse(3, &input, 16);
}
#[test]
fn forward_dct_16_arbitrary_roundtrip_via_inverse() {
let mut input = [0i64; 16];
let mut s: u64 = 0xCAFEBABE;
for v in input.iter_mut() {
s = s
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
*v = ((s >> 32) & 0xFF) as i64 - 128;
}
arbitrary_roundtrip_via_inverse(4, &input, 32);
}
#[test]
fn forward_dct_4x4_basis_concentration_for_each_input_position() {
for a in 0..4 {
for b in 0..4 {
let mut input = [0i64; 16];
input[a * 4 + b] = 4096;
let out = forward_dct_4x4(&input);
for (i, v) in out.iter().enumerate() {
assert!(v.abs() < 3500, "a={a} b={b} out[{i}]={v} exceeds bound");
}
}
}
}
}