From f688d20fe647a588cdf5cd3cd96e9f5cfe938de3 Mon Sep 17 00:00:00 2001 From: andrews05 Date: Sat, 29 Oct 2022 14:54:04 +1300 Subject: [PATCH] Allow setting libdeflate compression level (#455) * Allow --zc with libdeflater * Update libdeflater * Allocate sufficient space for libdefate --- Cargo.lock | 10 ++++----- Cargo.toml | 2 +- benches/libdeflater.rs | 10 ++++----- src/deflate/deflater.rs | 10 ++++++--- src/deflate/mod.rs | 5 ++++- src/lib.rs | 49 ++++++++++++++++++++++++++++------------- src/main.rs | 18 ++++++++++----- tests/flags.rs | 4 +++- 8 files changed, 71 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c3366bf..b90ce9c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -281,18 +281,18 @@ checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" [[package]] name = "libdeflate-sys" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43afa5b192ff058426ba20a4f35c290ef402478d6045ac934ac15aa947a3898d" +checksum = "cb6784b6b84b67d71b4307963d456a9c7c29f9b47c658f533e598de369e34277" dependencies = [ "cc", ] [[package]] name = "libdeflater" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e656b7960ec49e864badc7ad1b810427a7ac8b78511a699ce5cdc3ead0b32e5b" +checksum = "d8e285aa6a046fd338b2592c16bee148b2b00789138ed6b7bb56bb13d585050d" dependencies = [ "libdeflate-sys", ] @@ -387,7 +387,7 @@ checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" [[package]] name = "oxipng" -version = "6.0.0" +version = "6.0.1" dependencies = [ "bit-vec", "clap", diff --git a/Cargo.toml b/Cargo.toml index d0c8f13e..f445868f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ zopfli = { version = "0.7.1", optional = true } miniz_oxide = "0.6.2" rgb = "0.8.33" indexmap = "1.9.1" -libdeflater = { version = "0.10.0", optional = true } +libdeflater = { version = "0.11.0", optional = true } log = "0.4.17" stderrlog = { version = "0.5.3", optional = true, default-features = false } crossbeam-channel = "0.5.6" diff --git a/benches/libdeflater.rs b/benches/libdeflater.rs index cac7a723..d7917a0c 100644 --- a/benches/libdeflater.rs +++ b/benches/libdeflater.rs @@ -13,7 +13,7 @@ fn libdeflater_16_bits_strategy_0(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - libdeflater_deflate(png.raw.data.as_ref(), &AtomicMin::new(None)).ok(); + libdeflater_deflate(png.raw.data.as_ref(), 12, &AtomicMin::new(None)).ok(); }); } @@ -23,7 +23,7 @@ fn libdeflater_8_bits_strategy_0(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - libdeflater_deflate(png.raw.data.as_ref(), &AtomicMin::new(None)).ok(); + libdeflater_deflate(png.raw.data.as_ref(), 12, &AtomicMin::new(None)).ok(); }); } @@ -35,7 +35,7 @@ fn libdeflater_4_bits_strategy_0(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - libdeflater_deflate(png.raw.data.as_ref(), &AtomicMin::new(None)).ok(); + libdeflater_deflate(png.raw.data.as_ref(), 12, &AtomicMin::new(None)).ok(); }); } @@ -47,7 +47,7 @@ fn libdeflater_2_bits_strategy_0(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - libdeflater_deflate(png.raw.data.as_ref(), &AtomicMin::new(None)).ok(); + libdeflater_deflate(png.raw.data.as_ref(), 12, &AtomicMin::new(None)).ok(); }); } @@ -59,6 +59,6 @@ fn libdeflater_1_bits_strategy_0(b: &mut Bencher) { let png = PngData::new(&input, false).unwrap(); b.iter(|| { - libdeflater_deflate(png.raw.data.as_ref(), &AtomicMin::new(None)).ok(); + libdeflater_deflate(png.raw.data.as_ref(), 12, &AtomicMin::new(None)).ok(); }); } diff --git a/src/deflate/deflater.rs b/src/deflate/deflater.rs index 45db30f5..93dbd43f 100644 --- a/src/deflate/deflater.rs +++ b/src/deflate/deflater.rs @@ -2,9 +2,13 @@ use crate::atomicmin::AtomicMin; use crate::{PngError, PngResult}; use libdeflater::{CompressionError, CompressionLvl, Compressor}; -pub fn deflate(data: &[u8], max_size: &AtomicMin) -> PngResult> { - let mut compressor = Compressor::new(CompressionLvl::best()); - let capacity = max_size.get().unwrap_or(data.len() / 2); +pub fn deflate(data: &[u8], level: u8, max_size: &AtomicMin) -> PngResult> { + let mut compressor = Compressor::new(CompressionLvl::new(level.into()).unwrap()); + // If adhering to a max_size we need to include at least 9 extra bytes of slack space (as specified in docs). + let capacity = max_size + .get() + .unwrap_or_else(|| compressor.zlib_compress_bound(data.len())) + + 9; let mut dest = vec![0; capacity]; let len = compressor .zlib_compress(data, &mut dest) diff --git a/src/deflate/mod.rs b/src/deflate/mod.rs index e1b4fdf9..0c109f96 100644 --- a/src/deflate/mod.rs +++ b/src/deflate/mod.rs @@ -104,5 +104,8 @@ pub enum Deflaters { }, #[cfg(feature = "libdeflater")] /// Use libdeflater. - Libdeflater, + Libdeflater { + /// Which compression levels to try on the file (1-12) + compression: IndexSet, + }, } diff --git a/src/lib.rs b/src/lib.rs index 5aeef68c..a6249756 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -560,26 +560,43 @@ fn optimize_png( let mut results: Vec = Vec::with_capacity(combinations); for f in &filter { - if let Deflaters::Zlib { compression, .. } = &opts.deflate { - for zc in compression { - for zs in strategies.as_ref().unwrap() { + match &opts.deflate { + Deflaters::Zlib { compression, .. } => { + for zc in compression { + for zs in strategies.as_ref().unwrap() { + results.push(TrialOptions { + filter: *f, + compression: *zc, + strategy: *zs, + }); + } + if deadline.passed() { + break; + } + } + } + #[cfg(feature = "zopfli")] + Deflaters::Zopfli { .. } => { + // Zopfli has no additional options. + results.push(TrialOptions { + filter: *f, + compression: 0, + strategy: 0, + }); + } + #[cfg(feature = "libdeflater")] + Deflaters::Libdeflater { compression } => { + for zc in compression { results.push(TrialOptions { filter: *f, compression: *zc, - strategy: *zs, + strategy: 0, }); - } - if deadline.passed() { - break; + if deadline.passed() { + break; + } } } - } else { - // Zopfli and Libdeflater compression have no additional options. - results.push(TrialOptions { - filter: *f, - compression: 0, - strategy: 0, - }); } if deadline.passed() { @@ -620,7 +637,9 @@ fn optimize_png( #[cfg(feature = "zopfli")] Deflaters::Zopfli { iterations } => deflate::zopfli_deflate(filtered, iterations), #[cfg(feature = "libdeflater")] - Deflaters::Libdeflater => deflate::libdeflater_deflate(filtered, &best_size), + Deflaters::Libdeflater { .. } => { + deflate::libdeflater_deflate(filtered, trial.compression, &best_size) + } }; let new_idat = match new_idat { diff --git a/src/main.rs b/src/main.rs index adf51a4a..d503b585 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ #![allow(clippy::cognitive_complexity)] use clap::{AppSettings, Arg, ArgMatches, Command}; -use indexmap::IndexSet; +use indexmap::{indexset, IndexSet}; use log::{error, warn}; use oxipng::AlphaOptim; use oxipng::Deflaters; @@ -143,17 +143,17 @@ fn main() { } })) .arg(Arg::new("compression") - .help("zlib compression levels (1-9) - Default: 9") + .help("compression levels (zlib: 1-9, libdeflater: 1-12) - Default: 9 or 12") .long("zc") .takes_value(true) .value_name("levels") .validator(|x| { - match parse_numeric_range_opts(x, 1, 9) { + match parse_numeric_range_opts(x, 1, 12) { Ok(_) => Ok(()), Err(_) => Err("Invalid option for compression".to_owned()), } }) - .conflicts_with_all(&["zopfli", "libdeflater"])) + .conflicts_with("zopfli")) .arg(Arg::new("strategies") .help("zlib compression strategies (0-3) - Default: 0-3") .long("zs") @@ -500,7 +500,12 @@ fn parse_opts_into_struct( iterations: NonZeroU8::new(15).unwrap(), }; } else if matches.is_present("libdeflater") { - opts.deflate = Deflaters::Libdeflater; + opts.deflate = Deflaters::Libdeflater { + compression: match matches.value_of("compression") { + Some(x) => parse_numeric_range_opts(x, 1, 12).unwrap(), + _ => indexset! { 12 }, + }, + }; } else if let Deflaters::Zlib { compression, strategies, @@ -508,7 +513,8 @@ fn parse_opts_into_struct( } = &mut opts.deflate { if let Some(x) = matches.value_of("compression") { - *compression = parse_numeric_range_opts(x, 1, 9).unwrap(); + *compression = parse_numeric_range_opts(x, 1, 9) + .map_err(|_| "Compression levels 10-12 are only valid for libdeflater".to_owned())? } if let Some(x) = matches.value_of("strategies") { diff --git a/tests/flags.rs b/tests/flags.rs index f99324bd..11f117f1 100644 --- a/tests/flags.rs +++ b/tests/flags.rs @@ -603,7 +603,9 @@ fn zopfli_mode() { fn libdeflater_mode() { let input = PathBuf::from("tests/files/zopfli_mode.png"); let (output, mut opts) = get_opts(&input); - opts.deflate = Deflaters::Libdeflater; + let mut compression = IndexSet::new(); + compression.insert(0); + opts.deflate = Deflaters::Libdeflater { compression }; test_it_converts( input,