Use evaluator for main trials
This commit is contained in:
parent
99ccd94c29
commit
74c5c4173b
3 changed files with 67 additions and 101 deletions
|
|
@ -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")]
|
||||||
{
|
{
|
||||||
|
|
|
||||||
140
src/lib.rs
140
src/lib.rs
|
|
@ -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)]
|
||||||
|
|
|
||||||
|
|
@ -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 = ",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue