Add initial support for changing Zopfli iterations

PR https://github.com/shssoichiro/oxipng/pull/445 did some dependency
updates, which included using the latest zopfli version. The latest
version of this crate exposes new options in its API that allow users to
choose the desired number of Zopfli compression iterations, which
may greatly affect execution time. In fact, other optimizers such as
zopflipng dynamically select this number depending on the input file
size (see: https://github.com/shssoichiro/oxipng/issues/414).

As a first step towards making OxiPNG deal with Zopfli better, let's add
the necessary options for libraries to be able to choose the number of
iterations. This number is still fixed to 15 as before when using the
CLI.
This commit is contained in:
Alejandro González 2022-09-03 17:55:18 +02:00
parent 4557c0e76a
commit 046341357b
5 changed files with 30 additions and 11 deletions

View file

@ -4,16 +4,20 @@ extern crate oxipng;
extern crate test;
use oxipng::internal_tests::*;
use std::num::NonZeroU8;
use std::path::PathBuf;
use test::Bencher;
// SAFETY: trivially safe. Stopgap solution until const unwrap is stabilized.
const DEFAULT_ZOPFLI_ITERATIONS: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(15) };
#[bench]
fn zopfli_16_bits_strategy_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, false).unwrap();
b.iter(|| {
zopfli_deflate(png.raw.data.as_ref()).ok();
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
});
}
@ -23,7 +27,7 @@ fn zopfli_8_bits_strategy_0(b: &mut Bencher) {
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
zopfli_deflate(png.raw.data.as_ref()).ok();
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
});
}
@ -35,7 +39,7 @@ fn zopfli_4_bits_strategy_0(b: &mut Bencher) {
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
zopfli_deflate(png.raw.data.as_ref()).ok();
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
});
}
@ -47,7 +51,7 @@ fn zopfli_2_bits_strategy_0(b: &mut Bencher) {
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
zopfli_deflate(png.raw.data.as_ref()).ok();
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
});
}
@ -59,6 +63,6 @@ fn zopfli_1_bits_strategy_0(b: &mut Bencher) {
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
zopfli_deflate(png.raw.data.as_ref()).ok();
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
});
}

View file

@ -3,6 +3,7 @@ use crate::error::PngError;
use crate::Deadline;
use crate::PngResult;
use indexmap::IndexSet;
use std::num::NonZeroU8;
#[doc(hidden)]
pub mod miniz_stream;
@ -49,11 +50,14 @@ pub fn deflate(
}
#[cfg(feature = "zopfli")]
pub fn zopfli_deflate(data: &[u8]) -> PngResult<Vec<u8>> {
pub fn zopfli_deflate(data: &[u8], iterations: NonZeroU8) -> PngResult<Vec<u8>> {
use std::cmp::max;
let mut output = Vec::with_capacity(max(1024, data.len() / 20));
let options = zopfli::Options::default();
let options = zopfli::Options {
iteration_count: iterations,
..Default::default()
};
match zopfli::compress(&options, &zopfli::Format::Zlib, data, &mut output) {
Ok(_) => (),
Err(_) => return Err(PngError::new("Failed to compress in zopfli")),
@ -85,7 +89,12 @@ pub enum Deflaters {
},
#[cfg(feature = "zopfli")]
/// Use the better but slower Zopfli implementation
Zopfli,
Zopfli {
/// The number of compression iterations to do. 15 iterations are fine
/// for small files, but bigger files will need to be compressed with
/// less iterations, or else they will be too slow.
iterations: NonZeroU8,
},
#[cfg(feature = "libdeflater")]
/// Use libdeflater.
Libdeflater,

View file

@ -618,7 +618,7 @@ fn optimize_png(
&deadline,
),
#[cfg(feature = "zopfli")]
Deflaters::Zopfli => deflate::zopfli_deflate(filtered),
Deflaters::Zopfli { iterations } => deflate::zopfli_deflate(filtered, iterations),
#[cfg(feature = "libdeflater")]
Deflaters::Libdeflater => deflate::libdeflater_deflate(filtered, &best_size),
};

View file

@ -22,6 +22,7 @@ use oxipng::Headers;
use oxipng::Options;
use oxipng::{InFile, OutFile};
use std::fs::DirBuilder;
use std::num::NonZeroU8;
use std::path::PathBuf;
use std::process::exit;
use std::time::Duration;
@ -495,7 +496,9 @@ fn parse_opts_into_struct(
}
if matches.is_present("zopfli") {
opts.deflate = Deflaters::Zopfli;
opts.deflate = Deflaters::Zopfli {
iterations: NonZeroU8::new(15).unwrap(),
};
} else if matches.is_present("libdeflater") {
opts.deflate = Deflaters::Libdeflater;
} else if let Deflaters::Zlib {

View file

@ -4,6 +4,7 @@ use oxipng::{InFile, OutFile};
#[cfg(feature = "filetime")]
use std::cell::RefCell;
use std::fs::remove_file;
use std::num::NonZeroU8;
#[cfg(feature = "filetime")]
use std::ops::Deref;
use std::path::Path;
@ -582,7 +583,9 @@ fn fix_errors() {
fn zopfli_mode() {
let input = PathBuf::from("tests/files/zopfli_mode.png");
let (output, mut opts) = get_opts(&input);
opts.deflate = Deflaters::Zopfli;
opts.deflate = Deflaters::Zopfli {
iterations: NonZeroU8::new(15).unwrap(),
};
test_it_converts(
input,