Repository: shssoichiro/oxipng
Pull request #532 (2023-07-09 17:33:33)

Revert "Provide BufferedZopfliDeflater and allow user to pass in a custom Deflater"
Reverts shssoichiro/oxipng#530

As mentioned on that MR there are some concerns that still need to be worked out. @Pr0methean apologies for the confusion, and please feel free to reopen your MR and talk out what needs to be fixed with @andrews05 


Patch:
diff --git a/.whitesource b/.whitesource
deleted file mode 100644
index 9c7ae90b..00000000
--- a/.whitesource
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "scanSettings": {
-    "baseBranches": []
-  },
-  "checkRunSettings": {
-    "vulnerableCheckRunConclusionLevel": "failure",
-    "displayMode": "diff",
-    "useMendCheckNames": true
-  },
-  "issueSettings": {
-    "minSeverityLevel": "LOW",
-    "issueType": "DEPENDENCY"
-  }
-}
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 02c89caa..aa4dc97c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -479,7 +479,6 @@ dependencies = [
  "rgb",
  "rustc-hash",
  "rustc_version",
- "simd-adler32",
  "stderrlog",
  "wild",
  "zopfli",
diff --git a/Cargo.toml b/Cargo.toml
index 8898674a..20f25ffc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,7 +28,6 @@ required-features = ["zopfli"]
 
 [dependencies]
 zopfli = { version = "0.7.4", optional = true, default-features = false, features = ["std", "zlib"] }
-simd-adler32 = { version = "0.3.5", optional = true, default-features = false }
 rgb = "0.8.36"
 indexmap = "2.0.0"
 libdeflater = "0.14.0"
@@ -67,7 +66,6 @@ version = "0.24.6"
 rustc_version = "0.4.0"
 
 [features]
-zopfli = ["zopfli/std", "zopfli/zlib", "simd-adler32"]
 binary = ["clap", "wild", "stderrlog"]
 default = ["binary", "filetime", "parallel", "zopfli"]
 parallel = ["rayon", "indexmap/rayon", "crossbeam-channel"]
diff --git a/benches/zopfli.rs b/benches/zopfli.rs
index acef1951..03f83f65 100644
--- a/benches/zopfli.rs
+++ b/benches/zopfli.rs
@@ -5,19 +5,20 @@ extern crate test;
 
 use oxipng::internal_tests::*;
 use oxipng::*;
+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, &Options::default()).unwrap();
-    let max_size = AtomicMin::new(Some(png.idat_data.len()));
 
     b.iter(|| {
-        BufferedZopfliDeflater::default()
-            .deflate(png.raw.data.as_ref(), &max_size)
-            .ok();
+        zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
     });
 }
 
@@ -25,12 +26,9 @@ fn zopfli_16_bits_strategy_0(b: &mut Bencher) {
 fn zopfli_8_bits_strategy_0(b: &mut Bencher) {
     let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
     let png = PngData::new(&input, &Options::default()).unwrap();
-    let max_size = AtomicMin::new(Some(png.idat_data.len()));
 
     b.iter(|| {
-        BufferedZopfliDeflater::default()
-            .deflate(png.raw.data.as_ref(), &max_size)
-            .ok();
+        zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
     });
 }
 
@@ -40,12 +38,9 @@ fn zopfli_4_bits_strategy_0(b: &mut Bencher) {
         "tests/files/palette_4_should_be_palette_4.png",
     ));
     let png = PngData::new(&input, &Options::default()).unwrap();
-    let max_size = AtomicMin::new(Some(png.idat_data.len()));
 
     b.iter(|| {
-        BufferedZopfliDeflater::default()
-            .deflate(png.raw.data.as_ref(), &max_size)
-            .ok();
+        zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
     });
 }
 
@@ -55,12 +50,9 @@ fn zopfli_2_bits_strategy_0(b: &mut Bencher) {
         "tests/files/palette_2_should_be_palette_2.png",
     ));
     let png = PngData::new(&input, &Options::default()).unwrap();
-    let max_size = AtomicMin::new(Some(png.idat_data.len()));
 
     b.iter(|| {
-        BufferedZopfliDeflater::default()
-            .deflate(png.raw.data.as_ref(), &max_size)
-            .ok();
+        zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
     });
 }
 
@@ -70,11 +62,8 @@ fn zopfli_1_bits_strategy_0(b: &mut Bencher) {
         "tests/files/palette_1_should_be_palette_1.png",
     ));
     let png = PngData::new(&input, &Options::default()).unwrap();
-    let max_size = AtomicMin::new(Some(png.idat_data.len()));
 
     b.iter(|| {
-        BufferedZopfliDeflater::default()
-            .deflate(png.raw.data.as_ref(), &max_size)
-            .ok();
+        zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
     });
 }
diff --git a/src/atomicmin.rs b/src/atomicmin.rs
index 2df7028c..69a379f2 100644
--- a/src/atomicmin.rs
+++ b/src/atomicmin.rs
@@ -23,7 +23,7 @@ impl AtomicMin {
     }
 
     /// Unset value is usize_max
-    pub const fn as_atomic_usize(&self) -> &AtomicUsize {
+    pub fn as_atomic_usize(&self) -> &AtomicUsize {
         &self.val
     }
 
diff --git a/src/colors.rs b/src/colors.rs
index 238bf262..d81b1346 100644
--- a/src/colors.rs
+++ b/src/colors.rs
@@ -45,7 +45,7 @@ impl Display for ColorType {
 impl ColorType {
     /// Get the code used by the PNG specification to denote this color type
     #[inline]
-    pub const fn png_header_code(&self) -> u8 {
+    pub fn png_header_code(&self) -> u8 {
         match self {
             ColorType::Grayscale { .. } => 0,
             ColorType::RGB { .. } => 2,
@@ -56,7 +56,7 @@ impl ColorType {
     }
 
     #[inline]
-    pub(crate) const fn channels_per_pixel(&self) -> u8 {
+    pub(crate) fn channels_per_pixel(&self) -> u8 {
         match self {
             ColorType::Grayscale { .. } | ColorType::Indexed { .. } => 1,
             ColorType::GrayscaleAlpha => 2,
@@ -66,12 +66,12 @@ impl ColorType {
     }
 
     #[inline]
-    pub(crate) const fn is_rgb(&self) -> bool {
+    pub(crate) fn is_rgb(&self) -> bool {
         matches!(self, ColorType::RGB { .. } | ColorType::RGBA)
     }
 
     #[inline]
-    pub(crate) const fn is_gray(&self) -> bool {
+    pub(crate) fn is_gray(&self) -> bool {
         matches!(
             self,
             ColorType::Grayscale { .. } | ColorType::GrayscaleAlpha
@@ -79,12 +79,12 @@ impl ColorType {
     }
 
     #[inline]
-    pub(crate) const fn has_alpha(&self) -> bool {
+    pub(crate) fn has_alpha(&self) -> bool {
         matches!(self, ColorType::GrayscaleAlpha | ColorType::RGBA)
     }
 
     #[inline]
-    pub(crate) const fn has_trns(&self) -> bool {
+    pub(crate) fn has_trns(&self) -> bool {
         match self {
             ColorType::Grayscale { transparent_shade } => transparent_shade.is_some(),
             ColorType::RGB { transparent_color } => transparent_color.is_some(),
diff --git a/src/deflate/mod.rs b/src/deflate/mod.rs
index d7d4f962..0e8a65f3 100644
--- a/src/deflate/mod.rs
+++ b/src/deflate/mod.rs
@@ -7,15 +7,10 @@ pub use deflater::inflate;
 use std::{fmt, fmt::Display};
 
 #[cfg(feature = "zopfli")]
-use std::io::{self, copy, BufWriter, Cursor, Write};
-
-#[cfg(feature = "zopfli")]
-use zopfli::{DeflateEncoder, Options};
+use std::num::NonZeroU8;
 #[cfg(feature = "zopfli")]
 mod zopfli_oxipng;
 #[cfg(feature = "zopfli")]
-use simd_adler32::Adler32;
-#[cfg(feature = "zopfli")]
 pub use zopfli_oxipng::deflate as zopfli_deflate;
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -29,21 +24,19 @@ pub enum Deflaters {
     #[cfg(feature = "zopfli")]
     /// Use the better but slower Zopfli implementation
     Zopfli {
-        /// Zopfli compression options
-        options: Options,
+        /// 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,
     },
 }
 
-pub trait Deflater: Sync + Send {
-    fn deflate(&self, data: &[u8], max_size: &AtomicMin) -> PngResult<Vec<u8>>;
-}
-
-impl Deflater for Deflaters {
-    fn deflate(&self, data: &[u8], max_size: &AtomicMin) -> PngResult<Vec<u8>> {
+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)?,
+            Self::Libdeflater { compression } => deflate(data, compression, max_size)?,
             #[cfg(feature = "zopfli")]
-            Self::Zopfli { options } => zopfli_deflate(data, options)?,
+            Self::Zopfli { iterations } => zopfli_deflate(data, iterations)?,
         };
         if let Some(max) = max_size.get() {
             if compressed.len() > max {
@@ -54,75 +47,6 @@ impl Deflater for Deflaters {
     }
 }
 
-#[cfg(feature = "zopfli")]
-#[derive(Copy, Clone, Debug)]
-pub struct BufferedZopfliDeflater {
-    input_buffer_size: usize,
-    output_buffer_size: usize,
-    options: Options,
-}
-
-#[cfg(feature = "zopfli")]
-impl BufferedZopfliDeflater {
-    pub const fn new(
-        input_buffer_size: usize,
-        output_buffer_size: usize,
-        options: Options,
-    ) -> Self {
-        BufferedZopfliDeflater {
-            input_buffer_size,
-            output_buffer_size,
-            options,
-        }
-    }
-}
-
-#[cfg(feature = "zopfli")]
-impl Default for BufferedZopfliDeflater {
-    fn default() -> Self {
-        BufferedZopfliDeflater {
-            input_buffer_size: 1024 * 1024,
-            output_buffer_size: 64 * 1024,
-            options: Options::default(),
-        }
-    }
-}
-
-#[cfg(feature = "zopfli")]
-impl Deflater for BufferedZopfliDeflater {
-    /// Fork of the zlib_compress function in Zopfli.
-    fn deflate(&self, data: &[u8], max_size: &AtomicMin) -> PngResult<Vec<u8>> {
-        let mut out = Cursor::new(Vec::with_capacity(self.output_buffer_size));
-        let cmf = 120; /* CM 8, CINFO 7. See zlib spec.*/
-        let flevel = 3;
-        let fdict = 0;
-        let mut cmfflg: u16 = 256 * cmf + fdict * 32 + flevel * 64;
-        let fcheck = 31 - cmfflg % 31;
-        cmfflg += fcheck;
-
-        let out = (|| -> io::Result<Vec<u8>> {
-            let mut rolling_adler = Adler32::new();
-            let mut in_data =
-                zopfli_oxipng::HashingAndCountingRead::new(data, &mut rolling_adler, None);
-            out.write_all(&cmfflg.to_be_bytes())?;
-            let mut buffer = BufWriter::with_capacity(
-                self.input_buffer_size,
-                DeflateEncoder::new(self.options, Default::default(), &mut out),
-            );
-            copy(&mut in_data, &mut buffer)?;
-            buffer.into_inner()?.finish()?;
-            out.write_all(&rolling_adler.finish().to_be_bytes())?;
-            Ok(out.into_inner())
-        })();
-        let out = out.map_err(|e| PngError::new(&e.to_string()))?;
-        if max_size.get().map(|max| max < out.len()).unwrap_or(false) {
-            Err(PngError::DeflatedDataTooLong(out.len()))
-        } else {
-            Ok(out)
-        }
-    }
-}
-
 impl Display for Deflaters {
     #[inline]
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
diff --git a/src/deflate/zopfli_oxipng.rs b/src/deflate/zopfli_oxipng.rs
index 3c6c8d4f..b59b93df 100644
--- a/src/deflate/zopfli_oxipng.rs
+++ b/src/deflate/zopfli_oxipng.rs
@@ -1,64 +1,18 @@
 use crate::{PngError, PngResult};
-use simd_adler32::Adler32;
-use std::io::{Error, ErrorKind, Read};
+use std::num::NonZeroU8;
 
-pub fn deflate(data: &[u8], options: &zopfli::Options) -> PngResult<Vec<u8>> {
+pub fn deflate(data: &[u8], iterations: NonZeroU8) -> PngResult<Vec<u8>> {
     use std::cmp::max;
 
     let mut output = Vec::with_capacity(max(1024, data.len() / 20));
-    match zopfli::compress(options, &zopfli::Format::Zlib, data, &mut output) {
+    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")),
     };
     output.shrink_to_fit();
     Ok(output)
 }
-
-/// Forked from zopfli crate
-pub trait Hasher {
-    fn update(&mut self, data: &[u8]);
-}
-
-impl Hasher for &mut Adler32 {
-    fn update(&mut self, data: &[u8]) {
-        Adler32::write(self, data)
-    }
-}
-
-/// A reader that wraps another reader, a hasher and an optional counter,
-/// updating the hasher state and incrementing a counter of bytes read so
-/// far for each block of data read.
-pub struct HashingAndCountingRead<'counter, R: Read, H: Hasher> {
-    inner: R,
-    hasher: H,
-    bytes_read: Option<&'counter mut u32>,
-}
-
-impl<'counter, R: Read, H: Hasher> HashingAndCountingRead<'counter, R, H> {
-    pub fn new(inner: R, hasher: H, bytes_read: Option<&'counter mut u32>) -> Self {
-        Self {
-            inner,
-            hasher,
-            bytes_read,
-        }
-    }
-}
-
-impl<R: Read, H: Hasher> Read for HashingAndCountingRead<'_, R, H> {
-    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
-        match self.inner.read(buf) {
-            Ok(bytes_read) => {
-                self.hasher.update(&buf[..bytes_read]);
-
-                if let Some(total_bytes_read) = &mut self.bytes_read {
-                    **total_bytes_read = total_bytes_read
-                        .checked_add(bytes_read.try_into().map_err(|_| ErrorKind::Other)?)
-                        .ok_or(ErrorKind::Other)?;
-                }
-
-                Ok(bytes_read)
-            }
-            Err(err) => Err(err),
-        }
-    }
-}
diff --git a/src/headers.rs b/src/headers.rs
index 3a973c59..8a70a85e 100644
--- a/src/headers.rs
+++ b/src/headers.rs
@@ -1,8 +1,9 @@
 use crate::colors::{BitDepth, ColorType};
-use crate::deflate::{crc32, inflate, Deflater};
+use crate::deflate::{crc32, inflate};
 use crate::error::PngError;
 use crate::interlace::Interlacing;
 use crate::AtomicMin;
+use crate::Deflaters;
 use crate::PngResult;
 use indexmap::IndexSet;
 use log::warn;
@@ -27,7 +28,7 @@ impl IhdrData {
     /// Bits per pixel
     #[must_use]
     #[inline]
-    pub const fn bpp(&self) -> usize {
+    pub fn bpp(&self) -> usize {
         self.bit_depth as usize * self.color_type.channels_per_pixel() as usize
     }
 
@@ -38,7 +39,7 @@ impl IhdrData {
         let h = self.height as usize;
         let bpp = self.bpp();
 
-        const fn bitmap_size(bpp: usize, w: usize, h: usize) -> usize {
+        fn bitmap_size(bpp: usize, w: usize, h: usize) -> usize {
             ((w * bpp + 7) / 8) * h
         }
 
@@ -248,7 +249,7 @@ pub fn extract_icc(iccp: &Chunk) -> Option<Vec<u8>> {
 }
 
 /// Construct an iCCP chunk by compressing the ICC profile
-pub fn construct_iccp<T: Deflater>(icc: &[u8], deflater: &T) -> PngResult<Chunk> {
+pub fn construct_iccp(icc: &[u8], deflater: Deflaters) -> PngResult<Chunk> {
     let mut compressed = deflater.deflate(icc, &AtomicMin::new(None))?;
     let mut data = Vec::with_capacity(compressed.len() + 5);
     data.extend(b"icc"); // Profile name - generally unused, can be anything
diff --git a/src/lib.rs b/src/lib.rs
index 9c26a40c..23cc7a93 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -30,7 +30,7 @@ use crate::headers::*;
 use crate::png::PngData;
 use crate::png::PngImage;
 use crate::reduction::*;
-use log::{debug, error, info, trace, warn};
+use log::{debug, info, trace, warn};
 use rayon::prelude::*;
 use std::borrow::Cow;
 use std::fmt;
@@ -42,7 +42,6 @@ use std::sync::Arc;
 use std::time::{Duration, Instant};
 
 pub use crate::colors::{BitDepth, ColorType};
-use crate::deflate::Deflater;
 pub use crate::deflate::Deflaters;
 pub use crate::error::PngError;
 pub use crate::filters::RowFilter;
@@ -250,7 +249,7 @@ impl Options {
         self
     }
 
-    const fn apply_preset_2(self) -> Self {
+    fn apply_preset_2(self) -> Self {
         self
     }
 
@@ -382,19 +381,15 @@ impl RawImage {
     pub fn add_icc_profile(&mut self, data: &[u8]) {
         // Compress with fastest compression level - will be recompressed during optimization
         let deflater = Deflaters::Libdeflater { compression: 1 };
-        if let Ok(iccp) = construct_iccp(data, &deflater) {
+        if let Ok(iccp) = construct_iccp(data, deflater) {
             self.aux_chunks.push(iccp);
         }
     }
 
     /// Create an optimized png from the raw image data using the options provided
-    pub fn create_optimized_png<T: Deflater>(
-        &self,
-        opts: &Options,
-        deflater: &T,
-    ) -> PngResult<Vec<u8>> {
+    pub fn create_optimized_png(&self, opts: &Options) -> PngResult<Vec<u8>> {
         let deadline = Arc::new(Deadline::new(opts.timeout));
-        let mut png = optimize_raw(self.png.clone(), opts, deadline.clone(), None, deflater)
+        let mut png = optimize_raw(self.png.clone(), opts, deadline.clone(), None)
             .ok_or_else(|| PngError::new("Failed to optimize input data"))?;
 
         // Process aux chunks
@@ -404,7 +399,7 @@ impl RawImage {
             .filter(|c| opts.strip.keep(&c.name))
             .cloned()
             .collect();
-        postprocess_chunks(&mut png, opts, deadline, &self.png.ihdr, deflater);
+        postprocess_chunks(&mut png, opts, deadline, &self.png.ihdr);
 
         Ok(png.output())
     }
@@ -517,7 +512,7 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
                     ))
                 })?;
             // force drop and thereby closing of file handle before modifying any timestamp
-            drop(buffer);
+            std::mem::drop(buffer);
             if let Some(metadata_input) = &opt_metadata_preserved {
                 copy_times(metadata_input, output_path)?;
             }
@@ -588,18 +583,12 @@ fn optimize_png(
     } else {
         Some(png.estimated_output_size())
     };
-    if let Some(new_png) = optimize_raw(
-        raw.clone(),
-        &opts,
-        deadline.clone(),
-        max_size,
-        &opts.deflate,
-    ) {
+    if let Some(new_png) = optimize_raw(raw.clone(), &opts, deadline.clone(), max_size) {
         png.raw = new_png.raw;
         png.idat_data = new_png.idat_data;
     }
 
-    postprocess_chunks(png, &opts, deadline, &raw.ihdr, &opts.deflate);
+    postprocess_chunks(png, &opts, deadline, &raw.ihdr);
 
     let output = png.output();
 
@@ -639,12 +628,11 @@ fn optimize_png(
 }
 
 /// Perform optimization on the input image data using the options provided
-fn optimize_raw<T: Deflater>(
+fn optimize_raw(
     image: Arc<PngImage>,
     opts: &Options,
     deadline: Arc<Deadline>,
     max_size: Option<usize>,
-    deflater: &T,
 ) -> Option<PngData> {
     // Libdeflate has four algorithms: 1-4 = 'greedy', 5-7 = 'lazy', 8-9 = 'lazy2', 10-12 = 'near-optimal'
     // 5 is the minimumm required for a decent evaluation result
@@ -705,9 +693,17 @@ fn optimize_raw<T: Deflater>(
             // We should have a result here - fail if not (e.g. deadline passed)
             let result = eval_result?;
 
-            debug!("Trying: {}", result.filter);
-            let best_size = AtomicMin::new(max_size);
-            perform_trial(&result.filtered, opts, result.filter, &best_size, deflater)
+            match opts.deflate {
+                Deflaters::Libdeflater { compression } if compression <= eval_compression => {
+                    // No further compression required
+                    Some((result.filter, result.idat_data))
+                }
+                _ => {
+                    debug!("Trying: {}", result.filter);
+                    let best_size = AtomicMin::new(max_size);
+                    perform_trial(&result.filtered, opts, result.filter, &best_size)
+                }
+            }
         } else {
             // Perform full compression trials of selected filters and determine the best
 
@@ -731,7 +727,7 @@ fn optimize_raw<T: Deflater>(
                     return None;
                 }
                 let filtered = &png.filter_image(filter, opts.optimize_alpha);
-                perform_trial(filtered, opts, filter, &best_size, deflater)
+                perform_trial(filtered, opts, filter, &best_size)
             });
             best.reduce_with(|i, j| {
                 if i.1.len() < j.1.len() || (i.1.len() == j.1.len() && i.0 < j.0) {
@@ -783,15 +779,13 @@ fn optimize_raw<T: Deflater>(
 }
 
 /// Execute a compression trial
-fn perform_trial<T: Deflater>(
+fn perform_trial(
     filtered: &[u8],
     opts: &Options,
     filter: RowFilter,
     best_size: &AtomicMin,
-    deflater: &T,
 ) -> Option<TrialResult> {
-    let result = deflater.deflate(filtered, best_size);
-    match result {
+    match opts.deflate.deflate(filtered, best_size) {
         Ok(new_idat) => {
             let bytes = new_idat.len();
             best_size.set_min(bytes);
@@ -812,10 +806,7 @@ fn perform_trial<T: Deflater>(
             );
             None
         }
-        Err(e) => {
-            error!("I/O error: {}", e);
-            None
-        }
+        Err(_) => None,
     }
 }
 
@@ -877,15 +868,12 @@ fn report_format(prefix: &str, png: &PngImage) {
 }
 
 /// Perform cleanup of certain chunks from the `PngData` object, after optimization has been completed
-fn postprocess_chunks<T>(
+fn postprocess_chunks(
     png: &mut PngData,
     opts: &Options,
     deadline: Arc<Deadline>,
     orig_ihdr: &IhdrData,
-    deflater: &T,
-) where
-    T: Deflater,
-{
+) {
     if let Some(iccp_idx) = png.aux_chunks.iter().position(|c| &c.name == b"iCCP") {
         // See if we can replace an iCCP chunk with an sRGB chunk
         let may_replace_iccp = opts.strip != StripChunks::None && opts.strip.keep(b"sRGB");
@@ -907,7 +895,7 @@ fn postprocess_chunks<T>(
                     name: *b"sRGB",
                     data: vec![intent],
                 };
-            } else if let Ok(iccp) = construct_iccp(&icc, deflater) {
+            } else if let Ok(iccp) = construct_iccp(&icc, opts.deflate) {
                 let cur_len = png.aux_chunks[iccp_idx].data.len();
                 let new_len = iccp.data.len();
                 if new_len < cur_len {
@@ -973,7 +961,7 @@ fn postprocess_chunks<T>(
 }
 
 /// Check if an image was already optimized prior to oxipng's operations
-const fn is_fully_optimized(original_size: usize, optimized_size: usize, opts: &Options) -> bool {
+fn is_fully_optimized(original_size: usize, optimized_size: usize, opts: &Options) -> bool {
     original_size <= optimized_size && !opts.force
 }
 
diff --git a/src/main.rs b/src/main.rs
index 9afc43fa..19768cbd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -22,6 +22,8 @@ use oxipng::RowFilter;
 use oxipng::StripChunks;
 use oxipng::{InFile, OutFile};
 use std::fs::DirBuilder;
+#[cfg(feature = "zopfli")]
+use std::num::NonZeroU8;
 use std::path::PathBuf;
 use std::process::exit;
 use std::time::Duration;
@@ -515,10 +517,9 @@ fn parse_opts_into_struct(
 
     if matches.get_flag("zopfli") {
         #[cfg(feature = "zopfli")]
-        let zopfli_opts = zopfli::Options::default();
-        opts.deflate = Deflaters::Zopfli {
-            options: zopfli_opts,
-        };
+        if let Some(iterations) = NonZeroU8::new(15) {
+            opts.deflate = Deflaters::Zopfli { iterations };
+        }
     } else if let Deflaters::Libdeflater { compression } = &mut opts.deflate {
         if let Some(x) = matches.get_one::<i64>("compression") {
             *compression = *x as u8;
diff --git a/src/png/mod.rs b/src/png/mod.rs
index d6bdc861..46ccc04a 100644
--- a/src/png/mod.rs
+++ b/src/png/mod.rs
@@ -260,13 +260,13 @@ impl PngImage {
 
     /// Return the number of channels in the image, based on color type
     #[inline]
-    pub const fn channels_per_pixel(&self) -> usize {
+    pub fn channels_per_pixel(&self) -> usize {
         self.ihdr.color_type.channels_per_pixel() as usize
     }
 
     /// Return the number of bytes per channel in the image
     #[inline]
-    pub const fn bytes_per_channel(&self) -> usize {
+    pub fn bytes_per_channel(&self) -> usize {
         match self.ihdr.bit_depth {
             BitDepth::Sixteen => 2,
             // Depths lower than 8 will round up to 1 byte
@@ -491,7 +491,7 @@ fn write_png_block(key: &[u8], chunk: &[u8], output: &mut Vec<u8>) {
 }
 
 // Integer approximation for i * log2(i) - much faster than float calculations
-const fn ilog2i(i: u32) -> u32 {
+fn ilog2i(i: u32) -> u32 {
     let log = 32 - i.leading_zeros() - 1;
     i * log + ((i - (1 << log)) << 1)
 }


Test patch:
diff --git a/tests/flags.rs b/tests/flags.rs
index 1384a97f..5d3bdc2f 100644
--- a/tests/flags.rs
+++ b/tests/flags.rs
@@ -677,10 +677,7 @@ fn zopfli_mode() {
     let input = PathBuf::from("tests/files/zopfli_mode.png");
     let (output, mut opts) = get_opts(&input);
     opts.deflate = Deflaters::Zopfli {
-        options: zopfli::Options {
-            iteration_count: NonZeroU8::new(15).unwrap(),
-            maximum_block_splits: 15,
-        },
+        iterations: NonZeroU8::new(15).unwrap(),
     };
 
     test_it_converts(
diff --git a/tests/raw.rs b/tests/raw.rs
index 8aebc1f8..c3e16fb2 100644
--- a/tests/raw.rs
+++ b/tests/raw.rs
@@ -14,7 +14,6 @@ fn get_opts() -> Options {
 fn test_it_converts(input: &str) {
     let input = PathBuf::from(input);
     let opts = get_opts();
-    let deflater = BufferedZopfliDeflater::default();
 
     let original_data = PngData::read_file(&PathBuf::from(input)).unwrap();
     let image = PngData::from_slice(&original_data, &opts).unwrap();
@@ -36,7 +35,7 @@ fn test_it_converts(input: &str) {
         raw.add_png_chunk(chunk.name, chunk.data);
     }
 
-    let output = raw.create_optimized_png(&opts, &deflater).unwrap();
+    let output = raw.create_optimized_png(&opts).unwrap();
 
     let new = PngData::from_slice(&output, &opts).unwrap();
     assert!(new.aux_chunks.len() == num_chunks);
@@ -53,7 +52,6 @@ fn from_file() {
 #[test]
 fn custom_indexed() {
     let opts = get_opts();
-    let deflater = BufferedZopfliDeflater::default();
 
     let raw = RawImage::new(
         4,
@@ -71,7 +69,7 @@ fn custom_indexed() {
     )
     .unwrap();
 
-    raw.create_optimized_png(&opts, &deflater).unwrap();
+    raw.create_optimized_png(&opts).unwrap();
 }
 
 #[test]