use crate::api::EncoderMode;
use crate::entropy_coding::lz77::Lz77Method;
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct EntropyMulTable {
pub dct8: f32,
pub dct4x4: f32,
pub dct4x8: f32,
pub identity: f32,
pub dct2x2: f32,
pub afv: f32,
pub dct16x8: f32,
pub dct16x16: f32,
pub dct16x32: f32,
pub dct32x32: f32,
pub dct64x32: f32,
pub dct64x64: f32,
}
impl EntropyMulTable {
pub fn reference() -> Self {
Self {
dct8: 0.8,
dct4x4: 1.08,
dct4x8: 0.859_316_37,
identity: 1.0428,
dct2x2: 0.95,
afv: 0.817_794_9,
dct16x8: 1.21,
dct16x16: 1.34,
dct16x32: 1.49,
dct32x32: 1.48,
dct64x32: 2.25,
dct64x64: 2.25,
}
}
pub fn experimental() -> Self {
Self {
dct4x4: 0.88,
identity: 0.88,
afv: 0.75,
..Self::reference()
}
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct EffortProfile {
pub effort: u8,
pub use_ans: bool,
pub optimize_codes: bool,
pub custom_orders: bool,
pub gaborish: bool,
pub pixel_domain_loss: bool,
pub error_diffusion: bool,
pub patches: bool,
pub tree_learning: bool,
pub lz77: bool,
pub lz77_method: Lz77Method,
pub butteraugli_iters: u32,
pub ac_strategy_enabled: bool,
pub try_dct16: bool,
pub try_dct32: bool,
pub try_dct64: bool,
pub try_dct4x8_afv: bool,
pub non_aligned_eval: bool,
pub fine_grained_step: u8,
pub chromacity_adjustment: bool,
pub enhanced_clustering_vardct: bool,
pub optimize_uint_configs_vardct: bool,
pub epf_dynamic_sharpness: bool,
pub cfl_two_pass: bool,
pub cfl_newton: bool,
pub cfl_newton_eps: f32,
pub cfl_newton_max_iters: usize,
pub use_adaptive_quant: bool,
pub adjust_quant_ac: bool,
pub initial_q_numerator: f32,
pub fixed_thresholds_y: [f32; 4],
pub adjust_thresholds: [f32; 4],
pub k_favor_2x2: f32,
pub k_avoid_transforms_base: f32,
pub k_info_loss_mul_base: f32,
pub k_zeros_mul_base: f32,
pub k_cost_delta_base: f32,
pub k_ac_quant: f32,
pub k8x8: (f32, f32, f32),
pub k16x8: (f32, f32, f32),
pub k16x16: (f32, f32, f32),
pub k4x8: (f32, f32, f32),
pub k4x4: (f32, f32, f32),
pub entropy_mul_table: EntropyMulTable,
pub patch_ref_tree_learning: bool,
pub nb_rcts_to_try: u8,
pub wp_num_param_sets: u8,
pub tree_num_properties: u8,
pub tree_max_buckets: u16,
pub tree_threshold_base: f32,
pub tree_max_samples_fixed: u32,
pub tree_sample_fraction: f32,
}
impl EffortProfile {
pub fn lossy(effort: u8, mode: EncoderMode) -> Self {
let effort = effort.clamp(1, 10);
match mode {
EncoderMode::Reference => Self::lossy_reference(effort),
EncoderMode::Experimental => Self::lossy_experimental(effort),
}
}
pub fn lossless(effort: u8, mode: EncoderMode) -> Self {
let effort = effort.clamp(1, 10);
match mode {
EncoderMode::Reference => Self::lossless_reference(effort),
EncoderMode::Experimental => Self::lossless_experimental(effort),
}
}
fn lossy_reference(effort: u8) -> Self {
let speed_tier = 10u8.saturating_sub(effort);
Self {
effort,
use_ans: effort >= 3,
optimize_codes: effort >= 3,
custom_orders: effort >= 4,
gaborish: effort >= 5,
pixel_domain_loss: effort >= 5,
error_diffusion: false, patches: effort >= 7,
tree_learning: effort >= 7,
lz77: effort >= 9,
lz77_method: match effort {
0..=8 => Lz77Method::Rle,
_ => Lz77Method::Optimal,
},
butteraugli_iters: match effort {
0..=7 => 0,
8 => 2,
_ => 4,
},
ac_strategy_enabled: effort >= 5,
try_dct16: effort >= 5,
try_dct32: effort >= 5,
try_dct64: effort >= 7,
try_dct4x8_afv: effort >= 6,
non_aligned_eval: effort >= 6,
fine_grained_step: if effort >= 9 { 1 } else { 2 },
chromacity_adjustment: effort >= 7,
enhanced_clustering_vardct: effort >= 9,
optimize_uint_configs_vardct: effort >= 9,
epf_dynamic_sharpness: effort >= 6,
cfl_two_pass: effort >= 7,
cfl_newton: effort >= 7,
cfl_newton_eps: jxl_simd::NEWTON_EPS_DEFAULT,
cfl_newton_max_iters: jxl_simd::NEWTON_MAX_ITERS_DEFAULT,
use_adaptive_quant: effort >= 5,
adjust_quant_ac: effort >= 5,
initial_q_numerator: if effort >= 5 { 0.39 } else { 0.79 },
fixed_thresholds_y: [0.56, 0.62, 0.62, 0.62],
adjust_thresholds: [0.58, 0.64, 0.64, 0.64],
k_favor_2x2: -0.4,
k_avoid_transforms_base: 0.5,
k_info_loss_mul_base: 1.2,
k_zeros_mul_base: 9.308_906,
k_cost_delta_base: 10.833_273,
k_ac_quant: 0.765,
k8x8: (-0.55 * 0.75, 1.073_575_8 * 0.75, 1.4),
k16x8: (-0.55, 0.901_958_8, 1.6),
k16x16: (-0.65, 0.88, 1.8),
k4x8: (-0.50 * 0.75, 0.88, 1.3),
k4x4: (-0.45 * 0.75, 0.85, 1.2),
entropy_mul_table: EntropyMulTable::reference(),
patch_ref_tree_learning: false,
nb_rcts_to_try: match effort {
0..=4 => 0,
5 => 4,
6 => 5,
7 => 7,
8 => 9,
_ => 19,
},
wp_num_param_sets: match effort {
0..=7 => 0,
8 => 2,
_ => 5,
},
tree_num_properties: Self::tree_num_properties_for(effort),
tree_max_buckets: Self::tree_max_buckets_for(effort),
tree_threshold_base: 75.0 + 14.0 * speed_tier as f32,
tree_max_samples_fixed: if effort <= 4 { 65_000 } else { 0 },
tree_sample_fraction: Self::tree_sample_fraction_for(effort),
}
}
fn lossless_reference(effort: u8) -> Self {
let speed_tier = 10u8.saturating_sub(effort);
Self {
effort,
use_ans: effort >= 3,
optimize_codes: effort >= 2,
custom_orders: effort >= 3,
gaborish: false, pixel_domain_loss: false, error_diffusion: false, patches: effort >= 5,
tree_learning: effort >= 7,
lz77: effort >= 7,
lz77_method: match effort {
0..=7 => Lz77Method::Rle,
8 => Lz77Method::Greedy,
_ => Lz77Method::Optimal,
},
butteraugli_iters: 0,
ac_strategy_enabled: false,
try_dct16: false,
try_dct32: false,
try_dct64: false,
try_dct4x8_afv: false,
non_aligned_eval: false,
fine_grained_step: 2,
chromacity_adjustment: false,
enhanced_clustering_vardct: false,
optimize_uint_configs_vardct: false, epf_dynamic_sharpness: false,
cfl_two_pass: false,
cfl_newton: false,
cfl_newton_eps: jxl_simd::NEWTON_EPS_DEFAULT,
cfl_newton_max_iters: jxl_simd::NEWTON_MAX_ITERS_DEFAULT,
use_adaptive_quant: false,
adjust_quant_ac: false,
initial_q_numerator: 0.39,
fixed_thresholds_y: [0.56, 0.62, 0.62, 0.62],
adjust_thresholds: [0.58, 0.64, 0.64, 0.64],
k_favor_2x2: -0.4,
k_avoid_transforms_base: 0.5,
k_info_loss_mul_base: 1.2,
k_zeros_mul_base: 9.308_906,
k_cost_delta_base: 10.833_273,
k_ac_quant: 0.765,
k8x8: (-0.55 * 0.75, 1.073_575_8 * 0.75, 1.4),
k16x8: (-0.55, 0.901_958_8, 1.6),
k16x16: (-0.65, 0.88, 1.8),
k4x8: (-0.50 * 0.75, 0.88, 1.3),
k4x4: (-0.45 * 0.75, 0.85, 1.2),
entropy_mul_table: EntropyMulTable::reference(),
patch_ref_tree_learning: false,
nb_rcts_to_try: match effort {
0..=4 => 0,
5 => 4,
6 => 5,
7 => 7,
8 => 9,
_ => 19,
},
wp_num_param_sets: match effort {
0..=7 => 0,
8 => 2,
_ => 5,
},
tree_num_properties: Self::tree_num_properties_for(effort),
tree_max_buckets: Self::tree_max_buckets_for(effort),
tree_threshold_base: 75.0 + 14.0 * speed_tier as f32,
tree_max_samples_fixed: if effort <= 4 { 65_000 } else { 0 },
tree_sample_fraction: Self::tree_sample_fraction_for(effort),
}
}
fn lossy_experimental(effort: u8) -> Self {
let mut p = Self::lossy_reference(effort);
p.k_info_loss_mul_base = 1.3;
p.entropy_mul_table = EntropyMulTable::experimental();
if effort >= 7 {
p.enhanced_clustering_vardct = true;
}
if effort >= 7 {
p.patch_ref_tree_learning = true;
}
p
}
fn lossless_experimental(effort: u8) -> Self {
Self::lossless_reference(effort)
}
fn tree_num_properties_for(effort: u8) -> u8 {
match effort {
0..=4 => 3,
5 => 4,
6 => 5,
7 => 7,
8 => 10,
_ => 16,
}
}
fn tree_sample_fraction_for(effort: u8) -> f32 {
match effort {
0..=4 => 0.15,
5 => 0.25,
6 => 0.35,
7 => 0.5,
8 => 0.55,
_ => 0.65,
}
}
fn tree_max_buckets_for(effort: u8) -> u16 {
match effort {
0..=4 => 32, 5 => 48, 6 => 64, 7 => 96, 8 => 128, _ => 256, }
}
}
#[cfg(feature = "__expert")]
#[non_exhaustive]
#[derive(Default, Clone, Debug)]
pub struct LossyInternalParams {
pub try_dct16: Option<bool>,
pub try_dct32: Option<bool>,
pub try_dct64: Option<bool>,
pub try_dct4x8_afv: Option<bool>,
pub fine_grained_step: Option<u8>,
pub k_info_loss_mul_base: Option<f32>,
pub entropy_mul_table: Option<EntropyMulTable>,
pub cfl_two_pass: Option<bool>,
pub chromacity_adjustment: Option<bool>,
pub patch_ref_tree_learning: Option<bool>,
pub non_aligned_eval: Option<bool>,
pub enhanced_clustering_vardct: Option<bool>,
pub k_ac_quant: Option<f32>,
}
#[cfg(feature = "__expert")]
#[non_exhaustive]
#[derive(Default, Clone, Debug)]
pub struct LosslessInternalParams {
pub nb_rcts_to_try: Option<u8>,
pub wp_num_param_sets: Option<u8>,
pub tree_max_buckets: Option<u16>,
pub tree_num_properties: Option<u8>,
pub tree_threshold_base: Option<f32>,
pub tree_sample_fraction: Option<f32>,
pub tree_max_samples_fixed: Option<u32>,
}
#[cfg(feature = "__expert")]
impl LossyInternalParams {
pub(crate) fn apply_to(self, profile: &mut EffortProfile) {
let LossyInternalParams {
try_dct16,
try_dct32,
try_dct64,
try_dct4x8_afv,
fine_grained_step,
k_info_loss_mul_base,
entropy_mul_table,
cfl_two_pass,
chromacity_adjustment,
patch_ref_tree_learning,
non_aligned_eval,
enhanced_clustering_vardct,
k_ac_quant,
} = self;
if let Some(v) = try_dct16 {
profile.try_dct16 = v;
}
if let Some(v) = try_dct32 {
profile.try_dct32 = v;
}
if let Some(v) = try_dct64 {
profile.try_dct64 = v;
}
if let Some(v) = try_dct4x8_afv {
profile.try_dct4x8_afv = v;
}
if let Some(v) = fine_grained_step {
profile.fine_grained_step = v;
}
if let Some(v) = k_info_loss_mul_base {
profile.k_info_loss_mul_base = v;
}
if let Some(v) = entropy_mul_table {
profile.entropy_mul_table = v;
}
if let Some(v) = cfl_two_pass {
profile.cfl_two_pass = v;
}
if let Some(v) = chromacity_adjustment {
profile.chromacity_adjustment = v;
}
if let Some(v) = patch_ref_tree_learning {
profile.patch_ref_tree_learning = v;
}
if let Some(v) = non_aligned_eval {
profile.non_aligned_eval = v;
}
if let Some(v) = enhanced_clustering_vardct {
profile.enhanced_clustering_vardct = v;
}
if let Some(v) = k_ac_quant {
profile.k_ac_quant = v;
}
}
}
#[cfg(feature = "__expert")]
impl LosslessInternalParams {
pub(crate) fn apply_to(self, profile: &mut EffortProfile) {
let LosslessInternalParams {
nb_rcts_to_try,
wp_num_param_sets,
tree_max_buckets,
tree_num_properties,
tree_threshold_base,
tree_sample_fraction,
tree_max_samples_fixed,
} = self;
if let Some(v) = nb_rcts_to_try {
profile.nb_rcts_to_try = v;
}
if let Some(v) = wp_num_param_sets {
profile.wp_num_param_sets = v;
}
if let Some(v) = tree_max_buckets {
profile.tree_max_buckets = v;
}
if let Some(v) = tree_num_properties {
profile.tree_num_properties = v;
}
if let Some(v) = tree_threshold_base {
profile.tree_threshold_base = v;
}
if let Some(v) = tree_sample_fraction {
profile.tree_sample_fraction = v;
}
if let Some(v) = tree_max_samples_fixed {
profile.tree_max_samples_fixed = v;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lossy_reference_e7() {
let p = EffortProfile::lossy(7, EncoderMode::Reference);
assert_eq!(p.effort, 7);
assert!(p.use_ans);
assert!(p.optimize_codes);
assert!(p.custom_orders);
assert!(p.gaborish);
assert!(p.pixel_domain_loss);
assert!(!p.error_diffusion);
assert!(p.patches);
assert!(!p.lz77); assert_eq!(p.butteraugli_iters, 0); assert!(p.ac_strategy_enabled);
assert!(p.try_dct32);
assert!(p.try_dct64);
assert!(p.try_dct4x8_afv); assert!(p.non_aligned_eval);
assert_eq!(p.fine_grained_step, 2);
assert!(p.chromacity_adjustment); assert!(!p.enhanced_clustering_vardct); assert!(!p.optimize_uint_configs_vardct); assert!(p.epf_dynamic_sharpness); assert!(p.cfl_two_pass); assert!(p.cfl_newton); assert!(p.use_adaptive_quant);
assert!(p.adjust_quant_ac);
assert_eq!(p.initial_q_numerator, 0.39);
assert_eq!(p.k_favor_2x2, -0.4);
assert_eq!(p.k_ac_quant, 0.765);
assert_eq!(p.nb_rcts_to_try, 7);
assert_eq!(p.wp_num_param_sets, 0); assert_eq!(p.tree_num_properties, 7);
assert_eq!(p.tree_max_buckets, 96);
}
#[test]
fn test_lossy_reference_e5() {
let p = EffortProfile::lossy(5, EncoderMode::Reference);
assert_eq!(p.effort, 5);
assert!(p.use_ans);
assert!(p.gaborish);
assert!(p.pixel_domain_loss);
assert!(!p.error_diffusion); assert!(!p.patches); assert!(!p.lz77); assert!(p.ac_strategy_enabled);
assert!(p.try_dct32);
assert!(!p.try_dct64); assert!(!p.try_dct4x8_afv); assert!(!p.non_aligned_eval); assert!(!p.chromacity_adjustment); assert!(!p.enhanced_clustering_vardct); assert!(!p.optimize_uint_configs_vardct); assert!(!p.epf_dynamic_sharpness); assert!(!p.cfl_two_pass); assert!(!p.cfl_newton); assert!(p.use_adaptive_quant);
assert!(p.adjust_quant_ac);
assert_eq!(p.initial_q_numerator, 0.39);
assert_eq!(p.butteraugli_iters, 0); assert_eq!(p.nb_rcts_to_try, 4);
assert_eq!(p.wp_num_param_sets, 0); }
#[test]
fn test_lossy_reference_e9() {
let p = EffortProfile::lossy(9, EncoderMode::Reference);
assert!(p.lz77); assert_eq!(p.lz77_method, Lz77Method::Optimal);
assert_eq!(p.butteraugli_iters, 4);
assert_eq!(p.fine_grained_step, 1);
assert!(p.enhanced_clustering_vardct); assert!(p.optimize_uint_configs_vardct); assert_eq!(p.nb_rcts_to_try, 19);
assert_eq!(p.wp_num_param_sets, 5); assert_eq!(p.tree_num_properties, 16);
assert_eq!(p.tree_max_buckets, 256);
}
#[test]
fn test_lossy_reference_e8() {
let p = EffortProfile::lossy(8, EncoderMode::Reference);
assert!(!p.lz77); assert_eq!(p.lz77_method, Lz77Method::Rle);
assert_eq!(p.butteraugli_iters, 2);
assert_eq!(p.fine_grained_step, 2);
assert!(!p.enhanced_clustering_vardct); assert!(!p.optimize_uint_configs_vardct); assert_eq!(p.wp_num_param_sets, 2); }
#[test]
fn test_lossy_reference_e3() {
let p = EffortProfile::lossy(3, EncoderMode::Reference);
assert!(p.use_ans);
assert!(p.optimize_codes);
assert!(!p.gaborish);
assert!(!p.ac_strategy_enabled);
assert!(!p.use_adaptive_quant);
assert!(!p.adjust_quant_ac);
assert_eq!(p.initial_q_numerator, 0.79);
}
#[test]
fn test_lossless_reference_e7() {
let p = EffortProfile::lossless(7, EncoderMode::Reference);
assert!(p.use_ans);
assert!(p.tree_learning);
assert!(p.lz77);
assert_eq!(p.lz77_method, Lz77Method::Rle);
assert!(p.patches);
assert!(!p.gaborish); assert!(!p.pixel_domain_loss); assert!(!p.ac_strategy_enabled); }
#[test]
fn test_lossless_reference_e4() {
let p = EffortProfile::lossless(4, EncoderMode::Reference);
assert!(p.use_ans);
assert!(!p.tree_learning); assert!(!p.lz77); assert!(!p.patches); }
#[test]
fn test_effort_clamp() {
let p = EffortProfile::lossy(0, EncoderMode::Reference);
assert_eq!(p.effort, 1);
let p = EffortProfile::lossy(99, EncoderMode::Reference);
assert_eq!(p.effort, 10);
}
#[test]
fn test_experimental_diverges_from_reference() {
for effort in 1..=10 {
let r = EffortProfile::lossy(effort, EncoderMode::Reference);
let e = EffortProfile::lossy(effort, EncoderMode::Experimental);
assert_eq!(r.effort, e.effort);
assert_eq!(r.use_ans, e.use_ans);
assert_eq!(r.k_favor_2x2, e.k_favor_2x2);
assert_eq!(r.butteraugli_iters, e.butteraugli_iters);
assert_eq!(r.nb_rcts_to_try, e.nb_rcts_to_try);
}
let r = EffortProfile::lossy(7, EncoderMode::Reference);
let e = EffortProfile::lossy(7, EncoderMode::Experimental);
assert_eq!(r.k_info_loss_mul_base, 1.2);
assert_eq!(e.k_info_loss_mul_base, 1.3);
assert_eq!(r.entropy_mul_table.dct4x4, 1.08);
assert_eq!(e.entropy_mul_table.dct4x4, 0.88);
assert_eq!(r.entropy_mul_table.identity, 1.0428);
assert_eq!(e.entropy_mul_table.identity, 0.88);
assert_eq!(r.entropy_mul_table.afv, 0.817_794_9);
assert_eq!(e.entropy_mul_table.afv, 0.75);
assert_eq!(r.entropy_mul_table.dct8, e.entropy_mul_table.dct8);
assert_eq!(r.entropy_mul_table.dct16x8, e.entropy_mul_table.dct16x8);
assert_eq!(r.entropy_mul_table.dct32x32, e.entropy_mul_table.dct32x32);
assert!(!r.enhanced_clustering_vardct); assert!(e.enhanced_clustering_vardct);
assert!(!r.patch_ref_tree_learning);
assert!(e.patch_ref_tree_learning);
let e5 = EffortProfile::lossy(5, EncoderMode::Experimental);
assert!(!e5.enhanced_clustering_vardct);
assert!(!e5.patch_ref_tree_learning);
assert_eq!(e5.k_info_loss_mul_base, 1.3);
assert_eq!(e5.entropy_mul_table.dct4x4, 0.88);
}
#[test]
fn test_entropy_mul_table_reference_values() {
let t = EntropyMulTable::reference();
assert_eq!(t.dct8, 0.8);
assert_eq!(t.dct4x4, 1.08);
assert_eq!(t.dct4x8, 0.859_316_37);
assert_eq!(t.identity, 1.0428);
assert_eq!(t.dct2x2, 0.95);
assert_eq!(t.afv, 0.817_794_9);
assert_eq!(t.dct16x8, 1.21);
assert_eq!(t.dct16x16, 1.34);
assert_eq!(t.dct16x32, 1.49);
assert_eq!(t.dct32x32, 1.48);
assert_eq!(t.dct64x32, 2.25);
assert_eq!(t.dct64x64, 2.25);
}
#[test]
fn test_entropy_mul_table_experimental_values() {
let t = EntropyMulTable::experimental();
let r = EntropyMulTable::reference();
assert_eq!(t.dct4x4, 0.88); assert_eq!(t.identity, 0.88); assert_eq!(t.afv, 0.75);
assert_eq!(t.dct8, r.dct8);
assert_eq!(t.dct4x8, r.dct4x8);
assert_eq!(t.dct2x2, r.dct2x2);
assert_eq!(t.dct16x8, r.dct16x8);
assert_eq!(t.dct16x16, r.dct16x16);
assert_eq!(t.dct16x32, r.dct16x32);
assert_eq!(t.dct32x32, r.dct32x32);
assert_eq!(t.dct64x32, r.dct64x32);
assert_eq!(t.dct64x64, r.dct64x64);
}
#[test]
fn test_lossless_experimental_matches_reference() {
for effort in 1..=10 {
let r = EffortProfile::lossless(effort, EncoderMode::Reference);
let e = EffortProfile::lossless(effort, EncoderMode::Experimental);
assert_eq!(r.effort, e.effort);
assert_eq!(r.use_ans, e.use_ans);
assert_eq!(r.tree_learning, e.tree_learning);
assert_eq!(r.lz77, e.lz77);
}
}
#[test]
fn test_tree_threshold_base_formula() {
let p = EffortProfile::lossy(7, EncoderMode::Reference);
assert_eq!(p.tree_threshold_base, 75.0 + 14.0 * 3.0); let p = EffortProfile::lossy(9, EncoderMode::Reference);
assert_eq!(p.tree_threshold_base, 75.0 + 14.0 * 1.0); let p = EffortProfile::lossy(5, EncoderMode::Reference);
assert_eq!(p.tree_threshold_base, 75.0 + 14.0 * 5.0); }
}