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 oxipng;
extern crate test; extern crate test;
use std::{num::NonZeroU8, path::PathBuf}; use std::path::PathBuf;
use oxipng::{internal_tests::*, *}; use oxipng::{internal_tests::*, *};
use test::Bencher; use test::Bencher;
const DEFAULT_ZOPFLI_ITERATIONS: NonZeroU8 = NonZeroU8::new(15).unwrap();
#[bench] #[bench]
fn zopfli_16_bits_strategy_0(b: &mut Bencher) { 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 input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
let png = PngData::new(&input, &Options::default()).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { 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(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { 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(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { 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(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { 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(); let png = PngData::new(&input, &Options::default()).unwrap();
b.iter(|| { 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}; 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") .long("zi")
.value_name("iterations") .value_name("iterations")
.default_value("15") .default_value("15")
.value_parser(1..=255) .value_parser(value_parser!(NonZeroU64))
.requires("zopfli"), .requires("zopfli"),
) )
.arg( .arg(

View file

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

View file

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

View file

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