#[cfg(feature = "experimental-hybrid-trellis")]
use mozjpeg_rs::TrellisConfig;
pub const AQ_MEAN_THRESHOLD: f32 = 0.25;
pub fn should_use_hybrid(aq_mean: f32) -> bool {
aq_mean > AQ_MEAN_THRESHOLD
}
pub fn estimate_hybrid_improvement(aq_mean: f32) -> f32 {
(85.0 * aq_mean - 5.0).max(0.0)
}
#[derive(Debug, Clone, Copy)]
pub struct HybridConfig {
pub enabled: bool,
pub aq_lambda_scale: f32,
pub base_lambda_scale1: f32,
pub base_lambda_scale2: f32,
pub dc_enabled: bool,
pub num_loops: i32,
pub use_lambda_weight_tbl: bool,
pub aq_exponent: f32,
pub aq_threshold: f32,
pub quality_adaptive: bool,
pub chroma_scale: f32,
}
impl Default for HybridConfig {
fn default() -> Self {
Self {
enabled: true,
aq_lambda_scale: 0.0,
base_lambda_scale1: 14.75,
base_lambda_scale2: 16.5,
dc_enabled: false,
num_loops: 1,
use_lambda_weight_tbl: true,
aq_exponent: 1.0,
aq_threshold: 0.0,
quality_adaptive: false,
chroma_scale: 1.0,
}
}
}
impl HybridConfig {
pub fn new() -> Self {
Self::default()
}
pub fn disabled() -> Self {
Self {
enabled: false,
..Self::default()
}
}
pub fn favor_size() -> Self {
Self {
enabled: true,
aq_lambda_scale: 0.0, base_lambda_scale1: 14.0, dc_enabled: false,
..Self::default()
}
}
pub fn favor_quality() -> Self {
Self {
enabled: true,
aq_lambda_scale: 4.0, base_lambda_scale1: 15.5, dc_enabled: false,
..Self::default()
}
}
pub fn balanced() -> Self {
Self {
enabled: true,
aq_lambda_scale: 2.0,
base_lambda_scale1: 14.75,
dc_enabled: false,
..Self::default()
}
}
pub fn aq_lambda_scale(mut self, scale: f32) -> Self {
self.aq_lambda_scale = scale;
self
}
pub fn base_scale1(mut self, scale: f32) -> Self {
self.base_lambda_scale1 = scale;
self
}
pub fn base_scale2(mut self, scale: f32) -> Self {
self.base_lambda_scale2 = scale;
self
}
pub fn dc_trellis(mut self, enabled: bool) -> Self {
self.dc_enabled = enabled;
self
}
pub fn num_loops(mut self, loops: i32) -> Self {
self.num_loops = loops;
self
}
pub fn aq_exponent(mut self, exp: f32) -> Self {
self.aq_exponent = exp;
self
}
pub fn aq_threshold(mut self, threshold: f32) -> Self {
self.aq_threshold = threshold;
self
}
pub fn quality_adaptive(mut self, enabled: bool) -> Self {
self.quality_adaptive = enabled;
self
}
pub fn chroma_scale(mut self, scale: f32) -> Self {
self.chroma_scale = scale;
self
}
pub fn compute_lambda_adjustment(&self, aq_strength: f32, dampen: f32, is_chroma: bool) -> f32 {
if !self.enabled || aq_strength < self.aq_threshold {
return 0.0;
}
let effective_aq = if self.aq_exponent != 1.0 {
aq_strength.powf(self.aq_exponent)
} else {
aq_strength
};
let mut adjustment = effective_aq * self.aq_lambda_scale;
if self.quality_adaptive {
adjustment *= dampen;
}
if is_chroma {
adjustment *= self.chroma_scale;
}
adjustment
}
#[cfg(feature = "experimental-hybrid-trellis")]
pub fn to_trellis_config(
&self,
aq_strength: f32,
dampen: f32,
is_chroma: bool,
) -> TrellisConfig {
let adjustment = self.compute_lambda_adjustment(aq_strength, dampen, is_chroma);
TrellisConfig {
enabled: true,
dc_enabled: self.dc_enabled,
eob_opt: true,
use_lambda_weight_tbl: self.use_lambda_weight_tbl,
use_scans_in_trellis: false,
q_opt: false,
lambda_log_scale1: self.base_lambda_scale1 + adjustment,
lambda_log_scale2: self.base_lambda_scale2,
freq_split: 8,
num_loops: self.num_loops,
delta_dc_weight: 0.0,
speed_level: 7, }
}
pub fn id(&self) -> String {
format!(
"aq{:.1}_s1_{:.1}_dc{}_exp{:.1}",
self.aq_lambda_scale,
self.base_lambda_scale1,
if self.dc_enabled { 1 } else { 0 },
self.aq_exponent
)
}
}
#[derive(Debug, Clone)]
pub struct SweepConfig {
pub aq_lambda_scales: Vec<f32>,
pub base_scale1_values: Vec<f32>,
pub dc_enabled_values: Vec<bool>,
pub aq_exponents: Vec<f32>,
pub quality_levels: Vec<u8>,
}
impl Default for SweepConfig {
fn default() -> Self {
Self {
aq_lambda_scales: vec![0.0, 1.0, 2.0, 3.0, 4.0],
base_scale1_values: vec![14.0, 14.75, 15.5],
dc_enabled_values: vec![false, true],
aq_exponents: vec![1.0],
quality_levels: vec![75],
}
}
}
impl SweepConfig {
pub fn quick() -> Self {
Self {
aq_lambda_scales: vec![0.0, 2.0, 4.0],
base_scale1_values: vec![14.75],
dc_enabled_values: vec![false],
aq_exponents: vec![1.0],
quality_levels: vec![75],
}
}
pub fn comprehensive() -> Self {
Self {
aq_lambda_scales: vec![0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 6.0],
base_scale1_values: vec![13.5, 14.0, 14.5, 14.75, 15.0, 15.5, 16.0],
dc_enabled_values: vec![false, true],
aq_exponents: vec![0.5, 1.0, 2.0],
quality_levels: vec![50, 75, 90],
}
}
pub fn generate_configs(&self) -> Vec<HybridConfig> {
let mut configs = Vec::new();
for &aq_scale in &self.aq_lambda_scales {
for &base_s1 in &self.base_scale1_values {
for &dc_en in &self.dc_enabled_values {
for &aq_exp in &self.aq_exponents {
configs.push(HybridConfig {
enabled: true,
aq_lambda_scale: aq_scale,
base_lambda_scale1: base_s1,
dc_enabled: dc_en,
aq_exponent: aq_exp,
..HybridConfig::default()
});
}
}
}
}
configs
}
pub fn total_combinations(&self) -> usize {
self.aq_lambda_scales.len()
* self.base_scale1_values.len()
* self.dc_enabled_values.len()
* self.aq_exponents.len()
* self.quality_levels.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = HybridConfig::default();
assert!(config.enabled);
assert_eq!(config.aq_lambda_scale, 0.0);
assert_eq!(config.base_lambda_scale1, 14.75);
}
#[test]
fn test_presets() {
let favor_size = HybridConfig::favor_size();
assert_eq!(favor_size.base_lambda_scale1, 14.0);
let favor_quality = HybridConfig::favor_quality();
assert_eq!(favor_quality.aq_lambda_scale, 4.0);
assert_eq!(favor_quality.base_lambda_scale1, 15.5);
let balanced = HybridConfig::balanced();
assert_eq!(balanced.aq_lambda_scale, 2.0);
}
#[test]
fn test_lambda_adjustment() {
let config = HybridConfig::default();
assert_eq!(config.compute_lambda_adjustment(0.5, 1.0, false), 0.0);
assert_eq!(config.compute_lambda_adjustment(1.0, 1.0, false), 0.0);
let balanced = HybridConfig::balanced();
assert_eq!(balanced.compute_lambda_adjustment(0.0, 1.0, false), 0.0);
assert_eq!(balanced.compute_lambda_adjustment(0.5, 1.0, false), 1.0);
assert_eq!(balanced.compute_lambda_adjustment(1.0, 1.0, false), 2.0);
}
#[test]
fn test_quality_adaptive() {
let config = HybridConfig::balanced().quality_adaptive(true);
let adj_full = config.compute_lambda_adjustment(0.5, 1.0, false);
let adj_half = config.compute_lambda_adjustment(0.5, 0.5, false);
assert_eq!(adj_half, adj_full * 0.5);
}
#[test]
fn test_aq_exponent() {
let config = HybridConfig::balanced().aq_exponent(2.0);
let adj = config.compute_lambda_adjustment(0.5, 1.0, false);
assert_eq!(adj, 0.25 * 2.0); }
#[test]
fn test_sweep_config() {
let sweep = SweepConfig::quick();
let configs = sweep.generate_configs();
assert_eq!(configs.len(), 3); }
}