#![forbid(unsafe_code)]
#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "raster/QP indices below are bounded by fixed 4x4/QP (0..=51) ranges checked \
at each cast site or by the caller (running QP state uses checked wraparound)"
)]
use super::error::H264Error;
const NORM_ADJUST: [[i64; 3]; 6] = [
[10, 16, 13],
[11, 18, 14],
[13, 20, 16],
[14, 23, 18],
[16, 25, 20],
[18, 29, 23],
];
const FLAT_WEIGHT_SCALE: i64 = 16;
const QPI_TO_QPC: [i32; 22] = [
29, 30, 31, 32, 32, 33, 34, 34, 35, 35, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 39,
];
#[must_use]
pub(super) fn qpc_from_qp(qp: i32, chroma_qp_index_offset: i32) -> i32 {
let qpi = (qp + chroma_qp_index_offset).clamp(0, 51);
if qpi < 30 {
qpi
} else {
QPI_TO_QPC[usize::try_from(qpi - 30).unwrap_or(0)]
}
}
const fn position_class(row: usize, col: usize) -> usize {
if row % 2 == 0 && col % 2 == 0 {
0
} else if row % 2 == 1 && col % 2 == 1 {
1
} else {
2
}
}
pub(super) fn dequant_normal(raster: &[i32; 16], qp: i32) -> Result<[i32; 16], H264Error> {
let mut out = [0i32; 16];
for row in 0..4 {
for col in 0..4 {
let idx = row * 4 + col;
let c = i64::from(raster[idx]);
if c == 0 {
continue;
}
out[idx] = dequant_scalar(c, qp, position_class(row, col))?;
}
}
Ok(out)
}
fn dequant_scalar(c: i64, qp: i32, position_class: usize) -> Result<i32, H264Error> {
let level_scale = FLAT_WEIGHT_SCALE
.checked_mul(NORM_ADJUST[usize::try_from(qp.rem_euclid(6)).unwrap_or(0)][position_class])
.ok_or(H264Error::FieldOverflow)?;
let product = c.checked_mul(level_scale).ok_or(H264Error::FieldOverflow)?;
let shift = qp.div_euclid(6);
let d = if qp >= 24 {
let up = u32::try_from(shift - 4).map_err(|_err| H264Error::FieldOverflow)?;
product.checked_shl(up).ok_or(H264Error::FieldOverflow)?
} else {
let down = u32::try_from(4 - shift).map_err(|_err| H264Error::FieldOverflow)?;
let round = 1i64.checked_shl(down - 1).ok_or(H264Error::FieldOverflow)?;
product
.checked_add(round)
.ok_or(H264Error::FieldOverflow)?
.checked_shr(down)
.ok_or(H264Error::FieldOverflow)?
};
i32::try_from(d).map_err(|_err| H264Error::FieldOverflow)
}
pub(super) fn dequant_luma_dc(raster: &[i32; 16], qp: i32) -> Result<[i32; 16], H264Error> {
let mut out = [0i32; 16];
for (idx, &f) in raster.iter().enumerate() {
let c = i64::from(f);
if c == 0 {
out[idx] = 0;
continue;
}
let level_scale = FLAT_WEIGHT_SCALE
.checked_mul(NORM_ADJUST[usize::try_from(qp.rem_euclid(6)).unwrap_or(0)][0])
.ok_or(H264Error::FieldOverflow)?;
let product = c.checked_mul(level_scale).ok_or(H264Error::FieldOverflow)?;
let shift = qp.div_euclid(6);
let d = if qp >= 36 {
let up = u32::try_from(shift - 6).map_err(|_err| H264Error::FieldOverflow)?;
product.checked_shl(up).ok_or(H264Error::FieldOverflow)?
} else {
let down = u32::try_from(6 - shift).map_err(|_err| H264Error::FieldOverflow)?;
let round = 1i64.checked_shl(down - 1).ok_or(H264Error::FieldOverflow)?;
product
.checked_add(round)
.ok_or(H264Error::FieldOverflow)?
.checked_shr(down)
.ok_or(H264Error::FieldOverflow)?
};
out[idx] = i32::try_from(d).map_err(|_err| H264Error::FieldOverflow)?;
}
Ok(out)
}
pub(super) fn dequant_chroma_dc(c: &[i32; 4], qpc: i32) -> Result<[i32; 4], H264Error> {
let mut out = [0i32; 4];
for (idx, &f) in c.iter().enumerate() {
let value = i64::from(f);
if value == 0 {
continue;
}
let level_scale = FLAT_WEIGHT_SCALE
.checked_mul(NORM_ADJUST[usize::try_from(qpc.rem_euclid(6)).unwrap_or(0)][0])
.ok_or(H264Error::FieldOverflow)?;
let product = value
.checked_mul(level_scale)
.ok_or(H264Error::FieldOverflow)?;
let up = u32::try_from(qpc.div_euclid(6)).map_err(|_err| H264Error::FieldOverflow)?;
let shifted = product.checked_shl(up).ok_or(H264Error::FieldOverflow)?;
out[idx] = i32::try_from(shifted >> 5).map_err(|_err| H264Error::FieldOverflow)?;
}
Ok(out)
}
#[must_use]
pub(super) fn inverse_transform_4x4(d: &[i32; 16]) -> [i32; 16] {
let butterflied = hadamard_like_pass(d, false);
let mut out = [0i32; 16];
for (idx, &value) in butterflied.iter().enumerate() {
out[idx] = (value + 32) >> 6;
}
out
}
#[must_use]
pub(super) fn inverse_hadamard_4x4(c: &[i32; 16]) -> [i32; 16] {
hadamard_like_pass(c, true)
}
fn hadamard_like_pass(input: &[i32; 16], is_hadamard: bool) -> [i32; 16] {
let mut cols = [0i32; 16];
for col in 0..4 {
let c0 = input[col];
let c1 = input[4 + col];
let c2 = input[8 + col];
let c3 = input[12 + col];
let (e0, e1, e2, e3) = butterfly(c0, c1, c2, c3, is_hadamard);
cols[col] = e0 + e3;
cols[4 + col] = e1 + e2;
cols[8 + col] = e1 - e2;
cols[12 + col] = e0 - e3;
}
let mut out = [0i32; 16];
for row in 0..4 {
let base = row * 4;
let r0 = cols[base];
let r1 = cols[base + 1];
let r2 = cols[base + 2];
let r3 = cols[base + 3];
let (e0, e1, e2, e3) = butterfly(r0, r1, r2, r3, is_hadamard);
out[base] = e0 + e3;
out[base + 1] = e1 + e2;
out[base + 2] = e1 - e2;
out[base + 3] = e0 - e3;
}
out
}
const fn butterfly(v0: i32, v1: i32, v2: i32, v3: i32, is_hadamard: bool) -> (i32, i32, i32, i32) {
if is_hadamard {
(v0 + v2, v0 - v2, v1 - v3, v1 + v3)
} else {
(v0 + v2, v0 - v2, (v1 >> 1) - v3, v1 + (v3 >> 1))
}
}
#[must_use]
pub(super) const fn inverse_hadamard_2x2(c: &[i32; 4]) -> [i32; 4] {
let (c00, c01, c10, c11) = (c[0], c[1], c[2], c[3]);
[
c00 + c01 + c10 + c11,
c00 - c01 + c10 - c11,
c00 + c01 - c10 - c11,
c00 - c01 - c10 + c11,
]
}
#[cfg(test)]
#[path = "transform_tests.rs"]
mod tests;