diff --git a/.whitesource b/.whitesource new file mode 100644 index 00000000..9c7ae90b --- /dev/null +++ b/.whitesource @@ -0,0 +1,14 @@ +{ + "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 aa4dc97c..02c89caa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -479,6 +479,7 @@ dependencies = [ "rgb", "rustc-hash", "rustc_version", + "simd-adler32", "stderrlog", "wild", "zopfli", diff --git a/Cargo.toml b/Cargo.toml index 20f25ffc..8898674a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ 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" @@ -66,6 +67,7 @@ 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 03f83f65..acef1951 100644 --- a/benches/zopfli.rs +++ b/benches/zopfli.rs @@ -5,20 +5,19 @@ 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(|| { - zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok(); + BufferedZopfliDeflater::default() + .deflate(png.raw.data.as_ref(), &max_size) + .ok(); }); } @@ -26,9 +25,12 @@ 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(|| { - zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok(); + BufferedZopfliDeflater::default() + .deflate(png.raw.data.as_ref(), &max_size) + .ok(); }); } @@ -38,9 +40,12 @@ 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(|| { - zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok(); + BufferedZopfliDeflater::default() + .deflate(png.raw.data.as_ref(), &max_size) + .ok(); }); } @@ -50,9 +55,12 @@ 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(|| { - zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok(); + BufferedZopfliDeflater::default() + .deflate(png.raw.data.as_ref(), &max_size) + .ok(); }); } @@ -62,8 +70,11 @@ 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(|| { - zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok(); + BufferedZopfliDeflater::default() + .deflate(png.raw.data.as_ref(), &max_size) + .ok(); }); } diff --git a/src/atomicmin.rs b/src/atomicmin.rs index 69a379f2..2df7028c 100644 --- a/src/atomicmin.rs +++ b/src/atomicmin.rs @@ -23,7 +23,7 @@ impl AtomicMin { } /// Unset value is usize_max - pub fn as_atomic_usize(&self) -> &AtomicUsize { + pub const fn as_atomic_usize(&self) -> &AtomicUsize { &self.val } diff --git a/src/colors.rs b/src/colors.rs index d81b1346..238bf262 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 fn png_header_code(&self) -> u8 { + pub const fn png_header_code(&self) -> u8 { match self { ColorType::Grayscale { .. } => 0, ColorType::RGB { .. } => 2, @@ -56,7 +56,7 @@ impl ColorType { } #[inline] - pub(crate) fn channels_per_pixel(&self) -> u8 { + pub(crate) const 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) fn is_rgb(&self) -> bool { + pub(crate) const fn is_rgb(&self) -> bool { matches!(self, ColorType::RGB { .. } | ColorType::RGBA) } #[inline] - pub(crate) fn is_gray(&self) -> bool { + pub(crate) const fn is_gray(&self) -> bool { matches!( self, ColorType::Grayscale { .. } | ColorType::GrayscaleAlpha @@ -79,12 +79,12 @@ impl ColorType { } #[inline] - pub(crate) fn has_alpha(&self) -> bool { + pub(crate) const fn has_alpha(&self) -> bool { matches!(self, ColorType::GrayscaleAlpha | ColorType::RGBA) } #[inline] - pub(crate) fn has_trns(&self) -> bool { + pub(crate) const 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 0e8a65f3..d7d4f962 100644 --- a/src/deflate/mod.rs +++ b/src/deflate/mod.rs @@ -7,10 +7,15 @@ pub use deflater::inflate; use std::{fmt, fmt::Display}; #[cfg(feature = "zopfli")] -use std::num::NonZeroU8; +use std::io::{self, copy, BufWriter, Cursor, Write}; + +#[cfg(feature = "zopfli")] +use zopfli::{DeflateEncoder, Options}; #[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)] @@ -24,19 +29,21 @@ pub enum Deflaters { #[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 compression options + options: Options, }, } -impl Deflaters { - pub(crate) fn deflate(self, data: &[u8], max_size: &AtomicMin) -> PngResult> { +pub trait Deflater: Sync + Send { + fn deflate(&self, data: &[u8], max_size: &AtomicMin) -> PngResult>; +} + +impl Deflater for Deflaters { + fn deflate(&self, data: &[u8], max_size: &AtomicMin) -> PngResult> { 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 { iterations } => zopfli_deflate(data, iterations)?, + Self::Zopfli { options } => zopfli_deflate(data, options)?, }; if let Some(max) = max_size.get() { if compressed.len() > max { @@ -47,6 +54,75 @@ impl 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> { + 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> { + 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 b59b93df..3c6c8d4f 100644 --- a/src/deflate/zopfli_oxipng.rs +++ b/src/deflate/zopfli_oxipng.rs @@ -1,18 +1,64 @@ use crate::{PngError, PngResult}; -use std::num::NonZeroU8; +use simd_adler32::Adler32; +use std::io::{Error, ErrorKind, Read}; -pub fn deflate(data: &[u8], iterations: NonZeroU8) -> PngResult> { +pub fn deflate(data: &[u8], options: &zopfli::Options) -> PngResult> { use std::cmp::max; let mut output = Vec::with_capacity(max(1024, data.len() / 20)); - let options = zopfli::Options { - iteration_count: iterations, - ..Default::default() - }; - match zopfli::compress(&options, &zopfli::Format::Zlib, data, &mut output) { + 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 Read for HashingAndCountingRead<'_, R, H> { + fn read(&mut self, buf: &mut [u8]) -> Result { + 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 8a70a85e..3a973c59 100644 --- a/src/headers.rs +++ b/src/headers.rs @@ -1,9 +1,8 @@ use crate::colors::{BitDepth, ColorType}; -use crate::deflate::{crc32, inflate}; +use crate::deflate::{crc32, inflate, Deflater}; use crate::error::PngError; use crate::interlace::Interlacing; use crate::AtomicMin; -use crate::Deflaters; use crate::PngResult; use indexmap::IndexSet; use log::warn; @@ -28,7 +27,7 @@ impl IhdrData { /// Bits per pixel #[must_use] #[inline] - pub fn bpp(&self) -> usize { + pub const fn bpp(&self) -> usize { self.bit_depth as usize * self.color_type.channels_per_pixel() as usize } @@ -39,7 +38,7 @@ impl IhdrData { let h = self.height as usize; let bpp = self.bpp(); - fn bitmap_size(bpp: usize, w: usize, h: usize) -> usize { + const fn bitmap_size(bpp: usize, w: usize, h: usize) -> usize { ((w * bpp + 7) / 8) * h } @@ -249,7 +248,7 @@ pub fn extract_icc(iccp: &Chunk) -> Option> { } /// Construct an iCCP chunk by compressing the ICC profile -pub fn construct_iccp(icc: &[u8], deflater: Deflaters) -> PngResult { +pub fn construct_iccp(icc: &[u8], deflater: &T) -> PngResult { 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 13242044..1ad15e6b 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, info, trace, warn}; +use log::{debug, error, info, trace, warn}; use rayon::prelude::*; use std::borrow::Cow; use std::fmt; @@ -42,6 +42,7 @@ 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; @@ -249,7 +250,7 @@ impl Options { self } - fn apply_preset_2(self) -> Self { + const fn apply_preset_2(self) -> Self { self } @@ -381,15 +382,19 @@ 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(&self, opts: &Options) -> PngResult> { + pub fn create_optimized_png( + &self, + opts: &Options, + deflater: &T, + ) -> PngResult> { let deadline = Arc::new(Deadline::new(opts.timeout)); - let mut png = optimize_raw(self.png.clone(), opts, deadline.clone(), None) + let mut png = optimize_raw(self.png.clone(), opts, deadline.clone(), None, deflater) .ok_or_else(|| PngError::new("Failed to optimize input data"))?; // Process aux chunks @@ -399,7 +404,7 @@ impl RawImage { .filter(|c| opts.strip.keep(&c.name)) .cloned() .collect(); - postprocess_chunks(&mut png, opts, deadline, &self.png.ihdr); + postprocess_chunks(&mut png, opts, deadline, &self.png.ihdr, deflater); Ok(png.output()) } @@ -512,7 +517,7 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<( )) })?; // force drop and thereby closing of file handle before modifying any timestamp - std::mem::drop(buffer); + drop(buffer); if let Some(metadata_input) = &opt_metadata_preserved { copy_times(metadata_input, output_path)?; } @@ -583,12 +588,18 @@ fn optimize_png( } else { Some(png.estimated_output_size()) }; - if let Some(new_png) = optimize_raw(raw.clone(), &opts, deadline.clone(), max_size) { + if let Some(new_png) = optimize_raw( + raw.clone(), + &opts, + deadline.clone(), + max_size, + &opts.deflate, + ) { png.raw = new_png.raw; png.idat_data = new_png.idat_data; } - postprocess_chunks(png, &opts, deadline, &raw.ihdr); + postprocess_chunks(png, &opts, deadline, &raw.ihdr, &opts.deflate); let output = png.output(); @@ -628,11 +639,12 @@ fn optimize_png( } /// Perform optimization on the input image data using the options provided -fn optimize_raw( +fn optimize_raw( image: Arc, opts: &Options, deadline: Arc, max_size: Option, + deflater: &T, ) -> Option { // Must use normal (lazy) compression, as faster ones (greedy) are not representative let eval_compression = 5; @@ -683,17 +695,9 @@ fn optimize_raw( // We should have a result here - fail if not (e.g. deadline passed) let result = eval_result?; - 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) - } - } + debug!("Trying: {}", result.filter); + let best_size = AtomicMin::new(max_size); + perform_trial(&result.filtered, opts, result.filter, &best_size, deflater) } else { // Perform full compression trials of selected filters and determine the best @@ -717,7 +721,7 @@ fn optimize_raw( return None; } let filtered = &png.filter_image(filter, opts.optimize_alpha); - perform_trial(filtered, opts, filter, &best_size) + perform_trial(filtered, opts, filter, &best_size, deflater) }); best.reduce_with(|i, j| { if i.1.len() < j.1.len() || (i.1.len() == j.1.len() && i.0 < j.0) { @@ -769,13 +773,15 @@ fn optimize_raw( } /// Execute a compression trial -fn perform_trial( +fn perform_trial( filtered: &[u8], opts: &Options, filter: RowFilter, best_size: &AtomicMin, + deflater: &T, ) -> Option { - match opts.deflate.deflate(filtered, best_size) { + let result = deflater.deflate(filtered, best_size); + match result { Ok(new_idat) => { let bytes = new_idat.len(); best_size.set_min(bytes); @@ -796,7 +802,10 @@ fn perform_trial( ); None } - Err(_) => None, + Err(e) => { + error!("I/O error: {}", e); + None + } } } @@ -858,12 +867,15 @@ fn report_format(prefix: &str, png: &PngImage) { } /// Perform cleanup of certain chunks from the `PngData` object, after optimization has been completed -fn postprocess_chunks( +fn postprocess_chunks( png: &mut PngData, opts: &Options, deadline: Arc, 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"); @@ -885,7 +897,7 @@ fn postprocess_chunks( name: *b"sRGB", data: vec![intent], }; - } else if let Ok(iccp) = construct_iccp(&icc, opts.deflate) { + } else if let Ok(iccp) = construct_iccp(&icc, deflater) { let cur_len = png.aux_chunks[iccp_idx].data.len(); let new_len = iccp.data.len(); if new_len < cur_len { @@ -951,7 +963,7 @@ fn postprocess_chunks( } /// Check if an image was already optimized prior to oxipng's operations -fn is_fully_optimized(original_size: usize, optimized_size: usize, opts: &Options) -> bool { +const 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 519d68b4..422e79d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,8 +22,6 @@ 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; @@ -517,9 +515,10 @@ fn parse_opts_into_struct( if matches.get_flag("zopfli") { #[cfg(feature = "zopfli")] - if let Some(iterations) = NonZeroU8::new(15) { - opts.deflate = Deflaters::Zopfli { iterations }; - } + let zopfli_opts = zopfli::Options::default(); + opts.deflate = Deflaters::Zopfli { + options: zopfli_opts, + }; } else if let Deflaters::Libdeflater { compression } = &mut opts.deflate { if let Some(x) = matches.get_one::("compression") { *compression = *x as u8; diff --git a/src/png/mod.rs b/src/png/mod.rs index 46ccc04a..d6bdc861 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 fn channels_per_pixel(&self) -> usize { + pub const 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 fn bytes_per_channel(&self) -> usize { + pub const 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) { } // Integer approximation for i * log2(i) - much faster than float calculations -fn ilog2i(i: u32) -> u32 { +const fn ilog2i(i: u32) -> u32 { let log = 32 - i.leading_zeros() - 1; i * log + ((i - (1 << log)) << 1) } diff --git a/tests/flags.rs b/tests/flags.rs index 2df4ea0b..65dcb5fd 100644 --- a/tests/flags.rs +++ b/tests/flags.rs @@ -678,7 +678,10 @@ fn zopfli_mode() { let input = PathBuf::from("tests/files/zopfli_mode.png"); let (output, mut opts) = get_opts(&input); opts.deflate = Deflaters::Zopfli { - iterations: NonZeroU8::new(15).unwrap(), + options: zopfli::Options { + iteration_count: NonZeroU8::new(15).unwrap(), + maximum_block_splits: 15, + }, }; test_it_converts( diff --git a/tests/raw.rs b/tests/raw.rs index c3e16fb2..8aebc1f8 100644 --- a/tests/raw.rs +++ b/tests/raw.rs @@ -14,6 +14,7 @@ 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(); @@ -35,7 +36,7 @@ fn test_it_converts(input: &str) { raw.add_png_chunk(chunk.name, chunk.data); } - let output = raw.create_optimized_png(&opts).unwrap(); + let output = raw.create_optimized_png(&opts, &deflater).unwrap(); let new = PngData::from_slice(&output, &opts).unwrap(); assert!(new.aux_chunks.len() == num_chunks); @@ -52,6 +53,7 @@ fn from_file() { #[test] fn custom_indexed() { let opts = get_opts(); + let deflater = BufferedZopfliDeflater::default(); let raw = RawImage::new( 4, @@ -69,7 +71,7 @@ fn custom_indexed() { ) .unwrap(); - raw.create_optimized_png(&opts).unwrap(); + raw.create_optimized_png(&opts, &deflater).unwrap(); } #[test]