use super::dequant::intra_dc_mult;
pub const QF_AC_MIN: i32 = -2047;
pub const QF_AC_MAX: i32 = 2047;
pub const QF_DC_MIN: i32 = -2047;
pub const QF_DC_MAX: i32 = 2047;
#[must_use]
fn round_div(num: i64, den: i64) -> i64 {
if den == 0 {
return 0;
}
if (num >= 0) == (den > 0) {
(num + den.abs() / 2) / den
} else {
(num - den.abs() / 2) / den
}
}
#[must_use]
pub fn quantize_dc(f0: i32, intra_dc_precision: u8) -> i32 {
let mult = i64::from(intra_dc_mult(intra_dc_precision));
let qf = round_div(i64::from(f0), mult);
(qf as i32).clamp(QF_DC_MIN, QF_DC_MAX)
}
#[must_use]
pub fn quantize_ac(f: i32, w: u8, q_scale: i32) -> i32 {
if f == 0 {
return 0;
}
let den = i64::from(w) * i64::from(q_scale);
let qf = round_div(16 * i64::from(f), den);
(qf as i32).clamp(QF_AC_MIN, QF_AC_MAX)
}
#[must_use]
pub fn quantize_intra(
coeffs: &[i32; 64],
intra_matrix: &[u8; 64],
intra_dc_precision: u8,
q_scale: i32,
) -> [i32; 64] {
let mut qf = [0i32; 64];
qf[0] = quantize_dc(coeffs[0], intra_dc_precision);
for i in 1..64 {
qf[i] = quantize_ac(coeffs[i], intra_matrix[i], q_scale);
}
qf
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mpeg2::dequant::{dequantize_intra, DEFAULT_INTRA_MATRIX};
#[test]
fn round_div_rounds_to_nearest() {
assert_eq!(round_div(10, 3), 3); assert_eq!(round_div(11, 3), 4); assert_eq!(round_div(-10, 3), -3);
assert_eq!(round_div(-11, 3), -4);
assert_eq!(round_div(5, 2), 3); assert_eq!(round_div(-5, 2), -3);
}
#[test]
fn dc_forward_inverse_round_trip() {
for prec in 0u8..=3 {
let mult = intra_dc_mult(prec);
for f0 in [0i32, 8, 100, 1024, -16, -512] {
let qf0 = quantize_dc(f0, prec);
let recon = qf0 * mult;
assert!(
(recon - f0).abs() <= mult,
"prec {prec}, f0 {f0}: recon {recon}"
);
}
}
}
#[test]
fn ac_forward_inverse_round_trip() {
let w = 16u8;
let q = 8i32;
let step = (i32::from(w) * q) / 16; for f in [0i32, 8, 24, 100, -40, -160, 500] {
let qf = quantize_ac(f, w, q);
let recon = (2 * qf * i32::from(w) * q) / 32;
assert!(
(recon - f).abs() <= step,
"f {f}: qf {qf}, recon {recon}, step {step}"
);
}
}
#[test]
fn ac_zero_stays_zero() {
assert_eq!(quantize_ac(0, 16, 8), 0);
}
#[test]
fn ac_clamps_to_legal_range() {
let qf = quantize_ac(1_000_000, 1, 2);
assert_eq!(qf, QF_AC_MAX);
let qf = quantize_ac(-1_000_000, 1, 2);
assert_eq!(qf, QF_AC_MIN);
}
#[test]
fn quantize_intra_matches_dequant_dc() {
let mut coeffs = [0i32; 64];
coeffs[0] = 1024; let qf = quantize_intra(&coeffs, &DEFAULT_INTRA_MATRIX, 0, 4);
assert_eq!(qf[0], 128);
let recon = dequantize_intra(&qf, &DEFAULT_INTRA_MATRIX, 0, 4);
assert_eq!(recon[0], 1024);
}
#[test]
fn quantize_intra_full_block_round_trip() {
let coeffs: [i32; 64] = std::array::from_fn(|i| {
if i == 0 {
1024
} else {
((i as i32 % 7) - 3) * 20
}
});
let q_scale = 8;
let qf = quantize_intra(&coeffs, &DEFAULT_INTRA_MATRIX, 0, q_scale);
let recon = dequantize_intra(&qf, &DEFAULT_INTRA_MATRIX, 0, q_scale);
assert_eq!(recon[0], 1024);
for i in 1..64 {
let step = (i32::from(DEFAULT_INTRA_MATRIX[i]) * q_scale) / 16 + 1;
let tol = if i == 63 { step + 1 } else { step };
assert!(
(recon[i] - coeffs[i]).abs() <= tol.max(1),
"coeff[{i}] {} recon {} step {step}",
coeffs[i],
recon[i]
);
}
}
}