#![allow(dead_code)]
use thiserror::Error;
use crate::pps::Pps;
use crate::sps::{ScalingListEntry, Sps};
#[derive(Debug, Error, PartialEq, Eq)]
pub enum TransformError {
#[error("QP out of range (got {0}, must be 0..=51+QpBdOffset for the bit depth)")]
QpOutOfRange(i32),
#[error("bit_depth out of range (got {0}, must be 8..=14)")]
BitDepthOutOfRange(u32),
}
#[inline]
pub fn qp_bd_offset(bit_depth_minus8: u32) -> i32 {
6 * bit_depth_minus8 as i32
}
pub fn qp_c_table_lookup(qp_i: i32) -> i32 {
const QP_C_FROM_QPI_30_51: [i32; 22] = [
29, 30, 31, 32, 32, 33, 34, 34, 35, 35, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 39,
];
if qp_i < 30 {
qp_i
} else if qp_i <= 51 {
QP_C_FROM_QPI_30_51[(qp_i - 30) as usize]
} else {
QP_C_FROM_QPI_30_51[21]
}
}
pub fn qp_y_to_qp_c(qp_y: i32, chroma_qp_index_offset: i32) -> i32 {
qp_y_to_qp_c_with_bd_offset(qp_y, chroma_qp_index_offset, 0)
}
pub fn qp_y_to_qp_c_with_bd_offset(
qp_y: i32,
chroma_qp_index_offset: i32,
qp_bd_offset_c: i32,
) -> i32 {
let lo = -qp_bd_offset_c;
let qp_i = (qp_y + chroma_qp_index_offset).clamp(lo, 51);
qp_c_table_lookup(qp_i)
}
const NORM_ADJUST_4X4_V: [[i32; 3]; 6] = [
[10, 16, 13],
[11, 18, 14],
[13, 20, 16],
[14, 23, 18],
[16, 25, 20],
[18, 29, 23],
];
const NORM_ADJUST_8X8_V: [[i32; 6]; 6] = [
[20, 18, 32, 19, 25, 24],
[22, 19, 35, 21, 28, 26],
[26, 23, 42, 24, 33, 31],
[28, 25, 45, 26, 35, 33],
[32, 28, 51, 30, 40, 38],
[36, 32, 58, 34, 46, 43],
];
#[inline]
fn norm_adjust_4x4(m: usize, i: usize, j: usize) -> i32 {
let row = &NORM_ADJUST_4X4_V[m];
match (i & 1, j & 1) {
(0, 0) => row[0],
(1, 1) => row[1],
_ => row[2],
}
}
#[inline]
fn norm_adjust_8x8(m: usize, i: usize, j: usize) -> i32 {
let row = &NORM_ADJUST_8X8_V[m];
let im4 = i & 3;
let jm4 = j & 3;
let im2 = i & 1;
let jm2 = j & 1;
if im4 == 0 && jm4 == 0 {
row[0]
} else if im2 == 1 && jm2 == 1 {
row[1]
} else if im4 == 2 && jm4 == 2 {
row[2]
} else if (im4 == 0 && jm2 == 1) || (im2 == 1 && jm4 == 0) {
row[3]
} else if (im4 == 0 && jm4 == 2) || (im4 == 2 && jm4 == 0) {
row[4]
} else {
row[5]
}
}
#[inline]
fn level_scale_4x4(scaling_list: &[i32; 16], m: usize, i: usize, j: usize) -> i32 {
scaling_list[i * 4 + j] * norm_adjust_4x4(m, i, j)
}
#[inline]
fn level_scale_8x8(scaling_list: &[i32; 64], m: usize, i: usize, j: usize) -> i32 {
scaling_list[i * 8 + j] * norm_adjust_8x8(m, i, j)
}
pub fn default_scaling_list_4x4_flat() -> [i32; 16] {
FLAT_4X4_16
}
pub fn default_scaling_list_8x8_flat() -> [i32; 64] {
FLAT_8X8_16
}
pub const FLAT_4X4_16: [i32; 16] = [16; 16];
pub const FLAT_8X8_16: [i32; 64] = [16; 64];
pub const DEFAULT_4X4_INTRA: [i32; 16] = [
6, 13, 13, 20, 20, 20, 28, 28, 28, 28, 32, 32, 32, 37, 37, 42,
];
pub const DEFAULT_4X4_INTER: [i32; 16] = [
10, 14, 14, 20, 20, 20, 24, 24, 24, 24, 27, 27, 27, 30, 30, 34,
];
pub const DEFAULT_8X8_INTRA: [i32; 64] = [
6, 10, 10, 13, 11, 13, 16, 16, 16, 16, 18, 18, 18, 18, 18, 23, 23, 23, 23, 23, 23, 25, 25, 25,
25, 25, 25, 25, 27, 27, 27, 27, 27, 27, 27, 27, 29, 29, 29, 29, 29, 29, 29, 31, 31, 31, 31, 31,
31, 33, 33, 33, 33, 33, 36, 36, 36, 36, 38, 38, 38, 40, 40, 42,
];
pub const DEFAULT_8X8_INTER: [i32; 64] = [
9, 13, 13, 15, 13, 15, 17, 17, 17, 17, 19, 19, 19, 19, 19, 21, 21, 21, 21, 21, 21, 22, 22, 22,
22, 22, 22, 22, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27,
27, 28, 28, 28, 28, 28, 30, 30, 30, 30, 32, 32, 32, 33, 33, 35,
];
fn default_4x4_for(idx: usize) -> [i32; 16] {
if idx < 3 {
DEFAULT_4X4_INTRA
} else {
DEFAULT_4X4_INTER
}
}
fn default_8x8_for(idx: usize) -> [i32; 64] {
if idx & 1 == 0 {
DEFAULT_8X8_INTRA
} else {
DEFAULT_8X8_INTER
}
}
fn chain_prev_4x4(idx: usize) -> Option<usize> {
match idx {
1 => Some(0),
2 => Some(1),
4 => Some(3),
5 => Some(4),
_ => None, }
}
fn chain_prev_8x8(idx: usize) -> Option<usize> {
match idx {
2 => Some(0), 3 => Some(1), 4 => Some(2), 5 => Some(3), _ => None, }
}
fn derive_sps_4x4(idx: usize, sps: &Sps) -> [i32; 16] {
if !sps.seq_scaling_matrix_present_flag {
return FLAT_4X4_16;
}
let lists = match sps.seq_scaling_lists.as_ref() {
Some(l) => l,
None => return FLAT_4X4_16,
};
let entry = lists.entries.get(idx);
match entry {
Some(ScalingListEntry::Explicit(vals)) => vec_to_16(vals),
Some(ScalingListEntry::UseDefault) => default_4x4_for(idx),
Some(ScalingListEntry::NotPresent) => {
match chain_prev_4x4(idx) {
Some(prev) => derive_sps_4x4(prev, sps),
None => default_4x4_for(idx),
}
}
None => FLAT_4X4_16,
}
}
fn derive_sps_8x8(idx: usize, sps: &Sps) -> [i32; 64] {
if !sps.seq_scaling_matrix_present_flag {
return FLAT_8X8_16;
}
let lists = match sps.seq_scaling_lists.as_ref() {
Some(l) => l,
None => return FLAT_8X8_16,
};
let spec_idx = 6 + idx;
let entry = lists.entries.get(spec_idx);
match entry {
Some(ScalingListEntry::Explicit(vals)) => vec_to_64(vals),
Some(ScalingListEntry::UseDefault) => default_8x8_for(idx),
Some(ScalingListEntry::NotPresent) => {
match chain_prev_8x8(idx) {
Some(prev) => derive_sps_8x8(prev, sps),
None => default_8x8_for(idx),
}
}
None => FLAT_8X8_16,
}
}
pub fn select_scaling_list_4x4(list_idx: usize, sps: &Sps, pps: &Pps) -> [i32; 16] {
if list_idx >= 6 {
return FLAT_4X4_16;
}
let ext = pps.extension.as_ref();
let pic_present = ext.is_some_and(|e| e.pic_scaling_matrix_present_flag);
let scan_list = if !pic_present {
derive_sps_4x4(list_idx, sps)
} else {
match ext.and_then(|e| e.pic_scaling_lists.as_ref()) {
Some(l) => derive_pps_4x4(list_idx, sps, l),
None => derive_sps_4x4(list_idx, sps),
}
};
weight_scale_4x4_from_scan(&scan_list)
}
pub fn select_scaling_list_8x8(list_idx: usize, sps: &Sps, pps: &Pps) -> [i32; 64] {
if list_idx >= 6 {
return FLAT_8X8_16;
}
let ext = pps.extension.as_ref();
let pic_present = ext.is_some_and(|e| e.pic_scaling_matrix_present_flag);
let scan_list = if !pic_present {
derive_sps_8x8(list_idx, sps)
} else {
match ext.and_then(|e| e.pic_scaling_lists.as_ref()) {
Some(l) => derive_pps_8x8(list_idx, sps, l),
None => derive_sps_8x8(list_idx, sps),
}
};
weight_scale_8x8_from_scan(&scan_list)
}
fn derive_pps_4x4(idx: usize, sps: &Sps, pic: &crate::pps::PicScalingLists) -> [i32; 16] {
let entry = pic.entries.get(idx);
match entry {
Some(ScalingListEntry::Explicit(vals)) => vec_to_16(vals),
Some(ScalingListEntry::UseDefault) => default_4x4_for(idx),
Some(ScalingListEntry::NotPresent) => {
if !sps.seq_scaling_matrix_present_flag {
match chain_prev_4x4(idx) {
Some(prev) => derive_pps_4x4(prev, sps, pic),
None => default_4x4_for(idx),
}
} else {
match chain_prev_4x4(idx) {
Some(prev) => derive_pps_4x4(prev, sps, pic),
None => derive_sps_4x4(idx, sps),
}
}
}
None => derive_sps_4x4(idx, sps),
}
}
fn derive_pps_8x8(idx: usize, sps: &Sps, pic: &crate::pps::PicScalingLists) -> [i32; 64] {
let spec_idx = 6 + idx;
let entry = pic.entries.get(spec_idx);
match entry {
Some(ScalingListEntry::Explicit(vals)) => vec_to_64(vals),
Some(ScalingListEntry::UseDefault) => default_8x8_for(idx),
Some(ScalingListEntry::NotPresent) => {
if !sps.seq_scaling_matrix_present_flag {
match chain_prev_8x8(idx) {
Some(prev) => derive_pps_8x8(prev, sps, pic),
None => default_8x8_for(idx),
}
} else {
match chain_prev_8x8(idx) {
Some(prev) => derive_pps_8x8(prev, sps, pic),
None => derive_sps_8x8(idx, sps),
}
}
}
None => derive_sps_8x8(idx, sps),
}
}
#[inline]
fn vec_to_16(v: &[i32]) -> [i32; 16] {
let mut out = [0i32; 16];
let n = v.len().min(16);
out[..n].copy_from_slice(&v[..n]);
if n < 16 {
for slot in out.iter_mut().skip(n) {
*slot = 16;
}
}
out
}
#[inline]
fn vec_to_64(v: &[i32]) -> [i32; 64] {
let mut out = [0i32; 64];
let n = v.len().min(64);
out[..n].copy_from_slice(&v[..n]);
if n < 64 {
for slot in out.iter_mut().skip(n) {
*slot = 16;
}
}
out
}
const ZIGZAG_4X4: [(usize, usize); 16] = [
(0, 0),
(0, 1),
(1, 0),
(2, 0),
(1, 1),
(0, 2),
(0, 3),
(1, 2),
(2, 1),
(3, 0),
(3, 1),
(2, 2),
(1, 3),
(2, 3),
(3, 2),
(3, 3),
];
const FIELD_4X4: [(usize, usize); 16] = [
(0, 0),
(1, 0),
(0, 1),
(2, 0),
(3, 0),
(1, 1),
(2, 1),
(3, 1),
(0, 2),
(1, 2),
(2, 2),
(3, 2),
(0, 3),
(1, 3),
(2, 3),
(3, 3),
];
pub fn weight_scale_4x4_from_scan(list: &[i32; 16]) -> [i32; 16] {
inverse_scan_4x4_zigzag(list)
}
pub fn weight_scale_8x8_from_scan(list: &[i32; 64]) -> [i32; 64] {
let mut out = [0i32; 64];
for (k, &raster) in ZIGZAG_8X8_SCAN_TO_RASTER.iter().enumerate() {
out[raster] = list[k];
}
out
}
const ZIGZAG_8X8_SCAN_TO_RASTER: [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 fn inverse_scan_4x4_zigzag(levels: &[i32; 16]) -> [i32; 16] {
let mut out = [0i32; 16];
for (k, &(i, j)) in ZIGZAG_4X4.iter().enumerate() {
out[i * 4 + j] = levels[k];
}
out
}
pub fn inverse_scan_4x4_zigzag_ac(levels: &[i32; 16]) -> [i32; 16] {
let mut out = [0i32; 16];
for k in 1..16 {
let (i, j) = ZIGZAG_4X4[k];
out[i * 4 + j] = levels[k - 1];
}
out
}
pub fn inverse_scan_4x4_field(levels: &[i32; 16]) -> [i32; 16] {
let mut out = [0i32; 16];
for (k, &(i, j)) in FIELD_4X4.iter().enumerate() {
out[i * 4 + j] = levels[k];
}
out
}
pub fn inverse_scan_4x4_field_ac(levels: &[i32; 16]) -> [i32; 16] {
let mut out = [0i32; 16];
for k in 1..16 {
let (i, j) = FIELD_4X4[k];
out[i * 4 + j] = levels[k - 1];
}
out
}
fn scale_4x4(
coeffs: &[i32; 16],
qp: i32,
scaling_list: &[i32; 16],
is_dc_preserved: bool,
) -> [i32; 16] {
let qp_div = qp / 6;
let qp_mod = (qp % 6) as usize;
let mut d = [0i32; 16];
for i in 0..4 {
for j in 0..4 {
let idx = i * 4 + j;
if is_dc_preserved && i == 0 && j == 0 {
d[0] = coeffs[0];
continue;
}
let ls = level_scale_4x4(scaling_list, qp_mod, i, j);
let c = coeffs[idx];
d[idx] = if qp >= 24 {
let shift = (qp_div - 4) as u32 & 31;
c.wrapping_mul(ls).wrapping_shl(shift)
} else {
let shift = (4 - qp_div) as u32;
let round = 1i32.wrapping_shl(3 - qp_div as u32);
(c.wrapping_mul(ls).wrapping_add(round)) >> shift
};
}
}
d
}
fn inverse_core_4x4(d: &[i32; 16]) -> [i32; 16] {
let mut f = [0i32; 16];
for i in 0..4 {
let d_i0 = d[i * 4];
let d_i1 = d[i * 4 + 1];
let d_i2 = d[i * 4 + 2];
let d_i3 = d[i * 4 + 3];
let e_i0 = d_i0 + d_i2;
let e_i1 = d_i0 - d_i2;
let e_i2 = (d_i1 >> 1) - d_i3;
let e_i3 = d_i1 + (d_i3 >> 1);
f[i * 4] = e_i0 + e_i3;
f[i * 4 + 1] = e_i1 + e_i2;
f[i * 4 + 2] = e_i1 - e_i2;
f[i * 4 + 3] = e_i0 - e_i3;
}
let mut h = [0i32; 16];
for j in 0..4 {
let f_0j = f[j];
let f_1j = f[4 + j];
let f_2j = f[8 + j];
let f_3j = f[12 + j];
let g_0j = f_0j + f_2j;
let g_1j = f_0j - f_2j;
let g_2j = (f_1j >> 1) - f_3j;
let g_3j = f_1j + (f_3j >> 1);
h[j] = g_0j + g_3j;
h[4 + j] = g_1j + g_2j;
h[8 + j] = g_1j - g_2j;
h[12 + j] = g_0j - g_3j;
}
let mut r = [0i32; 16];
for k in 0..16 {
r[k] = (h[k] + 32) >> 6;
}
r
}
#[inline]
fn check_qp(qp: i32, bit_depth: u32) -> Result<(), TransformError> {
let bd_minus8 = bit_depth.saturating_sub(8) as i32;
let qp_bd_offset = 6 * bd_minus8;
if !(0..=51 + qp_bd_offset).contains(&qp) {
Err(TransformError::QpOutOfRange(qp))
} else {
Ok(())
}
}
#[inline]
fn check_bit_depth(bit_depth: u32) -> Result<(), TransformError> {
if !(8..=14).contains(&bit_depth) {
Err(TransformError::BitDepthOutOfRange(bit_depth))
} else {
Ok(())
}
}
pub fn inverse_transform_4x4(
coeffs: &[i32; 16],
qp: i32,
scaling_list: &[i32; 16],
bit_depth: u32,
) -> Result<[i32; 16], TransformError> {
check_qp(qp, bit_depth)?;
check_bit_depth(bit_depth)?;
let d = scale_4x4(coeffs, qp, scaling_list, false);
Ok(inverse_core_4x4(&d))
}
pub fn inverse_transform_4x4_dc_preserved(
coeffs: &[i32; 16],
qp: i32,
scaling_list: &[i32; 16],
bit_depth: u32,
) -> Result<[i32; 16], TransformError> {
check_qp(qp, bit_depth)?;
check_bit_depth(bit_depth)?;
let d = scale_4x4(coeffs, qp, scaling_list, true);
Ok(inverse_core_4x4(&d))
}
fn scale_8x8(coeffs: &[i32; 64], qp: i32, scaling_list: &[i32; 64]) -> [i32; 64] {
let qp_div = qp / 6;
let qp_mod = (qp % 6) as usize;
let mut d = [0i32; 64];
for i in 0..8 {
for j in 0..8 {
let idx = i * 8 + j;
let ls = level_scale_8x8(scaling_list, qp_mod, i, j);
let c = coeffs[idx];
d[idx] = if qp >= 36 {
let shift = (qp_div - 6) as u32 & 31;
c.wrapping_mul(ls).wrapping_shl(shift)
} else {
let shift = (6 - qp_div) as u32;
let round = 1i32.wrapping_shl(5 - qp_div as u32);
(c.wrapping_mul(ls).wrapping_add(round)) >> shift
};
}
}
d
}
fn inverse_core_8x8(d: &[i32; 64]) -> [i32; 64] {
let mut g = [0i32; 64];
for i in 0..8 {
let base = i * 8;
let d0 = d[base];
let d1 = d[base + 1];
let d2 = d[base + 2];
let d3 = d[base + 3];
let d4 = d[base + 4];
let d5 = d[base + 5];
let d6 = d[base + 6];
let d7 = d[base + 7];
let e0 = d0 + d4;
let e1 = -d3 + d5 - d7 - (d7 >> 1);
let e2 = d0 - d4;
let e3 = d1 + d7 - d3 - (d3 >> 1);
let e4 = (d2 >> 1) - d6;
let e5 = -d1 + d7 + d5 + (d5 >> 1);
let e6 = d2 + (d6 >> 1);
let e7 = d3 + d5 + d1 + (d1 >> 1);
let f0 = e0 + e6;
let f1 = e1 + (e7 >> 2);
let f2 = e2 + e4;
let f3 = e3 + (e5 >> 2);
let f4 = e2 - e4;
let f5 = (e3 >> 2) - e5;
let f6 = e0 - e6;
let f7 = e7 - (e1 >> 2);
g[base] = f0 + f7;
g[base + 1] = f2 + f5;
g[base + 2] = f4 + f3;
g[base + 3] = f6 + f1;
g[base + 4] = f6 - f1;
g[base + 5] = f4 - f3;
g[base + 6] = f2 - f5;
g[base + 7] = f0 - f7;
}
let mut m = [0i32; 64];
for j in 0..8 {
let g0 = g[j];
let g1 = g[8 + j];
let g2 = g[16 + j];
let g3 = g[24 + j];
let g4 = g[32 + j];
let g5 = g[40 + j];
let g6 = g[48 + j];
let g7 = g[56 + j];
let h0 = g0 + g4;
let h1 = -g3 + g5 - g7 - (g7 >> 1);
let h2 = g0 - g4;
let h3 = g1 + g7 - g3 - (g3 >> 1);
let h4 = (g2 >> 1) - g6;
let h5 = -g1 + g7 + g5 + (g5 >> 1);
let h6 = g2 + (g6 >> 1);
let h7 = g3 + g5 + g1 + (g1 >> 1);
let k0 = h0 + h6;
let k1 = h1 + (h7 >> 2);
let k2 = h2 + h4;
let k3 = h3 + (h5 >> 2);
let k4 = h2 - h4;
let k5 = (h3 >> 2) - h5;
let k6 = h0 - h6;
let k7 = h7 - (h1 >> 2);
m[j] = k0 + k7;
m[8 + j] = k2 + k5;
m[16 + j] = k4 + k3;
m[24 + j] = k6 + k1;
m[32 + j] = k6 - k1;
m[40 + j] = k4 - k3;
m[48 + j] = k2 - k5;
m[56 + j] = k0 - k7;
}
let mut r = [0i32; 64];
for k in 0..64 {
r[k] = (m[k] + 32) >> 6;
}
r
}
pub fn inverse_transform_8x8(
coeffs: &[i32; 64],
qp: i32,
scaling_list: &[i32; 64],
bit_depth: u32,
) -> Result<[i32; 64], TransformError> {
check_qp(qp, bit_depth)?;
check_bit_depth(bit_depth)?;
let d = scale_8x8(coeffs, qp, scaling_list);
Ok(inverse_core_8x8(&d))
}
pub fn intra_bypass_dpcm(r: &mut [i32], n_w: usize, n_h: usize, hor_pred_flag: bool) {
debug_assert_eq!(r.len(), n_w * n_h);
if hor_pred_flag {
for i in 0..n_h {
for j in 1..n_w {
r[i * n_w + j] = r[i * n_w + j].wrapping_add(r[i * n_w + j - 1]);
}
}
} else {
for i in 1..n_h {
for j in 0..n_w {
r[i * n_w + j] = r[i * n_w + j].wrapping_add(r[(i - 1) * n_w + j]);
}
}
}
}
pub fn inverse_hadamard_luma_dc_16x16(
dc_coeffs: &[i32; 16],
qp: i32,
scaling_list: &[i32; 16],
bit_depth: u32,
) -> Result<[i32; 16], TransformError> {
check_qp(qp, bit_depth)?;
check_bit_depth(bit_depth)?;
let c = dc_coeffs;
let mut t = [0i32; 16]; for j in 0..4 {
t[j] = c[j] + c[4 + j] + c[8 + j] + c[12 + j];
t[4 + j] = c[j] + c[4 + j] - c[8 + j] - c[12 + j];
t[8 + j] = c[j] - c[4 + j] - c[8 + j] + c[12 + j];
t[12 + j] = c[j] - c[4 + j] + c[8 + j] - c[12 + j];
}
let mut f = [0i32; 16]; for i in 0..4 {
let base = i * 4;
let t0 = t[base];
let t1 = t[base + 1];
let t2 = t[base + 2];
let t3 = t[base + 3];
f[base] = t0 + t1 + t2 + t3;
f[base + 1] = t0 + t1 - t2 - t3;
f[base + 2] = t0 - t1 - t2 + t3;
f[base + 3] = t0 - t1 + t2 - t3;
}
let qp_mod = (qp % 6) as usize;
let qp_div = qp / 6;
let ls = level_scale_4x4(scaling_list, qp_mod, 0, 0);
let mut dc_y = [0i32; 16];
if qp >= 36 {
let shift = qp_div - 6;
for k in 0..16 {
dc_y[k] = (f[k] * ls) << shift;
}
} else {
let shift = 6 - qp_div;
let round = 1 << (5 - qp_div);
for k in 0..16 {
dc_y[k] = (f[k] * ls + round) >> shift;
}
}
Ok(dc_y)
}
pub fn inverse_hadamard_chroma_dc_420(
dc_coeffs: &[i32; 4],
qp: i32,
scaling_list: &[i32; 16],
bit_depth: u32,
) -> Result<[i32; 4], TransformError> {
check_qp(qp, bit_depth)?;
check_bit_depth(bit_depth)?;
let c00 = dc_coeffs[0];
let c01 = dc_coeffs[1];
let c10 = dc_coeffs[2];
let c11 = dc_coeffs[3];
let t00 = c00.wrapping_add(c10);
let t01 = c01.wrapping_add(c11);
let t10 = c00.wrapping_sub(c10);
let t11 = c01.wrapping_sub(c11);
let f = [
t00.wrapping_add(t01),
t00.wrapping_sub(t01),
t10.wrapping_add(t11),
t10.wrapping_sub(t11),
];
let qp_mod = (qp % 6) as usize;
let qp_div = qp / 6;
let ls = level_scale_4x4(scaling_list, qp_mod, 0, 0);
let mut out = [0i32; 4];
for k in 0..4 {
out[k] = (f[k].wrapping_mul(ls).wrapping_shl((qp_div as u32) & 31)) >> 5;
}
Ok(out)
}
pub fn inverse_hadamard_chroma_dc_422(
dc_coeffs: &[i32; 8],
qp: i32,
scaling_list: &[i32; 16],
bit_depth: u32,
) -> Result<[i32; 8], TransformError> {
check_qp(qp, bit_depth)?;
check_bit_depth(bit_depth)?;
let c00 = dc_coeffs[0];
let c01 = dc_coeffs[2];
let c10 = dc_coeffs[1];
let c11 = dc_coeffs[5];
let c20 = dc_coeffs[3];
let c21 = dc_coeffs[6];
let c30 = dc_coeffs[4];
let c31 = dc_coeffs[7];
let t00 = c00.wrapping_add(c10).wrapping_add(c20).wrapping_add(c30);
let t01 = c01.wrapping_add(c11).wrapping_add(c21).wrapping_add(c31);
let t10 = c00.wrapping_add(c10).wrapping_sub(c20).wrapping_sub(c30);
let t11 = c01.wrapping_add(c11).wrapping_sub(c21).wrapping_sub(c31);
let t20 = c00.wrapping_sub(c10).wrapping_sub(c20).wrapping_add(c30);
let t21 = c01.wrapping_sub(c11).wrapping_sub(c21).wrapping_add(c31);
let t30 = c00.wrapping_sub(c10).wrapping_add(c20).wrapping_sub(c30);
let t31 = c01.wrapping_sub(c11).wrapping_add(c21).wrapping_sub(c31);
let f = [
t00.wrapping_add(t01),
t00.wrapping_sub(t01),
t10.wrapping_add(t11),
t10.wrapping_sub(t11),
t20.wrapping_add(t21),
t20.wrapping_sub(t21),
t30.wrapping_add(t31),
t30.wrapping_sub(t31),
];
let qp_dc = qp + 3;
let mut out = [0i32; 8];
if qp_dc >= 36 {
let qp_dc_mod = (qp_dc % 6) as usize;
let ls = level_scale_4x4(scaling_list, qp_dc_mod, 0, 0);
let shift = ((qp_dc / 6) - 6) as u32 & 31;
for k in 0..8 {
out[k] = f[k].wrapping_mul(ls).wrapping_shl(shift);
}
} else {
let qp_dc_mod = (qp_dc % 6) as usize;
let qp_dc_div = qp_dc / 6;
let ls = level_scale_4x4(scaling_list, qp_dc_mod, 0, 0);
let shift = 6 - qp_dc_div;
let round = 1i32.wrapping_shl((5 - qp_dc_div) as u32);
for k in 0..8 {
out[k] = f[k].wrapping_mul(ls).wrapping_add(round) >> shift;
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bypass_dpcm_vertical_4x4_column_sums() {
#[rustfmt::skip]
let mut r = [
1, 2, 3, 4,
1, 0, -1, 2,
0, 5, 0, -4,
2, 1, 1, 1,
];
intra_bypass_dpcm(&mut r, 4, 4, false);
#[rustfmt::skip]
let expect = [
1, 2, 3, 4,
2, 2, 2, 6,
2, 7, 2, 2,
4, 8, 3, 3,
];
assert_eq!(r, expect);
}
#[test]
fn bypass_dpcm_horizontal_4x4_row_sums() {
#[rustfmt::skip]
let mut r = [
1, 2, 3, 4,
1, 0, -1, 2,
0, 5, 0, -4,
2, 1, 1, 1,
];
intra_bypass_dpcm(&mut r, 4, 4, true);
#[rustfmt::skip]
let expect = [
1, 3, 6, 10,
1, 1, 0, 2,
0, 5, 5, 1,
2, 3, 4, 5,
];
assert_eq!(r, expect);
}
#[test]
fn bypass_dpcm_rectangular_8x16_chroma_shape() {
let (n_w, n_h) = (8usize, 16usize);
let mut r = vec![0i32; n_w * n_h];
for (j, v) in r.iter_mut().enumerate().take(n_w) {
*v = j as i32; }
intra_bypass_dpcm(&mut r, n_w, n_h, false);
for i in 0..n_h {
for j in 0..n_w {
assert_eq!(r[i * n_w + j], j as i32, "({i},{j})");
}
}
}
#[test]
fn qpc_identity_below_30() {
for qp in 0..30 {
assert_eq!(qp_c_table_lookup(qp), qp);
}
}
#[test]
fn qpc_table_boundaries() {
assert_eq!(qp_c_table_lookup(30), 29);
assert_eq!(qp_c_table_lookup(31), 30);
assert_eq!(qp_c_table_lookup(39), 35);
assert_eq!(qp_c_table_lookup(40), 36);
assert_eq!(qp_c_table_lookup(51), 39);
}
#[test]
fn qpc_clips_high_qpi() {
assert_eq!(qp_c_table_lookup(100), 39);
}
#[test]
fn qp_y_to_qp_c_with_offset() {
assert_eq!(qp_y_to_qp_c(28, 2), 29);
assert_eq!(qp_y_to_qp_c(40, 0), 36);
assert_eq!(qp_y_to_qp_c(-5, -10), 0);
assert_eq!(qp_y_to_qp_c(80, 10), 39);
}
#[test]
fn inverse_transform_4x4_all_zero() {
let coeffs = [0i32; 16];
let sl = default_scaling_list_4x4_flat();
let out = inverse_transform_4x4(&coeffs, 26, &sl, 8).unwrap();
assert_eq!(out, [0i32; 16]);
}
#[test]
fn inverse_transform_4x4_rejects_bad_qp() {
let coeffs = [0i32; 16];
let sl = default_scaling_list_4x4_flat();
assert_eq!(
inverse_transform_4x4(&coeffs, -1, &sl, 8).unwrap_err(),
TransformError::QpOutOfRange(-1)
);
assert_eq!(
inverse_transform_4x4(&coeffs, 52, &sl, 8).unwrap_err(),
TransformError::QpOutOfRange(52)
);
}
#[test]
fn inverse_transform_4x4_rejects_bad_bit_depth() {
let coeffs = [0i32; 16];
let sl = default_scaling_list_4x4_flat();
assert_eq!(
inverse_transform_4x4(&coeffs, 26, &sl, 7).unwrap_err(),
TransformError::BitDepthOutOfRange(7)
);
assert_eq!(
inverse_transform_4x4(&coeffs, 26, &sl, 15).unwrap_err(),
TransformError::BitDepthOutOfRange(15)
);
}
#[test]
fn inverse_transform_4x4_qp_range_extends_with_bit_depth() {
let coeffs = [0i32; 16];
let sl = default_scaling_list_4x4_flat();
inverse_transform_4x4(&coeffs, 51, &sl, 8).expect("qp=51 ok at 8-bit");
assert!(matches!(
inverse_transform_4x4(&coeffs, 52, &sl, 8),
Err(TransformError::QpOutOfRange(52))
));
inverse_transform_4x4(&coeffs, 63, &sl, 10).expect("qp=63 ok at 10-bit");
assert!(matches!(
inverse_transform_4x4(&coeffs, 64, &sl, 10),
Err(TransformError::QpOutOfRange(64))
));
inverse_transform_4x4(&coeffs, 75, &sl, 12).expect("qp=75 ok at 12-bit");
assert!(matches!(
inverse_transform_4x4(&coeffs, 76, &sl, 12),
Err(TransformError::QpOutOfRange(76))
));
inverse_transform_4x4(&coeffs, 87, &sl, 14).expect("qp=87 ok at 14-bit");
assert!(matches!(
inverse_transform_4x4(&coeffs, 88, &sl, 14),
Err(TransformError::QpOutOfRange(88))
));
}
#[test]
fn qp_bd_offset_eq_7_4() {
assert_eq!(qp_bd_offset(0), 0);
assert_eq!(qp_bd_offset(2), 12);
assert_eq!(qp_bd_offset(4), 24);
assert_eq!(qp_bd_offset(6), 36);
}
#[test]
fn qp_y_to_qp_c_with_bd_offset_extends_negative_range() {
assert_eq!(qp_y_to_qp_c_with_bd_offset(-10, -2, 12), -12);
assert_eq!(qp_y_to_qp_c_with_bd_offset(-10, -2, 12) + 12, 0);
assert_eq!(qp_y_to_qp_c_with_bd_offset(-10, -2, 0), 0);
assert_eq!(qp_y_to_qp_c_with_bd_offset(60, 0, 12), 39);
}
#[test]
fn inverse_transform_4x4_single_dc_qp24() {
let mut coeffs = [0i32; 16];
coeffs[0] = 1;
let sl = default_scaling_list_4x4_flat();
let out = inverse_transform_4x4(&coeffs, 24, &sl, 8).unwrap();
assert_eq!(out, [3i32; 16]);
}
#[test]
fn inverse_transform_4x4_single_dc_qp30() {
let mut coeffs = [0i32; 16];
coeffs[0] = 1;
let sl = default_scaling_list_4x4_flat();
let out = inverse_transform_4x4(&coeffs, 30, &sl, 8).unwrap();
assert_eq!(out, [5i32; 16]);
}
#[test]
fn inverse_transform_4x4_single_dc_qp18() {
let mut coeffs = [0i32; 16];
coeffs[0] = 1;
let sl = default_scaling_list_4x4_flat();
let out = inverse_transform_4x4(&coeffs, 18, &sl, 8).unwrap();
assert_eq!(out, [1i32; 16]);
}
#[test]
fn inverse_transform_4x4_dc_preserved_skips_scaling() {
let mut coeffs = [0i32; 16];
coeffs[0] = 64;
let sl = default_scaling_list_4x4_flat();
let out = inverse_transform_4x4_dc_preserved(&coeffs, 24, &sl, 8).unwrap();
assert_eq!(out, [1i32; 16]);
}
#[test]
fn inverse_transform_4x4_known_pattern_qp0() {
let mut coeffs = [0i32; 16];
coeffs[0] = 64;
let sl = default_scaling_list_4x4_flat();
let out = inverse_transform_4x4(&coeffs, 0, &sl, 8).unwrap();
assert_eq!(out, [10i32; 16]);
}
#[test]
fn inverse_transform_4x4_row0_ac_pattern() {
let mut coeffs = [0i32; 16];
coeffs[0] = 64;
coeffs[1] = 32;
coeffs[2] = 16;
coeffs[3] = 8;
let sl = default_scaling_list_4x4_flat();
let out = inverse_transform_4x4(&coeffs, 24, &sl, 8).unwrap();
let r0 = (20288 + 32) >> 6; let r1 = (9344 + 32) >> 6; let r2 = (6016 + 32) >> 6; let r3 = (5312 + 32) >> 6; for i in 0..4 {
assert_eq!(out[i * 4], r0, "row {} col 0", i);
assert_eq!(out[i * 4 + 1], r1, "row {} col 1", i);
assert_eq!(out[i * 4 + 2], r2, "row {} col 2", i);
assert_eq!(out[i * 4 + 3], r3, "row {} col 3", i);
}
}
#[test]
fn inverse_transform_4x4_col0_pattern_qp22() {
let mut coeffs = [0i32; 16];
coeffs[4] = 16; let sl = default_scaling_list_4x4_flat();
let out = inverse_transform_4x4(&coeffs, 22, &sl, 8).unwrap();
let r0 = (2560 + 32) >> 6; let r1 = (1280 + 32) >> 6; let r2 = (-1280 + 32) >> 6; let r3 = (-2560 + 32) >> 6; for j in 0..4 {
assert_eq!(out[j], r0, "row 0 col {}", j);
assert_eq!(out[4 + j], r1, "row 1 col {}", j);
assert_eq!(out[2 * 4 + j], r2, "row 2 col {}", j);
assert_eq!(out[3 * 4 + j], r3, "row 3 col {}", j);
}
}
#[test]
fn inverse_transform_4x4_is_approximate_inverse_of_forward() {
let coeffs = [0i32; 16];
let sl = default_scaling_list_4x4_flat();
for qp in 0..=51 {
let out = inverse_transform_4x4(&coeffs, qp, &sl, 8).unwrap();
assert_eq!(
out, [0i32; 16],
"zero input must produce zero output (qP={})",
qp
);
}
}
#[test]
fn inverse_transform_8x8_all_zero() {
let coeffs = [0i32; 64];
let sl = default_scaling_list_8x8_flat();
let out = inverse_transform_8x8(&coeffs, 26, &sl, 8).unwrap();
assert_eq!(out, [0i32; 64]);
}
#[test]
fn inverse_transform_8x8_rejects_bad_qp() {
let coeffs = [0i32; 64];
let sl = default_scaling_list_8x8_flat();
assert_eq!(
inverse_transform_8x8(&coeffs, -5, &sl, 8).unwrap_err(),
TransformError::QpOutOfRange(-5)
);
assert_eq!(
inverse_transform_8x8(&coeffs, 60, &sl, 8).unwrap_err(),
TransformError::QpOutOfRange(60)
);
}
#[test]
fn inverse_transform_8x8_single_dc_qp36() {
let mut coeffs = [0i32; 64];
coeffs[0] = 1;
let sl = default_scaling_list_8x8_flat();
let out = inverse_transform_8x8(&coeffs, 36, &sl, 8).unwrap();
assert_eq!(out, [5i32; 64]);
}
#[test]
fn inverse_transform_8x8_single_dc_qp24() {
let mut coeffs = [0i32; 64];
coeffs[0] = 1;
let sl = default_scaling_list_8x8_flat();
let out = inverse_transform_8x8(&coeffs, 24, &sl, 8).unwrap();
assert_eq!(out, [1i32; 64]);
}
#[test]
fn luma_dc_16x16_all_zero() {
let dc = [0i32; 16];
let sl = default_scaling_list_4x4_flat();
let out = inverse_hadamard_luma_dc_16x16(&dc, 26, &sl, 8).unwrap();
assert_eq!(out, [0i32; 16]);
}
#[test]
fn luma_dc_16x16_rejects_bad_qp() {
let dc = [0i32; 16];
let sl = default_scaling_list_4x4_flat();
assert_eq!(
inverse_hadamard_luma_dc_16x16(&dc, 52, &sl, 8).unwrap_err(),
TransformError::QpOutOfRange(52)
);
}
#[test]
fn luma_dc_16x16_single_dc_qp36() {
let mut dc = [0i32; 16];
dc[0] = 1;
let sl = default_scaling_list_4x4_flat();
let out = inverse_hadamard_luma_dc_16x16(&dc, 36, &sl, 8).unwrap();
assert_eq!(out, [160i32; 16]);
}
#[test]
fn luma_dc_16x16_hadamard_self_inverse() {
let dc = [1i32; 16];
let sl = default_scaling_list_4x4_flat();
let out = inverse_hadamard_luma_dc_16x16(&dc, 36, &sl, 8).unwrap();
let mut expected = [0i32; 16];
expected[0] = 2560;
assert_eq!(out, expected);
}
#[test]
fn chroma_dc_420_all_zero() {
let dc = [0i32; 4];
let sl = default_scaling_list_4x4_flat();
let out = inverse_hadamard_chroma_dc_420(&dc, 20, &sl, 8).unwrap();
assert_eq!(out, [0i32; 4]);
}
#[test]
fn chroma_dc_420_single_dc_qp0() {
let mut dc = [0i32; 4];
dc[0] = 1;
let sl = default_scaling_list_4x4_flat();
let out = inverse_hadamard_chroma_dc_420(&dc, 0, &sl, 8).unwrap();
assert_eq!(out, [5i32; 4]);
}
#[test]
fn chroma_dc_420_single_dc_qp6() {
let mut dc = [0i32; 4];
dc[0] = 1;
let sl = default_scaling_list_4x4_flat();
let out = inverse_hadamard_chroma_dc_420(&dc, 6, &sl, 8).unwrap();
assert_eq!(out, [10i32; 4]);
}
#[test]
fn chroma_dc_420_hadamard_self_inverse() {
let dc = [1i32; 4];
let sl = default_scaling_list_4x4_flat();
let out = inverse_hadamard_chroma_dc_420(&dc, 0, &sl, 8).unwrap();
assert_eq!(out, [20, 0, 0, 0]);
}
#[test]
fn chroma_dc_422_all_zero() {
let dc = [0i32; 8];
let sl = default_scaling_list_4x4_flat();
let out = inverse_hadamard_chroma_dc_422(&dc, 20, &sl, 8).unwrap();
assert_eq!(out, [0i32; 8]);
}
#[test]
fn chroma_dc_422_single_dc_qp0() {
let mut dc = [0i32; 8];
dc[0] = 1;
let sl = default_scaling_list_4x4_flat();
let out = inverse_hadamard_chroma_dc_422(&dc, 0, &sl, 8).unwrap();
assert_eq!(out, [4i32; 8]);
}
#[test]
fn chroma_dc_422_rejects_bad_qp() {
let dc = [0i32; 8];
let sl = default_scaling_list_4x4_flat();
assert_eq!(
inverse_hadamard_chroma_dc_422(&dc, -1, &sl, 8).unwrap_err(),
TransformError::QpOutOfRange(-1)
);
}
#[test]
fn chroma_dc_420_regression_qp36() {
let mut dc = [0i32; 4];
dc[0] = 1;
let sl = default_scaling_list_4x4_flat();
let out = inverse_hadamard_chroma_dc_420(&dc, 36, &sl, 8).unwrap();
assert_eq!(out, [320i32; 4]);
}
#[test]
fn chroma_dc_422_regression_qp_mod_lookup() {
let mut dc = [0i32; 8];
dc[0] = 1;
let sl = default_scaling_list_4x4_flat();
let out = inverse_hadamard_chroma_dc_422(&dc, 6, &sl, 8).unwrap();
assert_eq!(out, [7i32; 8]);
}
#[test]
fn chroma_dc_422_regression_qpdc_vs_qp_shift() {
let sl = default_scaling_list_4x4_flat();
let mut dc = [0i32; 8];
dc[0] = 1;
let out = inverse_hadamard_chroma_dc_422(&dc, 36, &sl, 8).unwrap();
assert_eq!(out, [224i32; 8]);
let out = inverse_hadamard_chroma_dc_422(&dc, 33, &sl, 8).unwrap();
assert_eq!(out, [160i32; 8]);
let out = inverse_hadamard_chroma_dc_422(&dc, 32, &sl, 8).unwrap();
assert_eq!(out, [144i32; 8]);
}
#[test]
fn chroma_dc_422_regression_inverse_raster_scan() {
let mut dc = [0i32; 8];
dc[5] = 8; let sl = default_scaling_list_4x4_flat();
let out = inverse_hadamard_chroma_dc_422(&dc, 36, &sl, 8).unwrap();
assert!(out.iter().any(|&v| v != 0), "L[5] should affect the output");
}
#[test]
fn chroma_dc_420_extreme_input_does_not_panic() {
let dc = [i32::MAX, i32::MIN, i32::MAX, i32::MIN];
let sl = default_scaling_list_4x4_flat();
let _ = inverse_hadamard_chroma_dc_420(&dc, 51, &sl, 8).unwrap();
let _ = inverse_hadamard_chroma_dc_420(&dc, 0, &sl, 8).unwrap();
}
#[test]
fn chroma_dc_422_extreme_input_does_not_panic() {
let dc = [
i32::MAX,
i32::MIN,
i32::MAX,
i32::MIN,
i32::MAX,
i32::MIN,
i32::MAX,
i32::MIN,
];
let sl = default_scaling_list_4x4_flat();
let _ = inverse_hadamard_chroma_dc_422(&dc, 51, &sl, 8).unwrap();
let _ = inverse_hadamard_chroma_dc_422(&dc, 0, &sl, 8).unwrap();
}
#[test]
fn default_scaling_lists_all_16s() {
assert_eq!(default_scaling_list_4x4_flat(), [16i32; 16]);
assert_eq!(default_scaling_list_8x8_flat(), [16i32; 64]);
}
#[test]
fn default_4x4_intra_table_7_3() {
assert_eq!(
DEFAULT_4X4_INTRA,
[6, 13, 13, 20, 20, 20, 28, 28, 28, 28, 32, 32, 32, 37, 37, 42]
);
}
#[test]
fn default_4x4_inter_table_7_3() {
assert_eq!(
DEFAULT_4X4_INTER,
[10, 14, 14, 20, 20, 20, 24, 24, 24, 24, 27, 27, 27, 30, 30, 34]
);
}
#[test]
fn default_8x8_intra_table_7_4_spot_checks() {
assert_eq!(DEFAULT_8X8_INTRA[0], 6);
assert_eq!(DEFAULT_8X8_INTRA[1], 10);
assert_eq!(DEFAULT_8X8_INTRA[15], 23);
assert_eq!(DEFAULT_8X8_INTRA[31], 27);
assert_eq!(DEFAULT_8X8_INTRA[63], 42);
}
#[test]
fn default_8x8_inter_table_7_4_spot_checks() {
assert_eq!(DEFAULT_8X8_INTER[0], 9);
assert_eq!(DEFAULT_8X8_INTER[1], 13);
assert_eq!(DEFAULT_8X8_INTER[15], 21);
assert_eq!(DEFAULT_8X8_INTER[31], 24);
assert_eq!(DEFAULT_8X8_INTER[63], 35);
}
fn minimal_sps() -> crate::sps::Sps {
crate::sps::Sps {
profile_idc: 66,
constraint_set_flags: 0,
level_idc: 30,
seq_parameter_set_id: 0,
chroma_format_idc: 1,
separate_colour_plane_flag: false,
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
qpprime_y_zero_transform_bypass_flag: false,
seq_scaling_matrix_present_flag: false,
seq_scaling_lists: None,
log2_max_frame_num_minus4: 0,
pic_order_cnt_type: 0,
log2_max_pic_order_cnt_lsb_minus4: 0,
delta_pic_order_always_zero_flag: false,
offset_for_non_ref_pic: 0,
offset_for_top_to_bottom_field: 0,
num_ref_frames_in_pic_order_cnt_cycle: 0,
offset_for_ref_frame: Vec::new(),
max_num_ref_frames: 1,
gaps_in_frame_num_value_allowed_flag: false,
pic_width_in_mbs_minus1: 0,
pic_height_in_map_units_minus1: 0,
frame_mbs_only_flag: true,
mb_adaptive_frame_field_flag: false,
direct_8x8_inference_flag: false,
frame_cropping: None,
vui_parameters_present_flag: false,
vui: None,
}
}
fn minimal_pps() -> crate::pps::Pps {
crate::pps::Pps {
pic_parameter_set_id: 0,
seq_parameter_set_id: 0,
entropy_coding_mode_flag: false,
bottom_field_pic_order_in_frame_present_flag: false,
num_slice_groups_minus1: 0,
slice_group_map: None,
num_ref_idx_l0_default_active_minus1: 0,
num_ref_idx_l1_default_active_minus1: 0,
weighted_pred_flag: false,
weighted_bipred_idc: 0,
pic_init_qp_minus26: 0,
pic_init_qs_minus26: 0,
chroma_qp_index_offset: 0,
deblocking_filter_control_present_flag: false,
constrained_intra_pred_flag: false,
redundant_pic_cnt_present_flag: false,
extension: None,
}
}
#[test]
fn select_4x4_returns_flat_when_neither_sps_nor_pps_present() {
let sps = minimal_sps();
let pps = minimal_pps();
for i in 0..6 {
assert_eq!(select_scaling_list_4x4(i, &sps, &pps), FLAT_4X4_16);
}
}
#[test]
fn select_8x8_returns_flat_when_neither_sps_nor_pps_present() {
let sps = minimal_sps();
let pps = minimal_pps();
for i in 0..6 {
assert_eq!(select_scaling_list_8x8(i, &sps, &pps), FLAT_8X8_16);
}
}
#[test]
fn select_4x4_sps_all_not_present_gives_rule_a_defaults() {
let mut sps = minimal_sps();
sps.seq_scaling_matrix_present_flag = true;
sps.seq_scaling_lists = Some(crate::sps::SeqScalingLists {
entries: vec![ScalingListEntry::NotPresent; 8],
});
let pps = minimal_pps();
let w_intra = weight_scale_4x4_from_scan(&DEFAULT_4X4_INTRA);
let w_inter = weight_scale_4x4_from_scan(&DEFAULT_4X4_INTER);
assert_eq!(select_scaling_list_4x4(0, &sps, &pps), w_intra);
assert_eq!(select_scaling_list_4x4(1, &sps, &pps), w_intra);
assert_eq!(select_scaling_list_4x4(2, &sps, &pps), w_intra);
assert_eq!(select_scaling_list_4x4(3, &sps, &pps), w_inter);
assert_eq!(select_scaling_list_4x4(4, &sps, &pps), w_inter);
assert_eq!(select_scaling_list_4x4(5, &sps, &pps), w_inter);
}
#[test]
fn select_4x4_sps_explicit_entries_are_returned_verbatim() {
let mut sps = minimal_sps();
sps.seq_scaling_matrix_present_flag = true;
let custom: Vec<i32> = (0..16).map(|k| k + 1).collect(); sps.seq_scaling_lists = Some(crate::sps::SeqScalingLists {
entries: vec![
ScalingListEntry::Explicit(custom.clone()),
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
],
});
let pps = minimal_pps();
let got = select_scaling_list_4x4(0, &sps, &pps);
let mut scan = [0i32; 16];
for (k, v) in custom.iter().enumerate() {
scan[k] = *v;
}
let expected = weight_scale_4x4_from_scan(&scan);
assert_eq!(got, expected);
assert_eq!(select_scaling_list_4x4(1, &sps, &pps), expected);
assert_eq!(select_scaling_list_4x4(2, &sps, &pps), expected);
assert_eq!(
select_scaling_list_4x4(3, &sps, &pps),
weight_scale_4x4_from_scan(&DEFAULT_4X4_INTER)
);
}
#[test]
fn select_4x4_use_default_returns_table_default() {
let mut sps = minimal_sps();
sps.seq_scaling_matrix_present_flag = true;
sps.seq_scaling_lists = Some(crate::sps::SeqScalingLists {
entries: vec![
ScalingListEntry::UseDefault, ScalingListEntry::UseDefault, ScalingListEntry::UseDefault, ScalingListEntry::UseDefault, ScalingListEntry::UseDefault, ScalingListEntry::UseDefault, ScalingListEntry::NotPresent, ScalingListEntry::NotPresent,
],
});
let pps = minimal_pps();
let w_intra = weight_scale_4x4_from_scan(&DEFAULT_4X4_INTRA);
let w_inter = weight_scale_4x4_from_scan(&DEFAULT_4X4_INTER);
assert_eq!(select_scaling_list_4x4(0, &sps, &pps), w_intra);
assert_eq!(select_scaling_list_4x4(1, &sps, &pps), w_intra);
assert_eq!(select_scaling_list_4x4(3, &sps, &pps), w_inter);
assert_eq!(select_scaling_list_4x4(5, &sps, &pps), w_inter);
}
#[test]
fn select_8x8_use_default_and_not_present() {
let mut sps = minimal_sps();
sps.seq_scaling_matrix_present_flag = true;
sps.seq_scaling_lists = Some(crate::sps::SeqScalingLists {
entries: vec![
ScalingListEntry::NotPresent, ScalingListEntry::NotPresent, ScalingListEntry::NotPresent, ScalingListEntry::NotPresent, ScalingListEntry::NotPresent, ScalingListEntry::NotPresent, ScalingListEntry::UseDefault, ScalingListEntry::NotPresent, ],
});
let pps = minimal_pps();
assert_eq!(
select_scaling_list_8x8(0, &sps, &pps),
weight_scale_8x8_from_scan(&DEFAULT_8X8_INTRA)
);
assert_eq!(
select_scaling_list_8x8(1, &sps, &pps),
weight_scale_8x8_from_scan(&DEFAULT_8X8_INTER)
);
}
#[test]
fn select_8x8_chroma_444_12_entries() {
let mut sps = minimal_sps();
sps.chroma_format_idc = 3;
sps.seq_scaling_matrix_present_flag = true;
let explicit: Vec<i32> = (1..=64).collect();
sps.seq_scaling_lists = Some(crate::sps::SeqScalingLists {
entries: vec![
ScalingListEntry::NotPresent, ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::Explicit(explicit.clone()), ScalingListEntry::NotPresent, ScalingListEntry::NotPresent, ScalingListEntry::NotPresent, ScalingListEntry::NotPresent, ScalingListEntry::NotPresent, ],
});
let pps = minimal_pps();
let got = select_scaling_list_8x8(0, &sps, &pps);
assert_eq!(got[0], 1);
assert_eq!(got[8], 3);
assert_eq!(got[63], 64);
assert_eq!(
select_scaling_list_8x8(1, &sps, &pps),
weight_scale_8x8_from_scan(&DEFAULT_8X8_INTER)
);
assert_eq!(select_scaling_list_8x8(2, &sps, &pps), got);
assert_eq!(
select_scaling_list_8x8(3, &sps, &pps),
weight_scale_8x8_from_scan(&DEFAULT_8X8_INTER)
);
assert_eq!(select_scaling_list_8x8(4, &sps, &pps), got);
assert_eq!(
select_scaling_list_8x8(5, &sps, &pps),
weight_scale_8x8_from_scan(&DEFAULT_8X8_INTER)
);
}
#[test]
fn select_4x4_pps_override_takes_precedence_rule_b() {
let mut sps = minimal_sps();
sps.seq_scaling_matrix_present_flag = true;
sps.seq_scaling_lists = Some(crate::sps::SeqScalingLists {
entries: vec![
ScalingListEntry::Explicit(vec![1; 16]),
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
],
});
let mut pps = minimal_pps();
pps.extension = Some(crate::pps::PpsExtension {
transform_8x8_mode_flag: false,
pic_scaling_matrix_present_flag: true,
pic_scaling_lists: Some(crate::pps::PicScalingLists {
entries: vec![
ScalingListEntry::Explicit(vec![2; 16]), ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
],
}),
second_chroma_qp_index_offset: 0,
});
assert_eq!(select_scaling_list_4x4(0, &sps, &pps), [2i32; 16]);
assert_eq!(select_scaling_list_4x4(1, &sps, &pps), [2i32; 16]);
assert_eq!(
select_scaling_list_4x4(3, &sps, &pps),
weight_scale_4x4_from_scan(&DEFAULT_4X4_INTER)
);
}
#[test]
fn select_4x4_pps_override_rule_a_when_sps_absent() {
let sps = minimal_sps(); let mut pps = minimal_pps();
pps.extension = Some(crate::pps::PpsExtension {
transform_8x8_mode_flag: false,
pic_scaling_matrix_present_flag: true,
pic_scaling_lists: Some(crate::pps::PicScalingLists {
entries: vec![
ScalingListEntry::NotPresent, ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
ScalingListEntry::NotPresent,
],
}),
second_chroma_qp_index_offset: 0,
});
assert_eq!(
select_scaling_list_4x4(0, &sps, &pps),
weight_scale_4x4_from_scan(&DEFAULT_4X4_INTRA)
);
assert_eq!(
select_scaling_list_4x4(3, &sps, &pps),
weight_scale_4x4_from_scan(&DEFAULT_4X4_INTER)
);
}
#[test]
fn weight_scale_4x4_default_intra_is_monotone_raster() {
let w = weight_scale_4x4_from_scan(&DEFAULT_4X4_INTRA);
assert_eq!(w[0], 6);
assert_eq!(&w[0..4], &[6, 13, 20, 28]);
assert_eq!(&w[4..8], &[13, 20, 28, 32]);
assert_eq!(&w[8..12], &[20, 28, 32, 37]);
assert_eq!(&w[12..16], &[28, 32, 37, 42]);
}
#[test]
fn weight_scale_8x8_default_intra_corners() {
let w = weight_scale_8x8_from_scan(&DEFAULT_8X8_INTRA);
assert_eq!(w[0], 6);
assert_eq!(w[63], 42);
assert_eq!(w[1], 10);
assert_eq!(w[8], 10);
for r in 0..8 {
for c in 0..7 {
assert!(w[r * 8 + c] <= w[r * 8 + c + 1], "row {r} col {c}");
assert!(w[c * 8 + r] <= w[(c + 1) * 8 + r], "col {r} row {c}");
}
}
}
#[test]
fn weight_scale_flat_is_invariant() {
assert_eq!(weight_scale_4x4_from_scan(&FLAT_4X4_16), FLAT_4X4_16);
assert_eq!(weight_scale_8x8_from_scan(&FLAT_8X8_16), FLAT_8X8_16);
}
#[test]
fn zigzag_scan_is_bijection() {
let mut levels = [0i32; 16];
for (k, slot) in levels.iter_mut().enumerate() {
*slot = k as i32;
}
let block = inverse_scan_4x4_zigzag(&levels);
let at = |row: usize, col: usize| block[row * 4 + col];
assert_eq!(at(0, 0), 0);
assert_eq!(at(0, 1), 1);
assert_eq!(at(1, 0), 2);
assert_eq!(at(2, 0), 3);
assert_eq!(at(1, 1), 4);
assert_eq!(at(0, 2), 5);
assert_eq!(at(3, 3), 15);
}
#[test]
fn zigzag_ac_scan_maps_slot_to_spec_positions_1_through_15() {
let mut levels = [0i32; 16];
for (k, slot) in levels.iter_mut().enumerate().take(15) {
*slot = (k + 1) as i32; }
levels[15] = 12345; let block = inverse_scan_4x4_zigzag_ac(&levels);
let at = |row: usize, col: usize| block[row * 4 + col];
assert_eq!(at(0, 0), 0);
assert_eq!(at(0, 1), 1);
assert_eq!(at(1, 0), 2);
assert_eq!(at(2, 0), 3);
assert_eq!(at(1, 1), 4);
assert_eq!(at(0, 2), 5);
assert_eq!(at(3, 3), 15);
assert_ne!(at(0, 0), 12345);
}
#[test]
fn zigzag_ac_scan_matches_zigzag_with_shift() {
let mut ac_levels = [0i32; 16];
for (k, slot) in ac_levels.iter_mut().enumerate().take(15) {
*slot = (100 + k as i32) * if k % 2 == 0 { 1 } else { -1 };
}
let mut shifted = [0i32; 16];
shifted[1..16].copy_from_slice(&ac_levels[..15]);
let via_ac = inverse_scan_4x4_zigzag_ac(&ac_levels);
let via_shifted = inverse_scan_4x4_zigzag(&shifted);
assert_eq!(via_ac, via_shifted);
}
#[test]
fn field_ac_scan_maps_slot_to_spec_positions_1_through_15() {
let mut levels = [0i32; 16];
for (k, slot) in levels.iter_mut().enumerate().take(15) {
*slot = (k + 1) as i32;
}
levels[15] = 9999;
let block = inverse_scan_4x4_field_ac(&levels);
let at = |row: usize, col: usize| block[row * 4 + col];
assert_eq!(at(0, 0), 0);
assert_eq!(at(1, 0), 1);
assert_eq!(at(0, 1), 2);
assert_eq!(at(3, 3), 15);
}
#[test]
fn norm_adjust_4x4_spot_checks() {
assert_eq!(norm_adjust_4x4(0, 0, 0), 10); assert_eq!(norm_adjust_4x4(0, 1, 1), 16); assert_eq!(norm_adjust_4x4(0, 0, 1), 13); assert_eq!(norm_adjust_4x4(0, 2, 3), 13); assert_eq!(norm_adjust_4x4(5, 0, 0), 18);
assert_eq!(norm_adjust_4x4(5, 3, 3), 29);
assert_eq!(norm_adjust_4x4(5, 2, 1), 23);
}
#[test]
fn norm_adjust_8x8_spot_checks() {
assert_eq!(norm_adjust_8x8(0, 0, 0), 20); assert_eq!(norm_adjust_8x8(0, 4, 4), 20); assert_eq!(norm_adjust_8x8(0, 1, 1), 18); assert_eq!(norm_adjust_8x8(0, 3, 3), 18);
assert_eq!(norm_adjust_8x8(0, 2, 2), 32); assert_eq!(norm_adjust_8x8(0, 6, 6), 32);
assert_eq!(norm_adjust_8x8(0, 0, 1), 19); assert_eq!(norm_adjust_8x8(0, 1, 0), 19); assert_eq!(norm_adjust_8x8(0, 0, 2), 25); assert_eq!(norm_adjust_8x8(0, 2, 0), 25); assert_eq!(norm_adjust_8x8(0, 1, 2), 24);
assert_eq!(norm_adjust_8x8(0, 2, 1), 24);
assert_eq!(norm_adjust_8x8(0, 2, 3), 24);
}
}