Simplify deflate
This commit is contained in:
parent
453021ffb6
commit
acfc63399d
3 changed files with 63 additions and 71 deletions
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
97
src/lib.rs
97
src/lib.rs
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue