use crate::transform::{inverse_adst, round2};
use std::sync::OnceLock;
fn inverse_adst_matrix(n: u32) -> &'static [i64] {
debug_assert!((2..=4).contains(&n));
static M2: OnceLock<Vec<i64>> = OnceLock::new();
static M3: OnceLock<Vec<i64>> = OnceLock::new();
static M4: OnceLock<Vec<i64>> = OnceLock::new();
let cell = match n {
2 => &M2,
3 => &M3,
4 => &M4,
_ => unreachable!("forward ADST size n must be in 2..=4"),
};
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_adst(&mut probe, n, 32);
for i in 0..nn {
m[i + nn * k] = probe[i];
}
}
m
})
}
fn forward_adst_n(t: &mut [i64], n: u32) {
assert!(
(2..=4).contains(&n),
"oxideav-av1 forward_adst_n requires n in 2..=4, got {n}"
);
let nn = 1usize << n;
assert!(
t.len() >= nn,
"oxideav-av1 forward_adst_n: buffer too short for length {nn}"
);
let m = inverse_adst_matrix(n);
let mut x = [0i64; 16];
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_adst_4(t: &mut [i64], _r: u32) {
forward_adst_n(t, 2);
}
pub fn forward_adst_8(t: &mut [i64], _r: u32) {
forward_adst_n(t, 3);
}
pub fn forward_adst_16(t: &mut [i64], _r: u32) {
forward_adst_n(t, 4);
}
fn forward_adst_nxn(input: &[i64], side: usize) -> Vec<i64> {
assert!(
matches!(side, 4 | 8 | 16),
"oxideav-av1 forward_adst_nxn requires side in {{4,8,16}}, got {side}"
);
assert_eq!(
input.len(),
side * side,
"oxideav-av1 forward_adst_nxn expects side * side = {} samples",
side * side
);
let n: u32 = match side {
4 => 2,
8 => 3,
16 => 4,
_ => 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_adst_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_adst_n(&mut col_buf, n);
for i in 0..side {
work[i * side + j] = col_buf[i];
}
}
work
}
pub fn forward_adst_4x4(input: &[i64]) -> [i64; 16] {
let v = forward_adst_nxn(input, 4);
let mut out = [0i64; 16];
out.copy_from_slice(&v);
out
}
pub fn forward_adst_8x8(input: &[i64]) -> [i64; 64] {
let v = forward_adst_nxn(input, 8);
let mut out = [0i64; 64];
out.copy_from_slice(&v);
out
}
pub fn forward_adst_16x16(input: &[i64]) -> [i64; 256] {
let v = forward_adst_nxn(input, 16);
let mut out = [0i64; 256];
out.copy_from_slice(&v);
out
}
pub fn forward_flipadst_4(t: &mut [i64], r: u32) {
t[..4].reverse();
forward_adst_4(t, r);
t[..4].reverse();
}
pub fn forward_flipadst_8(t: &mut [i64], r: u32) {
t[..8].reverse();
forward_adst_8(t, r);
t[..8].reverse();
}
pub fn forward_flipadst_16(t: &mut [i64], r: u32) {
t[..16].reverse();
forward_adst_16(t, r);
t[..16].reverse();
}
fn forward_flipadst_nxn(input: &[i64], side: usize) -> Vec<i64> {
let mut flipped = vec![0i64; input.len()];
for i in 0..side {
for j in 0..side {
flipped[i * side + j] = input[(side - 1 - i) * side + (side - 1 - j)];
}
}
let coeffs = forward_adst_nxn(&flipped, side);
let mut out = vec![0i64; input.len()];
for i in 0..side {
for j in 0..side {
out[i * side + j] = coeffs[(side - 1 - i) * side + (side - 1 - j)];
}
}
out
}
pub fn forward_flipadst_4x4(input: &[i64]) -> [i64; 16] {
let v = forward_flipadst_nxn(input, 4);
let mut out = [0i64; 16];
out.copy_from_slice(&v);
out
}
pub fn forward_flipadst_8x8(input: &[i64]) -> [i64; 64] {
let v = forward_flipadst_nxn(input, 8);
let mut out = [0i64; 64];
out.copy_from_slice(&v);
out
}
pub fn forward_flipadst_16x16(input: &[i64]) -> [i64; 256] {
let v = forward_flipadst_nxn(input, 16);
let mut out = [0i64; 256];
out.copy_from_slice(&v);
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transform::inverse_adst;
#[test]
fn forward_adst_4_zero_input_yields_zero() {
let mut t = [0i64; 4];
forward_adst_4(&mut t, 32);
assert_eq!(t, [0; 4]);
}
#[test]
fn forward_adst_8_zero_input_yields_zero() {
let mut t = [0i64; 8];
forward_adst_8(&mut t, 32);
assert_eq!(t, [0; 8]);
}
#[test]
fn forward_adst_16_zero_input_yields_zero() {
let mut t = [0i64; 16];
forward_adst_16(&mut t, 32);
assert_eq!(t, [0; 16]);
}
fn adst_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();
match n {
2 => forward_adst_4(&mut buf, 32),
3 => forward_adst_8(&mut buf, 32),
4 => forward_adst_16(&mut buf, 32),
_ => unreachable!(),
}
inverse_adst(&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_adst_4_roundtrip_via_inverse_dc() {
adst_roundtrip_via_inverse(2, &[1024i64; 4], 4);
}
#[test]
fn forward_adst_4_roundtrip_via_inverse_arbitrary() {
let mut input = [0i64; 4];
let mut s: u64 = 0xDEAD_BEEF_F00D_CAFE;
for v in input.iter_mut() {
s = s
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
*v = ((s >> 32) & 0xFF) as i64 - 128;
}
adst_roundtrip_via_inverse(2, &input, 8);
}
#[test]
fn forward_adst_8_roundtrip_via_inverse_dc() {
adst_roundtrip_via_inverse(3, &[1024i64; 8], 8);
}
#[test]
fn forward_adst_8_roundtrip_via_inverse_arbitrary() {
let mut input = [0i64; 8];
let mut s: u64 = 0x1234_5678_9ABC_DEF0;
for v in input.iter_mut() {
s = s
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
*v = ((s >> 32) & 0xFF) as i64 - 128;
}
adst_roundtrip_via_inverse(3, &input, 16);
}
#[test]
fn forward_adst_16_roundtrip_via_inverse_dc() {
adst_roundtrip_via_inverse(4, &[1024i64; 16], 16);
}
#[test]
fn forward_adst_16_roundtrip_via_inverse_arbitrary() {
let mut input = [0i64; 16];
let mut s: u64 = 0xFEED_FACE_BAAD_F00D;
for v in input.iter_mut() {
s = s
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
*v = ((s >> 32) & 0xFF) as i64 - 128;
}
adst_roundtrip_via_inverse(4, &input, 32);
}
#[test]
fn forward_adst_4_matrix_probe_is_transpose_of_inverse() {
let mut inv_probe = [0i64; 4];
for (i, slot) in inv_probe.iter_mut().enumerate() {
*slot = if i == 0 { 4096 } else { 0 };
}
let mut mat = [0i64; 16];
for k in 0..4 {
let mut probe = [0i64; 4];
probe[k] = 4096;
inverse_adst(&mut probe, 2, 32);
for i in 0..4 {
mat[i + 4 * k] = probe[i];
}
}
let mut t = [0i64; 4];
t[0] = 4096;
forward_adst_4(&mut t, 32);
for k in 0..4 {
assert_eq!(
t[k],
round2(mat[4 * k] * 4096, 12),
"row 0 col k={k} mismatch"
);
}
}
#[test]
fn forward_adst_4x4_zero_input_yields_zero() {
let input = [0i64; 16];
let out = forward_adst_4x4(&input);
assert_eq!(out, [0; 16]);
}
#[test]
fn forward_adst_8x8_zero_input_yields_zero() {
let input = [0i64; 64];
let out = forward_adst_8x8(&input);
assert_eq!(out, [0; 64]);
}
#[test]
fn forward_adst_16x16_zero_input_yields_zero() {
let input = [0i64; 256];
let out = forward_adst_16x16(&input);
assert_eq!(out, [0; 256]);
}
fn adst_2d_roundtrip(n: u32, input: &[i64], max_abs_error: i64) {
let side = 1usize << n;
assert_eq!(input.len(), side * side);
let coeffs: Vec<i64> = match side {
4 => forward_adst_4x4(input).to_vec(),
8 => forward_adst_8x8(input).to_vec(),
16 => forward_adst_16x16(input).to_vec(),
_ => unreachable!(),
};
let mut work = coeffs;
let mut row_buf = vec![0i64; side];
for i in 0..side {
row_buf.copy_from_slice(&work[i * side..(i + 1) * side]);
inverse_adst(&mut row_buf, n, 32);
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];
}
inverse_adst(&mut col_buf, n, 32);
for i in 0..side {
work[i * side + j] = col_buf[i];
}
}
let scale = ((side as i64) / 2).pow(2);
for (i, (&got, &orig)) in work.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_adst_4x4_roundtrip_via_inverse_dc() {
let input = [256i64; 16];
adst_2d_roundtrip(2, &input, 16);
}
#[test]
fn forward_adst_8x8_roundtrip_via_inverse_dc() {
let input = [256i64; 64];
adst_2d_roundtrip(3, &input, 64);
}
#[test]
fn forward_adst_16x16_roundtrip_via_inverse_dc() {
let input = [256i64; 256];
adst_2d_roundtrip(4, &input, 256);
}
#[test]
fn forward_flipadst_4_zero_input_yields_zero() {
let mut t = [0i64; 4];
forward_flipadst_4(&mut t, 32);
assert_eq!(t, [0; 4]);
}
#[test]
fn forward_flipadst_4_on_dc_is_reverse_of_adst() {
let mut a = [256i64; 4];
let mut b = [256i64; 4];
forward_adst_4(&mut a, 32);
forward_flipadst_4(&mut b, 32);
a.reverse();
assert_eq!(a, b);
}
#[test]
fn forward_flipadst_4_on_ramp_is_reverse_of_adst_of_reverse() {
let input = [10i64, 20, 30, 40];
let mut flipped_then_adst = input;
flipped_then_adst.reverse();
forward_adst_4(&mut flipped_then_adst, 32);
flipped_then_adst.reverse();
let mut got = input;
forward_flipadst_4(&mut got, 32);
assert_eq!(got, flipped_then_adst);
}
#[test]
fn forward_flipadst_8_on_ramp_is_reverse_of_adst_of_reverse() {
let input = [10i64, 20, 30, 40, 50, 60, 70, 80];
let mut flipped_then_adst = input;
flipped_then_adst.reverse();
forward_adst_8(&mut flipped_then_adst, 32);
flipped_then_adst.reverse();
let mut got = input;
forward_flipadst_8(&mut got, 32);
assert_eq!(got, flipped_then_adst);
}
#[test]
fn forward_flipadst_16_on_ramp_is_reverse_of_adst_of_reverse() {
let mut input = [0i64; 16];
for (i, slot) in input.iter_mut().enumerate() {
*slot = 10 * (i as i64 + 1);
}
let mut flipped_then_adst = input;
flipped_then_adst.reverse();
forward_adst_16(&mut flipped_then_adst, 32);
flipped_then_adst.reverse();
let mut got = input;
forward_flipadst_16(&mut got, 32);
assert_eq!(got, flipped_then_adst);
}
#[test]
fn forward_flipadst_4x4_zero_input_yields_zero() {
let input = [0i64; 16];
let out = forward_flipadst_4x4(&input);
assert_eq!(out, [0; 16]);
}
#[test]
fn forward_flipadst_4x4_on_dc_is_double_reverse_of_adst() {
let input = [256i64; 16];
let a = forward_adst_4x4(&input);
let b = forward_flipadst_4x4(&input);
let mut expected = [0i64; 16];
for i in 0..4 {
for j in 0..4 {
expected[i * 4 + j] = a[(3 - i) * 4 + (3 - j)];
}
}
assert_eq!(b, expected);
}
#[test]
fn forward_flipadst_8x8_zero_input_yields_zero() {
let input = [0i64; 64];
let out = forward_flipadst_8x8(&input);
assert_eq!(out, [0; 64]);
}
#[test]
fn forward_flipadst_16x16_zero_input_yields_zero() {
let input = [0i64; 256];
let out = forward_flipadst_16x16(&input);
assert_eq!(out, [0; 256]);
}
#[test]
fn forward_flipadst_4x4_equals_adst_of_double_flipped() {
let mut input = [0i64; 16];
for (i, slot) in input.iter_mut().enumerate() {
*slot = (i as i64) - 8;
}
let mut flipped = [0i64; 16];
for i in 0..4 {
for j in 0..4 {
flipped[i * 4 + j] = input[(3 - i) * 4 + (3 - j)];
}
}
let adst_of_flipped = forward_adst_4x4(&flipped);
let mut expected = [0i64; 16];
for i in 0..4 {
for j in 0..4 {
expected[i * 4 + j] = adst_of_flipped[(3 - i) * 4 + (3 - j)];
}
}
let got = forward_flipadst_4x4(&input);
assert_eq!(got, expected);
}
}