Simplify deflate

This commit is contained in:
Andrew 2023-05-16 21:11:47 +12:00
parent 453021ffb6
commit acfc63399d
3 changed files with 63 additions and 71 deletions

View file

@ -13,13 +13,8 @@ pub fn deflate(data: &[u8], level: u8, max_size: &AtomicMin) -> PngResult<Vec<u8
let len = compressor let len = compressor
.zlib_compress(data, &mut dest) .zlib_compress(data, &mut dest)
.map_err(|err| match err { .map_err(|err| match err {
CompressionError::InsufficientSpace => 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); dest.truncate(len);
Ok(dest) Ok(dest)
} }

View file

@ -1,7 +1,10 @@
mod deflater; mod deflater;
use crate::AtomicMin;
use crate::{PngError, PngResult};
pub use deflater::crc32; pub use deflater::crc32;
pub use deflater::deflate; pub use deflater::deflate;
pub use deflater::inflate; pub use deflater::inflate;
use std::{fmt, fmt::Display};
#[cfg(feature = "zopfli")] #[cfg(feature = "zopfli")]
use std::num::NonZeroU8; use std::num::NonZeroU8;
@ -27,3 +30,30 @@ pub enum Deflaters {
iterations: NonZeroU8, iterations: NonZeroU8,
}, },
} }
impl Deflaters {
pub(crate) fn deflate(self, data: &[u8], max_size: &AtomicMin) -> PngResult<Vec<u8>> {
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),
}
}
}

View file

@ -542,13 +542,7 @@ pub fn optimize_from_memory(data: &[u8], opts: &Options) -> PngResult<Vec<u8>> {
} }
} }
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] type TrialResult = (RowFilter, Vec<u8>);
/// Defines options to be used for a single compression trial
struct TrialOptions {
pub filter: RowFilter,
pub compression: u8,
}
type TrialWithData = (TrialOptions, Vec<u8>);
/// Perform optimization on the input PNG object using the options provided /// Perform optimization on the input PNG object using the options provided
fn optimize_png( fn optimize_png(
@ -654,7 +648,7 @@ fn optimize_raw(
if opts.idat_recoding || reduction_occurred { if opts.idat_recoding || reduction_occurred {
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<TrialWithData> = if fast_eval { let best: Option<TrialResult> = 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() {
@ -674,22 +668,18 @@ 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 eval_result = eval_result?; let result = eval_result?;
let trial = TrialOptions { match opts.deflate {
filter: eval_result.filter, Deflaters::Libdeflater { compression } if compression <= eval_compression => {
compression: match opts.deflate { // No further compression required
Deflaters::Libdeflater { compression } => compression, Some((result.filter, result.image.idat_data))
_ => 0, }
}, _ => {
}; debug!("Trying: {}", result.filter);
if trial.compression > 0 && trial.compression <= eval_compression { let best_size = AtomicMin::new(max_size);
// No further compression required perform_trial(&result.image.filtered, opts, result.filter, &best_size)
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)
} }
} else { } else {
// Perform full compression trials of selected filters and determine the best // Perform full compression trials of selected filters and determine the best
@ -705,28 +695,16 @@ fn optimize_raw(
} }
} }
let mut results: Vec<TrialOptions> = Vec::with_capacity(filters.len()); debug!("Trying: {} filters", 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());
let best_size = AtomicMin::new(max_size); let best_size = AtomicMin::new(max_size);
let results_iter = results.into_par_iter().with_max_len(1); let results_iter = filters.into_par_iter().with_max_len(1);
let best = results_iter.filter_map(|trial| { let best = results_iter.filter_map(|filter| {
if deadline.passed() { if deadline.passed() {
return None; return None;
} }
let filtered = &png.filter_image(trial.filter, opts.optimize_alpha); let filtered = &png.filter_image(filter, opts.optimize_alpha);
perform_trial(filtered, opts, trial, &best_size) perform_trial(filtered, opts, filter, &best_size)
}); });
best.reduce_with(|i, j| { best.reduce_with(|i, j| {
if i.1.len() < j.1.len() || (i.1.len() == j.1.len() && i.0 < j.0) { 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 { let image = PngData {
raw: png, raw: png,
// The filtered data has not been retained here, but we don't need to return it // 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!("Found better combination:");
debug!( debug!(
" zc = {} f = {:8} {} bytes", " zc = {} f = {:8} {} bytes",
trial.compression, opts.deflate,
trial.filter, filter,
image.idat_data.len() image.idat_data.len()
); );
return Some(image); return Some(image);
@ -779,37 +757,26 @@ fn optimize_raw(
fn perform_trial( fn perform_trial(
filtered: &[u8], filtered: &[u8],
opts: &Options, opts: &Options,
trial: TrialOptions, filter: RowFilter,
best_size: &AtomicMin, best_size: &AtomicMin,
) -> Option<TrialWithData> { ) -> Option<TrialResult> {
let new_idat = match opts.deflate { match opts.deflate.deflate(filtered, best_size) {
Deflaters::Libdeflater { .. } => deflate::deflate(filtered, trial.compression, best_size), Ok(new_idat) => {
#[cfg(feature = "zopfli")] let bytes = new_idat.len();
Deflaters::Zopfli { iterations } => deflate::zopfli_deflate(filtered, iterations), best_size.set_min(bytes);
};
// 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();
trace!( trace!(
" zc = {} f = {:8} {} bytes", " zc = {} f = {:8} {} bytes",
trial.compression, opts.deflate,
trial.filter, filter,
bytes bytes
); );
Some((trial, n)) Some((filter, new_idat))
} }
Err(PngError::DeflatedDataTooLong(bytes)) => { Err(PngError::DeflatedDataTooLong(bytes)) => {
trace!( trace!(
" zc = {} f = {:8} >{} bytes", " zc = {} f = {:8} >{} bytes",
trial.compression, opts.deflate,
trial.filter, filter,
bytes, bytes,
); );
None None