mod tables;
use gamut_dsp::round_div_nearest;
use tables::{AC_QLOOKUP, DC_QLOOKUP};
fn bit_depth_row(bit_depth: u32) -> usize {
debug_assert!(
matches!(bit_depth, 8 | 10 | 12),
"bit_depth must be 8, 10, or 12"
);
((bit_depth - 8) >> 1) as usize
}
#[must_use]
pub fn dc_q(bit_depth: u32, qindex: i32) -> i32 {
DC_QLOOKUP[bit_depth_row(bit_depth)][qindex.clamp(0, 255) as usize]
}
#[must_use]
pub fn ac_q(bit_depth: u32, qindex: i32) -> i32 {
AC_QLOOKUP[bit_depth_row(bit_depth)][qindex.clamp(0, 255) as usize]
}
#[must_use]
pub fn dequant(level: i32, q: i32, dq_denom: i32, bit_depth: u32) -> i32 {
let dq = i64::from(level) * i64::from(q);
let sign = if dq < 0 { -1 } else { 1 };
let dq2 = sign * ((dq.abs() & 0xFF_FFFF) / i64::from(dq_denom));
let lim = 1i64 << (7 + bit_depth);
dq2.clamp(-lim, lim - 1) as i32
}
#[must_use]
pub fn quantize(coeff: i32, q: i32) -> i32 {
assert!(q > 0, "quantize: q must be positive");
round_div_nearest(coeff, q)
}
#[cfg(test)]
mod tests {
use super::*;
struct Lcg(u64);
impl Lcg {
fn next(&mut self) -> u64 {
self.0 = self
.0
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
self.0
}
fn range(&mut self, lo: i32, hi: i32) -> i32 {
lo + (self.next() >> 33) as i32 % (hi - lo + 1)
}
}
#[test]
fn lookup_endpoints_match_spec() {
assert_eq!(dc_q(8, 0), 4);
assert_eq!(dc_q(8, 255), 1336);
assert_eq!(ac_q(8, 0), 4);
assert_eq!(ac_q(8, 255), 1828);
assert_eq!(dc_q(10, 255), 5347);
assert_eq!(dc_q(12, 255), 21387);
assert_eq!(ac_q(10, 255), 7312);
assert_eq!(ac_q(12, 255), 29247);
}
#[test]
fn lookup_clamps_index() {
assert_eq!(dc_q(8, -5), dc_q(8, 0));
assert_eq!(dc_q(8, 300), dc_q(8, 255));
assert_eq!(ac_q(8, 1000), ac_q(8, 255));
}
#[test]
fn lookup_tables_are_monotonic() {
for &bd in &[8u32, 10, 12] {
for q in 0..255 {
assert!(dc_q(bd, q) <= dc_q(bd, q + 1), "dc_q bd={bd} q={q}");
assert!(ac_q(bd, q) <= ac_q(bd, q + 1), "ac_q bd={bd} q={q}");
}
}
}
#[test]
fn dequant_lossless_is_times_four() {
assert_eq!(dc_q(8, 0), 4);
for level in -100..=100 {
assert_eq!(dequant(level, 4, 1, 8), level * 4);
}
}
#[test]
fn dequant_applies_denominator_and_clamp() {
assert_eq!(dequant(100, 100, 4, 8), 2500); assert_eq!(dequant(100, 100, 2, 8), 5000);
assert_eq!(dequant(10_000, 1336, 1, 8), 32767);
assert_eq!(dequant(-10_000, 1336, 1, 8), -32768);
}
#[test]
fn quantize_then_dequant_is_within_one_step() {
let mut rng = Lcg(0x9e37_79b9_7f4a_7c15);
for _ in 0..20_000 {
let qindex = rng.range(0, 255);
let q = ac_q(8, qindex);
let coeff = rng.range(-30_000, 30_000);
let level = quantize(coeff, q);
let recon = dequant(level, q, 1, 8);
if recon.abs() < 32_767 {
assert!(
(coeff - recon).abs() <= q,
"coeff={coeff} q={q} level={level} recon={recon}",
);
}
}
}
#[test]
fn quantize_is_sign_symmetric() {
for &q in &[4, 17, 200, 1336] {
for coeff in [-5000, -123, -1, 0, 1, 123, 5000] {
assert_eq!(
quantize(coeff, q),
-quantize(-coeff, q),
"q={q} coeff={coeff}"
);
}
}
}
}