#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap,
reason = "quantized levels and reconstructed coefficients are stored into i16 \
with the C int16_t wrapping semantics of the reference encoder, and \
the small 0..=16 zig-zag indices cast to i32; every value is bounded \
well within range and the casts reproduce the reference exactly"
)]
use crate::lossy::constants::{AC_TABLE, DC_TABLE, QUALITY_TO_BASE_Q, ZIGZAG};
const QFIX: u32 = 17;
const QBIAS: i32 = 1 << (QFIX - 1);
const MAX_LEVEL: i32 = 2047;
#[derive(Clone, Copy)]
pub(crate) struct QFactor {
pub(crate) q: i32,
iq: i32,
}
impl QFactor {
const fn new(q: i32) -> Self {
Self {
q,
iq: ((1 << QFIX) + q / 2) / q,
}
}
pub(crate) fn quantize(self, coeff: i32) -> i32 {
let level = ((coeff.abs() * self.iq + QBIAS) >> QFIX).min(MAX_LEVEL);
if coeff < 0 { -level } else { level }
}
}
#[derive(Clone, Copy)]
pub(crate) struct QPair {
pub(crate) dc: QFactor,
pub(crate) ac: QFactor,
}
#[derive(Clone, Copy)]
pub(crate) struct Quantizer {
pub(crate) y1: QPair,
pub(crate) y2: QPair,
pub(crate) uv: QPair,
}
impl Quantizer {
#[must_use]
pub(crate) fn new(base_q: i32) -> Self {
let q = clip_q(base_q);
let dc = i32::from(DC_TABLE[q as usize]);
let ac = i32::from(AC_TABLE[q as usize]);
let uv_dc = i32::from(DC_TABLE[clip_uv(base_q) as usize]);
Self {
y1: QPair {
dc: QFactor::new(dc),
ac: QFactor::new(ac),
},
y2: QPair {
dc: QFactor::new(dc * 2),
ac: QFactor::new(((ac * 101_581) >> 16).max(8)),
},
uv: QPair {
dc: QFactor::new(uv_dc),
ac: QFactor::new(ac),
},
}
}
}
pub(crate) struct Quantized {
pub(crate) levels: [i16; 16],
pub(crate) recon: [i16; 16],
pub(crate) last: i32,
}
#[must_use]
pub(crate) fn quantize_block(
coeffs: [i16; 16],
dc: QFactor,
ac: QFactor,
first: usize,
) -> Quantized {
let mut levels = [0i16; 16];
let mut recon = [0i16; 16];
let mut last = first as i32 - 1;
for n in first..16 {
let j = ZIGZAG[n];
let factor = if j == 0 { dc } else { ac };
let level = factor.quantize(i32::from(coeffs[j]));
if level != 0 {
last = n as i32;
}
levels[n] = level as i16;
recon[j] = (level * factor.q) as i16;
}
Quantized {
levels,
recon,
last,
}
}
#[must_use]
pub(crate) fn quality_to_base_q(quality: u8) -> i32 {
i32::from(QUALITY_TO_BASE_Q[usize::from(quality.min(100))])
}
fn clip_q(v: i32) -> i32 {
v.clamp(0, 127)
}
fn clip_uv(v: i32) -> i32 {
v.clamp(0, 117)
}
#[cfg(test)]
mod tests {
use super::{Quantizer, quality_to_base_q, quantize_block};
use crate::lossy::constants::ZIGZAG;
use crate::lossy::fdct::fdct4x4;
use crate::lossy::idct::transform_one;
#[test]
fn dequant_factors_match_the_decoder_derivation() {
let q = Quantizer::new(40);
assert_eq!((q.y1.dc.q, q.y1.ac.q), (37, 44));
assert_eq!((q.y2.dc.q, q.y2.ac.q), (74, 68));
assert_eq!((q.uv.dc.q, q.uv.ac.q), (37, 44));
}
#[test]
fn quantize_rounds_to_nearest_and_reconstructs_level_times_q() {
let dc = super::QFactor::new(10);
let ac = super::QFactor::new(20);
let mut coeffs = [0i16; 16];
coeffs[0] = 24;
coeffs[1] = -35;
coeffs[4] = 9;
coeffs[8] = 30;
let q = quantize_block(coeffs, dc, ac, 0);
assert_eq!(q.levels[0], 2, "DC level");
assert_eq!(q.levels[1], -2, "AC level at natural 1");
assert_eq!(q.levels[2], 0, "AC level at natural 4 rounds to 0");
assert_eq!(q.levels[3], 2, "AC level at natural 8");
assert_eq!(q.last, 3);
assert_eq!(q.recon[0], 20);
assert_eq!(q.recon[1], -40);
assert_eq!(q.recon[4], 0);
assert_eq!(q.recon[8], 40);
}
#[test]
fn empty_block_reports_last_below_first() {
let dc = super::QFactor::new(200);
let ac = super::QFactor::new(200);
let coeffs = [1i16; 16]; let q0 = quantize_block(coeffs, dc, ac, 0);
assert_eq!(q0.last, -1, "first=0 empty -> last -1 (nz 0)");
let q1 = quantize_block(coeffs, dc, ac, 1);
assert_eq!(q1.last, 0, "first=1 empty -> last 0 (nz 1)");
}
#[test]
fn quality_to_base_q_is_monotonic_and_bounded() {
assert_eq!(quality_to_base_q(100), 0, "finest at q100");
assert_eq!(quality_to_base_q(0), 127, "coarsest at q0");
let mut prev = quality_to_base_q(0);
for quality in 1..=100u8 {
let cur = quality_to_base_q(quality);
assert!(cur <= prev, "q{quality}: {cur} > {prev}");
assert!((0..=127).contains(&cur));
prev = cur;
}
assert_eq!(quality_to_base_q(200), quality_to_base_q(100));
}
#[test]
fn full_pipeline_high_quality_is_near_lossless() {
let q = Quantizer::new(0);
let residual: [i16; 16] = [
-40, 12, -5, 33, 7, -18, 22, -3, 15, -27, 9, 41, -11, 6, -30, 19,
];
let coeffs = fdct4x4(residual);
let quantized = quantize_block(coeffs, q.y1.dc, q.y1.ac, 0);
let mut plane = [128u8; 16];
transform_one(&quantized.recon, &mut plane, 0, 4);
for (i, (&r, &p)) in residual.iter().zip(&plane).enumerate() {
let want = 128 + i32::from(r);
let got = i32::from(p);
assert!(
(got - want).abs() <= 6,
"sample {i}: got {got}, want ~{want}"
);
}
}
#[test]
fn zigzag_positions_map_levels_to_natural_recon() {
let dc = super::QFactor::new(8);
let ac = super::QFactor::new(8);
let mut coeffs = [0i16; 16];
let nat = ZIGZAG[5];
coeffs[nat] = 40;
let q = quantize_block(coeffs, dc, ac, 0);
assert_eq!(q.levels[5], 5, "level at zig-zag index 5");
assert_eq!(q.last, 5);
assert_eq!(q.recon[nat], 40, "recon at natural ZIGZAG[5]");
assert_eq!(q.recon[0], 0, "DC untouched");
}
}