pub const DEFAULT_QMAT: [u8; 64] = [4u8; 64];
pub const PERCEPTUAL_LUMA_QMAT: [u8; 64] = [
2, 2, 2, 2, 3, 5, 6, 8, 2, 2, 2, 2, 3, 7, 8, 7, 2, 2, 2, 3, 5, 7, 9, 7, 2, 2, 3, 4, 6, 11, 10, 8, 2, 3, 5, 7, 9, 14, 13, 10, 3, 4, 7, 8, 10, 13, 14, 12, 6, 8, 10, 11, 13, 15, 15, 13, 9, 12, 12, 12, 14, 13, 13, 12, ];
pub const PERCEPTUAL_CHROMA_QMAT: [u8; 64] = [
2, 2, 3, 6, 12, 12, 12, 12, 2, 3, 3, 8, 12, 12, 12, 12, 3, 3, 7, 12, 12, 12, 12, 12, 6, 8, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, ];
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct QuantMatrices {
pub luma: [u8; 64],
pub chroma: [u8; 64],
}
impl QuantMatrices {
pub const fn flat() -> Self {
Self {
luma: DEFAULT_QMAT,
chroma: DEFAULT_QMAT,
}
}
pub const fn perceptual() -> Self {
Self {
luma: PERCEPTUAL_LUMA_QMAT,
chroma: PERCEPTUAL_CHROMA_QMAT,
}
}
pub fn is_default(&self) -> bool {
self.luma == DEFAULT_QMAT && self.chroma == DEFAULT_QMAT
}
pub fn weights_valid(&self) -> bool {
self.luma.iter().all(|w| (2..=63).contains(w))
&& self.chroma.iter().all(|w| (2..=63).contains(w))
}
}
impl Default for QuantMatrices {
fn default() -> Self {
Self::flat()
}
}
pub const BLOCK_SCAN_PROGRESSIVE: [u8; 64] = [
0, 1, 4, 5, 16, 17, 21, 22, 2, 3, 6, 7, 18, 20, 23, 28, 8, 9, 12, 13, 19, 24, 27, 29, 10, 11, 14, 15, 25, 26, 30, 31, 32, 33, 37, 38, 45, 46, 53, 54, 34, 36, 39, 44, 47, 52, 55, 60, 35, 40, 43, 48, 51, 56, 59, 61, 41, 42, 49, 50, 57, 58, 62, 63, ];
pub const BLOCK_SCAN_INTERLACED: [u8; 64] = [
0, 2, 8, 10, 32, 34, 35, 41, 1, 3, 9, 11, 33, 36, 40, 42, 4, 6, 12, 14, 37, 39, 43, 49, 5, 7, 13, 15, 38, 44, 48, 50, 16, 18, 19, 25, 45, 47, 51, 57, 17, 20, 24, 26, 46, 52, 56, 58, 21, 23, 27, 30, 53, 55, 59, 62, 22, 28, 29, 31, 54, 60, 61, 63, ];
pub const PROGRESSIVE_INV_SCAN: [u8; 64] = invert_scan(&BLOCK_SCAN_PROGRESSIVE);
pub const INTERLACED_INV_SCAN: [u8; 64] = invert_scan(&BLOCK_SCAN_INTERLACED);
const fn invert_scan(scan: &[u8; 64]) -> [u8; 64] {
let mut out = [0u8; 64];
let mut i = 0;
while i < 64 {
out[scan[i] as usize] = i as u8;
i += 1;
}
out
}
pub fn qscale(quantization_index: u8) -> i32 {
let i = quantization_index as i32;
if i <= 128 {
i
} else {
128 + 4 * (i - 128)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn progressive_scan_is_permutation() {
let mut seen = [false; 64];
for &v in &BLOCK_SCAN_PROGRESSIVE {
assert!(!seen[v as usize], "duplicate {v}");
seen[v as usize] = true;
}
assert!(seen.iter().all(|&b| b));
}
#[test]
fn interlaced_scan_is_permutation() {
let mut seen = [false; 64];
for &v in &BLOCK_SCAN_INTERLACED {
assert!(!seen[v as usize], "duplicate {v}");
seen[v as usize] = true;
}
assert!(seen.iter().all(|&b| b));
}
#[test]
fn inverse_progressive_scan_roundtrips() {
for k in 0..64 {
let nat = PROGRESSIVE_INV_SCAN[k] as usize;
assert_eq!(BLOCK_SCAN_PROGRESSIVE[nat], k as u8);
}
}
#[test]
fn perceptual_matrices_in_valid_weight_range() {
let m = QuantMatrices::perceptual();
assert!(m.weights_valid());
assert_eq!(m.luma[0], 2);
assert_eq!(m.chroma[0], 2);
assert!(m.luma[63] > 4);
}
#[test]
fn perceptual_matrices_match_jpeg_k1_k2_normalised_dc2() {
assert_eq!(PERCEPTUAL_LUMA_QMAT[0], 2);
assert_eq!(PERCEPTUAL_LUMA_QMAT[1], 2);
assert_eq!(PERCEPTUAL_LUMA_QMAT[7], 8);
assert_eq!(PERCEPTUAL_LUMA_QMAT[7 * 8], 9);
assert_eq!(PERCEPTUAL_CHROMA_QMAT[0], 2);
assert_eq!(PERCEPTUAL_CHROMA_QMAT[3], 6);
assert_eq!(PERCEPTUAL_CHROMA_QMAT[3 * 8 + 3], 12);
}
#[test]
fn quant_matrices_default_is_flat() {
assert_eq!(QuantMatrices::default(), QuantMatrices::flat());
assert!(QuantMatrices::default().is_default());
assert!(!QuantMatrices::perceptual().is_default());
}
#[test]
fn quant_matrices_weights_valid_rejects_out_of_range() {
let mut bad = QuantMatrices::flat();
bad.luma[0] = 1; assert!(!bad.weights_valid());
bad.luma[0] = 64; assert!(!bad.weights_valid());
bad.luma[0] = 4;
bad.chroma[5] = 0;
assert!(!bad.weights_valid());
}
#[test]
fn qscale_table15_samples() {
assert_eq!(qscale(1), 1);
assert_eq!(qscale(2), 2);
assert_eq!(qscale(126), 126);
assert_eq!(qscale(127), 127);
assert_eq!(qscale(128), 128);
assert_eq!(qscale(129), 132);
assert_eq!(qscale(130), 136);
assert_eq!(qscale(223), 508);
assert_eq!(qscale(224), 512);
}
}