Use ZopfliOptions for Zopfli Deflater

This commit is contained in:
Andrew 2025-08-04 19:11:38 +12:00
parent 79743a3b48
commit 8a5a3ab69e
7 changed files with 27 additions and 39 deletions

View file

@ -3,20 +3,18 @@
extern crate oxipng;
extern crate test;
use std::{num::NonZeroU8, path::PathBuf};
use std::path::PathBuf;
use oxipng::{internal_tests::*, *};
use test::Bencher;
const DEFAULT_ZOPFLI_ITERATIONS: NonZeroU8 = NonZeroU8::new(15).unwrap();
#[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, &Options::default()).unwrap();
b.iter(|| {
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
zopfli_deflate(png.raw.data.as_ref(), ZopfliOptions::default()).ok();
});
}
@ -26,7 +24,7 @@ fn zopfli_8_bits_strategy_0(b: &mut Bencher) {
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
zopfli_deflate(png.raw.data.as_ref(), ZopfliOptions::default()).ok();
});
}
@ -38,7 +36,7 @@ fn zopfli_4_bits_strategy_0(b: &mut Bencher) {
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
zopfli_deflate(png.raw.data.as_ref(), ZopfliOptions::default()).ok();
});
}
@ -50,7 +48,7 @@ fn zopfli_2_bits_strategy_0(b: &mut Bencher) {
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
zopfli_deflate(png.raw.data.as_ref(), ZopfliOptions::default()).ok();
});
}
@ -62,6 +60,6 @@ fn zopfli_1_bits_strategy_0(b: &mut Bencher) {
let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| {
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
zopfli_deflate(png.raw.data.as_ref(), ZopfliOptions::default()).ok();
});
}

View file

@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::{num::NonZeroU64, path::PathBuf};
use clap::{Arg, ArgAction, Command, builder::ArgPredicate, value_parser};
@ -337,7 +337,7 @@ speed up compression for large files. This option requires '--zopfli' to be set.
.long("zi")
.value_name("iterations")
.default_value("15")
.value_parser(1..=255)
.value_parser(value_parser!(NonZeroU64))
.requires("zopfli"),
)
.arg(

View file

@ -1,14 +1,14 @@
mod deflater;
#[cfg(feature = "zopfli")]
use std::num::NonZeroU8;
use std::{fmt, fmt::Display};
pub use deflater::{crc32, deflate, inflate};
use crate::{PngError, PngResult};
use std::{fmt, fmt::Display};
#[cfg(feature = "zopfli")]
mod zopfli_oxipng;
#[cfg(feature = "zopfli")]
pub use zopfli::Options as ZopfliOptions;
#[cfg(feature = "zopfli")]
pub use zopfli_oxipng::deflate as zopfli_deflate;
/// DEFLATE algorithms supported by oxipng (for use in [`Options`][crate::Options])
@ -21,12 +21,7 @@ pub enum Deflater {
},
#[cfg(feature = "zopfli")]
/// Use the better but slower Zopfli implementation
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,
},
Zopfli(ZopfliOptions),
}
impl Deflater {
@ -34,7 +29,7 @@ impl Deflater {
let compressed = match self {
Self::Libdeflater { compression } => deflate(data, compression, max_size)?,
#[cfg(feature = "zopfli")]
Self::Zopfli { iterations } => zopfli_deflate(data, iterations)?,
Self::Zopfli(options) => zopfli_deflate(data, options)?,
};
if let Some(max) = max_size {
if compressed.len() > max {
@ -51,7 +46,7 @@ impl Display for Deflater {
match self {
Self::Libdeflater { compression } => write!(f, "zc = {compression}"),
#[cfg(feature = "zopfli")]
Self::Zopfli { iterations } => write!(f, "zopfli, zi = {iterations}"),
Self::Zopfli(options) => write!(f, "zopfli, zi = {}", options.iteration_count),
}
}
}

View file

@ -1,13 +1,7 @@
use std::num::NonZeroU8;
use crate::{PngError, PngResult};
pub fn deflate(data: &[u8], iterations: NonZeroU8) -> PngResult<Vec<u8>> {
pub fn deflate(data: &[u8], options: zopfli::Options) -> PngResult<Vec<u8>> {
let mut output = Vec::with_capacity(data.len());
let options = zopfli::Options {
iteration_count: iterations.into(),
..Default::default()
};
// Since Rust v1.74, passing &[u8] directly into zopfli causes a regression in compressed size
// for some files. Wrapping the slice in another Read implementer such as Box fixes it for now.
match zopfli::compress(options, zopfli::Format::Zlib, Box::new(data), &mut output) {

View file

@ -41,6 +41,8 @@ use log::{debug, info, trace, warn};
use rayon::prelude::*;
pub use rgb::{RGB16, RGBA8};
#[cfg(feature = "zopfli")]
pub use crate::deflate::ZopfliOptions;
pub use crate::{
colors::{BitDepth, ColorType},
deflate::Deflater,

View file

@ -17,7 +17,7 @@
mod rayon;
#[cfg(feature = "zopfli")]
use std::num::NonZeroU8;
use std::num::NonZeroU64;
use std::{
ffi::OsString, fs::DirBuilder, io::Write, path::PathBuf, process::ExitCode, time::Duration,
};
@ -26,6 +26,8 @@ use clap::ArgMatches;
mod cli;
use indexmap::IndexSet;
use log::{Level, LevelFilter, error, warn};
#[cfg(feature = "zopfli")]
use oxipng::ZopfliOptions;
use oxipng::{Deflater, FilterStrategy, InFile, Options, OutFile, PngError, StripChunks};
use rayon::prelude::*;
@ -331,10 +333,11 @@ fn parse_opts_into_struct(
#[cfg(feature = "zopfli")]
if matches.get_flag("zopfli") {
let iterations = *matches.get_one::<i64>("iterations").unwrap();
opts.deflater = Deflater::Zopfli {
iterations: NonZeroU8::new(iterations as u8).unwrap(),
};
let iteration_count = *matches.get_one::<NonZeroU64>("iterations").unwrap();
opts.deflater = Deflater::Zopfli(ZopfliOptions {
iteration_count,
..Default::default()
});
}
if let (Deflater::Libdeflater { compression }, Some(x)) =
(&mut opts.deflater, matches.get_one::<i64>("compression"))

View file

@ -1,5 +1,3 @@
#[cfg(feature = "zopfli")]
use std::num::NonZeroU8;
use std::{
fs::remove_file,
path::{Path, PathBuf},
@ -641,9 +639,7 @@ fn scale_16() {
fn zopfli_mode() {
let input = PathBuf::from("tests/files/zopfli_mode.png");
let (output, mut opts) = get_opts(&input);
opts.deflater = Deflater::Zopfli {
iterations: NonZeroU8::new(15).unwrap(),
};
opts.deflater = Deflater::Zopfli(ZopfliOptions::default());
test_it_converts(
input,