diff --git a/benches/filters.rs b/benches/filters.rs index 6cf1e4cb..f010e2e7 100644 --- a/benches/filters.rs +++ b/benches/filters.rs @@ -13,7 +13,7 @@ fn filters_16_bits_filter_0(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::None, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::NONE, false)); } #[bench] @@ -21,7 +21,7 @@ fn filters_8_bits_filter_0(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::None, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::NONE, false)); } #[bench] @@ -31,7 +31,7 @@ fn filters_4_bits_filter_0(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::None, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::NONE, false)); } #[bench] @@ -41,7 +41,7 @@ fn filters_2_bits_filter_0(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::None, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::NONE, false)); } #[bench] @@ -51,7 +51,7 @@ fn filters_1_bits_filter_0(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::None, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::NONE, false)); } #[bench] @@ -59,7 +59,7 @@ fn filters_16_bits_filter_1(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Sub, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::SUB, false)); } #[bench] @@ -67,7 +67,7 @@ fn filters_8_bits_filter_1(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Sub, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::SUB, false)); } #[bench] @@ -77,7 +77,7 @@ fn filters_4_bits_filter_1(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Sub, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::SUB, false)); } #[bench] @@ -87,7 +87,7 @@ fn filters_2_bits_filter_1(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Sub, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::SUB, false)); } #[bench] @@ -97,7 +97,7 @@ fn filters_1_bits_filter_1(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Sub, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::SUB, false)); } #[bench] @@ -105,7 +105,7 @@ fn filters_16_bits_filter_2(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Up, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::UP, false)); } #[bench] @@ -113,7 +113,7 @@ fn filters_8_bits_filter_2(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Up, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::UP, false)); } #[bench] @@ -123,7 +123,7 @@ fn filters_4_bits_filter_2(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Up, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::UP, false)); } #[bench] @@ -133,7 +133,7 @@ fn filters_2_bits_filter_2(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Up, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::UP, false)); } #[bench] @@ -143,7 +143,7 @@ fn filters_1_bits_filter_2(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Up, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::UP, false)); } #[bench] @@ -151,7 +151,7 @@ fn filters_16_bits_filter_3(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Average, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::AVERAGE, false)); } #[bench] @@ -159,7 +159,7 @@ fn filters_8_bits_filter_3(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Average, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::AVERAGE, false)); } #[bench] @@ -169,7 +169,7 @@ fn filters_4_bits_filter_3(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Average, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::AVERAGE, false)); } #[bench] @@ -179,7 +179,7 @@ fn filters_2_bits_filter_3(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Average, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::AVERAGE, false)); } #[bench] @@ -189,7 +189,7 @@ fn filters_1_bits_filter_3(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Average, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::AVERAGE, false)); } #[bench] @@ -197,7 +197,7 @@ fn filters_16_bits_filter_4(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Paeth, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::PAETH, false)); } #[bench] @@ -205,7 +205,7 @@ fn filters_8_bits_filter_4(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Paeth, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::PAETH, false)); } #[bench] @@ -215,7 +215,7 @@ fn filters_4_bits_filter_4(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Paeth, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::PAETH, false)); } #[bench] @@ -225,7 +225,7 @@ fn filters_2_bits_filter_4(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Paeth, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::PAETH, false)); } #[bench] @@ -235,7 +235,7 @@ fn filters_1_bits_filter_4(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Paeth, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::PAETH, false)); } #[bench] @@ -243,7 +243,7 @@ fn filters_16_bits_filter_5(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::MinSum, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::MinSum, false)); } #[bench] @@ -251,7 +251,7 @@ fn filters_8_bits_filter_5(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::MinSum, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::MinSum, false)); } #[bench] @@ -261,7 +261,7 @@ fn filters_4_bits_filter_5(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::MinSum, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::MinSum, false)); } #[bench] @@ -271,7 +271,7 @@ fn filters_2_bits_filter_5(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::MinSum, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::MinSum, false)); } #[bench] @@ -281,5 +281,5 @@ fn filters_1_bits_filter_5(b: &mut Bencher) { )); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::MinSum, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::MinSum, false)); } diff --git a/benches/strategies.rs b/benches/strategies.rs index 15f546f1..72d19e90 100644 --- a/benches/strategies.rs +++ b/benches/strategies.rs @@ -13,7 +13,7 @@ fn filters_minsum(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::MinSum, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::MinSum, false)); } #[bench] @@ -21,7 +21,7 @@ fn filters_entropy(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Entropy, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::Entropy, false)); } #[bench] @@ -29,7 +29,7 @@ fn filters_bigrams(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Bigrams, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::Bigrams, false)); } #[bench] @@ -37,7 +37,7 @@ fn filters_bigent(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::BigEnt, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::BigEnt, false)); } #[bench] @@ -45,5 +45,5 @@ fn filters_brute(b: &mut Bencher) { let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png")); let png = PngData::new(&input, &Options::default()).unwrap(); - b.iter(|| png.raw.filter_image(RowFilter::Brute, false)); + b.iter(|| png.raw.filter_image(FilterStrategy::Brute, false)); } diff --git a/src/evaluate.rs b/src/evaluate.rs index af6c4070..9463cf6b 100644 --- a/src/evaluate.rs +++ b/src/evaluate.rs @@ -17,15 +17,20 @@ use rayon::prelude::*; #[cfg(not(feature = "parallel"))] use crate::rayon; -use crate::{Deadline, PngError, atomicmin::AtomicMin, deflate, filters::RowFilter, png::PngImage}; +use crate::{ + Deadline, PngError, atomicmin::AtomicMin, deflate, filters::FilterStrategy, png::PngImage, +}; pub(crate) struct Candidate { pub image: Arc, - pub data: Vec, - pub data_is_compressed: bool, + pub idat_data: Option>, pub estimated_output_size: usize, - pub filter: RowFilter, - // For determining tie-breaker + /// The input filter, which is retained for printing and for APNG frames. + pub filter: FilterStrategy, + /// The filter returned by the filter function, which may be Predefined. + /// Use this for the next round to avoid recomputing the filter. + pub filter_used: FilterStrategy, + /// For determining tie-breaker nth: usize, } @@ -34,7 +39,7 @@ impl Candidate { ( self.estimated_output_size, self.image.data.len(), - self.filter, + self.filter.clone(), // Prefer the later image added (e.g. baseline, which is always added last) usize::MAX - self.nth, ) @@ -44,7 +49,7 @@ impl Candidate { /// Collect image versions and pick one that compresses best pub(crate) struct Evaluator { deadline: Arc, - filters: IndexSet, + filters: IndexSet, deflater: Deflaters, optimize_alpha: bool, final_round: bool, @@ -62,7 +67,7 @@ pub(crate) struct Evaluator { impl Evaluator { pub fn new( deadline: Arc, - filters: IndexSet, + filters: IndexSet, deflater: Deflaters, optimize_alpha: bool, final_round: bool, @@ -141,21 +146,21 @@ impl Evaluator { // which are dangerous to do in side Rayon's loop. // Instead, only update (atomic) best size in real time, // and the best result later without need for locks. - filters_iter.for_each(|&filter| { + filters_iter.for_each(|filter| { if deadline.passed() { return; } - let filtered = image.filter_image(filter, optimize_alpha); + let (filtered, filter_used) = image.filter_image(filter.clone(), optimize_alpha); let idat_data = deflater.deflate(&filtered, best_candidate_size.get()); if let Ok(idat_data) = idat_data { let estimated_output_size = image.estimated_output_size(&idat_data); - // For the final round we need the IDAT data, otherwise the filtered data + // We only need to retain the IDAT data in the final round let new = Candidate { image: image.clone(), - data: if final_round { idat_data } else { filtered }, - data_is_compressed: final_round, + idat_data: if final_round { Some(idat_data) } else { None }, estimated_output_size, - filter, + filter: filter.clone(), + filter_used, nth, }; best_candidate_size.set_min(estimated_output_size); diff --git a/src/filters.rs b/src/filters.rs index d0b03b1d..26e93b68 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -1,30 +1,62 @@ use std::{fmt, fmt::Display, mem::transmute}; -use crate::error::PngError; - /// Filtering strategy for use in [`Options`][crate::Options] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)] +pub enum FilterStrategy { + /// Same filter for all rows + Basic(RowFilter), + /// Minimum sum of absolute differences + MinSum, + /// Shannon entropy + Entropy, + /// Count of distinct bigrams + Bigrams, + /// Shannon entropy of bigrams + BigEnt, + /// Deflate compression + Brute, + /// Predefined filter for each row + Predefined(Vec), +} + +impl FilterStrategy { + pub const NONE: Self = Self::Basic(RowFilter::None); + pub const SUB: Self = Self::Basic(RowFilter::Sub); + pub const UP: Self = Self::Basic(RowFilter::Up); + pub const AVERAGE: Self = Self::Basic(RowFilter::Average); + pub const PAETH: Self = Self::Basic(RowFilter::Paeth); +} + +impl Display for FilterStrategy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Basic(filter) => filter.fmt(f), + Self::MinSum => "MinSum".fmt(f), + Self::Entropy => "Entropy".fmt(f), + Self::Bigrams => "Bigrams".fmt(f), + Self::BigEnt => "BigEnt".fmt(f), + Self::Brute => "Brute".fmt(f), + Self::Predefined(_) => "Predefined".fmt(f), + } + } +} + +/// PNG delta filters #[repr(u8)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] pub enum RowFilter { - // Standard filter types None, Sub, Up, Average, Paeth, - // Heuristic strategies - MinSum, - Entropy, - Bigrams, - BigEnt, - Brute, } impl TryFrom for RowFilter { type Error = (); fn try_from(value: u8) -> Result { - if value > Self::LAST { + if value > 4 { return Err(()); } unsafe { transmute(value as i8) } @@ -40,11 +72,6 @@ impl Display for RowFilter { Self::Up => "Up", Self::Average => "Average", Self::Paeth => "Paeth", - Self::MinSum => "MinSum", - Self::Entropy => "Entropy", - Self::Bigrams => "Bigrams", - Self::BigEnt => "BigEnt", - Self::Brute => "Brute", }, f, ) @@ -52,9 +79,7 @@ impl Display for RowFilter { } impl RowFilter { - pub const LAST: u8 = Self::Brute as u8; - pub(crate) const STANDARD: [Self; 5] = - [Self::None, Self::Sub, Self::Up, Self::Average, Self::Paeth]; + pub(crate) const ALL: [Self; 5] = [Self::None, Self::Sub, Self::Up, Self::Average, Self::Paeth]; pub(crate) const SINGLE_LINE: [Self; 2] = [Self::None, Self::Sub]; pub(crate) fn filter_line( @@ -115,7 +140,6 @@ impl RowFilter { }); } } - _ => unreachable!(), } } @@ -140,6 +164,7 @@ impl RowFilter { _ => i - 1, }; match self { + Self::None => unreachable!(), Self::Sub => { for j in 0..color_bytes { pixels[i][j] = pixels[prev][j]; @@ -171,7 +196,6 @@ impl RowFilter { }; } } - _ => unreachable!(), } } } @@ -183,7 +207,7 @@ impl RowFilter { data: &[u8], prev_line: &[u8], buf: &mut Vec, - ) -> Result<(), PngError> { + ) { buf.clear(); buf.reserve(data.len()); assert!(data.len() >= bpp); @@ -232,9 +256,7 @@ impl RowFilter { ); } } - _ => return Err(PngError::InvalidData), } - Ok(()) } } diff --git a/src/lib.rs b/src/lib.rs index 1af2e8e3..d11c3ae3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,7 @@ pub use crate::{ colors::{BitDepth, ColorType}, deflate::Deflaters, error::PngError, - filters::RowFilter, + filters::{FilterStrategy, RowFilter}, headers::StripChunks, interlace::Interlacing, options::{InFile, Options, OutFile}, @@ -170,7 +170,7 @@ impl RawImage { let mut png = PngData { raw: result.image, - idat_data: result.data, + idat_data: result.idat_data.unwrap(), aux_chunks, frames: Vec::new(), }; @@ -359,7 +359,7 @@ fn optimize_png( }; if let Some(result) = optimize_raw(raw.clone(), &opts, deadline.clone(), max_size) { png.raw = result.image; - png.idat_data = result.data; + png.idat_data = result.idat_data.unwrap(); recompress_frames(png, &opts, deadline, result.filter)?; postprocess_chunks(&mut png.aux_chunks, &png.raw.ihdr, &raw.ihdr); } @@ -432,7 +432,7 @@ fn optimize_raw( opts.filter.clone() } else { // None and Bigrams work well together, especially for alpha reductions - indexset! {RowFilter::None, RowFilter::Bigrams} + indexset! {FilterStrategy::NONE, FilterStrategy::Bigrams} }; // This will collect all versions of images and pick one that compresses best let eval = Evaluator::new( @@ -472,7 +472,7 @@ fn optimize_raw( (eval_result?, eval_deflater) }; - if result.data_is_compressed + if result.idat_data.is_some() && max_size.is_none_or(|max_size| result.estimated_output_size < max_size) { debug!("Found better result:"); @@ -489,7 +489,7 @@ fn perform_trials( deadline: Arc, max_size: Option, mut eval_result: Option, - eval_filters: IndexSet, + eval_filters: IndexSet, eval_deflater: Deflaters, ) -> Option { let mut filters = opts.filter.clone(); @@ -499,7 +499,7 @@ fn perform_trials( 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(); + filters = filters.difference(&eval_filters).cloned().collect(); } if !filters.is_empty() { @@ -523,14 +523,14 @@ fn perform_trials( // We should have a result here - fail if not (e.g. deadline passed) let mut result = eval_result?; - if !result.data_is_compressed { + if result.idat_data.is_none() { // Compress with the main deflater debug!("Trying filter {} with {}", result.filter, opts.deflate); - match opts.deflate.deflate(&result.data, max_size) { + let (data, _) = image.filter_image(result.filter_used.clone(), opts.optimize_alpha); + match opts.deflate.deflate(&data, max_size) { Ok(idat_data) => { result.estimated_output_size = result.image.estimated_output_size(&idat_data); - result.data = idat_data; - result.data_is_compressed = true; + result.idat_data = Some(idat_data); trace!("{} bytes", result.estimated_output_size); } Err(PngError::DeflatedDataTooLong(bytes)) => { @@ -548,10 +548,10 @@ fn perform_trials( // 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); + filters.insert(FilterStrategy::Bigrams); } else { // Otherwise delta filters generally don't work well, so just stick with None - filters.insert(RowFilter::None); + filters.insert(FilterStrategy::NONE); } } @@ -627,11 +627,13 @@ fn recompress_frames( png: &mut PngData, opts: &Options, deadline: Arc, - filter: RowFilter, + filter: FilterStrategy, ) -> PngResult<()> { if !opts.idat_recoding || png.frames.is_empty() { return Ok(()); } + // Ensure we don't try to recompress frames with a predefined filter + debug_assert!(!matches!(filter, FilterStrategy::Predefined { .. })); png.frames .par_iter_mut() .with_max_len(1) @@ -644,7 +646,7 @@ fn recompress_frames( ihdr.width = frame.width; ihdr.height = frame.height; let image = PngImage::new(ihdr, &frame.data)?; - let filtered = image.filter_image(filter, opts.optimize_alpha); + let (filtered, _) = image.filter_image(filter.clone(), opts.optimize_alpha); let max_size = Some(frame.data.len() - 1); if let Ok(data) = opts.deflate.deflate(&filtered, max_size) { debug!( diff --git a/src/main.rs b/src/main.rs index 12794f44..17909628 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,7 @@ use clap::ArgMatches; mod cli; use indexmap::IndexSet; use log::{Level, LevelFilter, error, warn}; -use oxipng::{Deflaters, InFile, Options, OutFile, PngError, RowFilter, StripChunks}; +use oxipng::{Deflaters, FilterStrategy, InFile, Options, OutFile, PngError, StripChunks}; use rayon::prelude::*; use crate::cli::DISPLAY_CHUNKS; @@ -36,8 +36,7 @@ fn main() -> ExitCode { // Set the value parser for filters which isn't appropriate to do in the build_command function .mut_arg("filters", |arg| { arg.value_parser(|x: &str| { - parse_numeric_range_opts(x, 0, RowFilter::LAST) - .map_err(|_| "Invalid option for filters") + parse_numeric_range_opts(x, 0, 9).map_err(|_| "Invalid option for filters") }) }) .after_help("Run `oxipng --help` to see full details of all options") @@ -199,10 +198,18 @@ fn parse_opts_into_struct( }; if let Some(x) = matches.get_one::>("filters") { - opts.filter.clear(); - for &f in x { - opts.filter.insert(f.try_into().unwrap()); - } + opts.filter = x + .iter() + .map(|&f| match f { + 0..=4 => FilterStrategy::Basic(f.try_into().unwrap()), + 5 => FilterStrategy::MinSum, + 6 => FilterStrategy::Entropy, + 7 => FilterStrategy::Bigrams, + 8 => FilterStrategy::BigEnt, + 9 => FilterStrategy::Brute, + _ => unreachable!(), + }) + .collect(); } if let Some(&num) = matches.get_one::("timeout") { diff --git a/src/options.rs b/src/options.rs index 60353f82..48f30141 100644 --- a/src/options.rs +++ b/src/options.rs @@ -7,7 +7,9 @@ use std::{ use indexmap::{IndexSet, indexset}; use log::warn; -use crate::{deflate::Deflaters, filters::RowFilter, headers::StripChunks, interlace::Interlacing}; +use crate::{ + deflate::Deflaters, filters::FilterStrategy, headers::StripChunks, interlace::Interlacing, +}; /// Write destination for [`optimize`][crate::optimize]. /// You can use [`optimize_from_memory`](crate::optimize_from_memory) to avoid external I/O. @@ -94,10 +96,10 @@ pub struct Options { /// /// Default: `false` pub force: bool, - /// Which `RowFilters` to try on the file + /// Which `FilterStrategy` to try on the file /// /// Default: `None,Sub,Entropy,Bigrams` - pub filter: IndexSet, + pub filter: IndexSet, /// Whether to change the interlacing type of the file. /// /// These are the interlacing types avaliable: @@ -207,10 +209,10 @@ impl Options { fn apply_preset_3(mut self) -> Self { self.fast_evaluation = false; self.filter = indexset! { - RowFilter::None, - RowFilter::Bigrams, - RowFilter::BigEnt, - RowFilter::Brute + FilterStrategy::NONE, + FilterStrategy::Bigrams, + FilterStrategy::BigEnt, + FilterStrategy::Brute }; self } @@ -224,10 +226,10 @@ impl Options { fn apply_preset_5(mut self) -> Self { self.fast_evaluation = false; - self.filter.insert(RowFilter::Up); - self.filter.insert(RowFilter::MinSum); - self.filter.insert(RowFilter::BigEnt); - self.filter.insert(RowFilter::Brute); + self.filter.insert(FilterStrategy::UP); + self.filter.insert(FilterStrategy::MinSum); + self.filter.insert(FilterStrategy::BigEnt); + self.filter.insert(FilterStrategy::Brute); if let Deflaters::Libdeflater { compression } = &mut self.deflate { *compression = 12; } @@ -235,8 +237,8 @@ impl Options { } fn apply_preset_6(mut self) -> Self { - self.filter.insert(RowFilter::Average); - self.filter.insert(RowFilter::Paeth); + self.filter.insert(FilterStrategy::AVERAGE); + self.filter.insert(FilterStrategy::PAETH); self.apply_preset_5() } } @@ -247,7 +249,12 @@ impl Default for Options { Self { fix_errors: false, force: false, - filter: indexset! {RowFilter::None, RowFilter::Sub, RowFilter::Entropy, RowFilter::Bigrams}, + filter: indexset! { + FilterStrategy::NONE, + FilterStrategy::SUB, + FilterStrategy::Entropy, + FilterStrategy::Bigrams + }, interlace: Some(Interlacing::None), optimize_alpha: false, bit_depth_reduction: true, diff --git a/src/png/mod.rs b/src/png/mod.rs index 66ba002d..c13c761e 100644 --- a/src/png/mod.rs +++ b/src/png/mod.rs @@ -376,7 +376,7 @@ impl PngImage { } last_line.resize(line.data.len(), 0); let filter = RowFilter::try_from(line.filter).map_err(|_| PngError::InvalidData)?; - filter.unfilter_line(bpp, line.data, &last_line, &mut unfiltered_buf)?; + filter.unfilter_line(bpp, line.data, &last_line, &mut unfiltered_buf); unfiltered.extend_from_slice(&unfiltered_buf); std::mem::swap(&mut last_line, &mut unfiltered_buf); unfiltered_buf.clear(); @@ -386,7 +386,11 @@ impl PngImage { /// Apply the specified filter type to all rows in the image #[must_use] - pub fn filter_image(&self, filter: RowFilter, optimize_alpha: bool) -> Vec { + pub fn filter_image( + &self, + strategy: FilterStrategy, + optimize_alpha: bool, + ) -> (Vec, FilterStrategy) { let mut filtered = Vec::with_capacity(self.data.len()); let bpp = self.bytes_per_channel() * self.channels_per_pixel(); // If alpha optimization is enabled, determine how many bytes of alpha there are per pixel @@ -399,31 +403,39 @@ impl PngImage { let mut prev_line = Vec::new(); let mut prev_pass: Option = None; let mut f_buf = Vec::new(); - for line in self.scan_lines(false) { + // For heuristic strategies, keep track of the actual filter used for each line + let mut filters_used = Vec::new(); + for (i, line) in self.scan_lines(false).enumerate() { if prev_pass != line.pass || line.data.len() != prev_line.len() { prev_line = vec![0; line.data.len()]; } // Alpha optimisation may alter the line data, so we need a mutable copy of it let mut line_data = line.data.to_vec(); - if filter <= RowFilter::Paeth { + if let FilterStrategy::Basic(mut filter) = strategy { // Standard filters - let filter = if prev_pass == line.pass || filter <= RowFilter::Sub { - filter - } else { - RowFilter::None - }; + if prev_pass != line.pass && filter > RowFilter::Sub { + filter = RowFilter::None; + } + filter.filter_line(bpp, &mut line_data, &prev_line, &mut f_buf, alpha_bytes); + filtered.extend_from_slice(&f_buf); + prev_line = line_data; + } else if let FilterStrategy::Predefined(lines) = &strategy { + // Predefined filter for each line + let filter = lines.get(i).unwrap_or(&RowFilter::None); filter.filter_line(bpp, &mut line_data, &prev_line, &mut f_buf, alpha_bytes); filtered.extend_from_slice(&f_buf); prev_line = line_data; } else { // Heuristic filter selection strategies + let mut best_filter = RowFilter::None; if line_data.iter().all(|&x| x == 0) { // Assume None if the line is all zeros - filtered.push(RowFilter::None as u8); + filtered.push(best_filter as u8); filtered.extend_from_slice(&line_data); prev_line = line_data; + filters_used.push(best_filter); continue; } @@ -431,12 +443,12 @@ impl PngImage { let mut best_line_raw = Vec::new(); // Avoid vertical filtering on first line of each interlacing pass let try_filters = if prev_pass == line.pass { - RowFilter::STANDARD.iter() + RowFilter::ALL.iter() } else { RowFilter::SINGLE_LINE.iter() }; - match filter { - RowFilter::MinSum => { + match strategy { + FilterStrategy::MinSum => { // MSAD algorithm mentioned in libpng reference docs // http://www.libpng.org/pub/png/book/chapter09.html let mut best_size = usize::MAX; @@ -450,10 +462,11 @@ impl PngImage { best_size = size; std::mem::swap(&mut best_line, &mut f_buf); best_line_raw.clone_from(&line_data); + best_filter = *f; } } } - RowFilter::Entropy => { + FilterStrategy::Entropy => { // Shannon entropy algorithm, from LodePNG // https://github.com/lvandeve/lodepng let mut best_size = i32::MIN; @@ -473,10 +486,11 @@ impl PngImage { best_size = size; std::mem::swap(&mut best_line, &mut f_buf); best_line_raw.clone_from(&line_data); + best_filter = *f; } } } - RowFilter::Bigrams => { + FilterStrategy::Bigrams => { // Count distinct bigrams, from pngwolf // https://bjoern.hoehrmann.de/pngwolf/ let mut best_size = usize::MAX; @@ -492,10 +506,11 @@ impl PngImage { best_size = size; std::mem::swap(&mut best_line, &mut f_buf); best_line_raw.clone_from(&line_data); + best_filter = *f; } } } - RowFilter::BigEnt => { + FilterStrategy::BigEnt => { // Bigram entropy, combined from Entropy and Bigrams filters let mut best_size = i32::MIN; // FxHasher is the fastest rust hasher currently available for this purpose @@ -512,10 +527,11 @@ impl PngImage { best_size = size; std::mem::swap(&mut best_line, &mut f_buf); best_line_raw.clone_from(&line_data); + best_filter = *f; } } } - RowFilter::Brute => { + FilterStrategy::Brute => { // Brute force by compressing each filter attempt // Similar to that of LodePNG but includes some previous lines for context let mut best_size = usize::MAX; @@ -537,6 +553,7 @@ impl PngImage { best_size = size; std::mem::swap(&mut best_line, &mut f_buf); best_line_raw.clone_from(&line_data); + best_filter = *f; } } filtered.resize(line_start, 0); @@ -545,11 +562,17 @@ impl PngImage { } filtered.extend_from_slice(&best_line); prev_line = best_line_raw; + filters_used.push(best_filter); } prev_pass = line.pass; } - filtered + + if filters_used.is_empty() { + (filtered, strategy) + } else { + (filtered, FilterStrategy::Predefined(filters_used)) + } } } diff --git a/tests/filters.rs b/tests/filters.rs index 99984fda..3d696360 100644 --- a/tests/filters.rs +++ b/tests/filters.rs @@ -12,20 +12,17 @@ const GRAYSCALE_ALPHA: u8 = 4; const RGBA: u8 = 6; fn get_opts(input: &Path) -> (OutFile, oxipng::Options) { - let mut options = oxipng::Options { + let options = oxipng::Options { force: true, + filter: indexset! {FilterStrategy::NONE}, ..Default::default() }; - let mut filter = IndexSet::new(); - filter.insert(RowFilter::None); - options.filter = filter; - (OutFile::from_path(input.with_extension("out.png")), options) } fn test_it_converts( input: &str, - filter: RowFilter, + filter: FilterStrategy, color_type_in: u8, bit_depth_in: BitDepth, color_type_out: u8, @@ -68,7 +65,7 @@ fn test_it_converts( fn filter_0_for_rgba_16() { test_it_converts( "tests/files/filter_0_for_rgba_16.png", - RowFilter::None, + FilterStrategy::NONE, RGBA, BitDepth::Sixteen, RGBA, @@ -80,7 +77,7 @@ fn filter_0_for_rgba_16() { fn filter_1_for_rgba_16() { test_it_converts( "tests/files/filter_1_for_rgba_16.png", - RowFilter::Sub, + FilterStrategy::SUB, RGBA, BitDepth::Sixteen, RGBA, @@ -92,7 +89,7 @@ fn filter_1_for_rgba_16() { fn filter_2_for_rgba_16() { test_it_converts( "tests/files/filter_2_for_rgba_16.png", - RowFilter::Up, + FilterStrategy::UP, RGBA, BitDepth::Sixteen, RGBA, @@ -104,7 +101,7 @@ fn filter_2_for_rgba_16() { fn filter_3_for_rgba_16() { test_it_converts( "tests/files/filter_3_for_rgba_16.png", - RowFilter::Average, + FilterStrategy::AVERAGE, RGBA, BitDepth::Sixteen, RGBA, @@ -116,7 +113,7 @@ fn filter_3_for_rgba_16() { fn filter_4_for_rgba_16() { test_it_converts( "tests/files/filter_4_for_rgba_16.png", - RowFilter::Paeth, + FilterStrategy::PAETH, RGBA, BitDepth::Sixteen, RGBA, @@ -128,7 +125,7 @@ fn filter_4_for_rgba_16() { fn filter_5_for_rgba_16() { test_it_converts( "tests/files/filter_5_for_rgba_16.png", - RowFilter::MinSum, + FilterStrategy::MinSum, RGBA, BitDepth::Sixteen, RGBA, @@ -140,7 +137,7 @@ fn filter_5_for_rgba_16() { fn filter_0_for_rgba_8() { test_it_converts( "tests/files/filter_0_for_rgba_8.png", - RowFilter::None, + FilterStrategy::NONE, RGBA, BitDepth::Eight, RGBA, @@ -152,7 +149,7 @@ fn filter_0_for_rgba_8() { fn filter_1_for_rgba_8() { test_it_converts( "tests/files/filter_1_for_rgba_8.png", - RowFilter::Sub, + FilterStrategy::SUB, RGBA, BitDepth::Eight, RGBA, @@ -164,7 +161,7 @@ fn filter_1_for_rgba_8() { fn filter_2_for_rgba_8() { test_it_converts( "tests/files/filter_2_for_rgba_8.png", - RowFilter::Up, + FilterStrategy::UP, RGBA, BitDepth::Eight, RGBA, @@ -176,7 +173,7 @@ fn filter_2_for_rgba_8() { fn filter_3_for_rgba_8() { test_it_converts( "tests/files/filter_3_for_rgba_8.png", - RowFilter::Average, + FilterStrategy::AVERAGE, RGBA, BitDepth::Eight, RGBA, @@ -188,7 +185,7 @@ fn filter_3_for_rgba_8() { fn filter_4_for_rgba_8() { test_it_converts( "tests/files/filter_4_for_rgba_8.png", - RowFilter::Paeth, + FilterStrategy::PAETH, RGBA, BitDepth::Eight, RGBA, @@ -200,7 +197,7 @@ fn filter_4_for_rgba_8() { fn filter_5_for_rgba_8() { test_it_converts( "tests/files/filter_5_for_rgba_8.png", - RowFilter::MinSum, + FilterStrategy::MinSum, RGBA, BitDepth::Eight, RGBA, @@ -212,7 +209,7 @@ fn filter_5_for_rgba_8() { fn filter_0_for_rgb_16() { test_it_converts( "tests/files/filter_0_for_rgb_16.png", - RowFilter::None, + FilterStrategy::NONE, RGB, BitDepth::Sixteen, RGB, @@ -224,7 +221,7 @@ fn filter_0_for_rgb_16() { fn filter_1_for_rgb_16() { test_it_converts( "tests/files/filter_1_for_rgb_16.png", - RowFilter::Sub, + FilterStrategy::SUB, RGB, BitDepth::Sixteen, RGB, @@ -236,7 +233,7 @@ fn filter_1_for_rgb_16() { fn filter_2_for_rgb_16() { test_it_converts( "tests/files/filter_2_for_rgb_16.png", - RowFilter::Up, + FilterStrategy::UP, RGB, BitDepth::Sixteen, RGB, @@ -248,7 +245,7 @@ fn filter_2_for_rgb_16() { fn filter_3_for_rgb_16() { test_it_converts( "tests/files/filter_3_for_rgb_16.png", - RowFilter::Average, + FilterStrategy::AVERAGE, RGB, BitDepth::Sixteen, RGB, @@ -260,7 +257,7 @@ fn filter_3_for_rgb_16() { fn filter_4_for_rgb_16() { test_it_converts( "tests/files/filter_4_for_rgb_16.png", - RowFilter::Paeth, + FilterStrategy::PAETH, RGB, BitDepth::Sixteen, RGB, @@ -272,7 +269,7 @@ fn filter_4_for_rgb_16() { fn filter_5_for_rgb_16() { test_it_converts( "tests/files/filter_5_for_rgb_16.png", - RowFilter::MinSum, + FilterStrategy::MinSum, RGB, BitDepth::Sixteen, RGB, @@ -284,7 +281,7 @@ fn filter_5_for_rgb_16() { fn filter_0_for_rgb_8() { test_it_converts( "tests/files/filter_0_for_rgb_8.png", - RowFilter::None, + FilterStrategy::NONE, RGB, BitDepth::Eight, RGB, @@ -296,7 +293,7 @@ fn filter_0_for_rgb_8() { fn filter_1_for_rgb_8() { test_it_converts( "tests/files/filter_1_for_rgb_8.png", - RowFilter::Sub, + FilterStrategy::SUB, RGB, BitDepth::Eight, RGB, @@ -308,7 +305,7 @@ fn filter_1_for_rgb_8() { fn filter_2_for_rgb_8() { test_it_converts( "tests/files/filter_2_for_rgb_8.png", - RowFilter::Up, + FilterStrategy::UP, RGB, BitDepth::Eight, RGB, @@ -320,7 +317,7 @@ fn filter_2_for_rgb_8() { fn filter_3_for_rgb_8() { test_it_converts( "tests/files/filter_3_for_rgb_8.png", - RowFilter::Average, + FilterStrategy::AVERAGE, RGB, BitDepth::Eight, RGB, @@ -332,7 +329,7 @@ fn filter_3_for_rgb_8() { fn filter_4_for_rgb_8() { test_it_converts( "tests/files/filter_4_for_rgb_8.png", - RowFilter::Paeth, + FilterStrategy::PAETH, RGB, BitDepth::Eight, RGB, @@ -344,7 +341,7 @@ fn filter_4_for_rgb_8() { fn filter_5_for_rgb_8() { test_it_converts( "tests/files/filter_5_for_rgb_8.png", - RowFilter::MinSum, + FilterStrategy::MinSum, RGB, BitDepth::Eight, RGB, @@ -356,7 +353,7 @@ fn filter_5_for_rgb_8() { fn filter_0_for_grayscale_alpha_16() { test_it_converts( "tests/files/filter_0_for_grayscale_alpha_16.png", - RowFilter::None, + FilterStrategy::NONE, GRAYSCALE_ALPHA, BitDepth::Sixteen, GRAYSCALE_ALPHA, @@ -368,7 +365,7 @@ fn filter_0_for_grayscale_alpha_16() { fn filter_1_for_grayscale_alpha_16() { test_it_converts( "tests/files/filter_1_for_grayscale_alpha_16.png", - RowFilter::Sub, + FilterStrategy::SUB, GRAYSCALE_ALPHA, BitDepth::Sixteen, GRAYSCALE_ALPHA, @@ -380,7 +377,7 @@ fn filter_1_for_grayscale_alpha_16() { fn filter_2_for_grayscale_alpha_16() { test_it_converts( "tests/files/filter_2_for_grayscale_alpha_16.png", - RowFilter::Up, + FilterStrategy::UP, GRAYSCALE_ALPHA, BitDepth::Sixteen, GRAYSCALE_ALPHA, @@ -392,7 +389,7 @@ fn filter_2_for_grayscale_alpha_16() { fn filter_3_for_grayscale_alpha_16() { test_it_converts( "tests/files/filter_3_for_grayscale_alpha_16.png", - RowFilter::Average, + FilterStrategy::AVERAGE, GRAYSCALE_ALPHA, BitDepth::Sixteen, GRAYSCALE_ALPHA, @@ -404,7 +401,7 @@ fn filter_3_for_grayscale_alpha_16() { fn filter_4_for_grayscale_alpha_16() { test_it_converts( "tests/files/filter_4_for_grayscale_alpha_16.png", - RowFilter::Paeth, + FilterStrategy::PAETH, GRAYSCALE_ALPHA, BitDepth::Sixteen, GRAYSCALE_ALPHA, @@ -416,7 +413,7 @@ fn filter_4_for_grayscale_alpha_16() { fn filter_5_for_grayscale_alpha_16() { test_it_converts( "tests/files/filter_5_for_grayscale_alpha_16.png", - RowFilter::MinSum, + FilterStrategy::MinSum, GRAYSCALE_ALPHA, BitDepth::Sixteen, GRAYSCALE_ALPHA, @@ -428,7 +425,7 @@ fn filter_5_for_grayscale_alpha_16() { fn filter_0_for_grayscale_alpha_8() { test_it_converts( "tests/files/filter_0_for_grayscale_alpha_8.png", - RowFilter::None, + FilterStrategy::NONE, GRAYSCALE_ALPHA, BitDepth::Eight, GRAYSCALE_ALPHA, @@ -440,7 +437,7 @@ fn filter_0_for_grayscale_alpha_8() { fn filter_1_for_grayscale_alpha_8() { test_it_converts( "tests/files/filter_1_for_grayscale_alpha_8.png", - RowFilter::Sub, + FilterStrategy::SUB, GRAYSCALE_ALPHA, BitDepth::Eight, GRAYSCALE_ALPHA, @@ -452,7 +449,7 @@ fn filter_1_for_grayscale_alpha_8() { fn filter_2_for_grayscale_alpha_8() { test_it_converts( "tests/files/filter_2_for_grayscale_alpha_8.png", - RowFilter::Up, + FilterStrategy::UP, GRAYSCALE_ALPHA, BitDepth::Eight, GRAYSCALE_ALPHA, @@ -464,7 +461,7 @@ fn filter_2_for_grayscale_alpha_8() { fn filter_3_for_grayscale_alpha_8() { test_it_converts( "tests/files/filter_3_for_grayscale_alpha_8.png", - RowFilter::Average, + FilterStrategy::AVERAGE, GRAYSCALE_ALPHA, BitDepth::Eight, GRAYSCALE_ALPHA, @@ -476,7 +473,7 @@ fn filter_3_for_grayscale_alpha_8() { fn filter_4_for_grayscale_alpha_8() { test_it_converts( "tests/files/filter_4_for_grayscale_alpha_8.png", - RowFilter::Paeth, + FilterStrategy::PAETH, GRAYSCALE_ALPHA, BitDepth::Eight, GRAYSCALE_ALPHA, @@ -488,7 +485,7 @@ fn filter_4_for_grayscale_alpha_8() { fn filter_5_for_grayscale_alpha_8() { test_it_converts( "tests/files/filter_5_for_grayscale_alpha_8.png", - RowFilter::MinSum, + FilterStrategy::MinSum, GRAYSCALE_ALPHA, BitDepth::Eight, GRAYSCALE_ALPHA, @@ -500,7 +497,7 @@ fn filter_5_for_grayscale_alpha_8() { fn filter_0_for_grayscale_16() { test_it_converts( "tests/files/filter_0_for_grayscale_16.png", - RowFilter::None, + FilterStrategy::NONE, GRAYSCALE, BitDepth::Sixteen, GRAYSCALE, @@ -512,7 +509,7 @@ fn filter_0_for_grayscale_16() { fn filter_1_for_grayscale_16() { test_it_converts( "tests/files/filter_1_for_grayscale_16.png", - RowFilter::Sub, + FilterStrategy::SUB, GRAYSCALE, BitDepth::Sixteen, GRAYSCALE, @@ -524,7 +521,7 @@ fn filter_1_for_grayscale_16() { fn filter_2_for_grayscale_16() { test_it_converts( "tests/files/filter_2_for_grayscale_16.png", - RowFilter::Up, + FilterStrategy::UP, GRAYSCALE, BitDepth::Sixteen, GRAYSCALE, @@ -536,7 +533,7 @@ fn filter_2_for_grayscale_16() { fn filter_3_for_grayscale_16() { test_it_converts( "tests/files/filter_3_for_grayscale_16.png", - RowFilter::Average, + FilterStrategy::AVERAGE, GRAYSCALE, BitDepth::Sixteen, GRAYSCALE, @@ -548,7 +545,7 @@ fn filter_3_for_grayscale_16() { fn filter_4_for_grayscale_16() { test_it_converts( "tests/files/filter_4_for_grayscale_16.png", - RowFilter::Paeth, + FilterStrategy::PAETH, GRAYSCALE, BitDepth::Sixteen, GRAYSCALE, @@ -560,7 +557,7 @@ fn filter_4_for_grayscale_16() { fn filter_5_for_grayscale_16() { test_it_converts( "tests/files/filter_5_for_grayscale_16.png", - RowFilter::MinSum, + FilterStrategy::MinSum, GRAYSCALE, BitDepth::Sixteen, GRAYSCALE, @@ -572,7 +569,7 @@ fn filter_5_for_grayscale_16() { fn filter_0_for_grayscale_8() { test_it_converts( "tests/files/filter_0_for_grayscale_8.png", - RowFilter::None, + FilterStrategy::NONE, GRAYSCALE, BitDepth::Eight, GRAYSCALE, @@ -584,7 +581,7 @@ fn filter_0_for_grayscale_8() { fn filter_1_for_grayscale_8() { test_it_converts( "tests/files/filter_1_for_grayscale_8.png", - RowFilter::Sub, + FilterStrategy::SUB, GRAYSCALE, BitDepth::Eight, GRAYSCALE, @@ -596,7 +593,7 @@ fn filter_1_for_grayscale_8() { fn filter_2_for_grayscale_8() { test_it_converts( "tests/files/filter_2_for_grayscale_8.png", - RowFilter::Up, + FilterStrategy::UP, GRAYSCALE, BitDepth::Eight, GRAYSCALE, @@ -608,7 +605,7 @@ fn filter_2_for_grayscale_8() { fn filter_3_for_grayscale_8() { test_it_converts( "tests/files/filter_3_for_grayscale_8.png", - RowFilter::Average, + FilterStrategy::AVERAGE, GRAYSCALE, BitDepth::Eight, GRAYSCALE, @@ -620,7 +617,7 @@ fn filter_3_for_grayscale_8() { fn filter_4_for_grayscale_8() { test_it_converts( "tests/files/filter_4_for_grayscale_8.png", - RowFilter::Paeth, + FilterStrategy::PAETH, GRAYSCALE, BitDepth::Eight, GRAYSCALE, @@ -632,7 +629,7 @@ fn filter_4_for_grayscale_8() { fn filter_5_for_grayscale_8() { test_it_converts( "tests/files/filter_5_for_grayscale_8.png", - RowFilter::MinSum, + FilterStrategy::MinSum, GRAYSCALE, BitDepth::Eight, GRAYSCALE, @@ -644,7 +641,7 @@ fn filter_5_for_grayscale_8() { fn filter_0_for_palette_4() { test_it_converts( "tests/files/filter_0_for_palette_4.png", - RowFilter::None, + FilterStrategy::NONE, INDEXED, BitDepth::Four, INDEXED, @@ -656,7 +653,7 @@ fn filter_0_for_palette_4() { fn filter_1_for_palette_4() { test_it_converts( "tests/files/filter_1_for_palette_4.png", - RowFilter::Sub, + FilterStrategy::SUB, INDEXED, BitDepth::Four, INDEXED, @@ -668,7 +665,7 @@ fn filter_1_for_palette_4() { fn filter_2_for_palette_4() { test_it_converts( "tests/files/filter_2_for_palette_4.png", - RowFilter::Up, + FilterStrategy::UP, INDEXED, BitDepth::Four, INDEXED, @@ -680,7 +677,7 @@ fn filter_2_for_palette_4() { fn filter_3_for_palette_4() { test_it_converts( "tests/files/filter_3_for_palette_4.png", - RowFilter::Average, + FilterStrategy::AVERAGE, INDEXED, BitDepth::Four, INDEXED, @@ -692,7 +689,7 @@ fn filter_3_for_palette_4() { fn filter_4_for_palette_4() { test_it_converts( "tests/files/filter_4_for_palette_4.png", - RowFilter::Paeth, + FilterStrategy::PAETH, INDEXED, BitDepth::Four, INDEXED, @@ -704,7 +701,7 @@ fn filter_4_for_palette_4() { fn filter_5_for_palette_4() { test_it_converts( "tests/files/filter_5_for_palette_4.png", - RowFilter::MinSum, + FilterStrategy::MinSum, INDEXED, BitDepth::Four, INDEXED, @@ -716,7 +713,7 @@ fn filter_5_for_palette_4() { fn filter_0_for_palette_2() { test_it_converts( "tests/files/filter_0_for_palette_2.png", - RowFilter::None, + FilterStrategy::NONE, INDEXED, BitDepth::Two, INDEXED, @@ -728,7 +725,7 @@ fn filter_0_for_palette_2() { fn filter_1_for_palette_2() { test_it_converts( "tests/files/filter_1_for_palette_2.png", - RowFilter::Sub, + FilterStrategy::SUB, INDEXED, BitDepth::Two, INDEXED, @@ -740,7 +737,7 @@ fn filter_1_for_palette_2() { fn filter_2_for_palette_2() { test_it_converts( "tests/files/filter_2_for_palette_2.png", - RowFilter::Up, + FilterStrategy::UP, INDEXED, BitDepth::Two, INDEXED, @@ -752,7 +749,7 @@ fn filter_2_for_palette_2() { fn filter_3_for_palette_2() { test_it_converts( "tests/files/filter_3_for_palette_2.png", - RowFilter::Average, + FilterStrategy::AVERAGE, INDEXED, BitDepth::Two, INDEXED, @@ -764,7 +761,7 @@ fn filter_3_for_palette_2() { fn filter_4_for_palette_2() { test_it_converts( "tests/files/filter_4_for_palette_2.png", - RowFilter::Paeth, + FilterStrategy::PAETH, INDEXED, BitDepth::Two, INDEXED, @@ -776,7 +773,7 @@ fn filter_4_for_palette_2() { fn filter_5_for_palette_2() { test_it_converts( "tests/files/filter_5_for_palette_2.png", - RowFilter::MinSum, + FilterStrategy::MinSum, INDEXED, BitDepth::Two, INDEXED, @@ -788,7 +785,7 @@ fn filter_5_for_palette_2() { fn filter_0_for_palette_1() { test_it_converts( "tests/files/filter_0_for_palette_1.png", - RowFilter::None, + FilterStrategy::NONE, INDEXED, BitDepth::One, INDEXED, @@ -800,7 +797,7 @@ fn filter_0_for_palette_1() { fn filter_1_for_palette_1() { test_it_converts( "tests/files/filter_1_for_palette_1.png", - RowFilter::Sub, + FilterStrategy::SUB, INDEXED, BitDepth::One, INDEXED, @@ -812,7 +809,7 @@ fn filter_1_for_palette_1() { fn filter_2_for_palette_1() { test_it_converts( "tests/files/filter_2_for_palette_1.png", - RowFilter::Up, + FilterStrategy::UP, INDEXED, BitDepth::One, INDEXED, @@ -824,7 +821,7 @@ fn filter_2_for_palette_1() { fn filter_3_for_palette_1() { test_it_converts( "tests/files/filter_3_for_palette_1.png", - RowFilter::Average, + FilterStrategy::AVERAGE, INDEXED, BitDepth::One, INDEXED, @@ -836,7 +833,7 @@ fn filter_3_for_palette_1() { fn filter_4_for_palette_1() { test_it_converts( "tests/files/filter_4_for_palette_1.png", - RowFilter::Paeth, + FilterStrategy::PAETH, INDEXED, BitDepth::One, INDEXED, @@ -848,7 +845,7 @@ fn filter_4_for_palette_1() { fn filter_5_for_palette_1() { test_it_converts( "tests/files/filter_5_for_palette_1.png", - RowFilter::MinSum, + FilterStrategy::MinSum, INDEXED, BitDepth::One, INDEXED, diff --git a/tests/flags.rs b/tests/flags.rs index 1ecffe63..4baa619b 100644 --- a/tests/flags.rs +++ b/tests/flags.rs @@ -16,7 +16,7 @@ fn get_opts(input: &Path) -> (OutFile, Options) { let options = Options { force: true, fast_evaluation: false, - filter: indexset! {RowFilter::None}, + filter: indexset! {FilterStrategy::NONE}, ..Default::default() }; (OutFile::from_path(input.with_extension("out.png")), options) @@ -409,7 +409,7 @@ fn interlaced_0_to_1_other_filter_mode() { let input = PathBuf::from("tests/files/interlaced_0_to_1_other_filter_mode.png"); let (output, mut opts) = get_opts(&input); opts.interlace = Some(Interlacing::Adam7); - opts.filter = indexset! {RowFilter::Paeth}; + opts.filter = indexset! {FilterStrategy::PAETH}; test_it_converts_callbacks( input, diff --git a/tests/interlaced.rs b/tests/interlaced.rs index a8931419..b9007d9b 100644 --- a/tests/interlaced.rs +++ b/tests/interlaced.rs @@ -12,16 +12,13 @@ const GRAYSCALE_ALPHA: u8 = 4; const RGBA: u8 = 6; fn get_opts(input: &Path) -> (OutFile, oxipng::Options) { - let mut options = oxipng::Options { + let options = oxipng::Options { force: true, fast_evaluation: false, + filter: indexset! {FilterStrategy::NONE}, interlace: None, ..Default::default() }; - let mut filter = IndexSet::new(); - filter.insert(RowFilter::None); - options.filter = filter; - (OutFile::from_path(input.with_extension("out.png")), options) } diff --git a/tests/interlacing.rs b/tests/interlacing.rs index fd72df08..ea7fd5a9 100644 --- a/tests/interlacing.rs +++ b/tests/interlacing.rs @@ -9,14 +9,11 @@ const RGB: u8 = 2; const INDEXED: u8 = 3; fn get_opts(input: &Path) -> (OutFile, oxipng::Options) { - let mut options = oxipng::Options { + let options = oxipng::Options { force: true, + filter: indexset! {FilterStrategy::NONE}, ..Default::default() }; - let mut filter = IndexSet::new(); - filter.insert(RowFilter::None); - options.filter = filter; - (OutFile::from_path(input.with_extension("out.png")), options) } diff --git a/tests/raw.rs b/tests/raw.rs index 3bf49591..d50a58e1 100644 --- a/tests/raw.rs +++ b/tests/raw.rs @@ -5,7 +5,7 @@ use oxipng::{internal_tests::*, *}; fn get_opts() -> Options { Options { force: true, - filter: indexset! { RowFilter::None }, + filter: indexset! {FilterStrategy::NONE}, ..Default::default() } } diff --git a/tests/reduction.rs b/tests/reduction.rs index 4e33f7e2..6ba3236d 100644 --- a/tests/reduction.rs +++ b/tests/reduction.rs @@ -12,15 +12,12 @@ const GRAYSCALE_ALPHA: u8 = 4; const RGBA: u8 = 6; fn get_opts(input: &Path) -> (OutFile, oxipng::Options) { - let mut options = oxipng::Options { + let options = oxipng::Options { force: true, fast_evaluation: false, + filter: indexset! {FilterStrategy::NONE}, ..Default::default() }; - let mut filter = IndexSet::new(); - filter.insert(RowFilter::None); - options.filter = filter; - (OutFile::from_path(input.with_extension("out.png")), options) } diff --git a/tests/regression.rs b/tests/regression.rs index 765a04f5..89756c29 100644 --- a/tests/regression.rs +++ b/tests/regression.rs @@ -12,14 +12,11 @@ const GRAYSCALE_ALPHA: u8 = 4; const RGBA: u8 = 6; fn get_opts(input: &Path) -> (OutFile, oxipng::Options) { - let mut options = oxipng::Options { + let options = oxipng::Options { force: true, + filter: indexset! {FilterStrategy::NONE}, ..Default::default() }; - let mut filter = IndexSet::new(); - filter.insert(RowFilter::None); - options.filter = filter; - (OutFile::from_path(input.with_extension("out.png")), options) } diff --git a/tests/strategies.rs b/tests/strategies.rs index 7f8e0bcc..264e6983 100644 --- a/tests/strategies.rs +++ b/tests/strategies.rs @@ -11,20 +11,16 @@ const INDEXED: u8 = 3; const RGBA: u8 = 6; fn get_opts(input: &Path) -> (OutFile, oxipng::Options) { - let mut options = oxipng::Options { + let options = oxipng::Options { force: true, ..Default::default() }; - let mut filter = IndexSet::new(); - filter.insert(RowFilter::None); - options.filter = filter; - (OutFile::from_path(input.with_extension("out.png")), options) } fn test_it_converts( input: &str, - filter: RowFilter, + filter: FilterStrategy, color_type_in: u8, bit_depth_in: BitDepth, color_type_out: u8, @@ -34,8 +30,7 @@ fn test_it_converts( let (output, mut opts) = get_opts(&input); let png = PngData::new(&input, &opts).unwrap(); - opts.filter = IndexSet::new(); - opts.filter.insert(filter); + opts.filter = indexset! {filter}; assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in); assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in); @@ -67,7 +62,7 @@ fn test_it_converts( fn filter_minsum() { test_it_converts( "tests/files/rgb_16_should_be_rgb_16.png", - RowFilter::MinSum, + FilterStrategy::MinSum, RGB, BitDepth::Sixteen, RGB, @@ -79,7 +74,7 @@ fn filter_minsum() { fn filter_entropy() { test_it_converts( "tests/files/rgb_8_should_be_rgb_8.png", - RowFilter::Entropy, + FilterStrategy::Entropy, RGB, BitDepth::Eight, RGB, @@ -91,7 +86,7 @@ fn filter_entropy() { fn filter_bigrams() { test_it_converts( "tests/files/rgba_8_should_be_rgba_8.png", - RowFilter::Bigrams, + FilterStrategy::Bigrams, RGBA, BitDepth::Eight, RGBA, @@ -103,7 +98,7 @@ fn filter_bigrams() { fn filter_bigent() { test_it_converts( "tests/files/grayscale_8_should_be_grayscale_8.png", - RowFilter::BigEnt, + FilterStrategy::BigEnt, GRAYSCALE, BitDepth::Eight, GRAYSCALE, @@ -115,7 +110,7 @@ fn filter_bigent() { fn filter_brute() { test_it_converts( "tests/files/palette_8_should_be_palette_8.png", - RowFilter::Brute, + FilterStrategy::Brute, INDEXED, BitDepth::Eight, INDEXED,