Dynamically set eval filters

This commit is contained in:
Andrew 2022-11-12 15:14:24 +13:00 committed by Josh Holmer
parent a330998e4e
commit 301b413dc5
2 changed files with 18 additions and 15 deletions

View file

@ -11,6 +11,7 @@ use crate::rayon;
use crate::Deadline; use crate::Deadline;
#[cfg(feature = "parallel")] #[cfg(feature = "parallel")]
use crossbeam_channel::{unbounded, Receiver, Sender}; use crossbeam_channel::{unbounded, Receiver, Sender};
use indexmap::IndexSet;
use rayon::prelude::*; use rayon::prelude::*;
#[cfg(not(feature = "parallel"))] #[cfg(not(feature = "parallel"))]
use std::cell::RefCell; use std::cell::RefCell;
@ -18,10 +19,6 @@ use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::SeqCst; use std::sync::atomic::Ordering::SeqCst;
use std::sync::Arc; use std::sync::Arc;
/// Must use normal (lazy) compression, as faster ones (greedy) are not representative
const STD_COMPRESSION: u8 = 5;
const STD_FILTERS: [RowFilter; 2] = [RowFilter::None, RowFilter::MinSum];
pub struct Candidate { pub struct Candidate {
pub image: PngData, pub image: PngData,
pub filter: RowFilter, pub filter: RowFilter,
@ -45,6 +42,8 @@ impl Candidate {
/// Collect image versions and pick one that compresses best /// Collect image versions and pick one that compresses best
pub(crate) struct Evaluator { pub(crate) struct Evaluator {
deadline: Arc<Deadline>, deadline: Arc<Deadline>,
filters: IndexSet<RowFilter>,
compression: u8,
nth: AtomicUsize, nth: AtomicUsize,
best_candidate_size: Arc<AtomicMin>, best_candidate_size: Arc<AtomicMin>,
/// images are sent to the caller thread for evaluation /// images are sent to the caller thread for evaluation
@ -56,11 +55,13 @@ pub(crate) struct Evaluator {
} }
impl Evaluator { impl Evaluator {
pub fn new(deadline: Arc<Deadline>) -> Self { pub fn new(deadline: Arc<Deadline>, filters: IndexSet<RowFilter>, compression: u8) -> Self {
#[cfg(feature = "parallel")] #[cfg(feature = "parallel")]
let eval_channel = unbounded(); let eval_channel = unbounded();
Self { Self {
deadline, deadline,
filters,
compression,
best_candidate_size: Arc::new(AtomicMin::new(None)), best_candidate_size: Arc::new(AtomicMin::new(None)),
nth: AtomicUsize::new(0), nth: AtomicUsize::new(0),
#[cfg(feature = "parallel")] #[cfg(feature = "parallel")]
@ -98,13 +99,15 @@ impl Evaluator {
let nth = self.nth.fetch_add(1, SeqCst); let nth = self.nth.fetch_add(1, SeqCst);
// 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 compression = self.compression;
let best_candidate_size = self.best_candidate_size.clone(); let best_candidate_size = self.best_candidate_size.clone();
// sends it off asynchronously for compression, // sends it off asynchronously for compression,
// but results will be collected via the message queue // but results will be collected via the message queue
#[cfg(feature = "parallel")] #[cfg(feature = "parallel")]
let eval_send = self.eval_channel.0.clone(); let eval_send = self.eval_channel.0.clone();
rayon::spawn(move || { rayon::spawn(move || {
let filters_iter = STD_FILTERS.par_iter().with_max_len(1); let filters_iter = filters.par_iter().with_max_len(1);
// Updating of best result inside the parallel loop would require locks, // Updating of best result inside the parallel loop would require locks,
// which are dangerous to do in side Rayon's loop. // which are dangerous to do in side Rayon's loop.
@ -116,7 +119,7 @@ impl Evaluator {
} }
if let Ok(idat_data) = deflate::deflate( if let Ok(idat_data) = deflate::deflate(
&image.filter_image(filter), &image.filter_image(filter),
STD_COMPRESSION, compression,
&best_candidate_size, &best_candidate_size,
) { ) {
best_candidate_size.set_min(idat_data.len()); best_candidate_size.set_min(idat_data.len());

View file

@ -484,22 +484,22 @@ fn optimize_png(
let mut filter = opts.filter.clone(); let mut filter = opts.filter.clone();
if opts.use_heuristics { if filter.is_empty() {
// Heuristically determine which set of options to use // Heuristically determine which set of options to use
let use_filter = if png.raw.ihdr.bit_depth.as_u8() >= 8 if png.raw.ihdr.bit_depth.as_u8() >= 8
&& png.raw.ihdr.color_type != colors::ColorType::Indexed && png.raw.ihdr.color_type != colors::ColorType::Indexed
{ {
RowFilter::MinSum filter.insert(RowFilter::MinSum);
} else { } else {
RowFilter::None filter.insert(RowFilter::None);
}; };
if filter.is_empty() {
filter.insert(use_filter);
}
} }
// Must use normal (lazy) compression, as faster ones (greedy) are not representative
let eval_compression = 5;
let eval_filters = indexset! {RowFilter::None, RowFilter::MinSum};
// 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(deadline.clone()); let eval = Evaluator::new(deadline.clone(), eval_filters, eval_compression);
perform_reductions(png.raw.clone(), opts, &deadline, &eval); perform_reductions(png.raw.clone(), opts, &deadline, &eval);
let reduction_occurred = if let Some(result) = eval.get_best_candidate() { let reduction_occurred = if let Some(result) = eval.get_best_candidate() {
*png = result.image; *png = result.image;