const STD_LUMINANCE_TABLE: [u8; 64] = [
16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55, 14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62, 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, 104, 113,
92, 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99,
];
const STD_CHROMINANCE_TABLE: [u8; 64] = [
17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, 24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
];
pub const ZIGZAG: [usize; 64] = [
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20,
13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59,
52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
];
pub struct QuantizationTables {
pub luminance: [u8; 64],
pub chrominance: [u8; 64],
pub luminance_table: [f32; 64],
pub chrominance_table: [f32; 64],
pub luminance_table_int: [u16; 64],
pub chrominance_table_int: [u16; 64],
}
impl QuantizationTables {
pub fn with_quality(quality: u8) -> Self {
let quality = quality.clamp(1, 100);
let scale = if quality < 50 {
5000 / quality as u32
} else {
200 - 2 * quality as u32
};
let mut luminance = [0u8; 64];
let mut chrominance = [0u8; 64];
let mut luminance_table = [0.0f32; 64];
let mut chrominance_table = [0.0f32; 64];
let mut luminance_table_int = [0u16; 64];
let mut chrominance_table_int = [0u16; 64];
for i in 0..64 {
let lum_val =
((STD_LUMINANCE_TABLE[ZIGZAG[i]] as u32 * scale + 50) / 100).clamp(1, 255);
let chrom_val =
((STD_CHROMINANCE_TABLE[ZIGZAG[i]] as u32 * scale + 50) / 100).clamp(1, 255);
luminance[i] = lum_val as u8;
chrominance[i] = chrom_val as u8;
}
for i in 0..64 {
let lum_val = ((STD_LUMINANCE_TABLE[i] as u32 * scale + 50) / 100).clamp(1, 255);
let chrom_val = ((STD_CHROMINANCE_TABLE[i] as u32 * scale + 50) / 100).clamp(1, 255);
luminance_table[i] = lum_val as f32;
chrominance_table[i] = chrom_val as f32;
luminance_table_int[i] = lum_val as u16;
chrominance_table_int[i] = chrom_val as u16;
}
Self {
luminance,
chrominance,
luminance_table,
chrominance_table,
luminance_table_int,
chrominance_table_int,
}
}
}
impl Default for QuantizationTables {
fn default() -> Self {
Self::with_quality(75)
}
}
pub fn quantize_block(dct: &[f32; 64], quant_table: &[f32; 64]) -> [i16; 64] {
let mut result = [0i16; 64];
for i in 0..64 {
result[i] = (dct[i] / quant_table[i]).round() as i16;
}
result
}
pub fn zigzag_reorder(block: &[i16; 64]) -> [i16; 64] {
let mut result = [0i16; 64];
for i in 0..64 {
result[i] = block[ZIGZAG[i]];
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quantize_block() {
let mut dct = [0.0f32; 64];
dct[0] = 160.0;
let tables = QuantizationTables::with_quality(75);
let quantized = quantize_block(&dct, &tables.luminance_table);
assert_ne!(quantized[0], 0);
}
#[test]
fn test_quality_scaling() {
let q50 = QuantizationTables::with_quality(50);
let q90 = QuantizationTables::with_quality(90);
assert!(q90.luminance[0] < q50.luminance[0]);
}
#[test]
fn test_zigzag_order() {
assert_eq!(ZIGZAG[0], 0);
assert_eq!(ZIGZAG[1], 1);
assert_eq!(ZIGZAG[2], 8);
assert_eq!(ZIGZAG[3], 16);
assert_eq!(ZIGZAG[4], 9);
assert_eq!(ZIGZAG[5], 2);
}
#[test]
fn test_zigzag_reorder() {
let mut block = [0i16; 64];
block[0] = 100; block[1] = 50;
block[8] = 30;
let reordered = zigzag_reorder(&block);
assert_eq!(reordered[0], 100); assert_eq!(reordered[1], 50); assert_eq!(reordered[2], 30); }
#[test]
fn test_quantization_tables_quality_1() {
let tables = QuantizationTables::with_quality(1);
assert!(tables.luminance[0] > 100); }
#[test]
fn test_quantization_tables_quality_100() {
let tables = QuantizationTables::with_quality(100);
assert!(tables.luminance[0] < 20); }
#[test]
fn test_quantization_tables_quality_clamping() {
let tables_q0 = QuantizationTables::with_quality(0);
let tables_q1 = QuantizationTables::with_quality(1);
assert_eq!(tables_q0.luminance, tables_q1.luminance);
let tables_q101 = QuantizationTables::with_quality(101);
let tables_q100 = QuantizationTables::with_quality(100);
assert_eq!(tables_q101.luminance, tables_q100.luminance);
}
#[test]
fn test_quantization_tables_default() {
let tables = QuantizationTables::default();
let tables_q75 = QuantizationTables::with_quality(75);
assert_eq!(tables.luminance, tables_q75.luminance);
}
#[test]
fn test_quantization_tables_all_formats() {
let tables = QuantizationTables::with_quality(85);
assert_eq!(tables.luminance.len(), 64);
assert_eq!(tables.chrominance.len(), 64);
assert_eq!(tables.luminance_table.len(), 64);
assert_eq!(tables.chrominance_table.len(), 64);
assert_eq!(tables.luminance_table_int.len(), 64);
assert_eq!(tables.chrominance_table_int.len(), 64);
}
#[test]
fn test_quantization_values_range() {
for q in [1, 25, 50, 75, 100] {
let tables = QuantizationTables::with_quality(q);
for &val in &tables.luminance {
assert!(val >= 1, "Quality {q}: value {val} is zero");
}
for &val in &tables.chrominance {
assert!(val >= 1, "Quality {q}: chrom value {val} is zero");
}
}
}
#[test]
fn test_quantize_block_rounding() {
let mut dct = [0.0f32; 64];
dct[0] = 16.5; dct[1] = 16.4; dct[2] = 16.6;
let mut quant = [16.0f32; 64];
quant[0] = 16.0;
let result = quantize_block(&dct, &quant);
assert_eq!(result[0], 1); assert_eq!(result[1], 1); assert_eq!(result[2], 1); }
#[test]
fn test_quantize_block_negative() {
let mut dct = [0.0f32; 64];
dct[0] = -160.0;
let quant = [16.0f32; 64];
let result = quantize_block(&dct, &quant);
assert_eq!(result[0], -10); }
#[test]
fn test_quantize_block_zeros() {
let dct = [0.0f32; 64];
let quant = [16.0f32; 64];
let result = quantize_block(&dct, &quant);
for &val in &result {
assert_eq!(val, 0);
}
}
#[test]
fn test_zigzag_complete() {
let mut seen = [false; 64];
for &pos in &ZIGZAG {
assert!(!seen[pos], "Duplicate position {pos} in zigzag");
seen[pos] = true;
}
for (i, &s) in seen.iter().enumerate() {
assert!(s, "Position {i} missing from zigzag");
}
}
#[test]
fn test_zigzag_reorder_all_values() {
let mut block = [0i16; 64];
for i in 0..64 {
block[i] = i as i16;
}
let reordered = zigzag_reorder(&block);
let mut seen = [false; 64];
for &val in &reordered {
seen[val as usize] = true;
}
for (i, &s) in seen.iter().enumerate() {
assert!(s, "Value {i} missing after zigzag reorder");
}
}
#[test]
fn test_quality_50_is_identity() {
let tables = QuantizationTables::with_quality(50);
assert_eq!(tables.luminance[0], 16);
}
#[test]
fn test_float_and_int_tables_consistent() {
let tables = QuantizationTables::with_quality(85);
for i in 0..64 {
assert_eq!(
tables.luminance_table[i] as u16, tables.luminance_table_int[i],
"Mismatch at position {i}"
);
assert_eq!(
tables.chrominance_table[i] as u16, tables.chrominance_table_int[i],
"Chroma mismatch at position {i}"
);
}
}
}