use super::encoder_config::EncoderConfig;
use super::encoder_types::{
ColorMode, DownsamplingMethod, HuffmanStrategy, OptimizationPreset, ProgressiveScanMode,
Quality, QuantTableConfig,
};
use super::trellis::HybridConfig;
use super::trellis::{TrellisConfig, TrellisSpeedMode};
use super::tuning::{EncodingTables, PerComponent};
#[derive(Clone, Debug)]
pub struct ExpertConfig {
pub tables: EncodingTables,
pub zero_bias_hq: PerComponent<[f32; 64]>,
pub zero_bias_lq: PerComponent<[f32; 64]>,
pub zero_bias_hq_distance: f32,
pub zero_bias_lq_distance: f32,
pub trellis_enabled: bool,
pub trellis_dc_enabled: bool,
pub trellis_lambda_log_scale1: f32,
pub trellis_lambda_log_scale2: f32,
pub trellis_speed_mode: TrellisSpeedMode,
pub trellis_delta_dc_weight: f32,
pub aq_trellis_coupling: f32,
pub aq_trellis_exponent: f32,
pub aq_trellis_threshold: f32,
pub aq_trellis_chroma_scale: f32,
pub aq_trellis_quality_adaptive: bool,
pub aq_trellis_multiplicative: bool,
pub aq_trellis_max_adjustment: f32,
pub scan_mode: ProgressiveScanMode,
pub deringing: bool,
pub allow_16bit_quant_tables: bool,
pub quality: Quality,
pub downsampling_method: DownsamplingMethod,
pub aq_enabled: bool,
}
impl ExpertConfig {
#[must_use]
pub fn default_ycbcr(quality: impl Into<Quality>) -> Self {
let quality = quality.into();
let tables = EncodingTables::default_ycbcr();
let zero_bias_hq = EncodingTables::ycbcr_hq_zero_bias_mul();
let zero_bias_lq = EncodingTables::ycbcr_lq_zero_bias_mul();
let mut config = Self {
tables,
zero_bias_hq,
zero_bias_lq,
zero_bias_hq_distance: 1.0,
zero_bias_lq_distance: 3.0,
trellis_enabled: false,
trellis_dc_enabled: true,
trellis_lambda_log_scale1: 14.75,
trellis_lambda_log_scale2: 16.5,
trellis_speed_mode: TrellisSpeedMode::Adaptive,
trellis_delta_dc_weight: 0.0,
aq_trellis_coupling: 0.0,
aq_trellis_exponent: 1.0,
aq_trellis_threshold: 0.0,
aq_trellis_chroma_scale: 1.0,
aq_trellis_quality_adaptive: false,
aq_trellis_multiplicative: false,
aq_trellis_max_adjustment: 0.0,
scan_mode: ProgressiveScanMode::Progressive,
deringing: true,
allow_16bit_quant_tables: false,
quality,
downsampling_method: DownsamplingMethod::default(),
aq_enabled: true,
};
config.blend_zero_bias();
config
}
#[must_use]
pub fn from_preset(preset: OptimizationPreset, quality: impl Into<Quality>) -> Self {
use OptimizationPreset::*;
let quality = quality.into();
let (tables, zero_bias_hq, zero_bias_lq) = match preset {
MozjpegBaseline | MozjpegProgressive | MozjpegMaxCompression => {
let mozjpeg_tables = super::tables::robidoux::generate_mozjpeg_default_tables(
quality.for_mozjpeg_tables(),
false,
);
let neutral = PerComponent::new([0.0f32; 64], [0.0f32; 64], [0.0f32; 64]);
(*mozjpeg_tables, neutral.clone(), neutral)
}
_ => {
let tables = EncodingTables::default_ycbcr();
let hq = EncodingTables::ycbcr_hq_zero_bias_mul();
let lq = EncodingTables::ycbcr_lq_zero_bias_mul();
(tables, hq, lq)
}
};
let (trellis_enabled, trellis_speed_mode) = match preset {
JpegliBaseline | JpegliProgressive => (false, TrellisSpeedMode::Adaptive),
MozjpegBaseline | MozjpegProgressive | MozjpegMaxCompression => {
(true, TrellisSpeedMode::Thorough)
}
HybridBaseline | HybridProgressive => (true, TrellisSpeedMode::Adaptive),
HybridMaxCompression => (true, TrellisSpeedMode::Thorough),
};
let scan_mode = match preset {
JpegliBaseline | MozjpegBaseline | HybridBaseline => ProgressiveScanMode::Baseline,
JpegliProgressive | HybridProgressive => ProgressiveScanMode::Progressive,
MozjpegProgressive => ProgressiveScanMode::ProgressiveMozjpeg,
MozjpegMaxCompression | HybridMaxCompression => ProgressiveScanMode::ProgressiveSearch,
};
let deringing = !matches!(preset, MozjpegBaseline | MozjpegProgressive);
let mut config = Self {
tables,
zero_bias_hq,
zero_bias_lq,
zero_bias_hq_distance: 1.0,
zero_bias_lq_distance: 3.0,
trellis_enabled,
trellis_dc_enabled: true,
trellis_lambda_log_scale1: 14.75,
trellis_lambda_log_scale2: 16.5,
trellis_speed_mode,
trellis_delta_dc_weight: 0.0,
aq_trellis_coupling: 0.0,
aq_trellis_exponent: 1.0,
aq_trellis_threshold: 0.0,
aq_trellis_chroma_scale: 1.0,
aq_trellis_quality_adaptive: false,
aq_trellis_multiplicative: false,
aq_trellis_max_adjustment: 0.0,
scan_mode,
deringing,
allow_16bit_quant_tables: false,
quality,
downsampling_method: DownsamplingMethod::default(),
aq_enabled: preset.uses_aq(),
};
config.blend_zero_bias();
config
}
pub fn blend_zero_bias(&mut self) {
let distance = self.quality.to_distance();
let t = if distance <= self.zero_bias_hq_distance {
1.0
} else if distance >= self.zero_bias_lq_distance {
0.0
} else {
let range = self.zero_bias_lq_distance - self.zero_bias_hq_distance;
if range <= 0.0 {
0.0
} else {
1.0 - (distance - self.zero_bias_hq_distance) / range
}
};
self.tables.zero_bias_mul = self.zero_bias_lq.blend(&self.zero_bias_hq, t);
}
#[must_use]
pub fn uses_quality_scaling(&self) -> bool {
!self.tables.is_exact()
}
#[must_use]
pub fn to_encoder_config(&self, color_mode: ColorMode) -> EncoderConfig {
let mut config = match color_mode {
ColorMode::YCbCr { subsampling } => EncoderConfig::ycbcr(self.quality, subsampling),
ColorMode::Xyb { subsampling } => EncoderConfig::xyb(self.quality, subsampling),
ColorMode::Grayscale => EncoderConfig::grayscale(self.quality),
};
config.quant_table_config = QuantTableConfig::Custom(Box::new(self.tables.clone()));
config.scan_mode = self.scan_mode;
if self.scan_mode.is_progressive() {
config.huffman = HuffmanStrategy::Optimize;
}
config.deringing = self.deringing;
config.aq_enabled = self.aq_enabled;
config.allow_16bit_quant_tables = self.allow_16bit_quant_tables;
config.downsampling_method = self.downsampling_method;
let (trellis, hybrid) = self.build_trellis_or_hybrid();
config.trellis = trellis;
config.hybrid_config = hybrid;
config
}
fn build_trellis_or_hybrid(&self) -> (Option<TrellisConfig>, HybridConfig) {
if !self.trellis_enabled {
return (None, HybridConfig::disabled());
}
if self.aq_trellis_coupling != 0.0 {
let hybrid = HybridConfig {
enabled: true,
aq_lambda_scale: self.aq_trellis_coupling,
base_lambda_scale1: self.trellis_lambda_log_scale1,
base_lambda_scale2: self.trellis_lambda_log_scale2,
dc_enabled: self.trellis_dc_enabled,
aq_exponent: self.aq_trellis_exponent,
aq_threshold: self.aq_trellis_threshold,
quality_adaptive: self.aq_trellis_quality_adaptive,
chroma_scale: self.aq_trellis_chroma_scale,
multiplicative: self.aq_trellis_multiplicative,
max_adjustment: self.aq_trellis_max_adjustment,
};
(None, hybrid)
} else {
let trellis = TrellisConfig {
enabled: true,
dc_enabled: self.trellis_dc_enabled,
lambda_log_scale1: self.trellis_lambda_log_scale1,
lambda_log_scale2: self.trellis_lambda_log_scale2,
speed_mode: self.trellis_speed_mode,
delta_dc_weight: self.trellis_delta_dc_weight,
};
(Some(trellis), HybridConfig::disabled())
}
}
}
#[cfg(test)]
mod tests {
use super::super::encoder_types::ChromaSubsampling;
use super::*;
#[test]
fn test_default_ycbcr_fields() {
let config = ExpertConfig::default_ycbcr(90.0);
assert!(!config.trellis_enabled);
assert!(config.deringing);
assert_eq!(config.scan_mode, ProgressiveScanMode::Progressive);
assert!(!config.allow_16bit_quant_tables);
assert!(config.uses_quality_scaling());
}
#[test]
fn test_from_preset_jpegli_baseline() {
let config = ExpertConfig::from_preset(OptimizationPreset::JpegliBaseline, 85.0);
assert!(!config.trellis_enabled);
assert!(config.deringing);
assert_eq!(config.scan_mode, ProgressiveScanMode::Baseline);
assert!(config.uses_quality_scaling());
}
#[test]
fn test_from_preset_jpegli_progressive() {
let config = ExpertConfig::from_preset(OptimizationPreset::JpegliProgressive, 85.0);
assert!(!config.trellis_enabled);
assert!(config.deringing);
assert_eq!(config.scan_mode, ProgressiveScanMode::Progressive);
}
#[test]
fn test_from_preset_mozjpeg_baseline() {
let config = ExpertConfig::from_preset(OptimizationPreset::MozjpegBaseline, 85.0);
assert!(config.trellis_enabled);
assert!(!config.deringing);
assert_eq!(config.scan_mode, ProgressiveScanMode::Baseline);
assert_eq!(config.trellis_speed_mode, TrellisSpeedMode::Thorough);
assert!(!config.uses_quality_scaling());
}
#[test]
fn test_from_preset_mozjpeg_progressive() {
let config = ExpertConfig::from_preset(OptimizationPreset::MozjpegProgressive, 85.0);
assert!(config.trellis_enabled);
assert!(!config.deringing);
assert_eq!(config.scan_mode, ProgressiveScanMode::ProgressiveMozjpeg);
}
#[test]
fn test_from_preset_mozjpeg_max_compression() {
let config = ExpertConfig::from_preset(OptimizationPreset::MozjpegMaxCompression, 85.0);
assert!(config.trellis_enabled);
assert!(config.deringing);
assert_eq!(config.scan_mode, ProgressiveScanMode::ProgressiveSearch);
assert_eq!(config.trellis_speed_mode, TrellisSpeedMode::Thorough);
}
#[test]
fn test_from_preset_hybrid_baseline() {
let config = ExpertConfig::from_preset(OptimizationPreset::HybridBaseline, 85.0);
assert!(config.trellis_enabled);
assert!(config.deringing);
assert_eq!(config.scan_mode, ProgressiveScanMode::Baseline);
assert_eq!(config.trellis_speed_mode, TrellisSpeedMode::Adaptive);
assert_eq!(config.aq_trellis_coupling, 0.0);
}
#[test]
fn test_from_preset_hybrid_progressive() {
let config = ExpertConfig::from_preset(OptimizationPreset::HybridProgressive, 85.0);
assert!(config.trellis_enabled);
assert!(config.deringing);
assert_eq!(config.scan_mode, ProgressiveScanMode::Progressive);
assert_eq!(config.trellis_speed_mode, TrellisSpeedMode::Adaptive);
}
#[test]
fn test_from_preset_hybrid_max_compression() {
let config = ExpertConfig::from_preset(OptimizationPreset::HybridMaxCompression, 85.0);
assert!(config.trellis_enabled);
assert!(config.deringing);
assert_eq!(config.scan_mode, ProgressiveScanMode::ProgressiveSearch);
assert_eq!(config.trellis_speed_mode, TrellisSpeedMode::Thorough);
}
#[test]
fn test_to_encoder_config_no_trellis() {
let expert = ExpertConfig::default_ycbcr(90.0);
let enc = expert.to_encoder_config(ColorMode::YCbCr {
subsampling: ChromaSubsampling::Quarter,
});
assert!(enc.trellis.is_none());
assert!(!enc.hybrid_config.enabled);
assert!(enc.deringing);
assert_eq!(enc.scan_mode, ProgressiveScanMode::Progressive);
}
#[test]
fn test_to_encoder_config_standalone_trellis() {
let mut expert = ExpertConfig::from_preset(OptimizationPreset::MozjpegBaseline, 85.0);
expert.aq_trellis_coupling = 0.0;
let enc = expert.to_encoder_config(ColorMode::YCbCr {
subsampling: ChromaSubsampling::Quarter,
});
assert!(enc.trellis.is_some());
assert!(!enc.hybrid_config.enabled);
let trellis = enc.trellis.unwrap();
assert!(trellis.enabled);
assert!(trellis.dc_enabled);
assert_eq!(trellis.speed_mode, TrellisSpeedMode::Thorough);
}
#[test]
fn test_to_encoder_config_hybrid_mode() {
let mut expert = ExpertConfig::from_preset(OptimizationPreset::HybridProgressive, 85.0);
expert.aq_trellis_coupling = 2.0;
expert.aq_trellis_exponent = 0.5;
expert.aq_trellis_chroma_scale = 0.8;
let enc = expert.to_encoder_config(ColorMode::YCbCr {
subsampling: ChromaSubsampling::Quarter,
});
assert!(enc.trellis.is_none());
assert!(enc.hybrid_config.enabled);
assert_eq!(enc.hybrid_config.aq_lambda_scale, 2.0);
assert_eq!(enc.hybrid_config.aq_exponent, 0.5);
assert_eq!(enc.hybrid_config.chroma_scale, 0.8);
}
#[test]
fn test_blend_zero_bias_high_quality() {
let config = ExpertConfig::default_ycbcr(Quality::ApproxButteraugli(0.5));
let hq = EncodingTables::ycbcr_hq_zero_bias_mul();
assert!(
(config.tables.zero_bias_mul.c0[5] - hq.c0[5]).abs() < 1e-6,
"At high quality, zero-bias should match HQ tables"
);
}
#[test]
fn test_blend_zero_bias_low_quality() {
let config = ExpertConfig::default_ycbcr(Quality::ApproxButteraugli(5.0));
let lq = EncodingTables::ycbcr_lq_zero_bias_mul();
assert!(
(config.tables.zero_bias_mul.c0[5] - lq.c0[5]).abs() < 1e-6,
"At low quality, zero-bias should match LQ tables"
);
}
#[test]
fn test_blend_zero_bias_mid_quality() {
let config = ExpertConfig::default_ycbcr(Quality::ApproxButteraugli(2.0));
let hq = EncodingTables::ycbcr_hq_zero_bias_mul();
let lq = EncodingTables::ycbcr_lq_zero_bias_mul();
let expected = (hq.c0[5] + lq.c0[5]) / 2.0;
assert!(
(config.tables.zero_bias_mul.c0[5] - expected).abs() < 1e-5,
"At mid quality, zero-bias should be midpoint of HQ/LQ: got {} expected {}",
config.tables.zero_bias_mul.c0[5],
expected
);
}
#[test]
fn test_all_presets_round_trip() {
for preset in OptimizationPreset::all() {
let expert = ExpertConfig::from_preset(preset, 85.0);
let _enc = expert.to_encoder_config(ColorMode::YCbCr {
subsampling: ChromaSubsampling::Quarter,
});
}
}
#[test]
fn test_trellis_fields_pass_through_standalone() {
let mut expert = ExpertConfig::default_ycbcr(85.0);
expert.trellis_enabled = true;
expert.trellis_dc_enabled = false;
expert.trellis_lambda_log_scale1 = 15.0;
expert.trellis_lambda_log_scale2 = 17.0;
expert.trellis_speed_mode = TrellisSpeedMode::Level(5);
expert.trellis_delta_dc_weight = 0.5;
let enc = expert.to_encoder_config(ColorMode::YCbCr {
subsampling: ChromaSubsampling::None,
});
let trellis = enc.trellis.unwrap();
assert!(trellis.enabled);
assert!(!trellis.dc_enabled);
assert!((trellis.lambda_log_scale1 - 15.0).abs() < 1e-6);
assert!((trellis.lambda_log_scale2 - 17.0).abs() < 1e-6);
assert_eq!(trellis.speed_mode, TrellisSpeedMode::Level(5));
assert!((trellis.delta_dc_weight - 0.5).abs() < 1e-6);
}
#[test]
fn test_hybrid_fields_pass_through() {
let mut expert = ExpertConfig::default_ycbcr(85.0);
expert.trellis_enabled = true;
expert.aq_trellis_coupling = 3.5;
expert.aq_trellis_exponent = 2.0;
expert.aq_trellis_threshold = 0.1;
expert.aq_trellis_chroma_scale = 0.7;
expert.aq_trellis_quality_adaptive = true;
expert.trellis_lambda_log_scale1 = 15.0;
expert.trellis_lambda_log_scale2 = 17.0;
expert.trellis_dc_enabled = false;
let enc = expert.to_encoder_config(ColorMode::YCbCr {
subsampling: ChromaSubsampling::None,
});
assert!(enc.trellis.is_none());
assert!(enc.hybrid_config.enabled);
assert_eq!(enc.hybrid_config.aq_lambda_scale, 3.5);
assert_eq!(enc.hybrid_config.aq_exponent, 2.0);
assert_eq!(enc.hybrid_config.aq_threshold, 0.1);
assert_eq!(enc.hybrid_config.chroma_scale, 0.7);
assert!(enc.hybrid_config.quality_adaptive);
assert!((enc.hybrid_config.base_lambda_scale1 - 15.0).abs() < 1e-6);
assert!((enc.hybrid_config.base_lambda_scale2 - 17.0).abs() < 1e-6);
assert!(!enc.hybrid_config.dc_enabled);
}
#[test]
fn test_custom_tables_preserved() {
let mut expert = ExpertConfig::default_ycbcr(85.0);
expert.tables.quant.c0[0] = 42.0;
expert.tables.quant.c1[63] = 99.0;
let enc = expert.to_encoder_config(ColorMode::YCbCr {
subsampling: ChromaSubsampling::Quarter,
});
let custom = enc.quant_table_config.custom_tables().unwrap();
assert!((custom.quant.c0[0] - 42.0).abs() < 1e-6);
assert!((custom.quant.c1[63] - 99.0).abs() < 1e-6);
}
#[test]
fn test_quality_types_accepted() {
let _ = ExpertConfig::default_ycbcr(85.0f32);
let _ = ExpertConfig::default_ycbcr(85u8);
let _ = ExpertConfig::default_ycbcr(85i32);
let _ = ExpertConfig::default_ycbcr(Quality::ApproxMozjpeg(80));
let _ = ExpertConfig::default_ycbcr(Quality::ApproxSsim2(90.0));
let _ = ExpertConfig::default_ycbcr(Quality::ApproxButteraugli(1.0));
}
#[test]
fn test_scan_mode_progressive_enables_optimize() {
let mut expert = ExpertConfig::default_ycbcr(85.0);
expert.scan_mode = ProgressiveScanMode::ProgressiveSearch;
let enc = expert.to_encoder_config(ColorMode::YCbCr {
subsampling: ChromaSubsampling::Quarter,
});
assert!(matches!(enc.huffman, HuffmanStrategy::Optimize));
}
#[test]
fn test_blend_zero_bias_idempotent() {
let mut config = ExpertConfig::default_ycbcr(85.0);
let first = config.tables.zero_bias_mul.c0[5];
config.blend_zero_bias();
config.blend_zero_bias();
config.blend_zero_bias();
assert!(
(config.tables.zero_bias_mul.c0[5] - first).abs() < 1e-6,
"blend_zero_bias should be idempotent"
);
}
fn make_test_image(width: u32, height: u32) -> Vec<u8> {
let mut pixels = vec![0u8; (width * height * 3) as usize];
let mut state: u64 = 0xDEAD_BEEF;
let next = |s: &mut u64| -> u8 {
*s = s.wrapping_mul(1103515245).wrapping_add(12345) & 0x7FFF_FFFF;
((*s >> 16) & 0xFF) as u8
};
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 3) as usize;
let patch_x = x / 32;
let patch_y = y / 32;
let base_r = ((patch_x * 73 + 50) % 256) as u8;
let base_g = ((patch_y * 97 + 80) % 256) as u8;
let base_b = (((patch_x + patch_y) * 131 + 30) % 256) as u8;
let noise_r = (next(&mut state) % 61) as i16 - 30;
let noise_g = (next(&mut state) % 61) as i16 - 30;
let noise_b = (next(&mut state) % 61) as i16 - 30;
pixels[idx] = (base_r as i16 + noise_r).clamp(0, 255) as u8;
pixels[idx + 1] = (base_g as i16 + noise_g).clamp(0, 255) as u8;
pixels[idx + 2] = (base_b as i16 + noise_b).clamp(0, 255) as u8;
}
}
pixels
}
fn encode_expert(expert: &ExpertConfig, pixels: &[u8], w: u32, h: u32) -> usize {
use super::super::encoder_types::PixelLayout;
let enc_config = expert.to_encoder_config(ColorMode::YCbCr {
subsampling: ChromaSubsampling::Quarter,
});
let mut enc = enc_config
.encode_from_bytes(w, h, PixelLayout::Rgb8Srgb)
.expect("encoder creation failed");
enc.push_packed(pixels, enough::Unstoppable)
.expect("push failed");
let jpeg = enc.finish().expect("finish failed");
jpeg.len()
}
#[test]
fn test_parameter_sensitivity() {
let w = 256u32;
let h = 256u32;
let pixels = make_test_image(w, h);
println!("\n=== Preset baseline sizes (Q85, 256x256, 4:2:0) ===");
for preset in OptimizationPreset::all() {
let expert = ExpertConfig::from_preset(preset, 85.0);
let size = encode_expert(&expert, &pixels, w, h);
println!(" {:?}: {} bytes", preset, size);
}
let base = ExpertConfig::from_preset(OptimizationPreset::MozjpegBaseline, 85.0);
let base_size = encode_expert(&base, &pixels, w, h);
println!("\n=== Base: MozjpegBaseline Q85 = {} bytes ===", base_size);
let mut results: Vec<(&str, i64, String)> = Vec::new();
let mut test_field = |name: &'static str, config: &ExpertConfig, note: &str| {
let size = encode_expert(config, &pixels, w, h);
let delta = size as i64 - base_size as i64;
let pct = (delta as f64 / base_size as f64) * 100.0;
results.push((name, delta, note.to_string()));
println!(" {:<45} {:>7} bytes {:>+7.2}% {}", name, size, pct, note);
};
println!("\n--- Trellis on/off ---");
{
let mut c = base.clone();
c.trellis_enabled = false;
test_field("trellis_enabled=false", &c, "(was true)");
}
println!("\n--- Trellis DC ---");
{
let mut c = base.clone();
c.trellis_dc_enabled = false;
test_field("trellis_dc_enabled=false", &c, "(was true)");
}
println!("\n--- Trellis lambda_log_scale1 (rate penalty) ---");
for val in [12.0, 13.0, 14.0, 14.75, 15.5, 16.0, 17.0] {
let mut c = base.clone();
c.trellis_lambda_log_scale1 = val;
test_field(
Box::leak(format!("trellis_lambda_log_scale1={}", val).into_boxed_str()),
&c,
if (val - 14.75).abs() < 0.01 {
"(default)"
} else {
""
},
);
}
println!("\n--- Trellis lambda_log_scale2 (distortion sensitivity) ---");
for val in [14.0, 15.0, 16.0, 16.5, 17.0, 18.0] {
let mut c = base.clone();
c.trellis_lambda_log_scale2 = val;
test_field(
Box::leak(format!("trellis_lambda_log_scale2={}", val).into_boxed_str()),
&c,
if (val - 16.5).abs() < 0.01 {
"(default)"
} else {
""
},
);
}
println!("\n--- Trellis speed_mode ---");
{
let mut c = base.clone();
c.trellis_speed_mode = TrellisSpeedMode::Adaptive;
test_field("trellis_speed_mode=Adaptive", &c, "(was Thorough)");
}
for level in [1, 3, 5, 8] {
let mut c = base.clone();
c.trellis_speed_mode = TrellisSpeedMode::Level(level);
test_field(
Box::leak(format!("trellis_speed_mode=Level({})", level).into_boxed_str()),
&c,
"",
);
}
println!("\n--- Trellis delta_dc_weight ---");
for val in [0.0, 0.1, 0.5, 1.0, 2.0, 5.0] {
let mut c = base.clone();
c.trellis_delta_dc_weight = val;
test_field(
Box::leak(format!("trellis_delta_dc_weight={}", val).into_boxed_str()),
&c,
if val == 0.0 { "(default)" } else { "" },
);
}
println!("\n--- Deringing ---");
{
let mut c = base.clone();
c.deringing = true;
test_field("deringing=true", &c, "(was false for mozjpeg)");
}
println!("\n--- Scan mode ---");
for mode in [
ProgressiveScanMode::Baseline,
ProgressiveScanMode::Progressive,
ProgressiveScanMode::ProgressiveMozjpeg,
ProgressiveScanMode::ProgressiveSearch,
] {
let mut c = base.clone();
c.scan_mode = mode;
test_field(
Box::leak(format!("scan_mode={:?}", mode).into_boxed_str()),
&c,
if mode == ProgressiveScanMode::Baseline {
"(default for this preset)"
} else {
""
},
);
}
println!("\n--- Quality (with Exact scaling, changes zero-bias only) ---");
for q in [50.0, 70.0, 85.0, 90.0, 95.0] {
let mut c = base.clone();
c.quality = Quality::from(q);
c.blend_zero_bias();
test_field(
Box::leak(format!("quality={} (exact tables)", q).into_boxed_str()),
&c,
if (q - 85.0).abs() < 0.01 {
"(default)"
} else {
""
},
);
}
println!("\n--- Quality (with Scaled tables, jpegli preset) ---");
let jpegli_base = ExpertConfig::from_preset(OptimizationPreset::JpegliBaseline, 85.0);
let jpegli_base_size = encode_expert(&jpegli_base, &pixels, w, h);
println!(" JpegliBaseline Q85 base = {} bytes", jpegli_base_size);
for q in [50.0, 70.0, 85.0, 90.0, 95.0] {
let c = ExpertConfig::from_preset(OptimizationPreset::JpegliBaseline, q);
let size = encode_expert(&c, &pixels, w, h);
let delta = size as i64 - jpegli_base_size as i64;
let pct = (delta as f64 / jpegli_base_size as f64) * 100.0;
println!(
" {:<45} {:>7} bytes {:>+7.2}%",
format!("quality={} (scaled tables)", q),
size,
pct
);
}
println!("\n--- Zero-bias blend range ---");
{
let mut c = ExpertConfig::from_preset(OptimizationPreset::JpegliBaseline, 85.0);
c.zero_bias_hq_distance = 0.5;
c.zero_bias_lq_distance = 5.0;
c.blend_zero_bias();
let size = encode_expert(&c, &pixels, w, h);
let delta = size as i64 - jpegli_base_size as i64;
let pct = (delta as f64 / jpegli_base_size as f64) * 100.0;
println!(
" {:<45} {:>7} bytes {:>+7.2}% (was 1.0-3.0)",
"zero_bias range 0.5-5.0", size, pct
);
}
{
let mut c = ExpertConfig::from_preset(OptimizationPreset::JpegliBaseline, 85.0);
c.zero_bias_hq_distance = 2.0;
c.zero_bias_lq_distance = 2.0;
c.blend_zero_bias();
let size = encode_expert(&c, &pixels, w, h);
let delta = size as i64 - jpegli_base_size as i64;
let pct = (delta as f64 / jpegli_base_size as f64) * 100.0;
println!(
" {:<45} {:>7} bytes {:>+7.2}% (forced LQ)",
"zero_bias range 2.0-2.0", size, pct
);
}
{
let mut c = ExpertConfig::from_preset(OptimizationPreset::JpegliBaseline, 85.0);
c.tables.zero_bias_mul = PerComponent::new([0.0f32; 64], [0.0f32; 64], [0.0f32; 64]);
let size = encode_expert(&c, &pixels, w, h);
let delta = size as i64 - jpegli_base_size as i64;
let pct = (delta as f64 / jpegli_base_size as f64) * 100.0;
println!(
" {:<45} {:>7} bytes {:>+7.2}%",
"zero_bias_mul=all zeros (disabled)", size, pct
);
}
{
let mut c = ExpertConfig::from_preset(OptimizationPreset::JpegliBaseline, 85.0);
c.tables.zero_bias_mul = PerComponent::new([1.0f32; 64], [1.0f32; 64], [1.0f32; 64]);
let size = encode_expert(&c, &pixels, w, h);
let delta = size as i64 - jpegli_base_size as i64;
let pct = (delta as f64 / jpegli_base_size as f64) * 100.0;
println!(
" {:<45} {:>7} bytes {:>+7.2}%",
"zero_bias_mul=all ones (maximum)", size, pct
);
}
println!("\n--- allow_16bit_quant_tables ---");
{
let mut c = base.clone();
c.allow_16bit_quant_tables = true;
test_field("allow_16bit_quant_tables=true", &c, "(was false)");
}
println!("\n--- Downsampling method ---");
{
let mut c = base.clone();
c.downsampling_method = DownsamplingMethod::GammaAware;
test_field("downsampling_method=GammaAware", &c, "(was Box)");
}
{
let mut c = base.clone();
c.downsampling_method = DownsamplingMethod::GammaAwareIterative;
test_field("downsampling_method=GammaAwareIterative", &c, "(SharpYUV)");
}
println!("\n--- Hybrid mode: aq_trellis_coupling sweep ---");
for coupling in [0.0, 0.5, 1.0, 2.0, 4.0, 8.0] {
let mut c = base.clone();
c.aq_trellis_coupling = coupling;
test_field(
Box::leak(format!("aq_trellis_coupling={}", coupling).into_boxed_str()),
&c,
if coupling == 0.0 {
"(standalone)"
} else {
"(hybrid)"
},
);
}
println!("\n--- Hybrid mode internals (coupling=2.0) ---");
{
let mut c = base.clone();
c.aq_trellis_coupling = 2.0;
let hybrid_base_size = encode_expert(&c, &pixels, w, h);
println!(" Hybrid base (coupling=2.0) = {} bytes", hybrid_base_size);
for exp in [0.5, 1.0, 2.0] {
let mut c2 = c.clone();
c2.aq_trellis_exponent = exp;
let size = encode_expert(&c2, &pixels, w, h);
let delta = size as i64 - hybrid_base_size as i64;
let pct = (delta as f64 / hybrid_base_size as f64) * 100.0;
println!(
" {:<45} {:>7} bytes {:>+7.2}%",
format!("aq_trellis_exponent={}", exp),
size,
pct
);
}
for thresh in [0.0, 0.5, 1.0, 2.0] {
let mut c2 = c.clone();
c2.aq_trellis_threshold = thresh;
let size = encode_expert(&c2, &pixels, w, h);
let delta = size as i64 - hybrid_base_size as i64;
let pct = (delta as f64 / hybrid_base_size as f64) * 100.0;
println!(
" {:<45} {:>7} bytes {:>+7.2}%",
format!("aq_trellis_threshold={}", thresh),
size,
pct
);
}
for cs in [0.0, 0.5, 1.0, 2.0] {
let mut c2 = c.clone();
c2.aq_trellis_chroma_scale = cs;
let size = encode_expert(&c2, &pixels, w, h);
let delta = size as i64 - hybrid_base_size as i64;
let pct = (delta as f64 / hybrid_base_size as f64) * 100.0;
println!(
" {:<45} {:>7} bytes {:>+7.2}%",
format!("aq_trellis_chroma_scale={}", cs),
size,
pct
);
}
{
let mut c2 = c.clone();
c2.aq_trellis_quality_adaptive = true;
let size = encode_expert(&c2, &pixels, w, h);
let delta = size as i64 - hybrid_base_size as i64;
let pct = (delta as f64 / hybrid_base_size as f64) * 100.0;
println!(
" {:<45} {:>7} bytes {:>+7.2}%",
"aq_trellis_quality_adaptive=true", size, pct
);
}
}
println!("\n--- Quant table values (direct manipulation) ---");
{
let mut c = base.clone();
for v in c.tables.quant.c0.iter_mut() {
*v *= 0.5;
}
for v in c.tables.quant.c1.iter_mut() {
*v *= 0.5;
}
for v in c.tables.quant.c2.iter_mut() {
*v *= 0.5;
}
test_field("quant_tables * 0.5 (finer quant)", &c, "");
}
{
let mut c = base.clone();
for v in c.tables.quant.c0.iter_mut() {
*v *= 2.0;
}
for v in c.tables.quant.c1.iter_mut() {
*v *= 2.0;
}
for v in c.tables.quant.c2.iter_mut() {
*v *= 2.0;
}
test_field("quant_tables * 2.0 (coarser quant)", &c, "");
}
println!("\n=== SENSITIVITY SUMMARY ===");
println!("Fields with |delta| == 0 are effectively dead for this config:");
for (name, delta, note) in &results {
if *delta == 0 {
println!(" DEAD: {} {}", name, note);
}
}
println!("\nFields with |delta| > 0:");
let mut active: Vec<_> = results.iter().filter(|(_, d, _)| *d != 0).collect();
active.sort_by_key(|(_, d, _)| d.unsigned_abs());
for (name, delta, note) in active.iter().rev() {
let pct = (*delta as f64 / base_size as f64) * 100.0;
println!(" {:>+7} bytes ({:>+6.2}%): {} {}", delta, pct, name, note);
}
}
#[test]
fn test_expert_aq_enabled_default() {
let config = ExpertConfig::default_ycbcr(90.0);
assert!(config.aq_enabled, "default_ycbcr should enable AQ");
}
#[test]
fn test_expert_aq_enabled_mozjpeg() {
let config = ExpertConfig::from_preset(OptimizationPreset::MozjpegBaseline, 85.0);
assert!(!config.aq_enabled, "Mozjpeg preset should disable AQ");
let config = ExpertConfig::from_preset(OptimizationPreset::MozjpegProgressive, 85.0);
assert!(!config.aq_enabled);
let config = ExpertConfig::from_preset(OptimizationPreset::MozjpegMaxCompression, 85.0);
assert!(!config.aq_enabled);
}
#[test]
fn test_expert_aq_enabled_jpegli() {
let config = ExpertConfig::from_preset(OptimizationPreset::JpegliBaseline, 85.0);
assert!(config.aq_enabled, "Jpegli preset should enable AQ");
let config = ExpertConfig::from_preset(OptimizationPreset::JpegliProgressive, 85.0);
assert!(config.aq_enabled);
}
#[test]
fn test_expert_aq_enabled_hybrid() {
let config = ExpertConfig::from_preset(OptimizationPreset::HybridProgressive, 85.0);
assert!(config.aq_enabled, "Hybrid preset should enable AQ");
}
#[test]
fn test_expert_aq_passthrough() {
let mut config = ExpertConfig::from_preset(OptimizationPreset::MozjpegBaseline, 85.0);
assert!(!config.aq_enabled);
let enc = config.to_encoder_config(ColorMode::YCbCr {
subsampling: ChromaSubsampling::Quarter,
});
assert!(
!enc.is_aq_enabled(),
"aq_enabled should pass through to EncoderConfig"
);
config.aq_enabled = true;
let enc = config.to_encoder_config(ColorMode::YCbCr {
subsampling: ChromaSubsampling::Quarter,
});
assert!(
enc.is_aq_enabled(),
"flipped aq_enabled should pass through"
);
}
}