use gamut_dsp::round_div_nearest;
use super::header::QuantIndices;
const QINDEX_RANGE: usize = 128;
#[rustfmt::skip]
const DC_QLOOKUP: [i16; QINDEX_RANGE] = [
4, 5, 6, 7, 8, 9, 10, 10, 11, 12, 13, 14, 15,
16, 17, 17, 18, 19, 20, 20, 21, 21, 22, 22, 23, 23,
24, 25, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 46,
47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 76, 77, 78, 79, 80, 81, 82, 83, 84,
85, 86, 87, 88, 89, 91, 93, 95, 96, 98, 100, 101, 102,
104, 106, 108, 110, 112, 114, 116, 118, 122, 124, 126, 128, 130,
132, 134, 136, 138, 140, 143, 145, 148, 151, 154, 157,
];
#[rustfmt::skip]
const AC_QLOOKUP: [i16; QINDEX_RANGE] = [
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78,
80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104,
106, 108, 110, 112, 114, 116, 119, 122, 125, 128, 131, 134, 137,
140, 143, 146, 149, 152, 155, 158, 161, 164, 167, 170, 173, 177,
181, 185, 189, 193, 197, 201, 205, 209, 213, 217, 221, 225, 229,
234, 239, 245, 249, 254, 259, 264, 269, 274, 279, 284,
];
fn clamp_q(q: i32) -> usize {
q.clamp(0, (QINDEX_RANGE - 1) as i32) as usize
}
fn dc_q(q: i32) -> i16 {
DC_QLOOKUP[clamp_q(q)]
}
fn ac_q(q: i32) -> i16 {
AC_QLOOKUP[clamp_q(q)]
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct QuantFactors {
pub y1_dc: i16,
pub y1_ac: i16,
pub y2_dc: i16,
pub y2_ac: i16,
pub uv_dc: i16,
pub uv_ac: i16,
}
impl QuantFactors {
#[must_use]
pub fn new(base_q: i32, indices: &QuantIndices) -> Self {
let y2_ac = (i32::from(ac_q(base_q + i32::from(indices.y2_ac_delta))) * 155 / 100).max(8);
let uv_dc = dc_q(base_q + i32::from(indices.uv_dc_delta)).min(132);
Self {
y1_dc: dc_q(base_q + i32::from(indices.y_dc_delta)),
y1_ac: ac_q(base_q),
y2_dc: dc_q(base_q + i32::from(indices.y2_dc_delta)) * 2,
y2_ac: y2_ac as i16,
uv_dc,
uv_ac: ac_q(base_q + i32::from(indices.uv_ac_delta)),
}
}
#[must_use]
pub fn for_frame(indices: &QuantIndices) -> Self {
Self::new(i32::from(indices.y_ac), indices)
}
}
#[must_use]
pub fn dequantize(level: i16, factor: i16) -> i16 {
(i32::from(level) * i32::from(factor)) as i16
}
#[must_use]
pub fn quantize(coeff: i16, factor: i16) -> i16 {
debug_assert!(factor > 0, "dequant factor is always >= 4");
round_div_nearest(i32::from(coeff), i32::from(factor)) as i16
}
#[cfg(test)]
mod tests {
use super::*;
fn indices(y_ac: u8) -> QuantIndices {
QuantIndices {
y_ac,
y_dc_delta: 0,
y2_dc_delta: 0,
y2_ac_delta: 0,
uv_dc_delta: 0,
uv_ac_delta: 0,
}
}
#[test]
fn lookup_tables_have_expected_shape() {
assert_eq!(DC_QLOOKUP.len(), 128);
assert_eq!(AC_QLOOKUP.len(), 128);
assert_eq!((DC_QLOOKUP[0], DC_QLOOKUP[127]), (4, 157));
assert_eq!((AC_QLOOKUP[0], AC_QLOOKUP[127]), (4, 284));
assert!(DC_QLOOKUP.windows(2).all(|w| w[0] <= w[1]));
assert!(AC_QLOOKUP.windows(2).all(|w| w[0] <= w[1]));
}
#[test]
fn factors_at_q0_apply_floors() {
let f = QuantFactors::for_frame(&indices(0));
assert_eq!(
f,
QuantFactors {
y1_dc: 4,
y1_ac: 4,
y2_dc: 8,
y2_ac: 8,
uv_dc: 4,
uv_ac: 4,
}
);
}
#[test]
fn factors_at_q127_apply_caps() {
let f = QuantFactors::for_frame(&indices(127));
assert_eq!(
f,
QuantFactors {
y1_dc: 157,
y1_ac: 284,
y2_dc: 314,
y2_ac: 440,
uv_dc: 132,
uv_ac: 284,
}
);
}
#[test]
fn uv_dc_cap_engages_at_high_q() {
for q in [117u8, 120, 127] {
assert_eq!(QuantFactors::for_frame(&indices(q)).uv_dc, 132);
}
assert_eq!(QuantFactors::for_frame(&indices(116)).uv_dc, 130);
}
#[test]
fn y2_ac_floor_engages_at_low_q() {
assert_eq!(QuantFactors::for_frame(&indices(0)).y2_ac, 8);
assert_eq!(QuantFactors::for_frame(&indices(1)).y2_ac, 8);
assert_eq!(QuantFactors::for_frame(&indices(2)).y2_ac, 9);
}
#[test]
fn deltas_are_added_then_clamped() {
let mut idx = indices(125);
idx.y_dc_delta = 10;
assert_eq!(QuantFactors::new(i32::from(idx.y_ac), &idx).y1_dc, 157);
let mut idx2 = indices(3);
idx2.uv_ac_delta = -50;
assert_eq!(
QuantFactors::new(i32::from(idx2.y_ac), &idx2).uv_ac,
AC_QLOOKUP[0]
);
}
#[test]
fn quantize_dequantize_round_trip_is_near() {
for &factor in &[4i16, 8, 26, 157, 284, 440] {
for coeff in [-2000i16, -301, -1, 0, 1, 17, 255, 2000] {
let level = quantize(coeff, factor);
let back = dequantize(level, factor);
assert!(
(i32::from(coeff) - i32::from(back)).abs() < i32::from(factor),
"coeff={coeff} factor={factor} back={back}"
);
}
}
assert_eq!(quantize(0, 26), 0);
assert_eq!(dequantize(0, 26), 0);
}
}