Separate perform_trials into new function
This commit is contained in:
parent
8a44cdbc84
commit
99ccd94c29
3 changed files with 126 additions and 122 deletions
|
|
@ -10,6 +10,7 @@ use std::sync::{
|
|||
|
||||
#[cfg(feature = "parallel")]
|
||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||
use deflate::Deflaters;
|
||||
use indexmap::IndexSet;
|
||||
use log::trace;
|
||||
use rayon::prelude::*;
|
||||
|
|
@ -43,7 +44,7 @@ impl Candidate {
|
|||
pub(crate) struct Evaluator {
|
||||
deadline: Arc<Deadline>,
|
||||
filters: IndexSet<RowFilter>,
|
||||
compression: u8,
|
||||
deflater: Deflaters,
|
||||
optimize_alpha: bool,
|
||||
nth: AtomicUsize,
|
||||
executed: Arc<AtomicUsize>,
|
||||
|
|
@ -60,7 +61,7 @@ impl Evaluator {
|
|||
pub fn new(
|
||||
deadline: Arc<Deadline>,
|
||||
filters: IndexSet<RowFilter>,
|
||||
compression: u8,
|
||||
deflater: Deflaters,
|
||||
optimize_alpha: bool,
|
||||
) -> Self {
|
||||
#[cfg(feature = "parallel")]
|
||||
|
|
@ -68,7 +69,7 @@ impl Evaluator {
|
|||
Self {
|
||||
deadline,
|
||||
filters,
|
||||
compression,
|
||||
deflater,
|
||||
optimize_alpha,
|
||||
nth: AtomicUsize::new(0),
|
||||
executed: Arc::new(AtomicUsize::new(0)),
|
||||
|
|
@ -118,7 +119,7 @@ impl Evaluator {
|
|||
// These clones are only cheap refcounts
|
||||
let deadline = self.deadline.clone();
|
||||
let filters = self.filters.clone();
|
||||
let compression = self.compression;
|
||||
let deflater = self.deflater;
|
||||
let optimize_alpha = self.optimize_alpha;
|
||||
let executed = self.executed.clone();
|
||||
let best_candidate_size = self.best_candidate_size.clone();
|
||||
|
|
@ -140,7 +141,7 @@ impl Evaluator {
|
|||
return;
|
||||
}
|
||||
let filtered = image.filter_image(filter, optimize_alpha);
|
||||
let idat_data = deflate::deflate(&filtered, compression, &best_candidate_size);
|
||||
let idat_data = deflater.deflate(&filtered, &best_candidate_size);
|
||||
if let Ok(idat_data) = idat_data {
|
||||
let size = idat_data.len() + image.key_chunks_size();
|
||||
best_candidate_size.set_min(size);
|
||||
|
|
|
|||
235
src/lib.rs
235
src/lib.rs
|
|
@ -44,7 +44,7 @@ pub use rgb::{RGB16, RGBA8};
|
|||
|
||||
use crate::{
|
||||
atomicmin::AtomicMin,
|
||||
evaluate::Evaluator,
|
||||
evaluate::{Candidate, Evaluator},
|
||||
headers::*,
|
||||
png::{PngData, PngImage},
|
||||
reduction::*,
|
||||
|
|
@ -425,12 +425,13 @@ fn optimize_raw(
|
|||
// 8 is a little slower but not noticeably when used only for reductions (o3 and higher)
|
||||
// 9 is not appreciably better than 8
|
||||
// 10 and higher are quite slow - good for filters but only good for reductions if matching the main zc level
|
||||
let eval_compression = match opts.deflate {
|
||||
let compression = match opts.deflate {
|
||||
Deflaters::Libdeflater { compression } => {
|
||||
if opts.fast_evaluation { 7 } else { 8 }.min(compression)
|
||||
}
|
||||
_ => 8,
|
||||
};
|
||||
let eval_deflater = Deflaters::Libdeflater { compression };
|
||||
// If only one filter is selected, use this for evaluations
|
||||
let eval_filters = if opts.filter.len() == 1 {
|
||||
opts.filter.clone()
|
||||
|
|
@ -439,137 +440,139 @@ fn optimize_raw(
|
|||
indexset! {RowFilter::None, RowFilter::Bigrams}
|
||||
};
|
||||
// This will collect all versions of images and pick one that compresses best
|
||||
let eval = Evaluator::new(
|
||||
deadline.clone(),
|
||||
eval_filters.clone(),
|
||||
eval_compression,
|
||||
false,
|
||||
);
|
||||
let mut png = perform_reductions(image.clone(), opts, &deadline, &eval);
|
||||
let mut eval_result = eval.get_best_candidate();
|
||||
let eval = Evaluator::new(deadline.clone(), eval_filters.clone(), eval_deflater, false);
|
||||
let mut new_image = perform_reductions(image.clone(), opts, &deadline, &eval);
|
||||
let eval_result = eval.get_best_candidate();
|
||||
if let Some(ref result) = eval_result {
|
||||
png = result.image.clone();
|
||||
new_image = result.image.clone();
|
||||
}
|
||||
let reduction_occurred = png.ihdr.color_type != image.ihdr.color_type
|
||||
|| png.ihdr.bit_depth != image.ihdr.bit_depth
|
||||
|| png.ihdr.interlaced != image.ihdr.interlaced;
|
||||
let reduction_occurred = new_image.ihdr.color_type != image.ihdr.color_type
|
||||
|| new_image.ihdr.bit_depth != image.ihdr.bit_depth
|
||||
|| new_image.ihdr.interlaced != image.ihdr.interlaced;
|
||||
|
||||
if reduction_occurred {
|
||||
report_format("Transformed image to ", &png);
|
||||
report_format("Transformed image to ", &new_image);
|
||||
}
|
||||
|
||||
if opts.idat_recoding || reduction_occurred {
|
||||
let mut filters = opts.filter.clone();
|
||||
let fast_eval = opts.fast_evaluation && (filters.len() > 1 || eval_result.is_some());
|
||||
let best: Option<TrialResult> = if fast_eval {
|
||||
// Perform a fast evaluation of selected filters followed by a single main compression trial
|
||||
|
||||
if eval_result.is_some() {
|
||||
// Some filters have already been evaluated, we don't need to try them again
|
||||
filters = filters.difference(&eval_filters).copied().collect();
|
||||
}
|
||||
|
||||
if !filters.is_empty() {
|
||||
trace!("Evaluating: {} filters", filters.len());
|
||||
let eval = Evaluator::new(deadline, filters, eval_compression, opts.optimize_alpha);
|
||||
if let Some(ref result) = eval_result {
|
||||
eval.set_best_size(result.idat_data.len());
|
||||
}
|
||||
eval.try_image(png.clone());
|
||||
if let Some(result) = eval.get_best_candidate() {
|
||||
eval_result = Some(result);
|
||||
}
|
||||
}
|
||||
// We should have a result here - fail if not (e.g. deadline passed)
|
||||
let result = eval_result?;
|
||||
|
||||
match opts.deflate {
|
||||
Deflaters::Libdeflater { compression } if compression <= eval_compression => {
|
||||
// No further compression required
|
||||
Some((result.filter, result.idat_data))
|
||||
}
|
||||
_ => {
|
||||
debug!("Trying: {}", result.filter);
|
||||
let best_size = AtomicMin::new(max_size);
|
||||
perform_trial(&result.filtered, opts, result.filter, &best_size)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Perform full compression trials of selected filters and determine the best
|
||||
|
||||
if filters.is_empty() {
|
||||
// Pick a filter automatically
|
||||
if png.ihdr.bit_depth as u8 >= 8 {
|
||||
// Bigrams is the best all-rounder when there's at least one byte per pixel
|
||||
filters.insert(RowFilter::Bigrams);
|
||||
} else {
|
||||
// Otherwise delta filters generally don't work well, so just stick with None
|
||||
filters.insert(RowFilter::None);
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Trying: {} filters", filters.len());
|
||||
|
||||
let best_size = AtomicMin::new(max_size);
|
||||
let results_iter = filters.into_par_iter().with_max_len(1);
|
||||
let best = results_iter.filter_map(|filter| {
|
||||
if deadline.passed() {
|
||||
return None;
|
||||
}
|
||||
let filtered = &png.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
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
if let Some((filter, idat_data)) = best {
|
||||
let image = PngData {
|
||||
raw: png,
|
||||
idat_data,
|
||||
aux_chunks: Vec::new(),
|
||||
frames: Vec::new(),
|
||||
filter: Some(filter),
|
||||
};
|
||||
if image.estimated_output_size() < max_size.unwrap_or(usize::MAX) {
|
||||
debug!("Found better combination:");
|
||||
debug!(
|
||||
" zc = {} f = {:8} {} bytes",
|
||||
opts.deflate,
|
||||
filter,
|
||||
image.idat_data.len()
|
||||
);
|
||||
return Some(image);
|
||||
}
|
||||
}
|
||||
} else if let Some(result) = eval_result {
|
||||
let mut deflater = opts.deflate;
|
||||
let mut png = if opts.idat_recoding || reduction_occurred {
|
||||
perform_trials(
|
||||
new_image.clone(),
|
||||
opts,
|
||||
deadline.clone(),
|
||||
max_size,
|
||||
eval_result,
|
||||
eval_filters,
|
||||
eval_deflater,
|
||||
)
|
||||
.map(|(filter, idat_data)| PngData {
|
||||
raw: new_image,
|
||||
idat_data,
|
||||
aux_chunks: Vec::new(),
|
||||
frames: Vec::new(),
|
||||
filter: Some(filter),
|
||||
})
|
||||
} else {
|
||||
// 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.
|
||||
let image = PngData {
|
||||
deflater = eval_deflater;
|
||||
eval_result.map(|result| PngData {
|
||||
raw: result.image,
|
||||
idat_data: result.idat_data,
|
||||
aux_chunks: Vec::new(),
|
||||
frames: Vec::new(),
|
||||
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
|
||||
}
|
||||
|
||||
/// Perform compression trials
|
||||
fn perform_trials(
|
||||
image: Arc<PngImage>,
|
||||
opts: &Options,
|
||||
deadline: Arc<Deadline>,
|
||||
max_size: Option<usize>,
|
||||
mut eval_result: Option<Candidate>,
|
||||
eval_filters: IndexSet<RowFilter>,
|
||||
eval_deflater: Deflaters,
|
||||
) -> Option<TrialResult> {
|
||||
let mut filters = opts.filter.clone();
|
||||
let fast_eval = opts.fast_evaluation && (filters.len() > 1 || eval_result.is_some());
|
||||
if fast_eval {
|
||||
// Perform a fast evaluation of selected filters followed by a single main compression trial
|
||||
|
||||
if eval_result.is_some() {
|
||||
// Some filters have already been evaluated, we don't need to try them again
|
||||
filters = filters.difference(&eval_filters).copied().collect();
|
||||
}
|
||||
|
||||
if !filters.is_empty() {
|
||||
trace!("Evaluating: {} filters", filters.len());
|
||||
let eval = Evaluator::new(deadline, filters, eval_deflater, opts.optimize_alpha);
|
||||
if let Some(ref result) = eval_result {
|
||||
eval.set_best_size(result.idat_data.len());
|
||||
}
|
||||
eval.try_image(image.clone());
|
||||
if let Some(result) = eval.get_best_candidate() {
|
||||
eval_result = Some(result);
|
||||
}
|
||||
}
|
||||
// We should have a result here - fail if not (e.g. deadline passed)
|
||||
let result = eval_result?;
|
||||
|
||||
return if opts.deflate == eval_deflater {
|
||||
// No further compression required
|
||||
Some((result.filter, result.idat_data))
|
||||
} else {
|
||||
debug!("Trying: {}", result.filter);
|
||||
let best_size = AtomicMin::new(max_size);
|
||||
perform_trial(&result.filtered, opts, result.filter, &best_size)
|
||||
};
|
||||
if image.estimated_output_size() < max_size.unwrap_or(usize::MAX) {
|
||||
debug!("Found better combination:");
|
||||
debug!(
|
||||
" zc = {} f = {:8} {} bytes",
|
||||
eval_compression,
|
||||
result.filter,
|
||||
image.idat_data.len()
|
||||
);
|
||||
return Some(image);
|
||||
}
|
||||
|
||||
// Perform full compression trials of selected filters and determine the best
|
||||
|
||||
if filters.is_empty() {
|
||||
// Pick a filter automatically
|
||||
if image.ihdr.bit_depth as u8 >= 8 {
|
||||
// Bigrams is the best all-rounder when there's at least one byte per pixel
|
||||
filters.insert(RowFilter::Bigrams);
|
||||
} else {
|
||||
// Otherwise delta filters generally don't work well, so just stick with None
|
||||
filters.insert(RowFilter::None);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
debug!("Trying: {} filters", filters.len());
|
||||
|
||||
let best_size = AtomicMin::new(max_size);
|
||||
let results_iter = filters.into_par_iter().with_max_len(1);
|
||||
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
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ fn verbose_mode() {
|
|||
" IDAT size = 113794 bytes",
|
||||
" File size = 114708 bytes",
|
||||
"Trying: 1 filters",
|
||||
"Found better combination:",
|
||||
"Found better result:",
|
||||
" zc = 11 f = None ",
|
||||
" IDAT size = ",
|
||||
" file size = ",
|
||||
|
|
|
|||
Loading…
Reference in a new issue