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")]
|
#[cfg(feature = "parallel")]
|
||||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||||
|
use deflate::Deflaters;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
|
@ -43,7 +44,7 @@ impl Candidate {
|
||||||
pub(crate) struct Evaluator {
|
pub(crate) struct Evaluator {
|
||||||
deadline: Arc<Deadline>,
|
deadline: Arc<Deadline>,
|
||||||
filters: IndexSet<RowFilter>,
|
filters: IndexSet<RowFilter>,
|
||||||
compression: u8,
|
deflater: Deflaters,
|
||||||
optimize_alpha: bool,
|
optimize_alpha: bool,
|
||||||
nth: AtomicUsize,
|
nth: AtomicUsize,
|
||||||
executed: Arc<AtomicUsize>,
|
executed: Arc<AtomicUsize>,
|
||||||
|
|
@ -60,7 +61,7 @@ impl Evaluator {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
deadline: Arc<Deadline>,
|
deadline: Arc<Deadline>,
|
||||||
filters: IndexSet<RowFilter>,
|
filters: IndexSet<RowFilter>,
|
||||||
compression: u8,
|
deflater: Deflaters,
|
||||||
optimize_alpha: bool,
|
optimize_alpha: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
|
|
@ -68,7 +69,7 @@ impl Evaluator {
|
||||||
Self {
|
Self {
|
||||||
deadline,
|
deadline,
|
||||||
filters,
|
filters,
|
||||||
compression,
|
deflater,
|
||||||
optimize_alpha,
|
optimize_alpha,
|
||||||
nth: AtomicUsize::new(0),
|
nth: AtomicUsize::new(0),
|
||||||
executed: Arc::new(AtomicUsize::new(0)),
|
executed: Arc::new(AtomicUsize::new(0)),
|
||||||
|
|
@ -118,7 +119,7 @@ impl Evaluator {
|
||||||
// These clones are only cheap refcounts
|
// These clones are only cheap refcounts
|
||||||
let deadline = self.deadline.clone();
|
let deadline = self.deadline.clone();
|
||||||
let filters = self.filters.clone();
|
let filters = self.filters.clone();
|
||||||
let compression = self.compression;
|
let deflater = self.deflater;
|
||||||
let optimize_alpha = self.optimize_alpha;
|
let optimize_alpha = self.optimize_alpha;
|
||||||
let executed = self.executed.clone();
|
let executed = self.executed.clone();
|
||||||
let best_candidate_size = self.best_candidate_size.clone();
|
let best_candidate_size = self.best_candidate_size.clone();
|
||||||
|
|
@ -140,7 +141,7 @@ impl Evaluator {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let filtered = image.filter_image(filter, optimize_alpha);
|
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 {
|
if let Ok(idat_data) = idat_data {
|
||||||
let size = idat_data.len() + image.key_chunks_size();
|
let size = idat_data.len() + image.key_chunks_size();
|
||||||
best_candidate_size.set_min(size);
|
best_candidate_size.set_min(size);
|
||||||
|
|
|
||||||
145
src/lib.rs
145
src/lib.rs
|
|
@ -44,7 +44,7 @@ pub use rgb::{RGB16, RGBA8};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
atomicmin::AtomicMin,
|
atomicmin::AtomicMin,
|
||||||
evaluate::Evaluator,
|
evaluate::{Candidate, Evaluator},
|
||||||
headers::*,
|
headers::*,
|
||||||
png::{PngData, PngImage},
|
png::{PngData, PngImage},
|
||||||
reduction::*,
|
reduction::*,
|
||||||
|
|
@ -425,12 +425,13 @@ fn optimize_raw(
|
||||||
// 8 is a little slower but not noticeably when used only for reductions (o3 and higher)
|
// 8 is a little slower but not noticeably when used only for reductions (o3 and higher)
|
||||||
// 9 is not appreciably better than 8
|
// 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
|
// 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 } => {
|
Deflaters::Libdeflater { compression } => {
|
||||||
if opts.fast_evaluation { 7 } else { 8 }.min(compression)
|
if opts.fast_evaluation { 7 } else { 8 }.min(compression)
|
||||||
}
|
}
|
||||||
_ => 8,
|
_ => 8,
|
||||||
};
|
};
|
||||||
|
let eval_deflater = Deflaters::Libdeflater { compression };
|
||||||
// If only one filter is selected, use this for evaluations
|
// If only one filter is selected, use this for evaluations
|
||||||
let eval_filters = if opts.filter.len() == 1 {
|
let eval_filters = if opts.filter.len() == 1 {
|
||||||
opts.filter.clone()
|
opts.filter.clone()
|
||||||
|
|
@ -439,29 +440,77 @@ fn optimize_raw(
|
||||||
indexset! {RowFilter::None, RowFilter::Bigrams}
|
indexset! {RowFilter::None, RowFilter::Bigrams}
|
||||||
};
|
};
|
||||||
// This will collect all versions of images and pick one that compresses best
|
// This will collect all versions of images and pick one that compresses best
|
||||||
let eval = Evaluator::new(
|
let eval = Evaluator::new(deadline.clone(), eval_filters.clone(), eval_deflater, false);
|
||||||
deadline.clone(),
|
let mut new_image = perform_reductions(image.clone(), opts, &deadline, &eval);
|
||||||
eval_filters.clone(),
|
let eval_result = eval.get_best_candidate();
|
||||||
eval_compression,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
let mut png = perform_reductions(image.clone(), opts, &deadline, &eval);
|
|
||||||
let mut eval_result = eval.get_best_candidate();
|
|
||||||
if let Some(ref result) = eval_result {
|
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
|
let reduction_occurred = new_image.ihdr.color_type != image.ihdr.color_type
|
||||||
|| png.ihdr.bit_depth != image.ihdr.bit_depth
|
|| new_image.ihdr.bit_depth != image.ihdr.bit_depth
|
||||||
|| png.ihdr.interlaced != image.ihdr.interlaced;
|
|| new_image.ihdr.interlaced != image.ihdr.interlaced;
|
||||||
|
|
||||||
if reduction_occurred {
|
if reduction_occurred {
|
||||||
report_format("Transformed image to ", &png);
|
report_format("Transformed image to ", &new_image);
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.idat_recoding || reduction_occurred {
|
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.
|
||||||
|
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 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());
|
||||||
let best: Option<TrialResult> = if fast_eval {
|
if fast_eval {
|
||||||
// Perform a fast evaluation of selected filters followed by a single main compression trial
|
// Perform a fast evaluation of selected filters followed by a single main compression trial
|
||||||
|
|
||||||
if eval_result.is_some() {
|
if eval_result.is_some() {
|
||||||
|
|
@ -471,11 +520,11 @@ fn optimize_raw(
|
||||||
|
|
||||||
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_compression, opts.optimize_alpha);
|
let eval = Evaluator::new(deadline, filters, eval_deflater, opts.optimize_alpha);
|
||||||
if let Some(ref result) = eval_result {
|
if let Some(ref result) = eval_result {
|
||||||
eval.set_best_size(result.idat_data.len());
|
eval.set_best_size(result.idat_data.len());
|
||||||
}
|
}
|
||||||
eval.try_image(png.clone());
|
eval.try_image(image.clone());
|
||||||
if let Some(result) = eval.get_best_candidate() {
|
if let Some(result) = eval.get_best_candidate() {
|
||||||
eval_result = Some(result);
|
eval_result = Some(result);
|
||||||
}
|
}
|
||||||
|
|
@ -483,23 +532,21 @@ fn optimize_raw(
|
||||||
// We should have a result here - fail if not (e.g. deadline passed)
|
// We should have a result here - fail if not (e.g. deadline passed)
|
||||||
let result = eval_result?;
|
let result = eval_result?;
|
||||||
|
|
||||||
match opts.deflate {
|
return if opts.deflate == eval_deflater {
|
||||||
Deflaters::Libdeflater { compression } if compression <= eval_compression => {
|
|
||||||
// No further compression required
|
// No further compression required
|
||||||
Some((result.filter, result.idat_data))
|
Some((result.filter, result.idat_data))
|
||||||
}
|
} else {
|
||||||
_ => {
|
|
||||||
debug!("Trying: {}", result.filter);
|
debug!("Trying: {}", result.filter);
|
||||||
let best_size = AtomicMin::new(max_size);
|
let best_size = AtomicMin::new(max_size);
|
||||||
perform_trial(&result.filtered, opts, result.filter, &best_size)
|
perform_trial(&result.filtered, opts, result.filter, &best_size)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Perform full compression trials of selected filters and determine the best
|
// Perform full compression trials of selected filters and determine the best
|
||||||
|
|
||||||
if filters.is_empty() {
|
if filters.is_empty() {
|
||||||
// Pick a filter automatically
|
// Pick a filter automatically
|
||||||
if png.ihdr.bit_depth as u8 >= 8 {
|
if image.ihdr.bit_depth as u8 >= 8 {
|
||||||
// Bigrams is the best all-rounder when there's at least one byte per pixel
|
// Bigrams is the best all-rounder when there's at least one byte per pixel
|
||||||
filters.insert(RowFilter::Bigrams);
|
filters.insert(RowFilter::Bigrams);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -516,7 +563,7 @@ fn optimize_raw(
|
||||||
if deadline.passed() {
|
if deadline.passed() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let filtered = &png.filter_image(filter, opts.optimize_alpha);
|
let filtered = &image.filter_image(filter, opts.optimize_alpha);
|
||||||
perform_trial(filtered, opts, filter, &best_size)
|
perform_trial(filtered, opts, filter, &best_size)
|
||||||
});
|
});
|
||||||
best.reduce_with(|i, j| {
|
best.reduce_with(|i, j| {
|
||||||
|
|
@ -526,50 +573,6 @@ fn optimize_raw(
|
||||||
j
|
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 {
|
|
||||||
// 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 {
|
|
||||||
raw: result.image,
|
|
||||||
idat_data: result.idat_data,
|
|
||||||
aux_chunks: Vec::new(),
|
|
||||||
frames: Vec::new(),
|
|
||||||
filter: Some(result.filter),
|
|
||||||
};
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a compression trial
|
/// Execute a compression trial
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ fn verbose_mode() {
|
||||||
" IDAT size = 113794 bytes",
|
" IDAT size = 113794 bytes",
|
||||||
" File size = 114708 bytes",
|
" File size = 114708 bytes",
|
||||||
"Trying: 1 filters",
|
"Trying: 1 filters",
|
||||||
"Found better combination:",
|
"Found better result:",
|
||||||
" zc = 11 f = None ",
|
" zc = 11 f = None ",
|
||||||
" IDAT size = ",
|
" IDAT size = ",
|
||||||
" file size = ",
|
" file size = ",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue