Bug fix: need to implement Zlib format

This commit is contained in:
Chris Hennick 2023-06-21 10:08:30 -07:00
parent a330187a51
commit e8a8dede55
No known key found for this signature in database
GPG key ID: 25653935CC8B6C74
4 changed files with 83 additions and 16 deletions

1
Cargo.lock generated
View file

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

View file

@ -28,6 +28,7 @@ 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 = "1.9.3" indexmap = "1.9.3"
libdeflater = "0.11.0" libdeflater = "0.11.0"
@ -66,6 +67,7 @@ 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

@ -4,18 +4,19 @@ use crate::{PngError, PngResult};
pub use deflater::crc32; pub use deflater::crc32;
pub use deflater::deflate; pub use deflater::deflate;
pub use deflater::inflate; pub use deflater::inflate;
use std::io::{BufWriter, Cursor, Write}; use std::io::{BufWriter, copy, Cursor, Write};
use std::{fmt, fmt::Display, io}; use std::{fmt, fmt::Display, io};
#[cfg(feature = "zopfli")] #[cfg(feature = "zopfli")]
use std::num::NonZeroU8; use std::num::NonZeroU8;
#[cfg(feature = "zopfli")] #[cfg(feature = "zopfli")]
use zopfli::{DeflateEncoder, Options}; use zopfli::{DeflateEncoder, Options};
#[cfg(feature = "zopfli")] #[cfg(feature = "zopfli")]
mod zopfli_oxipng; mod zopfli_oxipng;
#[cfg(feature = "zopfli")] #[cfg(feature = "zopfli")]
pub use zopfli_oxipng::deflate as zopfli_deflate; pub use zopfli_oxipng::deflate as zopfli_deflate;
#[cfg(feature = "zopfli")]
use simd_adler32::Adler32;
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
/// DEFLATE algorithms supported by oxipng /// DEFLATE algorithms supported by oxipng
@ -100,6 +101,8 @@ impl Default for BufferedZopfliDeflater {
#[cfg(feature = "zopfli")] #[cfg(feature = "zopfli")]
impl Deflater for BufferedZopfliDeflater { impl Deflater for BufferedZopfliDeflater {
/// Fork of the zlib_compress function in Zopfli.
fn deflate(&self, data: &[u8], max_size: &AtomicMin) -> PngResult<Vec<u8>> { fn deflate(&self, data: &[u8], max_size: &AtomicMin) -> PngResult<Vec<u8>> {
#[allow(clippy::needless_update)] #[allow(clippy::needless_update)]
let options = Options { let options = Options {
@ -107,22 +110,32 @@ impl Deflater for BufferedZopfliDeflater {
maximum_block_splits: self.max_block_splits, maximum_block_splits: self.max_block_splits,
..Default::default() // for forward compatibility ..Default::default() // for forward compatibility
}; };
let mut out = Vec::with_capacity(self.output_buffer_size); let mut out = Cursor::new(Vec::with_capacity(self.output_buffer_size));
let mut buffer = BufWriter::with_capacity( let cmf = 120; /* CM 8, CINFO 7. See zlib spec.*/
self.input_buffer_size, let flevel = 3;
DeflateEncoder::new( let fdict = 0;
options, let mut cmfflg: u16 = 256 * cmf + fdict * 32 + flevel * 64;
Default::default(), let fcheck = 31 - cmfflg % 31;
&mut out, cmfflg += fcheck;
),
); let out = (|| -> io::Result<Vec<u8>> {
let result = (|| -> io::Result<()> { let mut rolling_adler = Adler32::new();
buffer.write_all(data)?; 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(
options,
Default::default(),
&mut out,
),
);
copy(&mut in_data, &mut buffer)?;
buffer.into_inner()?.finish()?; buffer.into_inner()?.finish()?;
Ok(()) out.write_all(&rolling_adler.finish().to_be_bytes())?;
Ok(out.into_inner())
})(); })();
result.map_err(|e| PngError::new(&e.to_string()))?; let out = out.map_err(|e| PngError::new(&e.to_string()))?;
println!("Compressed {} -> {} bytes", data.len(), out.len());
if max_size.get().is_some_and(|max| max < out.len()) { if max_size.get().is_some_and(|max| max < out.len()) {
Err(PngError::DeflatedDataTooLong(out.len())) Err(PngError::DeflatedDataTooLong(out.len()))
} else { } else {

View file

@ -1,5 +1,7 @@
use std::io::{Error, ErrorKind, Read};
use crate::{PngError, PngResult}; use crate::{PngError, PngResult};
use std::num::NonZeroU8; use std::num::NonZeroU8;
use simd_adler32::Adler32;
pub fn deflate(data: &[u8], iterations: NonZeroU8) -> PngResult<Vec<u8>> { pub fn deflate(data: &[u8], iterations: NonZeroU8) -> PngResult<Vec<u8>> {
use std::cmp::max; use std::cmp::max;
@ -16,3 +18,52 @@ pub fn deflate(data: &[u8], iterations: NonZeroU8) -> PngResult<Vec<u8>> {
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),
}
}
}