Fudge factor for reductions to prefer better reductions even if gzip estimation says otherwise
This commit is contained in:
parent
1f9ed834bd
commit
c5959d3a53
3 changed files with 21 additions and 18 deletions
|
|
@ -19,7 +19,7 @@ use std::thread;
|
|||
/// Collect image versions and pick one that compresses best
|
||||
pub struct Evaluator {
|
||||
/// images are sent to the thread for evaluation
|
||||
eval_send: Option<SyncSender<(Arc<PngImage>, bool)>>,
|
||||
eval_send: Option<SyncSender<(Arc<PngImage>, f32, bool)>>,
|
||||
// the thread helps evaluate images asynchronously
|
||||
eval_thread: thread::JoinHandle<Option<PngData>>,
|
||||
}
|
||||
|
|
@ -43,24 +43,26 @@ impl Evaluator {
|
|||
|
||||
/// Set baseline image. It will be used only to measure minimum compression level required
|
||||
pub fn set_baseline(&self, image: Arc<PngImage>) {
|
||||
self.try_image_inner(image, false)
|
||||
self.try_image_inner(image, 1.0, false)
|
||||
}
|
||||
|
||||
/// Check if the image is smaller than others
|
||||
pub fn try_image(&self, image: Arc<PngImage>) {
|
||||
self.try_image_inner(image, true)
|
||||
/// Bias is a value in 0..=1 range. Compressed size is multiplied by
|
||||
/// this fraction when comparing to the best, so 0.95 allows 5% larger size.
|
||||
pub fn try_image(&self, image: Arc<PngImage>, bias: f32) {
|
||||
self.try_image_inner(image, bias, true)
|
||||
}
|
||||
|
||||
fn try_image_inner(&self, image: Arc<PngImage>, is_reduction: bool) {
|
||||
self.eval_send.as_ref().expect("not finished yet").send((image, is_reduction)).expect("send")
|
||||
fn try_image_inner(&self, image: Arc<PngImage>, bias: f32, is_reduction: bool) {
|
||||
self.eval_send.as_ref().expect("not finished yet").send((image, bias, is_reduction)).expect("send")
|
||||
}
|
||||
|
||||
/// Main loop of evaluation thread
|
||||
fn evaluate_images(from_channel: Receiver<(Arc<PngImage>, bool)>) -> Option<PngData> {
|
||||
fn evaluate_images(from_channel: Receiver<(Arc<PngImage>, f32, bool)>) -> Option<PngData> {
|
||||
let best_candidate_size = AtomicMin::new(None);
|
||||
let best_result: Mutex<Option<(PngData, _, _)>> = Mutex::new(None);
|
||||
// ends when sender is dropped
|
||||
for (nth, (image, is_reduction)) in from_channel.iter().enumerate() {
|
||||
for (nth, (image, bias, is_reduction)) in from_channel.iter().enumerate() {
|
||||
#[cfg(feature = "parallel")]
|
||||
let filters_iter = STD_FILTERS.par_iter().with_max_len(1);
|
||||
#[cfg(not(feature = "parallel"))]
|
||||
|
|
@ -75,15 +77,16 @@ impl Evaluator {
|
|||
&best_candidate_size,
|
||||
) {
|
||||
let mut res = best_result.lock().unwrap();
|
||||
if best_candidate_size.get().map_or(true, |best_len| {
|
||||
if best_candidate_size.get().map_or(true, |old_best_len| {
|
||||
let new_len = (idat_data.len() as f64 * bias as f64) as usize;
|
||||
// a tie-breaker is required to make evaluation deterministic
|
||||
if let Some(res) = res.as_ref() {
|
||||
// choose smallest compressed, or if compresses the same, smallest uncompressed, or cheaper filter
|
||||
let old_img = &res.0.raw;
|
||||
let new = (idat_data.len(), image.data.len(), image.ihdr.bit_depth, f, nth);
|
||||
let old = (best_len, old_img.data.len(), old_img.ihdr.bit_depth, res.1, res.2);
|
||||
let new = (new_len, image.data.len(), image.ihdr.bit_depth, f, nth);
|
||||
let old = (old_best_len, old_img.data.len(), old_img.ihdr.bit_depth, res.1, res.2);
|
||||
new < old
|
||||
} else if best_len > idat_data.len() {
|
||||
} else if new_len < old_best_len {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
|
|
|||
10
src/lib.rs
10
src/lib.rs
|
|
@ -747,7 +747,7 @@ fn perform_reductions(mut png: Arc<PngImage>, opts: &Options, deadline: &Deadlin
|
|||
if let Some(interlacing) = opts.interlace {
|
||||
if let Some(reduced) = png.change_interlacing(interlacing) {
|
||||
png = Arc::new(reduced);
|
||||
eval.try_image(png.clone());
|
||||
eval.try_image(png.clone(), 0.);
|
||||
}
|
||||
if deadline.passed() {
|
||||
return;
|
||||
|
|
@ -757,7 +757,7 @@ fn perform_reductions(mut png: Arc<PngImage>, opts: &Options, deadline: &Deadlin
|
|||
if opts.palette_reduction {
|
||||
if let Some(reduced) = reduced_palette(&png) {
|
||||
png = Arc::new(reduced);
|
||||
eval.try_image(png.clone());
|
||||
eval.try_image(png.clone(), 0.95);
|
||||
if opts.verbosity == Some(1) {
|
||||
report_reduction(&png);
|
||||
}
|
||||
|
|
@ -772,11 +772,11 @@ fn perform_reductions(mut png: Arc<PngImage>, opts: &Options, deadline: &Deadlin
|
|||
let previous = png.clone();
|
||||
let bits = reduced.ihdr.bit_depth;
|
||||
png = Arc::new(reduced);
|
||||
eval.try_image(png.clone());
|
||||
eval.try_image(png.clone(), 1.0);
|
||||
if (bits == BitDepth::One || bits == BitDepth::Two) && previous.ihdr.bit_depth != BitDepth::Four {
|
||||
// Also try 16-color mode for all lower bits images, since that may compress better
|
||||
if let Some(reduced) = reduce_bit_depth(&previous, 4) {
|
||||
eval.try_image(Arc::new(reduced));
|
||||
eval.try_image(Arc::new(reduced), 0.98);
|
||||
}
|
||||
}
|
||||
if opts.verbosity == Some(1) {
|
||||
|
|
@ -791,7 +791,7 @@ fn perform_reductions(mut png: Arc<PngImage>, opts: &Options, deadline: &Deadlin
|
|||
if opts.color_type_reduction {
|
||||
if let Some(reduced) = reduce_color_type(&png) {
|
||||
png = Arc::new(reduced);
|
||||
eval.try_image(png.clone());
|
||||
eval.try_image(png.clone(), 0.96);
|
||||
if opts.verbosity == Some(1) {
|
||||
report_reduction(&png);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ pub fn try_alpha_reductions(png: Arc<PngImage>, alphas: &HashSet<AlphaOptim>, ev
|
|||
let alphas_iter = alphas.iter();
|
||||
alphas_iter
|
||||
.filter_map(|&alpha| filtered_alpha_channel(&png, *alpha))
|
||||
.for_each(|image| eval.try_image(Arc::new(image)));
|
||||
.for_each(|image| eval.try_image(Arc::new(image), 0.99));
|
||||
}
|
||||
|
||||
pub fn filtered_alpha_channel(png: &PngImage, optim: AlphaOptim) -> Option<PngImage> {
|
||||
|
|
|
|||
Loading…
Reference in a new issue