Revert "Provide BufferedZopfliDeflater and allow user to pass in a custom Deflater (#530)"

This reverts commit 2a59419bdf.
This commit is contained in:
Josh Holmer 2023-07-09 13:32:32 -04:00
parent 775e718dff
commit 31e796c4e8
14 changed files with 77 additions and 242 deletions

View file

@ -1,14 +0,0 @@
{
"scanSettings": {
"baseBranches": []
},
"checkRunSettings": {
"vulnerableCheckRunConclusionLevel": "failure",
"displayMode": "diff",
"useMendCheckNames": true
},
"issueSettings": {
"minSeverityLevel": "LOW",
"issueType": "DEPENDENCY"
}
}

1
Cargo.lock generated
View file

@ -479,7 +479,6 @@ dependencies = [
"rgb", "rgb",
"rustc-hash", "rustc-hash",
"rustc_version", "rustc_version",
"simd-adler32",
"stderrlog", "stderrlog",
"wild", "wild",
"zopfli", "zopfli",

View file

@ -28,7 +28,6 @@ required-features = ["zopfli"]
[dependencies] [dependencies]
zopfli = { version = "0.7.4", optional = true, default-features = false, features = ["std", "zlib"] } 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" rgb = "0.8.36"
indexmap = "2.0.0" indexmap = "2.0.0"
libdeflater = "0.14.0" libdeflater = "0.14.0"
@ -67,7 +66,6 @@ version = "0.24.6"
rustc_version = "0.4.0" rustc_version = "0.4.0"
[features] [features]
zopfli = ["zopfli/std", "zopfli/zlib", "simd-adler32"]
binary = ["clap", "wild", "stderrlog"] binary = ["clap", "wild", "stderrlog"]
default = ["binary", "filetime", "parallel", "zopfli"] default = ["binary", "filetime", "parallel", "zopfli"]
parallel = ["rayon", "indexmap/rayon", "crossbeam-channel"] parallel = ["rayon", "indexmap/rayon", "crossbeam-channel"]

View file

@ -5,19 +5,20 @@ extern crate test;
use oxipng::internal_tests::*; use oxipng::internal_tests::*;
use oxipng::*; use oxipng::*;
use std::num::NonZeroU8;
use std::path::PathBuf; use std::path::PathBuf;
use test::Bencher; use test::Bencher;
// SAFETY: trivially safe. Stopgap solution until const unwrap is stabilized.
const DEFAULT_ZOPFLI_ITERATIONS: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(15) };
#[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();
let max_size = AtomicMin::new(Some(png.idat_data.len()));
b.iter(|| { b.iter(|| {
BufferedZopfliDeflater::default() zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
.deflate(png.raw.data.as_ref(), &max_size)
.ok();
}); });
} }
@ -25,12 +26,9 @@ fn zopfli_16_bits_strategy_0(b: &mut Bencher) {
fn zopfli_8_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 input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
let png = PngData::new(&input, &Options::default()).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
let max_size = AtomicMin::new(Some(png.idat_data.len()));
b.iter(|| { b.iter(|| {
BufferedZopfliDeflater::default() zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
.deflate(png.raw.data.as_ref(), &max_size)
.ok();
}); });
} }
@ -40,12 +38,9 @@ fn zopfli_4_bits_strategy_0(b: &mut Bencher) {
"tests/files/palette_4_should_be_palette_4.png", "tests/files/palette_4_should_be_palette_4.png",
)); ));
let png = PngData::new(&input, &Options::default()).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
let max_size = AtomicMin::new(Some(png.idat_data.len()));
b.iter(|| { b.iter(|| {
BufferedZopfliDeflater::default() zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
.deflate(png.raw.data.as_ref(), &max_size)
.ok();
}); });
} }
@ -55,12 +50,9 @@ fn zopfli_2_bits_strategy_0(b: &mut Bencher) {
"tests/files/palette_2_should_be_palette_2.png", "tests/files/palette_2_should_be_palette_2.png",
)); ));
let png = PngData::new(&input, &Options::default()).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
let max_size = AtomicMin::new(Some(png.idat_data.len()));
b.iter(|| { b.iter(|| {
BufferedZopfliDeflater::default() zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
.deflate(png.raw.data.as_ref(), &max_size)
.ok();
}); });
} }
@ -70,11 +62,8 @@ fn zopfli_1_bits_strategy_0(b: &mut Bencher) {
"tests/files/palette_1_should_be_palette_1.png", "tests/files/palette_1_should_be_palette_1.png",
)); ));
let png = PngData::new(&input, &Options::default()).unwrap(); let png = PngData::new(&input, &Options::default()).unwrap();
let max_size = AtomicMin::new(Some(png.idat_data.len()));
b.iter(|| { b.iter(|| {
BufferedZopfliDeflater::default() zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
.deflate(png.raw.data.as_ref(), &max_size)
.ok();
}); });
} }

View file

@ -23,7 +23,7 @@ impl AtomicMin {
} }
/// Unset value is usize_max /// Unset value is usize_max
pub const fn as_atomic_usize(&self) -> &AtomicUsize { pub fn as_atomic_usize(&self) -> &AtomicUsize {
&self.val &self.val
} }

View file

@ -45,7 +45,7 @@ impl Display for ColorType {
impl ColorType { impl ColorType {
/// Get the code used by the PNG specification to denote this color type /// Get the code used by the PNG specification to denote this color type
#[inline] #[inline]
pub const fn png_header_code(&self) -> u8 { pub fn png_header_code(&self) -> u8 {
match self { match self {
ColorType::Grayscale { .. } => 0, ColorType::Grayscale { .. } => 0,
ColorType::RGB { .. } => 2, ColorType::RGB { .. } => 2,
@ -56,7 +56,7 @@ impl ColorType {
} }
#[inline] #[inline]
pub(crate) const fn channels_per_pixel(&self) -> u8 { pub(crate) fn channels_per_pixel(&self) -> u8 {
match self { match self {
ColorType::Grayscale { .. } | ColorType::Indexed { .. } => 1, ColorType::Grayscale { .. } | ColorType::Indexed { .. } => 1,
ColorType::GrayscaleAlpha => 2, ColorType::GrayscaleAlpha => 2,
@ -66,12 +66,12 @@ impl ColorType {
} }
#[inline] #[inline]
pub(crate) const fn is_rgb(&self) -> bool { pub(crate) fn is_rgb(&self) -> bool {
matches!(self, ColorType::RGB { .. } | ColorType::RGBA) matches!(self, ColorType::RGB { .. } | ColorType::RGBA)
} }
#[inline] #[inline]
pub(crate) const fn is_gray(&self) -> bool { pub(crate) fn is_gray(&self) -> bool {
matches!( matches!(
self, self,
ColorType::Grayscale { .. } | ColorType::GrayscaleAlpha ColorType::Grayscale { .. } | ColorType::GrayscaleAlpha
@ -79,12 +79,12 @@ impl ColorType {
} }
#[inline] #[inline]
pub(crate) const fn has_alpha(&self) -> bool { pub(crate) fn has_alpha(&self) -> bool {
matches!(self, ColorType::GrayscaleAlpha | ColorType::RGBA) matches!(self, ColorType::GrayscaleAlpha | ColorType::RGBA)
} }
#[inline] #[inline]
pub(crate) const fn has_trns(&self) -> bool { pub(crate) fn has_trns(&self) -> bool {
match self { match self {
ColorType::Grayscale { transparent_shade } => transparent_shade.is_some(), ColorType::Grayscale { transparent_shade } => transparent_shade.is_some(),
ColorType::RGB { transparent_color } => transparent_color.is_some(), ColorType::RGB { transparent_color } => transparent_color.is_some(),

View file

@ -7,15 +7,10 @@ pub use deflater::inflate;
use std::{fmt, fmt::Display}; use std::{fmt, fmt::Display};
#[cfg(feature = "zopfli")] #[cfg(feature = "zopfli")]
use std::io::{self, copy, BufWriter, Cursor, Write}; use std::num::NonZeroU8;
#[cfg(feature = "zopfli")]
use zopfli::{DeflateEncoder, Options};
#[cfg(feature = "zopfli")] #[cfg(feature = "zopfli")]
mod zopfli_oxipng; mod zopfli_oxipng;
#[cfg(feature = "zopfli")] #[cfg(feature = "zopfli")]
use simd_adler32::Adler32;
#[cfg(feature = "zopfli")]
pub use zopfli_oxipng::deflate as zopfli_deflate; pub use zopfli_oxipng::deflate as zopfli_deflate;
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -29,21 +24,19 @@ pub enum Deflaters {
#[cfg(feature = "zopfli")] #[cfg(feature = "zopfli")]
/// Use the better but slower Zopfli implementation /// Use the better but slower Zopfli implementation
Zopfli { Zopfli {
/// Zopfli compression options /// The number of compression iterations to do. 15 iterations are fine
options: Options, /// 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 { impl Deflaters {
fn deflate(&self, data: &[u8], max_size: &AtomicMin) -> PngResult<Vec<u8>>; pub(crate) 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>> {
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 { options } => zopfli_deflate(data, options)?, Self::Zopfli { iterations } => zopfli_deflate(data, iterations)?,
}; };
if let Some(max) = max_size.get() { if let Some(max) = max_size.get() {
if compressed.len() > max { 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 { impl Display for Deflaters {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

View file

@ -1,64 +1,18 @@
use crate::{PngError, PngResult}; use crate::{PngError, PngResult};
use simd_adler32::Adler32; use std::num::NonZeroU8;
use std::io::{Error, ErrorKind, Read};
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; use std::cmp::max;
let mut output = Vec::with_capacity(max(1024, data.len() / 20)); 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(_) => (), Ok(_) => (),
Err(_) => return Err(PngError::new("Failed to compress in zopfli")), Err(_) => return Err(PngError::new("Failed to compress in zopfli")),
}; };
output.shrink_to_fit(); output.shrink_to_fit();
Ok(output) 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),
}
}
}

View file

@ -1,8 +1,9 @@
use crate::colors::{BitDepth, ColorType}; use crate::colors::{BitDepth, ColorType};
use crate::deflate::{crc32, inflate, Deflater}; use crate::deflate::{crc32, inflate};
use crate::error::PngError; use crate::error::PngError;
use crate::interlace::Interlacing; use crate::interlace::Interlacing;
use crate::AtomicMin; use crate::AtomicMin;
use crate::Deflaters;
use crate::PngResult; use crate::PngResult;
use indexmap::IndexSet; use indexmap::IndexSet;
use log::warn; use log::warn;
@ -27,7 +28,7 @@ impl IhdrData {
/// Bits per pixel /// Bits per pixel
#[must_use] #[must_use]
#[inline] #[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 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 h = self.height as usize;
let bpp = self.bpp(); 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 ((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 /// 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 compressed = deflater.deflate(icc, &AtomicMin::new(None))?;
let mut data = Vec::with_capacity(compressed.len() + 5); let mut data = Vec::with_capacity(compressed.len() + 5);
data.extend(b"icc"); // Profile name - generally unused, can be anything data.extend(b"icc"); // Profile name - generally unused, can be anything

View file

@ -30,7 +30,7 @@ use crate::headers::*;
use crate::png::PngData; use crate::png::PngData;
use crate::png::PngImage; use crate::png::PngImage;
use crate::reduction::*; use crate::reduction::*;
use log::{debug, error, info, trace, warn}; use log::{debug, info, trace, warn};
use rayon::prelude::*; use rayon::prelude::*;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt; use std::fmt;
@ -42,7 +42,6 @@ use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
pub use crate::colors::{BitDepth, ColorType}; pub use crate::colors::{BitDepth, ColorType};
use crate::deflate::Deflater;
pub use crate::deflate::Deflaters; pub use crate::deflate::Deflaters;
pub use crate::error::PngError; pub use crate::error::PngError;
pub use crate::filters::RowFilter; pub use crate::filters::RowFilter;
@ -250,7 +249,7 @@ impl Options {
self self
} }
const fn apply_preset_2(self) -> Self { fn apply_preset_2(self) -> Self {
self self
} }
@ -382,19 +381,15 @@ impl RawImage {
pub fn add_icc_profile(&mut self, data: &[u8]) { pub fn add_icc_profile(&mut self, data: &[u8]) {
// Compress with fastest compression level - will be recompressed during optimization // Compress with fastest compression level - will be recompressed during optimization
let deflater = Deflaters::Libdeflater { compression: 1 }; 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); self.aux_chunks.push(iccp);
} }
} }
/// Create an optimized png from the raw image data using the options provided /// Create an optimized png from the raw image data using the options provided
pub fn create_optimized_png<T: Deflater>( pub fn create_optimized_png(&self, opts: &Options) -> PngResult<Vec<u8>> {
&self,
opts: &Options,
deflater: &T,
) -> PngResult<Vec<u8>> {
let deadline = Arc::new(Deadline::new(opts.timeout)); 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"))?; .ok_or_else(|| PngError::new("Failed to optimize input data"))?;
// Process aux chunks // Process aux chunks
@ -404,7 +399,7 @@ impl RawImage {
.filter(|c| opts.strip.keep(&c.name)) .filter(|c| opts.strip.keep(&c.name))
.cloned() .cloned()
.collect(); .collect();
postprocess_chunks(&mut png, opts, deadline, &self.png.ihdr, deflater); postprocess_chunks(&mut png, opts, deadline, &self.png.ihdr);
Ok(png.output()) 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 // 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 { if let Some(metadata_input) = &opt_metadata_preserved {
copy_times(metadata_input, output_path)?; copy_times(metadata_input, output_path)?;
} }
@ -588,18 +583,12 @@ fn optimize_png(
} else { } else {
Some(png.estimated_output_size()) Some(png.estimated_output_size())
}; };
if let Some(new_png) = optimize_raw( if let Some(new_png) = optimize_raw(raw.clone(), &opts, deadline.clone(), max_size) {
raw.clone(),
&opts,
deadline.clone(),
max_size,
&opts.deflate,
) {
png.raw = new_png.raw; png.raw = new_png.raw;
png.idat_data = new_png.idat_data; 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(); let output = png.output();
@ -639,12 +628,11 @@ fn optimize_png(
} }
/// Perform optimization on the input image data using the options provided /// Perform optimization on the input image data using the options provided
fn optimize_raw<T: Deflater>( fn optimize_raw(
image: Arc<PngImage>, image: Arc<PngImage>,
opts: &Options, opts: &Options,
deadline: Arc<Deadline>, deadline: Arc<Deadline>,
max_size: Option<usize>, max_size: Option<usize>,
deflater: &T,
) -> Option<PngData> { ) -> Option<PngData> {
// Libdeflate has four algorithms: 1-4 = 'greedy', 5-7 = 'lazy', 8-9 = 'lazy2', 10-12 = 'near-optimal' // 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 // 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) // We should have a result here - fail if not (e.g. deadline passed)
let result = eval_result?; let result = eval_result?;
debug!("Trying: {}", result.filter); match opts.deflate {
let best_size = AtomicMin::new(max_size); Deflaters::Libdeflater { compression } if compression <= eval_compression => {
perform_trial(&result.filtered, opts, result.filter, &best_size, deflater) // 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 { } else {
// Perform full compression trials of selected filters and determine the best // Perform full compression trials of selected filters and determine the best
@ -731,7 +727,7 @@ fn optimize_raw<T: Deflater>(
return None; return None;
} }
let filtered = &png.filter_image(filter, opts.optimize_alpha); 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| { best.reduce_with(|i, j| {
if i.1.len() < j.1.len() || (i.1.len() == j.1.len() && i.0 < j.0) { 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 /// Execute a compression trial
fn perform_trial<T: Deflater>( fn perform_trial(
filtered: &[u8], filtered: &[u8],
opts: &Options, opts: &Options,
filter: RowFilter, filter: RowFilter,
best_size: &AtomicMin, best_size: &AtomicMin,
deflater: &T,
) -> Option<TrialResult> { ) -> Option<TrialResult> {
let result = deflater.deflate(filtered, best_size); match opts.deflate.deflate(filtered, best_size) {
match result {
Ok(new_idat) => { Ok(new_idat) => {
let bytes = new_idat.len(); let bytes = new_idat.len();
best_size.set_min(bytes); best_size.set_min(bytes);
@ -812,10 +806,7 @@ fn perform_trial<T: Deflater>(
); );
None None
} }
Err(e) => { Err(_) => None,
error!("I/O error: {}", e);
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 /// Perform cleanup of certain chunks from the `PngData` object, after optimization has been completed
fn postprocess_chunks<T>( fn postprocess_chunks(
png: &mut PngData, png: &mut PngData,
opts: &Options, opts: &Options,
deadline: Arc<Deadline>, deadline: Arc<Deadline>,
orig_ihdr: &IhdrData, orig_ihdr: &IhdrData,
deflater: &T, ) {
) where
T: Deflater,
{
if let Some(iccp_idx) = png.aux_chunks.iter().position(|c| &c.name == b"iCCP") { 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 // 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"); let may_replace_iccp = opts.strip != StripChunks::None && opts.strip.keep(b"sRGB");
@ -907,7 +895,7 @@ fn postprocess_chunks<T>(
name: *b"sRGB", name: *b"sRGB",
data: vec![intent], 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 cur_len = png.aux_chunks[iccp_idx].data.len();
let new_len = iccp.data.len(); let new_len = iccp.data.len();
if new_len < cur_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 /// 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 original_size <= optimized_size && !opts.force
} }

View file

@ -22,6 +22,8 @@ use oxipng::RowFilter;
use oxipng::StripChunks; use oxipng::StripChunks;
use oxipng::{InFile, OutFile}; use oxipng::{InFile, OutFile};
use std::fs::DirBuilder; use std::fs::DirBuilder;
#[cfg(feature = "zopfli")]
use std::num::NonZeroU8;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::exit; use std::process::exit;
use std::time::Duration; use std::time::Duration;
@ -515,10 +517,9 @@ fn parse_opts_into_struct(
if matches.get_flag("zopfli") { if matches.get_flag("zopfli") {
#[cfg(feature = "zopfli")] #[cfg(feature = "zopfli")]
let zopfli_opts = zopfli::Options::default(); if let Some(iterations) = NonZeroU8::new(15) {
opts.deflate = Deflaters::Zopfli { opts.deflate = Deflaters::Zopfli { iterations };
options: zopfli_opts, }
};
} else if let Deflaters::Libdeflater { compression } = &mut opts.deflate { } else if let Deflaters::Libdeflater { compression } = &mut opts.deflate {
if let Some(x) = matches.get_one::<i64>("compression") { if let Some(x) = matches.get_one::<i64>("compression") {
*compression = *x as u8; *compression = *x as u8;

View file

@ -260,13 +260,13 @@ impl PngImage {
/// Return the number of channels in the image, based on color type /// Return the number of channels in the image, based on color type
#[inline] #[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 self.ihdr.color_type.channels_per_pixel() as usize
} }
/// Return the number of bytes per channel in the image /// Return the number of bytes per channel in the image
#[inline] #[inline]
pub const fn bytes_per_channel(&self) -> usize { pub fn bytes_per_channel(&self) -> usize {
match self.ihdr.bit_depth { match self.ihdr.bit_depth {
BitDepth::Sixteen => 2, BitDepth::Sixteen => 2,
// Depths lower than 8 will round up to 1 byte // 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 // 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; let log = 32 - i.leading_zeros() - 1;
i * log + ((i - (1 << log)) << 1) i * log + ((i - (1 << log)) << 1)
} }

View file

@ -677,10 +677,7 @@ 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.deflate = Deflaters::Zopfli { opts.deflate = Deflaters::Zopfli {
options: zopfli::Options { iterations: NonZeroU8::new(15).unwrap(),
iteration_count: NonZeroU8::new(15).unwrap(),
maximum_block_splits: 15,
},
}; };
test_it_converts( test_it_converts(

View file

@ -14,7 +14,6 @@ fn get_opts() -> Options {
fn test_it_converts(input: &str) { fn test_it_converts(input: &str) {
let input = PathBuf::from(input); let input = PathBuf::from(input);
let opts = get_opts(); let opts = get_opts();
let deflater = BufferedZopfliDeflater::default();
let original_data = PngData::read_file(&PathBuf::from(input)).unwrap(); let original_data = PngData::read_file(&PathBuf::from(input)).unwrap();
let image = PngData::from_slice(&original_data, &opts).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); 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(); let new = PngData::from_slice(&output, &opts).unwrap();
assert!(new.aux_chunks.len() == num_chunks); assert!(new.aux_chunks.len() == num_chunks);
@ -53,7 +52,6 @@ fn from_file() {
#[test] #[test]
fn custom_indexed() { fn custom_indexed() {
let opts = get_opts(); let opts = get_opts();
let deflater = BufferedZopfliDeflater::default();
let raw = RawImage::new( let raw = RawImage::new(
4, 4,
@ -71,7 +69,7 @@ fn custom_indexed() {
) )
.unwrap(); .unwrap();
raw.create_optimized_png(&opts, &deflater).unwrap(); raw.create_optimized_png(&opts).unwrap();
} }
#[test] #[test]