Full switch to Libdeflater (#457)

* Switch main compressor to libdeflate

* Use libdeflater in evaluate

* Use libdeflater to inflate

* Use libdeflater crc

* Tidy up

* Fix benches

* Allow libdeflater/freestanding feature

* Fix building without zopfli
This commit is contained in:
andrews05 2022-11-02 02:02:08 +13:00 committed by GitHub
parent f688d20fe6
commit 446c788eb3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 367 additions and 733 deletions

34
Cargo.lock generated
View file

@ -91,24 +91,6 @@ dependencies = [
"os_str_bytes",
]
[[package]]
name = "cloudflare-zlib"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cfcefb5df07f146eb15756342a135eb7d76b8bb609eff9c111f7539d060f94d"
dependencies = [
"cloudflare-zlib-sys",
]
[[package]]
name = "cloudflare-zlib-sys"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2040b6d1edfee6d75f172d81e2d2a7807534f3f294ce18184c70e7bb0105cd6f"
dependencies = [
"cc",
]
[[package]]
name = "color_quant"
version = "1.1.0"
@ -209,7 +191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
dependencies = [
"crc32fast",
"miniz_oxide 0.5.4",
"miniz_oxide",
]
[[package]]
@ -324,15 +306,6 @@ dependencies = [
"adler",
]
[[package]]
name = "miniz_oxide"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
dependencies = [
"adler",
]
[[package]]
name = "num-integer"
version = "0.1.45"
@ -391,8 +364,6 @@ version = "6.0.1"
dependencies = [
"bit-vec",
"clap",
"cloudflare-zlib",
"crc",
"crossbeam-channel",
"filetime",
"image",
@ -400,7 +371,6 @@ dependencies = [
"itertools",
"libdeflater",
"log",
"miniz_oxide 0.6.2",
"rayon",
"rgb",
"rustc_version",
@ -418,7 +388,7 @@ dependencies = [
"bitflags",
"crc32fast",
"flate2",
"miniz_oxide 0.5.4",
"miniz_oxide",
]
[[package]]

View file

@ -24,13 +24,11 @@ required-features = ["binary"]
[dependencies]
bit-vec = "0.6.3"
crc = "3.0.0"
itertools = "0.10.3"
zopfli = { version = "0.7.1", optional = true }
miniz_oxide = "0.6.2"
rgb = "0.8.33"
indexmap = "1.9.1"
libdeflater = { version = "0.11.0", optional = true }
libdeflater = "0.11.0"
log = "0.4.17"
stderrlog = { version = "0.5.3", optional = true, default-features = false }
crossbeam-channel = "0.5.6"
@ -56,17 +54,14 @@ default-features = false
features = ["png"]
version = "0.24.3"
[target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies.cloudflare-zlib]
features = ["arm-always"]
version = "0.2.9"
[build-dependencies]
rustc_version = "0.4.0"
[features]
binary = ["clap", "wild", "stderrlog"]
default = ["binary", "filetime", "parallel", "libdeflater", "zopfli"]
default = ["binary", "filetime", "parallel", "zopfli"]
parallel = ["rayon", "indexmap/rayon"]
freestanding = ["libdeflater/freestanding"]
[lib]
name = "oxipng"

View file

@ -7,7 +7,6 @@ use std::path::PathBuf;
use test::Bencher;
use oxipng::internal_tests::*;
use oxipng::Deadline;
#[bench]
fn deflate_16_bits_strategy_0(b: &mut Bencher) {
@ -16,7 +15,7 @@ fn deflate_16_bits_strategy_0(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 0, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -27,7 +26,7 @@ fn deflate_8_bits_strategy_0(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 0, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -40,7 +39,7 @@ fn deflate_4_bits_strategy_0(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 0, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -53,7 +52,7 @@ fn deflate_2_bits_strategy_0(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 0, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -66,7 +65,7 @@ fn deflate_1_bits_strategy_0(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 0, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -77,7 +76,7 @@ fn deflate_16_bits_strategy_1(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 1, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -88,7 +87,7 @@ fn deflate_8_bits_strategy_1(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 1, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -101,7 +100,7 @@ fn deflate_4_bits_strategy_1(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 1, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -114,7 +113,7 @@ fn deflate_2_bits_strategy_1(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 1, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -127,7 +126,7 @@ fn deflate_1_bits_strategy_1(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 1, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -138,7 +137,7 @@ fn deflate_16_bits_strategy_2(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 2, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -149,7 +148,7 @@ fn deflate_8_bits_strategy_2(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 2, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -162,7 +161,7 @@ fn deflate_4_bits_strategy_2(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 2, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -175,7 +174,7 @@ fn deflate_2_bits_strategy_2(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 2, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -188,7 +187,7 @@ fn deflate_1_bits_strategy_2(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 2, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -199,7 +198,7 @@ fn deflate_16_bits_strategy_3(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 3, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -210,7 +209,7 @@ fn deflate_8_bits_strategy_3(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 3, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -223,7 +222,7 @@ fn deflate_4_bits_strategy_3(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 3, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -236,7 +235,7 @@ fn deflate_2_bits_strategy_3(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 3, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -249,7 +248,7 @@ fn deflate_1_bits_strategy_3(b: &mut Bencher) {
b.iter(|| {
let min = AtomicMin::new(None);
deflate(png.raw.data.as_ref(), 9, 3, 15, &min, &Deadline::new(None))
deflate(png.raw.data.as_ref(), 12, &min)
});
}
@ -258,5 +257,5 @@ fn inflate_generic(b: &mut Bencher) {
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
let png = PngData::new(&input, false).unwrap();
b.iter(|| inflate(png.idat_data.as_ref()));
b.iter(|| inflate(png.idat_data.as_ref(), png.raw.ihdr.raw_data_size()));
}

View file

@ -1,64 +0,0 @@
#![feature(test)]
extern crate oxipng;
extern crate test;
use oxipng::internal_tests::*;
use std::path::PathBuf;
use test::Bencher;
#[bench]
fn libdeflater_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, false).unwrap();
b.iter(|| {
libdeflater_deflate(png.raw.data.as_ref(), 12, &AtomicMin::new(None)).ok();
});
}
#[bench]
fn libdeflater_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, false).unwrap();
b.iter(|| {
libdeflater_deflate(png.raw.data.as_ref(), 12, &AtomicMin::new(None)).ok();
});
}
#[bench]
fn libdeflater_4_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"tests/files/palette_4_should_be_palette_4.png",
));
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
libdeflater_deflate(png.raw.data.as_ref(), 12, &AtomicMin::new(None)).ok();
});
}
#[bench]
fn libdeflater_2_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"tests/files/palette_2_should_be_palette_2.png",
));
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
libdeflater_deflate(png.raw.data.as_ref(), 12, &AtomicMin::new(None)).ok();
});
}
#[bench]
fn libdeflater_1_bits_strategy_0(b: &mut Bencher) {
let input = test::black_box(PathBuf::from(
"tests/files/palette_1_should_be_palette_1.png",
));
let png = PngData::new(&input, false).unwrap();
b.iter(|| {
libdeflater_deflate(png.raw.data.as_ref(), 12, &AtomicMin::new(None)).ok();
});
}

View file

@ -1,54 +0,0 @@
use crate::atomicmin::AtomicMin;
use crate::Deadline;
use crate::PngError;
use crate::PngResult;
pub use cloudflare_zlib::is_supported;
use cloudflare_zlib::*;
impl From<ZError> for PngError {
fn from(err: ZError) -> Self {
match err {
ZError::DeflatedDataTooLarge(n) => PngError::DeflatedDataTooLong(n),
other => PngError::Other(other.to_string().into()),
}
}
}
pub(crate) fn cfzlib_deflate(
data: &[u8],
level: u8,
strategy: u8,
window_bits: u8,
max_size: &AtomicMin,
deadline: &Deadline,
) -> PngResult<Vec<u8>> {
let mut stream = Deflate::new(level.into(), strategy.into(), window_bits.into())?;
stream.reserve(max_size.get().unwrap_or(data.len() / 2));
let max_size = max_size.as_atomic_usize();
// max size is generally checked after each split,
// so splitting the buffer into pieces gives more checks
// = better chance of hitting it sooner.
let chunk_size = (data.len() / 4).max(1 << 15).min(1 << 18); // 32-256KB
for chunk in data.chunks(chunk_size) {
stream.compress_with_limit(chunk, max_size)?;
if deadline.passed() {
return Err(PngError::TimedOut);
}
}
Ok(stream.finish()?)
}
#[test]
fn compress_test() {
let vec = cfzlib_deflate(
b"azxcvbnm",
Z_BEST_COMPRESSION as u8,
Z_DEFAULT_STRATEGY as u8,
15,
&AtomicMin::new(None),
&Deadline::new(None),
)
.unwrap();
let res = crate::deflate::inflate(&vec).unwrap();
assert_eq!(&res, b"azxcvbnm");
}

View file

@ -1,6 +1,6 @@
use crate::atomicmin::AtomicMin;
use crate::{PngError, PngResult};
use libdeflater::{CompressionError, CompressionLvl, Compressor};
use libdeflater::*;
pub fn deflate(data: &[u8], level: u8, max_size: &AtomicMin) -> PngResult<Vec<u8>> {
let mut compressor = Compressor::new(CompressionLvl::new(level.into()).unwrap());
@ -23,3 +23,22 @@ pub fn deflate(data: &[u8], level: u8, max_size: &AtomicMin) -> PngResult<Vec<u8
dest.truncate(len);
Ok(dest)
}
pub fn inflate(data: &[u8], out_size: usize) -> PngResult<Vec<u8>> {
let mut decompressor = Decompressor::new();
let mut dest = vec![0; out_size];
let len = decompressor
.zlib_decompress(data, &mut dest)
.map_err(|err| match err {
DecompressionError::BadData => PngError::InvalidData,
DecompressionError::InsufficientSpace => PngError::new("inflated data too long"),
})?;
dest.truncate(len);
Ok(dest)
}
pub fn crc32(data: &[u8]) -> u32 {
let mut crc = Crc::new();
crc.update(data);
crc.sum()
}

View file

@ -1,71 +0,0 @@
use crate::atomicmin::AtomicMin;
use crate::error::PngError;
use crate::PngResult;
use miniz_oxide::deflate::core::*;
pub(crate) fn compress_to_vec_oxipng(
input: &[u8],
level: u8,
window_bits: i32,
strategy: i32,
max_size: &AtomicMin,
deadline: &crate::Deadline,
) -> PngResult<Vec<u8>> {
// The comp flags function sets the zlib flag if the window_bits parameter is > 0.
let flags = create_comp_flags_from_zip_params(level.into(), window_bits, strategy);
let mut compressor = CompressorOxide::new(flags);
// if max size is known, then expect that much data (but no more than input.len())
let mut output = Vec::with_capacity(max_size.get().unwrap_or(input.len() / 2).min(input.len()));
// # Unsafe
// We trust compress to not read the uninitialized bytes.
unsafe {
let cap = output.capacity();
output.set_len(cap);
}
let mut in_pos = 0;
let mut out_pos = 0;
loop {
let (status, bytes_in, bytes_out) = compress(
&mut compressor,
&input[in_pos..],
&mut output[out_pos..],
TDEFLFlush::Finish,
);
out_pos += bytes_out;
in_pos += bytes_in;
match status {
TDEFLStatus::Done => {
output.truncate(out_pos);
break;
}
TDEFLStatus::Okay => {
if let Some(max) = max_size.get() {
if output.len() > max {
return Err(PngError::DeflatedDataTooLong(output.len()));
}
}
if deadline.passed() {
return Err(PngError::TimedOut);
}
// We need more space, so extend the vector.
if output.len().saturating_sub(out_pos) < 30 {
let current_len = output.len();
output.reserve(current_len);
// # Unsafe
// We trust compress to not read the uninitialized bytes.
unsafe {
let cap = output.capacity();
output.set_len(cap);
}
}
}
// Not supposed to happen unless there is a bug.
_ => panic!("Bug! Unexpectedly failed to compress!"),
}
}
Ok(output)
}

View file

@ -1,98 +1,24 @@
use crate::atomicmin::AtomicMin;
use crate::error::PngError;
use crate::Deadline;
use crate::PngResult;
use indexmap::IndexSet;
mod deflater;
pub use deflater::crc32;
pub use deflater::deflate;
pub use deflater::inflate;
#[cfg(feature = "zopfli")]
use std::num::NonZeroU8;
#[doc(hidden)]
pub mod miniz_stream;
#[cfg(feature = "libdeflater")]
mod deflater;
#[cfg(feature = "libdeflater")]
pub use deflater::deflate as libdeflater_deflate;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub mod cfzlib;
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
pub mod cfzlib {
pub fn is_supported() -> bool {
return false;
}
}
/// Decompress a data stream using the DEFLATE algorithm
pub fn inflate(data: &[u8]) -> PngResult<Vec<u8>> {
miniz_oxide::inflate::decompress_to_vec_zlib(data).map_err(|e| {
PngError::new(&format!(
"Error on decompress: {:?} (after {:?} decompressed bytes)",
e.status,
e.output.len()
))
})
}
/// Compress a data stream using the DEFLATE algorithm
#[doc(hidden)]
pub fn deflate(
data: &[u8],
zc: u8,
zs: u8,
zw: u8,
max_size: &AtomicMin,
deadline: &Deadline,
) -> PngResult<Vec<u8>> {
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
{
if cfzlib::is_supported() {
return cfzlib::cfzlib_deflate(data, zc, zs, zw, max_size, deadline);
}
}
miniz_stream::compress_to_vec_oxipng(data, zc, zw.into(), zs.into(), max_size, deadline)
}
#[cfg(feature = "zopfli")]
pub fn zopfli_deflate(data: &[u8], iterations: NonZeroU8) -> PngResult<Vec<u8>> {
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) {
Ok(_) => (),
Err(_) => return Err(PngError::new("Failed to compress in zopfli")),
};
output.shrink_to_fit();
Ok(output)
}
mod zopfli_oxipng;
#[cfg(feature = "zopfli")]
pub use zopfli_oxipng::deflate as zopfli_deflate;
#[derive(Clone, Debug, PartialEq, Eq)]
/// DEFLATE algorithms supported by oxipng
pub enum Deflaters {
/// Use the Zlib/Miniz DEFLATE implementation
Zlib {
/// Which zlib compression levels to try on the file (1-9)
///
/// Default: `9`
/// Use libdeflater.
Libdeflater {
/// Which compression levels to try on the file (1-12)
compression: IndexSet<u8>,
/// Which zlib compression strategies to try on the file (0-3)
///
/// Default: `0-3`
strategies: IndexSet<u8>,
/// Window size to use when compressing the file, as `2^window` bytes.
///
/// Doesn't affect compression but may affect speed and memory usage.
/// 8-15 are valid values.
///
/// Default: `15`
window: u8,
},
#[cfg(feature = "zopfli")]
/// Use the better but slower Zopfli implementation
@ -102,10 +28,4 @@ pub enum Deflaters {
/// less iterations, or else they will be too slow.
iterations: NonZeroU8,
},
#[cfg(feature = "libdeflater")]
/// Use libdeflater.
Libdeflater {
/// Which compression levels to try on the file (1-12)
compression: IndexSet<u8>,
},
}

View file

@ -0,0 +1,18 @@
use crate::{PngError, PngResult};
use std::num::NonZeroU8;
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));
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)
}

View file

@ -7,8 +7,6 @@ use crate::png::PngData;
use crate::png::PngImage;
use crate::png::STD_COMPRESSION;
use crate::png::STD_FILTERS;
use crate::png::STD_STRATEGY;
use crate::png::STD_WINDOW;
#[cfg(not(feature = "parallel"))]
use crate::rayon;
use crate::Deadline;
@ -119,10 +117,7 @@ impl Evaluator {
if let Ok(idat_data) = deflate::deflate(
&image.filter_image(filter),
STD_COMPRESSION,
STD_STRATEGY,
STD_WINDOW,
&best_candidate_size,
&deadline,
) {
best_candidate_size.set_min(idat_data.len());
// ignore baseline images after this point

View file

@ -1,7 +1,7 @@
use crate::colors::{BitDepth, ColorType};
use crate::deflate::crc32;
use crate::error::PngError;
use crate::PngResult;
use crc::{Crc, CRC_32_ISO_HDLC};
use indexmap::IndexSet;
use std::io;
use std::io::{Cursor, Read};
@ -130,7 +130,7 @@ pub fn parse_next_header<'a>(
let header_bytes = byte_data
.get(header_start..header_start + 4 + length as usize)
.ok_or(PngError::TruncatedData)?;
if !fix_errors && Crc::<u32>::new(&CRC_32_ISO_HDLC).checksum(header_bytes) != crc {
if !fix_errors && crc32(header_bytes) != crc {
return Err(PngError::new(&format!(
"CRC Mismatch in {} header; May be recoverable by using --fix",
String::from_utf8_lossy(chunk_name)

View file

@ -14,7 +14,7 @@
#![allow(clippy::cognitive_complexity)]
#![allow(clippy::upper_case_acronyms)]
#![cfg_attr(
not(any(feature = "libdeflater", feature = "zopfli")),
not(feature = "zopfli"),
allow(irrefutable_let_patterns),
allow(unreachable_patterns)
)]
@ -26,12 +26,11 @@ mod rayon;
use crate::atomicmin::AtomicMin;
use crate::colors::BitDepth;
use crate::deflate::inflate;
use crate::deflate::{crc32, inflate};
use crate::evaluate::Evaluator;
use crate::png::PngData;
use crate::png::PngImage;
use crate::reduction::*;
use crc::{Crc, CRC_32_ISO_HDLC};
use image::{DynamicImage, GenericImageView, ImageFormat, Pixel};
use log::{debug, error, info, warn};
use rayon::prelude::*;
@ -190,7 +189,7 @@ pub struct Options {
pub strip: Headers,
/// Which DEFLATE algorithm to use
///
/// Default: `Zlib`
/// Default: `Libdeflater`
pub deflate: Deflaters,
/// Whether to use heuristics to pick the best filter and compression
///
@ -212,10 +211,7 @@ impl Options {
1 => opts.apply_preset_1(),
2 => opts.apply_preset_2(),
3 => opts.apply_preset_3(),
4 => {
warn!("Level 4 is deprecated and is identical to level 3");
opts.apply_preset_4()
}
4 => opts.apply_preset_4(),
5 => opts.apply_preset_5(),
6 => opts.apply_preset_6(),
_ => {
@ -233,17 +229,20 @@ impl Options {
// on an `Options` struct generated by the `default` method.
fn apply_preset_0(mut self) -> Self {
self.idat_recoding = false;
if let Deflaters::Zlib { compression, .. } = &mut self.deflate {
self.filter.clear();
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
compression.clear();
compression.insert(3);
compression.insert(5);
}
self.apply_preset_1()
self.use_heuristics = true;
self
}
fn apply_preset_1(mut self) -> Self {
self.filter.clear();
if let Deflaters::Zlib { strategies, .. } = &mut self.deflate {
strategies.clear();
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
compression.clear();
compression.insert(10);
}
self.use_heuristics = true;
self
@ -254,34 +253,38 @@ impl Options {
}
fn apply_preset_3(mut self) -> Self {
for i in 1..5 {
for i in 1..=4 {
self.filter.insert(i);
}
self
}
fn apply_preset_4(self) -> Self {
fn apply_preset_4(mut self) -> Self {
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
compression.clear();
compression.insert(12);
}
self.apply_preset_3()
}
fn apply_preset_5(mut self) -> Self {
if let Deflaters::Zlib { compression, .. } = &mut self.deflate {
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
compression.clear();
for i in 3..10 {
for i in 9..=12 {
compression.insert(i);
}
}
self.apply_preset_4()
self.apply_preset_3()
}
fn apply_preset_6(mut self) -> Self {
if let Deflaters::Zlib { compression, .. } = &mut self.deflate {
if let Deflaters::Libdeflater { compression } = &mut self.deflate {
compression.clear();
for i in 1..10 {
for i in 1..=12 {
compression.insert(i);
}
}
self.apply_preset_4()
self.apply_preset_3()
}
}
@ -292,11 +295,7 @@ impl Default for Options {
filter.insert(0);
filter.insert(5);
let mut compression = IndexSet::new();
compression.insert(9);
let mut strategies = IndexSet::new();
for i in 0..4 {
strategies.insert(i);
}
compression.insert(11);
// We always need NoOp to be present
let mut alphas = IndexSet::new();
alphas.insert(AlphaOptim::NoOp);
@ -317,11 +316,7 @@ impl Default for Options {
grayscale_reduction: true,
idat_recoding: true,
strip: Headers::None,
deflate: Deflaters::Zlib {
compression,
strategies,
window: 15,
},
deflate: Deflaters::Libdeflater { compression },
use_heuristics: false,
timeout: None,
}
@ -472,7 +467,6 @@ pub fn optimize_from_memory(data: &[u8], opts: &Options) -> PngResult<Vec<u8>> {
struct TrialOptions {
pub filter: u8,
pub compression: u8,
pub strategy: u8,
}
/// Perform optimization on the input PNG object using the options provided
@ -511,28 +505,19 @@ fn optimize_png(
info!(" File size = {} bytes", file_original_size);
let mut filter = opts.filter.clone();
let mut strategies = match &opts.deflate {
Deflaters::Zlib { strategies, .. } => Some(strategies.clone()),
_ => None,
};
if opts.use_heuristics {
// Heuristically determine which set of options to use
let (use_filter, use_strategy) = if png.raw.ihdr.bit_depth.as_u8() >= 8
let use_filter = if png.raw.ihdr.bit_depth.as_u8() >= 8
&& png.raw.ihdr.color_type != colors::ColorType::Indexed
{
(5, 1)
5
} else {
(0, 0)
0
};
if filter.is_empty() {
filter.insert(use_filter);
}
if let Some(strategies) = &mut strategies {
if strategies.is_empty() {
strategies.insert(use_strategy);
}
}
}
// This will collect all versions of images and pick one that compresses best
@ -552,51 +537,30 @@ fn optimize_png(
if opts.idat_recoding || reduction_occurred {
// Go through selected permutations and determine the best
let combinations = if let Deflaters::Zlib { compression, .. } = &opts.deflate {
filter.len() * compression.len() * strategies.as_ref().unwrap().len()
let combinations = if let Deflaters::Libdeflater { compression } = &opts.deflate {
filter.len() * compression.len()
} else {
filter.len()
};
let mut results: Vec<TrialOptions> = Vec::with_capacity(combinations);
for f in &filter {
match &opts.deflate {
Deflaters::Zlib { compression, .. } => {
for zc in compression {
for zs in strategies.as_ref().unwrap() {
results.push(TrialOptions {
filter: *f,
compression: *zc,
strategy: *zs,
});
}
if deadline.passed() {
break;
}
}
}
#[cfg(feature = "zopfli")]
Deflaters::Zopfli { .. } => {
// Zopfli has no additional options.
if let Deflaters::Libdeflater { compression } = &opts.deflate {
for zc in compression {
results.push(TrialOptions {
filter: *f,
compression: 0,
strategy: 0,
compression: *zc,
});
}
#[cfg(feature = "libdeflater")]
Deflaters::Libdeflater { compression } => {
for zc in compression {
results.push(TrialOptions {
filter: *f,
compression: *zc,
strategy: 0,
});
if deadline.passed() {
break;
}
if deadline.passed() {
break;
}
}
} else {
// Zopfli has no additional options.
results.push(TrialOptions {
filter: *f,
compression: 0,
});
}
if deadline.passed() {
@ -626,28 +590,19 @@ fn optimize_png(
}
let filtered = &filters[&trial.filter];
let new_idat = match opts.deflate {
Deflaters::Zlib { window, .. } => deflate::deflate(
filtered,
trial.compression,
trial.strategy,
window,
&best_size,
&deadline,
),
Deflaters::Libdeflater { .. } => {
deflate::deflate(filtered, trial.compression, &best_size)
}
#[cfg(feature = "zopfli")]
Deflaters::Zopfli { iterations } => deflate::zopfli_deflate(filtered, iterations),
#[cfg(feature = "libdeflater")]
Deflaters::Libdeflater { .. } => {
deflate::libdeflater_deflate(filtered, trial.compression, &best_size)
}
};
let new_idat = match new_idat {
Ok(n) => n,
Err(PngError::DeflatedDataTooLong(max)) => {
debug!(
" zc = {} zs = {} f = {} >{} bytes",
trial.compression, trial.strategy, trial.filter, max,
" zc = {} f = {} >{} bytes",
trial.compression, trial.filter, max,
);
return None;
}
@ -659,9 +614,8 @@ fn optimize_png(
best_size.set_min(new_size);
debug!(
" zc = {} zs = {} f = {} {} bytes",
" zc = {} f = {} {} bytes",
trial.compression,
trial.strategy,
trial.filter,
new_idat.len()
);
@ -684,9 +638,8 @@ fn optimize_png(
png.idat_data = idat_data;
info!("Found better combination:");
info!(
" zc = {} zs = {} f = {} {} bytes",
" zc = {} f = {} {} bytes",
opts.compression,
opts.strategy,
opts.filter,
png.idat_data.len()
);
@ -952,7 +905,9 @@ fn srgb_rendering_intent(mut iccp: &[u8]) -> Option<u8> {
if compression_method != 0 {
return None; // The profile is supposed to be compressed (method 0)
}
let icc_data = inflate(compressed_data).ok()?;
// The decompressed size is unknown so we have to guess the required buffer size
let max_size = (compressed_data.len() * 2).max(1000);
let icc_data = inflate(compressed_data, max_size).ok()?;
let rendering_intent = *icc_data.get(67)?;
@ -968,10 +923,7 @@ fn srgb_rendering_intent(mut iccp: &[u8]) -> Option<u8> {
}
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" => {
// Known-bad profiles are identified by their CRC
match (
Crc::<u32>::new(&CRC_32_ISO_HDLC).checksum(&icc_data),
icc_data.len(),
) {
match (crc32(&icc_data), icc_data.len()) {
(0x5d51_29ce, 3024) | (0x182e_a552, 3144) | (0xf29e_526d, 3144) => {
Some(rendering_intent)
}

View file

@ -14,7 +14,7 @@
#![allow(clippy::cognitive_complexity)]
use clap::{AppSettings, Arg, ArgMatches, Command};
use indexmap::{indexset, IndexSet};
use indexmap::IndexSet;
use log::{error, warn};
use oxipng::AlphaOptim;
use oxipng::Deflaters;
@ -22,6 +22,7 @@ use oxipng::Headers;
use oxipng::Options;
use oxipng::{InFile, OutFile};
use std::fs::DirBuilder;
#[cfg(feature = "zopfli")]
use std::num::NonZeroU8;
use std::path::PathBuf;
use std::process::exit;
@ -33,199 +34,214 @@ fn main() {
.author("Joshua Holmer <jholmer.in@gmail.com>")
.about("Losslessly improves compression of PNG files")
.setting(AppSettings::DeriveDisplayOrder)
.arg(Arg::new("files")
.help("File(s) to compress (use \"-\" for stdin)")
.index(1)
.multiple_values(true)
.use_value_delimiter(false)
.required(true))
.arg(Arg::new("optimization")
.help("Optimization level - Default: 2")
.short('o')
.long("opt")
.takes_value(true)
.value_name("level")
.possible_value("0")
.possible_value("1")
.possible_value("2")
.possible_value("3")
.possible_value("4")
.possible_value("5")
.possible_value("6")
.possible_value("max"))
.arg(Arg::new("backup")
.help("Back up modified files")
.short('b')
.long("backup"))
.arg(Arg::new("recursive")
.help("Recurse into subdirectories")
.short('r')
.long("recursive"))
.arg(Arg::new("output_dir")
.help("Write output file(s) to <directory>")
.long("dir")
.takes_value(true)
.value_name("directory")
.conflicts_with("output_file")
.conflicts_with("stdout"))
.arg(Arg::new("output_file")
.help("Write output file to <file>")
.long("out")
.takes_value(true)
.value_name("file")
.conflicts_with("output_dir")
.conflicts_with("stdout"))
.arg(Arg::new("stdout")
.help("Write output to stdout")
.long("stdout")
.conflicts_with("output_dir")
.conflicts_with("output_file"))
.arg(Arg::new("preserve")
.help("Preserve file attributes if possible")
.short('p')
.long("preserve"))
.arg(Arg::new("check")
.help("Do not run any optimization passes")
.short('c')
.long("check"))
.arg(Arg::new("pretend")
.help("Do not write any files, only calculate compression gains")
.short('P')
.long("pretend"))
.arg(Arg::new("strip-safe")
.help("Strip safely-removable metadata objects")
.short('s')
.conflicts_with("strip"))
.arg(Arg::new("strip")
.help("Strip metadata objects ['safe', 'all', or comma-separated list]")
.long("strip")
.takes_value(true)
.value_name("mode")
.conflicts_with("strip-safe"))
.arg(Arg::new("keep")
.help("Strip all optional metadata except objects in the comma-separated list")
.long("keep")
.takes_value(true)
.value_name("list")
.conflicts_with("strip")
.conflicts_with("strip-safe"))
.arg(Arg::new("alpha")
.help("Perform additional alpha optimizations")
.short('a')
.long("alpha"))
.arg(Arg::new("interlace")
.help("PNG interlace type")
.short('i')
.long("interlace")
.takes_value(true)
.value_name("0/1")
.possible_value("0")
.possible_value("1"))
.arg(Arg::new("verbose")
.help("Run in verbose mode")
.short('v')
.long("verbose")
.conflicts_with("quiet"))
.arg(Arg::new("quiet")
.help("Run in quiet mode")
.short('q')
.long("quiet")
.conflicts_with("verbose"))
.arg(Arg::new("filters")
.help("PNG delta filters (0-5) - Default: 0,5")
.short('f')
.long("filters")
.takes_value(true)
.validator(|x| {
match parse_numeric_range_opts(x, 0, 5) {
.arg(
Arg::new("files")
.help("File(s) to compress (use \"-\" for stdin)")
.index(1)
.multiple_values(true)
.use_value_delimiter(false)
.required(true),
)
.arg(
Arg::new("optimization")
.help("Optimization level - Default: 2")
.short('o')
.long("opt")
.takes_value(true)
.value_name("level")
.possible_value("0")
.possible_value("1")
.possible_value("2")
.possible_value("3")
.possible_value("4")
.possible_value("5")
.possible_value("6")
.possible_value("max"),
)
.arg(
Arg::new("backup")
.help("Back up modified files")
.short('b')
.long("backup"),
)
.arg(
Arg::new("recursive")
.help("Recurse into subdirectories")
.short('r')
.long("recursive"),
)
.arg(
Arg::new("output_dir")
.help("Write output file(s) to <directory>")
.long("dir")
.takes_value(true)
.value_name("directory")
.conflicts_with("output_file")
.conflicts_with("stdout"),
)
.arg(
Arg::new("output_file")
.help("Write output file to <file>")
.long("out")
.takes_value(true)
.value_name("file")
.conflicts_with("output_dir")
.conflicts_with("stdout"),
)
.arg(
Arg::new("stdout")
.help("Write output to stdout")
.long("stdout")
.conflicts_with("output_dir")
.conflicts_with("output_file"),
)
.arg(
Arg::new("preserve")
.help("Preserve file attributes if possible")
.short('p')
.long("preserve"),
)
.arg(
Arg::new("check")
.help("Do not run any optimization passes")
.short('c')
.long("check"),
)
.arg(
Arg::new("pretend")
.help("Do not write any files, only calculate compression gains")
.short('P')
.long("pretend"),
)
.arg(
Arg::new("strip-safe")
.help("Strip safely-removable metadata objects")
.short('s')
.conflicts_with("strip"),
)
.arg(
Arg::new("strip")
.help("Strip metadata objects ['safe', 'all', or comma-separated list]")
.long("strip")
.takes_value(true)
.value_name("mode")
.conflicts_with("strip-safe"),
)
.arg(
Arg::new("keep")
.help("Strip all optional metadata except objects in the comma-separated list")
.long("keep")
.takes_value(true)
.value_name("list")
.conflicts_with("strip")
.conflicts_with("strip-safe"),
)
.arg(
Arg::new("alpha")
.help("Perform additional alpha optimizations")
.short('a')
.long("alpha"),
)
.arg(
Arg::new("interlace")
.help("PNG interlace type")
.short('i')
.long("interlace")
.takes_value(true)
.value_name("0/1")
.possible_value("0")
.possible_value("1"),
)
.arg(
Arg::new("verbose")
.help("Run in verbose mode")
.short('v')
.long("verbose")
.conflicts_with("quiet"),
)
.arg(
Arg::new("quiet")
.help("Run in quiet mode")
.short('q')
.long("quiet")
.conflicts_with("verbose"),
)
.arg(
Arg::new("filters")
.help("PNG delta filters (0-5) - Default: 0,5")
.short('f')
.long("filters")
.takes_value(true)
.validator(|x| match parse_numeric_range_opts(x, 0, 5) {
Ok(_) => Ok(()),
Err(_) => Err("Invalid option for filters".to_owned()),
}
}))
.arg(Arg::new("compression")
.help("compression levels (zlib: 1-9, libdeflater: 1-12) - Default: 9 or 12")
.long("zc")
.takes_value(true)
.value_name("levels")
.validator(|x| {
match parse_numeric_range_opts(x, 1, 12) {
}),
)
.arg(
Arg::new("compression")
.help("zlib compression levels (1-12) - Default: 12")
.long("zc")
.takes_value(true)
.value_name("levels")
.validator(|x| match parse_numeric_range_opts(x, 1, 12) {
Ok(_) => Ok(()),
Err(_) => Err("Invalid option for compression".to_owned()),
}
})
.conflicts_with("zopfli"))
.arg(Arg::new("strategies")
.help("zlib compression strategies (0-3) - Default: 0-3")
.long("zs")
.takes_value(true)
.validator(|x| {
match parse_numeric_range_opts(x, 0, 3) {
Ok(_) => Ok(()),
Err(_) => Err("Invalid option for strategies".to_owned()),
}
})
.conflicts_with_all(&["zopfli", "libdeflater"]))
.arg(Arg::new("window")
.help("zlib window size - Default: 32k")
.long("zw")
.takes_value(true)
.value_name("size")
.possible_value("256")
.possible_value("512")
.possible_value("1k")
.possible_value("2k")
.possible_value("4k")
.possible_value("8k")
.possible_value("16k")
.possible_value("32k")
.conflicts_with_all(&["zopfli", "libdeflater"]))
.arg(Arg::new("no-bit-reduction")
.help("No bit depth reduction")
.long("nb"))
.arg(Arg::new("no-color-reduction")
.help("No color type reduction")
.long("nc"))
.arg(Arg::new("no-palette-reduction")
.help("No palette reduction")
.long("np"))
.arg(Arg::new("no-grayscale-reduction")
.help("No grayscale reduction")
.long("ng"))
.arg(Arg::new("no-reductions")
.help("No reductions")
.long("nx"))
.arg(Arg::new("no-recoding")
.help("No IDAT recoding unless necessary")
.long("nz"))
.arg(Arg::new("fix")
.help("Enable error recovery")
.long("fix"))
.arg(Arg::new("force")
.help("Write the output even if it is larger than the input")
.long("force"))
.arg(Arg::new("zopfli")
.help("Use the slower but better compressing Zopfli algorithm, overrides zlib-specific options")
.short('Z')
.long("zopfli")
.conflicts_with("libdeflater"))
.arg(Arg::new("libdeflater")
.help("Use an alternative Libdeflater algorithm, overrides zlib-specific options")
.short('D')
.long("libdeflater")
.conflicts_with("zopfli"))
.arg(Arg::new("timeout")
.help("Maximum amount of time, in seconds, to spend on optimizations")
.takes_value(true)
.value_name("secs")
.long("timeout"))
.arg(Arg::new("threads")
.help("Set number of threads to use - default 1.5x CPU cores")
.long("threads")
.short('t')
.takes_value(true)
.value_name("num")
.validator(|x| {
match x.parse::<usize>() {
})
.conflicts_with("zopfli"),
)
.arg(
Arg::new("no-bit-reduction")
.help("No bit depth reduction")
.long("nb"),
)
.arg(
Arg::new("no-color-reduction")
.help("No color type reduction")
.long("nc"),
)
.arg(
Arg::new("no-palette-reduction")
.help("No palette reduction")
.long("np"),
)
.arg(
Arg::new("no-grayscale-reduction")
.help("No grayscale reduction")
.long("ng"),
)
.arg(Arg::new("no-reductions").help("No reductions").long("nx"))
.arg(
Arg::new("no-recoding")
.help("No IDAT recoding unless necessary")
.long("nz"),
)
.arg(Arg::new("fix").help("Enable error recovery").long("fix"))
.arg(
Arg::new("force")
.help("Write the output even if it is larger than the input")
.long("force"),
)
.arg(
Arg::new("zopfli")
.help("Use the slower but better compressing Zopfli algorithm")
.short('Z')
.long("zopfli"),
)
.arg(
Arg::new("timeout")
.help("Maximum amount of time, in seconds, to spend on optimizations")
.takes_value(true)
.value_name("secs")
.long("timeout"),
)
.arg(
Arg::new("threads")
.help("Set number of threads to use - default 1.5x CPU cores")
.long("threads")
.short('t')
.takes_value(true)
.value_name("num")
.validator(|x| match x.parse::<usize>() {
Ok(val) => {
if val > 0 {
Ok(())
@ -234,20 +250,22 @@ fn main() {
}
}
Err(_) => Err("Thread count must be >= 1".to_owned()),
}
}))
.after_help("Optimization levels:
-o 0 => --zc 3 --nz (0 or 1 trials)
-o 1 => --zc 9 (1 trial, determined heuristically)
-o 2 => --zc 9 --zs 0-3 -f 0,5 (8 trials with zlib or 2 trials with other compressors)
-o 3 => --zc 9 --zs 0-3 -f 0-5 (24 trials with zlib or 6 trials with other compressors)
-o 4 => (deprecated; same as `-o 3`)
-o 5 => --zc 3-9 --zs 0-3 -f 0-5 (168 trials with zlib; same as `-o 3` for other compressors)
-o 6 => --zc 1-9 --zs 0-3 -f 0-5 (216 trials with zlib; same as `-o 3` for other compressors)
-o max => (stable alias for the max compression)
}),
)
.after_help(
"Optimization levels:
-o 0 => --zc 6 --nz (0 or 1 trials)
-o 1 => --zc 10 (1 trial, determined heuristically)
-o 2 => --zc 11 -f 0,5 (2 trials)
-o 3 => --zc 11 -f 0-5 (6 trials)
-o 4 => --zc 12 -f 0-5 (6 trials; same as `-o 3` for zopfli)
-o 5 => --zc 9-12 -f 0-5 (24 trials; same as `-o 3` for zopfli)
-o 6 => --zc 1-12 -f 0-5 (72 trials; same as `-o 3` for zopfli)
-o max => (stable alias for the max compression)
Manually specifying a compression option (zc, zs, etc.) will override the optimization preset,
regardless of the order you write the arguments.")
Manually specifying a compression option (zc, f, etc.) will override the optimization preset,
regardless of the order you write the arguments.",
)
.get_matches_from(wild::args());
let (out_file, out_dir, opts) = match parse_opts_into_struct(&matches) {
@ -496,55 +514,20 @@ fn parse_opts_into_struct(
}
if matches.is_present("zopfli") {
opts.deflate = Deflaters::Zopfli {
iterations: NonZeroU8::new(15).unwrap(),
};
} else if matches.is_present("libdeflater") {
opts.deflate = Deflaters::Libdeflater {
compression: match matches.value_of("compression") {
Some(x) => parse_numeric_range_opts(x, 1, 12).unwrap(),
_ => indexset! { 12 },
},
};
} else if let Deflaters::Zlib {
compression,
strategies,
window,
} = &mut opts.deflate
{
if explicit_level > Some(3) {
warn!("Level 4 and above are equivalent to level 3 for zopfli");
}
#[cfg(feature = "zopfli")]
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.value_of("compression") {
*compression = parse_numeric_range_opts(x, 1, 9)
.map_err(|_| "Compression levels 10-12 are only valid for libdeflater".to_owned())?
}
if let Some(x) = matches.value_of("strategies") {
*strategies = parse_numeric_range_opts(x, 0, 3).unwrap();
}
match matches.value_of("window") {
Some("256") => *window = 8,
Some("512") => *window = 9,
Some("1k") => *window = 10,
Some("2k") => *window = 11,
Some("4k") => *window = 12,
Some("8k") => *window = 13,
Some("16k") => *window = 14,
// 32k is default
_ => (),
}
}
if explicit_level > Some(3) {
match opts.deflate {
Deflaters::Zlib { .. } => {}
_ => {
warn!(
"Level 4 and above are equivalent to level 3 for compressors other than zlib"
);
}
*compression = parse_numeric_range_opts(x, 1, 12).unwrap();
}
}
#[cfg(feature = "parallel")]
if let Some(x) = matches.value_of("threads") {
let threads = x.parse::<usize>().unwrap();

View file

@ -4,7 +4,6 @@ use crate::error::PngError;
use crate::filters::*;
use crate::headers::*;
use crate::interlace::{deinterlace_image, interlace_image};
use crc::{Crc, CRC_32_ISO_HDLC};
use indexmap::IndexMap;
use rgb::ComponentSlice;
use rgb::RGBA8;
@ -14,11 +13,8 @@ use std::iter::Iterator;
use std::path::Path;
use std::sync::Arc;
pub(crate) const STD_COMPRESSION: u8 = 6;
/// Must use normal compression, as faster ones (Huffman/RLE-only) are not representative
pub(crate) const STD_STRATEGY: u8 = 0;
/// OK to use a bit smalller window for evaluation
pub(crate) const STD_WINDOW: u8 = 13;
/// Must use normal (lazy) compression, as faster ones (greedy) are not representative
pub(crate) const STD_COMPRESSION: u8 = 5;
pub(crate) const STD_FILTERS: [u8; 2] = [0, 5];
pub(crate) mod scan_lines;
@ -115,7 +111,7 @@ impl PngData {
None => return Err(PngError::ChunkMissing("IHDR")),
};
let ihdr_header = parse_ihdr_header(&ihdr)?;
let raw_data = deflate::inflate(idat_headers.as_ref())?;
let raw_data = deflate::inflate(idat_headers.as_ref(), ihdr_header.raw_data_size())?;
// Reject files with incorrect width/height or truncated data
if raw_data.len() != ihdr_header.raw_data_size() {
@ -373,7 +369,7 @@ fn write_png_block(key: &[u8], header: &[u8], output: &mut Vec<u8>) {
header_data.extend_from_slice(header);
output.reserve(header_data.len() + 8);
output.extend_from_slice(&(header_data.len() as u32 - 4).to_be_bytes());
let crc = Crc::<u32>::new(&CRC_32_ISO_HDLC).checksum(&header_data);
let crc = deflate::crc32(&header_data);
output.append(&mut header_data);
output.extend_from_slice(&crc.to_be_bytes());
}

View file

@ -170,10 +170,10 @@ fn verbose_mode() {
});
let mut logs: Vec<_> = receiver.into_iter().collect();
assert_eq!(logs.len(), 4);
assert_eq!(logs.len(), 1);
logs.sort();
for (i, log) in logs.into_iter().enumerate() {
let expected_prefix = format!(" zc = 9 zs = {} f = 0 ", i);
let expected_prefix = format!(" zc = 11 f = 0 ");
assert!(
log.starts_with(&expected_prefix),
"logs[{}] = {:?} doesn't start with {:?}",
@ -597,23 +597,3 @@ fn zopfli_mode() {
BitDepth::Eight,
);
}
#[test]
#[cfg(feature = "libdeflater")]
fn libdeflater_mode() {
let input = PathBuf::from("tests/files/zopfli_mode.png");
let (output, mut opts) = get_opts(&input);
let mut compression = IndexSet::new();
compression.insert(0);
opts.deflate = Deflaters::Libdeflater { compression };
test_it_converts(
input,
&output,
&opts,
ColorType::RGB,
BitDepth::Eight,
ColorType::RGB,
BitDepth::Eight,
);
}

View file

@ -443,10 +443,6 @@ fn interlaced_palette_8_should_be_palette_8() {
#[test]
fn interlaced_palette_8_should_be_palette_4() {
// miniz doesn't estimate compression that well
if !oxipng::internal_tests::cfzlib::is_supported() {
return;
}
test_it_converts(
"tests/files/interlaced_palette_8_should_be_palette_4.png",
ColorType::Indexed,