From acfc63399d5844df6485a0f1eacb798570b97d2d Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 16 May 2023 21:11:47 +1200 Subject: [PATCH] Simplify deflate --- src/deflate/deflater.rs | 7 +-- src/deflate/mod.rs | 30 +++++++++++++ src/lib.rs | 97 ++++++++++++++--------------------------- 3 files changed, 63 insertions(+), 71 deletions(-) diff --git a/src/deflate/deflater.rs b/src/deflate/deflater.rs index a2f9dacb..efffe123 100644 --- a/src/deflate/deflater.rs +++ b/src/deflate/deflater.rs @@ -13,13 +13,8 @@ pub fn deflate(data: &[u8], level: u8, max_size: &AtomicMin) -> PngResult PngError::DeflatedDataTooLong(capacity), + CompressionError::InsufficientSpace => PngError::DeflatedDataTooLong(capacity - 9), })?; - if let Some(max) = max_size.get() { - if len > max { - return Err(PngError::DeflatedDataTooLong(max)); - } - } dest.truncate(len); Ok(dest) } diff --git a/src/deflate/mod.rs b/src/deflate/mod.rs index 9606a852..0e8a65f3 100644 --- a/src/deflate/mod.rs +++ b/src/deflate/mod.rs @@ -1,7 +1,10 @@ mod deflater; +use crate::AtomicMin; +use crate::{PngError, PngResult}; pub use deflater::crc32; pub use deflater::deflate; pub use deflater::inflate; +use std::{fmt, fmt::Display}; #[cfg(feature = "zopfli")] use std::num::NonZeroU8; @@ -27,3 +30,30 @@ pub enum Deflaters { iterations: NonZeroU8, }, } + +impl Deflaters { + pub(crate) fn deflate(self, data: &[u8], max_size: &AtomicMin) -> PngResult> { + let compressed = match self { + Self::Libdeflater { compression } => deflate(data, compression, max_size)?, + #[cfg(feature = "zopfli")] + Self::Zopfli { iterations } => zopfli_deflate(data, iterations)?, + }; + if let Some(max) = max_size.get() { + if compressed.len() > max { + return Err(PngError::DeflatedDataTooLong(max)); + } + } + Ok(compressed) + } +} + +impl Display for Deflaters { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Libdeflater { compression } => Display::fmt(compression, f), + #[cfg(feature = "zopfli")] + Self::Zopfli { .. } => Display::fmt("zopfli", f), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index cdd0d043..11af6171 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -542,13 +542,7 @@ pub fn optimize_from_memory(data: &[u8], opts: &Options) -> PngResult> { } } -#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] -/// Defines options to be used for a single compression trial -struct TrialOptions { - pub filter: RowFilter, - pub compression: u8, -} -type TrialWithData = (TrialOptions, Vec); +type TrialResult = (RowFilter, Vec); /// Perform optimization on the input PNG object using the options provided fn optimize_png( @@ -654,7 +648,7 @@ fn optimize_raw( 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 = if fast_eval { + let best: Option = if fast_eval { // Perform a fast evaluation of selected filters followed by a single main compression trial if eval_result.is_some() { @@ -674,22 +668,18 @@ fn optimize_raw( } } // We should have a result here - fail if not (e.g. deadline passed) - let eval_result = eval_result?; + let result = eval_result?; - let trial = TrialOptions { - filter: eval_result.filter, - compression: match opts.deflate { - Deflaters::Libdeflater { compression } => compression, - _ => 0, - }, - }; - if trial.compression > 0 && trial.compression <= eval_compression { - // No further compression required - Some((trial, eval_result.image.idat_data)) - } else { - debug!("Trying: {}", trial.filter); - let best_size = AtomicMin::new(max_size); - perform_trial(&eval_result.image.filtered, opts, trial, &best_size) + match opts.deflate { + Deflaters::Libdeflater { compression } if compression <= eval_compression => { + // No further compression required + Some((result.filter, result.image.idat_data)) + } + _ => { + debug!("Trying: {}", result.filter); + let best_size = AtomicMin::new(max_size); + perform_trial(&result.image.filtered, opts, result.filter, &best_size) + } } } else { // Perform full compression trials of selected filters and determine the best @@ -705,28 +695,16 @@ fn optimize_raw( } } - let mut results: Vec = Vec::with_capacity(filters.len()); - - for f in &filters { - results.push(TrialOptions { - filter: *f, - compression: match opts.deflate { - Deflaters::Libdeflater { compression } => compression, - _ => 0, - }, - }); - } - - debug!("Trying: {} filters", results.len()); + debug!("Trying: {} filters", filters.len()); let best_size = AtomicMin::new(max_size); - let results_iter = results.into_par_iter().with_max_len(1); - let best = results_iter.filter_map(|trial| { + 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(trial.filter, opts.optimize_alpha); - perform_trial(filtered, opts, trial, &best_size) + 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) { @@ -737,7 +715,7 @@ fn optimize_raw( }) }; - if let Some((trial, idat_data)) = best { + if let Some((filter, idat_data)) = best { let image = PngData { raw: png, // The filtered data has not been retained here, but we don't need to return it @@ -749,8 +727,8 @@ fn optimize_raw( debug!("Found better combination:"); debug!( " zc = {} f = {:8} {} bytes", - trial.compression, - trial.filter, + opts.deflate, + filter, image.idat_data.len() ); return Some(image); @@ -779,37 +757,26 @@ fn optimize_raw( fn perform_trial( filtered: &[u8], opts: &Options, - trial: TrialOptions, + filter: RowFilter, best_size: &AtomicMin, -) -> Option { - let new_idat = match opts.deflate { - Deflaters::Libdeflater { .. } => deflate::deflate(filtered, trial.compression, best_size), - #[cfg(feature = "zopfli")] - Deflaters::Zopfli { iterations } => deflate::zopfli_deflate(filtered, iterations), - }; - - // update best size or convert to error if not smaller - let new_idat = match new_idat { - Ok(n) if !best_size.set_min(n.len()) => Err(PngError::DeflatedDataTooLong(n.len())), - _ => new_idat, - }; - - match new_idat { - Ok(n) => { - let bytes = n.len(); +) -> Option { + match opts.deflate.deflate(filtered, best_size) { + Ok(new_idat) => { + let bytes = new_idat.len(); + best_size.set_min(bytes); trace!( " zc = {} f = {:8} {} bytes", - trial.compression, - trial.filter, + opts.deflate, + filter, bytes ); - Some((trial, n)) + Some((filter, new_idat)) } Err(PngError::DeflatedDataTooLong(bytes)) => { trace!( " zc = {} f = {:8} >{} bytes", - trial.compression, - trial.filter, + opts.deflate, + filter, bytes, ); None