mod config;
mod estimate;
mod generate;
mod select;
pub(crate) use config::ScanSearchConfig;
use super::config::ProgressiveScan;
use crate::error::Result;
use crate::foundation::consts::DCT_BLOCK_SIZE;
const MAX_TRIAL_ENCODES: usize = 2;
pub(crate) fn generate_candidate_scripts(
y_blocks: &[[i16; DCT_BLOCK_SIZE]],
cb_blocks: &[[i16; DCT_BLOCK_SIZE]],
cr_blocks: &[[i16; DCT_BLOCK_SIZE]],
num_components: u8,
) -> Result<Vec<Vec<ProgressiveScan>>> {
let config = ScanSearchConfig::default();
let split_points: &[u8] = &config.frequency_splits;
let al_levels: &[u8] = &[1, 2, 3];
let mut mixed_sa_scripts: Vec<Vec<ProgressiveScan>> =
Vec::with_capacity(split_points.len() * al_levels.len());
for &split in split_points {
for &al in al_levels {
let al_c = al.min(config.al_max_chroma);
mixed_sa_scripts.push(mixed_sa_split_progressive_scans(
num_components,
split,
al,
al_c,
));
}
}
let default_script = default_jpegli_progressive_scans(num_components);
let trial_scans = generate::generate_search_scans(num_components, &config);
let mut cache = estimate::ScanHistogramCache::warm(
&trial_scans,
&mixed_sa_scripts,
y_blocks,
cb_blocks,
cr_blocks,
num_components,
);
let mut best_mixed_sa: Option<(Vec<ProgressiveScan>, usize)> = None;
for script in mixed_sa_scripts {
let est = cache.estimate_script_cost_cached(&script);
if best_mixed_sa.as_ref().is_none_or(|(_, best)| est < *best) {
best_mixed_sa = Some((script, est));
}
}
let scan_sizes = cache.estimate_all_scan_sizes_cached(&trial_scans);
let selector = select::ScanSelector::new(num_components, config.clone());
let search_result = selector.select_best(&scan_sizes);
let optimizer_script = search_result.build_final_scans(num_components, &config);
let mut candidates: Vec<Vec<ProgressiveScan>> = Vec::with_capacity(MAX_TRIAL_ENCODES);
candidates.push(default_script);
let optimizer_est = cache.estimate_script_cost_cached(&optimizer_script);
let best_alternative = match best_mixed_sa {
Some((mixed_script, mixed_est)) => {
if scripts_equivalent(&mixed_script, &optimizer_script) {
Some(optimizer_script)
} else if mixed_est < optimizer_est {
Some(mixed_script)
} else {
Some(optimizer_script)
}
}
None => Some(optimizer_script),
};
if let Some(alt) = best_alternative
&& !scripts_equivalent(&alt, &candidates[0])
{
candidates.push(alt);
}
Ok(candidates)
}
fn scripts_equivalent(a: &[ProgressiveScan], b: &[ProgressiveScan]) -> bool {
if a.len() != b.len() {
return false;
}
a.iter().zip(b.iter()).all(|(sa, sb)| {
sa.components == sb.components
&& sa.ss == sb.ss
&& sa.se == sb.se
&& sa.ah == sb.ah
&& sa.al == sb.al
})
}
fn mixed_sa_split_progressive_scans(
num_components: u8,
split: u8,
al_luma: u8,
al_chroma: u8,
) -> Vec<ProgressiveScan> {
let nc = num_components as usize;
let mut scans = Vec::with_capacity(nc * 6);
for c in 0..nc {
scans.push(ProgressiveScan {
components: vec![c as u8],
ss: 0,
se: 0,
ah: 0,
al: 0,
});
}
for c in 0..nc {
scans.push(ProgressiveScan {
components: vec![c as u8],
ss: 1,
se: split,
ah: 0,
al: 0,
});
}
if split < 63 {
for c in 0..nc {
let al = if c == 0 { al_luma } else { al_chroma };
scans.push(ProgressiveScan {
components: vec![c as u8],
ss: split + 1,
se: 63,
ah: 0,
al,
});
}
let max_al = al_luma.max(al_chroma);
for refine_al in (0..max_al).rev() {
for c in 0..nc {
let al = if c == 0 { al_luma } else { al_chroma };
if refine_al < al {
scans.push(ProgressiveScan {
components: vec![c as u8],
ss: split + 1,
se: 63,
ah: refine_al + 1,
al: refine_al,
});
}
}
}
}
scans
}
fn default_jpegli_progressive_scans(num_components: u8) -> Vec<ProgressiveScan> {
let nc = num_components as usize;
let mut scans = Vec::with_capacity(nc * 5);
for c in 0..nc {
scans.push(ProgressiveScan {
components: vec![c as u8],
ss: 0,
se: 0,
ah: 0,
al: 0,
});
}
for c in 0..nc {
scans.push(ProgressiveScan {
components: vec![c as u8],
ss: 1,
se: 2,
ah: 0,
al: 0,
});
}
for c in 0..nc {
scans.push(ProgressiveScan {
components: vec![c as u8],
ss: 3,
se: 63,
ah: 0,
al: 2,
});
}
for c in 0..nc {
scans.push(ProgressiveScan {
components: vec![c as u8],
ss: 3,
se: 63,
ah: 2,
al: 1,
});
}
for c in 0..nc {
scans.push(ProgressiveScan {
components: vec![c as u8],
ss: 3,
se: 63,
ah: 1,
al: 0,
});
}
scans
}