#[cfg(feature = "experimental-hybrid-trellis")]
use mozjpeg_rs::{
consts::{AC_CHROMINANCE_BITS, AC_CHROMINANCE_VALUES, AC_LUMINANCE_BITS, AC_LUMINANCE_VALUES},
huffman::{DerivedTable, HuffTable},
trellis::trellis_quantize_block,
TrellisConfig,
};
use crate::foundation::consts::DCT_BLOCK_SIZE;
#[cfg(feature = "experimental-hybrid-trellis")]
pub struct StandardHuffmanTables {
pub luma_ac: DerivedTable,
pub chroma_ac: DerivedTable,
}
#[cfg(feature = "experimental-hybrid-trellis")]
impl StandardHuffmanTables {
pub fn new() -> Self {
Self {
luma_ac: Self::build_luma_ac(),
chroma_ac: Self::build_chroma_ac(),
}
}
fn build_luma_ac() -> DerivedTable {
let mut htbl = HuffTable::default();
htbl.bits.copy_from_slice(&AC_LUMINANCE_BITS);
for (i, &v) in AC_LUMINANCE_VALUES.iter().enumerate() {
htbl.huffval[i] = v;
}
DerivedTable::from_huff_table(&htbl, false)
.expect("Standard AC luminance table should be valid")
}
fn build_chroma_ac() -> DerivedTable {
let mut htbl = HuffTable::default();
htbl.bits.copy_from_slice(&AC_CHROMINANCE_BITS);
for (i, &v) in AC_CHROMINANCE_VALUES.iter().enumerate() {
htbl.huffval[i] = v;
}
DerivedTable::from_huff_table(&htbl, false)
.expect("Standard AC chrominance table should be valid")
}
}
#[cfg(feature = "experimental-hybrid-trellis")]
impl Default for StandardHuffmanTables {
fn default() -> Self {
Self::new()
}
}
pub fn scale_quant_by_aq(
base_quant: &[u16; DCT_BLOCK_SIZE],
aq_strength: f32,
) -> [u16; DCT_BLOCK_SIZE] {
let mut scaled = [0u16; DCT_BLOCK_SIZE];
let multiplier = 1.0 + aq_strength;
use wide::f32x8;
let mul = f32x8::splat(multiplier);
let one = f32x8::splat(1.0);
let max_val = f32x8::splat(255.0);
for chunk in 0..8 {
let k = chunk * 8;
let base_f = f32x8::from([
base_quant[k] as f32,
base_quant[k + 1] as f32,
base_quant[k + 2] as f32,
base_quant[k + 3] as f32,
base_quant[k + 4] as f32,
base_quant[k + 5] as f32,
base_quant[k + 6] as f32,
base_quant[k + 7] as f32,
]);
let val = (base_f * mul).round().max(one).min(max_val);
let arr: [f32; 8] = val.into();
for j in 0..8 {
scaled[k + j] = arr[j] as u16;
}
}
scaled
}
pub fn dct_f32_to_i32(coeffs: &[f32; DCT_BLOCK_SIZE]) -> [i32; DCT_BLOCK_SIZE] {
let mut result = [0i32; DCT_BLOCK_SIZE];
use wide::f32x8;
let scale = f32x8::splat(64.0);
for chunk in 0..8 {
let k = chunk * 8;
let v = f32x8::from([
coeffs[k],
coeffs[k + 1],
coeffs[k + 2],
coeffs[k + 3],
coeffs[k + 4],
coeffs[k + 5],
coeffs[k + 6],
coeffs[k + 7],
]);
let scaled = (v * scale).round();
let arr: [f32; 8] = scaled.into();
for j in 0..8 {
result[k + j] = arr[j] as i32;
}
}
result
}
#[cfg(feature = "experimental-hybrid-trellis")]
const AQ_LAMBDA_SCALE: f32 = 2.0;
#[cfg(feature = "experimental-hybrid-trellis")]
pub fn hybrid_quantize_block(
dct_coeffs: &[f32; DCT_BLOCK_SIZE],
base_quant: &[u16; DCT_BLOCK_SIZE],
aq_strength: f32,
ac_table: &DerivedTable,
base_config: &TrellisConfig,
) -> [i16; DCT_BLOCK_SIZE] {
let mut config = *base_config;
config.lambda_log_scale1 += aq_strength * AQ_LAMBDA_SCALE;
let dct_i32 = dct_f32_to_i32(dct_coeffs);
let mut quantized = [0i16; DCT_BLOCK_SIZE];
trellis_quantize_block(&dct_i32, &mut quantized, base_quant, ac_table, &config);
quantized
}
pub fn hybrid_quantize_block_simple(
dct_coeffs: &[f32; DCT_BLOCK_SIZE],
base_quant: &[u16; DCT_BLOCK_SIZE],
aq_strength: f32,
) -> [i16; DCT_BLOCK_SIZE] {
let scaled_quant = scale_quant_by_aq(base_quant, aq_strength);
let mut quantized = [0i16; DCT_BLOCK_SIZE];
use wide::f32x8;
for chunk in 0..8 {
let k = chunk * 8;
let dct = f32x8::from([
dct_coeffs[k],
dct_coeffs[k + 1],
dct_coeffs[k + 2],
dct_coeffs[k + 3],
dct_coeffs[k + 4],
dct_coeffs[k + 5],
dct_coeffs[k + 6],
dct_coeffs[k + 7],
]);
let q = f32x8::from([
scaled_quant[k] as f32,
scaled_quant[k + 1] as f32,
scaled_quant[k + 2] as f32,
scaled_quant[k + 3] as f32,
scaled_quant[k + 4] as f32,
scaled_quant[k + 5] as f32,
scaled_quant[k + 6] as f32,
scaled_quant[k + 7] as f32,
]);
let eight = f32x8::splat(8.0);
let val = (dct * eight / q).round();
let arr: [f32; 8] = val.into();
for j in 0..8 {
quantized[k + j] = arr[j] as i16;
}
}
quantized
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scale_quant_by_aq() {
let base = [16u16; 64];
let scaled = scale_quant_by_aq(&base, 0.0);
assert_eq!(scaled[0], 16);
let scaled = scale_quant_by_aq(&base, 0.5);
assert_eq!(scaled[0], 24);
let scaled = scale_quant_by_aq(&base, 1.0);
assert_eq!(scaled[0], 32);
}
#[test]
fn test_scale_quant_clamping() {
let base = [200u16; 64];
let scaled = scale_quant_by_aq(&base, 0.5);
assert_eq!(scaled[0], 255);
let base_low = [1u16; 64];
let scaled = scale_quant_by_aq(&base_low, 0.0);
assert_eq!(scaled[0], 1);
}
#[test]
fn test_dct_f32_to_i32() {
let f32_coeffs = [127.4f32; 64];
let i32_coeffs = dct_f32_to_i32(&f32_coeffs);
assert_eq!(i32_coeffs[0], 1019);
let f32_coeffs = [-127.6f32; 64];
let i32_coeffs = dct_f32_to_i32(&f32_coeffs);
assert_eq!(i32_coeffs[0], -1021);
}
#[test]
fn test_hybrid_quantize_simple() {
let mut dct = [0.0f32; 64];
dct[0] = 1024.0;
let base_quant = [16u16; 64];
let quantized = hybrid_quantize_block_simple(&dct, &base_quant, 0.0);
assert_eq!(quantized[0], 64);
let quantized = hybrid_quantize_block_simple(&dct, &base_quant, 0.5);
assert_eq!(quantized[0], 43); }
}