Use evaluator for main trials

This commit is contained in:
Andrew 2025-01-19 13:55:28 +13:00
parent 99ccd94c29
commit 74c5c4173b
3 changed files with 67 additions and 101 deletions

View file

@ -29,9 +29,15 @@ pub(crate) struct Candidate {
} }
impl Candidate { impl Candidate {
/// Return an estimate of the output size which can help with evaluation of very small data
#[must_use]
pub fn estimated_output_size(&self) -> usize {
self.idat_data.len() + self.image.key_chunks_size()
}
fn cmp_key(&self) -> impl Ord { fn cmp_key(&self) -> impl Ord {
( (
self.idat_data.len() + self.image.key_chunks_size(), self.estimated_output_size(),
self.image.data.len(), self.image.data.len(),
self.filter, self.filter,
// Prefer the later image added (e.g. baseline, which is always added last) // Prefer the later image added (e.g. baseline, which is always added last)
@ -143,7 +149,14 @@ impl Evaluator {
let filtered = image.filter_image(filter, optimize_alpha); let filtered = image.filter_image(filter, optimize_alpha);
let idat_data = deflater.deflate(&filtered, &best_candidate_size); let idat_data = deflater.deflate(&filtered, &best_candidate_size);
if let Ok(idat_data) = idat_data { if let Ok(idat_data) = idat_data {
let size = idat_data.len() + image.key_chunks_size(); let new = Candidate {
image: image.clone(),
idat_data,
filtered,
filter,
nth,
};
let size = new.estimated_output_size();
best_candidate_size.set_min(size); best_candidate_size.set_min(size);
trace!( trace!(
"Eval: {}-bit {:23} {:8} {} bytes", "Eval: {}-bit {:23} {:8} {} bytes",
@ -152,13 +165,6 @@ impl Evaluator {
filter, filter,
size size
); );
let new = Candidate {
image: image.clone(),
idat_data,
filtered,
filter,
nth,
};
#[cfg(feature = "parallel")] #[cfg(feature = "parallel")]
{ {

View file

@ -323,8 +323,6 @@ pub fn optimize_from_memory(data: &[u8], opts: &Options) -> PngResult<Vec<u8>> {
} }
} }
type TrialResult = (RowFilter, Vec<u8>);
/// Perform optimization on the input PNG object using the options provided /// Perform optimization on the input PNG object using the options provided
fn optimize_png( fn optimize_png(
png: &mut PngData, png: &mut PngData,
@ -454,9 +452,8 @@ fn optimize_raw(
report_format("Transformed image to ", &new_image); report_format("Transformed image to ", &new_image);
} }
let mut deflater = opts.deflate; let (result, deflater) = if opts.idat_recoding || reduction_occurred {
let mut png = if opts.idat_recoding || reduction_occurred { let result = perform_trials(
perform_trials(
new_image.clone(), new_image.clone(),
opts, opts,
deadline.clone(), deadline.clone(),
@ -464,38 +461,26 @@ fn optimize_raw(
eval_result, eval_result,
eval_filters, eval_filters,
eval_deflater, eval_deflater,
) );
.map(|(filter, idat_data)| PngData { (result?, opts.deflate)
raw: new_image,
idat_data,
aux_chunks: Vec::new(),
frames: Vec::new(),
filter: Some(filter),
})
} else { } else {
// If idat_recoding is off and reductions were attempted but ended up choosing the baseline, // If idat_recoding is off and reductions were attempted but ended up choosing the baseline,
// we should still check if the evaluator compressed the baseline smaller than the original. // we should still check if the evaluator compressed the baseline smaller than the original.
deflater = eval_deflater; (eval_result?, eval_deflater)
eval_result.map(|result| PngData { };
if max_size.map_or(true, |max_size| result.estimated_output_size() < max_size) {
debug!("Found better result:");
debug!(" zc = {} f = {}", deflater, result.filter);
return Some(PngData {
raw: result.image, raw: result.image,
idat_data: result.idat_data, idat_data: result.idat_data,
aux_chunks: Vec::new(), aux_chunks: Vec::new(),
frames: Vec::new(), frames: Vec::new(),
filter: Some(result.filter), filter: Some(result.filter),
}) });
};
png = png.filter(|png| png.estimated_output_size() < max_size.unwrap_or(usize::MAX));
if let Some(png) = &png {
debug!("Found better result:");
debug!(
" zc = {} f = {:8} {} bytes",
deflater,
png.filter.unwrap(),
png.idat_data.len()
);
} }
png None
} }
/// Perform compression trials /// Perform compression trials
@ -507,7 +492,7 @@ fn perform_trials(
mut eval_result: Option<Candidate>, mut eval_result: Option<Candidate>,
eval_filters: IndexSet<RowFilter>, eval_filters: IndexSet<RowFilter>,
eval_deflater: Deflaters, eval_deflater: Deflaters,
) -> Option<TrialResult> { ) -> Option<Candidate> {
let mut filters = opts.filter.clone(); let mut filters = opts.filter.clone();
let fast_eval = opts.fast_evaluation && (filters.len() > 1 || eval_result.is_some()); let fast_eval = opts.fast_evaluation && (filters.len() > 1 || eval_result.is_some());
if fast_eval { if fast_eval {
@ -519,9 +504,14 @@ fn perform_trials(
} }
if !filters.is_empty() { if !filters.is_empty() {
trace!("Evaluating: {} filters", filters.len()); trace!("Evaluating {} filters", filters.len());
let eval = Evaluator::new(deadline, filters, eval_deflater, opts.optimize_alpha); let eval = Evaluator::new(
if let Some(ref result) = eval_result { deadline.clone(),
filters,
eval_deflater,
opts.optimize_alpha,
);
if let Some(result) = &eval_result {
eval.set_best_size(result.idat_data.len()); eval.set_best_size(result.idat_data.len());
} }
eval.try_image(image.clone()); eval.try_image(image.clone());
@ -529,17 +519,30 @@ fn perform_trials(
eval_result = Some(result); eval_result = Some(result);
} }
} }
// We should have a result here - fail if not (e.g. deadline passed) if opts.deflate == eval_deflater {
let result = eval_result?;
return if opts.deflate == eval_deflater {
// No further compression required // No further compression required
Some((result.filter, result.idat_data)) return eval_result;
} else { }
debug!("Trying: {}", result.filter);
let best_size = AtomicMin::new(max_size); // We should have a result here - fail if not (e.g. deadline passed)
perform_trial(&result.filtered, opts, result.filter, &best_size) let mut result = eval_result?;
// Recompress with the main deflater
debug!("Trying filter {}, zc = {}", result.filter, opts.deflate);
match opts
.deflate
.deflate(&result.filtered, &AtomicMin::new(max_size))
{
Ok(idat_data) => {
result.idat_data = idat_data;
trace!("{} bytes", result.estimated_output_size());
}
Err(PngError::DeflatedDataTooLong(bytes)) => {
trace!(">{bytes} bytes");
}
Err(_) => (),
}; };
return Some(result);
} }
// Perform full compression trials of selected filters and determine the best // Perform full compression trials of selected filters and determine the best
@ -555,56 +558,13 @@ fn perform_trials(
} }
} }
debug!("Trying: {} filters", filters.len()); debug!("Trying {} filters, zc = {}", filters.len(), opts.deflate);
let eval = Evaluator::new(deadline, filters, opts.deflate, opts.optimize_alpha);
let best_size = AtomicMin::new(max_size); if let Some(max_size) = max_size {
let results_iter = filters.into_par_iter().with_max_len(1); eval.set_best_size(max_size);
let best = results_iter.filter_map(|filter| {
if deadline.passed() {
return None;
}
let filtered = &image.filter_image(filter, opts.optimize_alpha);
perform_trial(filtered, opts, filter, &best_size)
});
best.reduce_with(|i, j| {
if i.1.len() < j.1.len() || (i.1.len() == j.1.len() && i.0 < j.0) {
i
} else {
j
}
})
}
/// Execute a compression trial
fn perform_trial(
filtered: &[u8],
opts: &Options,
filter: RowFilter,
best_size: &AtomicMin,
) -> Option<TrialResult> {
match opts.deflate.deflate(filtered, best_size) {
Ok(new_idat) => {
let bytes = new_idat.len();
best_size.set_min(bytes);
trace!(
" zc = {} f = {:8} {} bytes",
opts.deflate,
filter,
bytes
);
Some((filter, new_idat))
}
Err(PngError::DeflatedDataTooLong(bytes)) => {
trace!(
" zc = {} f = {:8} >{} bytes",
opts.deflate,
filter,
bytes,
);
None
}
Err(_) => None,
} }
eval.try_image(image);
eval.get_best_candidate()
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -181,7 +181,7 @@ fn verbose_mode() {
" 8-bit RGB, non-interlaced", " 8-bit RGB, non-interlaced",
" IDAT size = 113794 bytes", " IDAT size = 113794 bytes",
" File size = 114708 bytes", " File size = 114708 bytes",
"Trying: 1 filters", "Trying 1 filters, zc = ",
"Found better result:", "Found better result:",
" zc = 11 f = None", " zc = 11 f = None",
" IDAT size = ", " IDAT size = ",