Skip to content

Commit

Permalink
Bug fix: need to implement Zlib format
Browse files Browse the repository at this point in the history
  • Loading branch information
Pr0methean committed Dec 1, 2023
1 parent 556b51f commit 12fa289
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 16 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ required-features = ["zopfli"]
[dependencies]
zopfli = {git = "https://github.com/Pr0methean/zopfli.git", default-features = false, features = ["zlib"] }
simd-adler32 = { version = "0.3.5", optional = true, default-features = false }
simd-adler32 = { version = "0.3.5", optional = true, default-features = false }
rgb = "0.8.36"
indexmap = "2.0.0"
libdeflater = "1.19.0"
Expand Down Expand Up @@ -73,6 +74,7 @@ version = "0.24.6"
rustc_version = "0.4.0"

[features]
zopfli = ["zopfli/std", "zopfli/zlib", "simd-adler32"]
binary = ["clap", "glob", "env_logger"]
default = ["binary", "filetime", "parallel", "zopfli"]
parallel = ["rayon", "indexmap/rayon", "crossbeam-channel"]
Expand Down
45 changes: 29 additions & 16 deletions src/deflate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ use crate::{PngError, PngResult};
pub use deflater::crc32;
pub use deflater::deflate;
pub use deflater::inflate;
use std::io::{copy, BufWriter, Cursor, Write};
use std::io::{copy, BufWriter, copy, Cursor, Write};
use std::{fmt, fmt::Display, io};

#[cfg(feature = "zopfli")]
use std::num::NonZeroU8;
#[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;
#[cfg(feature = "zopfli")]
use simd_adler32::Adler32;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
/// DEFLATE algorithms supported by oxipng
Expand Down Expand Up @@ -102,28 +103,40 @@ impl Default for BufferedZopfliDeflater {

#[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>> {
#[allow(clippy::needless_update)]
let options = Options {
iteration_count: self.iterations,
..Default::default() // for forward compatibility
};
let mut out = Vec::with_capacity(self.output_buffer_size);
let mut buffer = BufWriter::with_capacity(
self.buffer_size,
DeflateEncoder::new(
options,
Default::default(),
&mut out,
),
);
let result = (|| -> io::Result<()> {
buffer.write_all(data)?;
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.buffer_size,
DeflateEncoder::new(
options,
Default::default(),
&mut out,
),
);
copy(&mut in_data, &mut buffer)?;
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()))?;
println!("Compressed {} -> {} bytes", data.len(), out.len());
let out = out.map_err(|e| PngError::new(&e.to_string()))?;
if max_size.get().is_some_and(|max| max < out.len()) {
Err(PngError::DeflatedDataTooLong(out.len()))
} else {
Expand Down
51 changes: 51 additions & 0 deletions src/deflate/zopfli_oxipng.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::io::{Error, ErrorKind, Read};
use crate::{PngError, PngResult};
use std::num::NonZeroU8;
use simd_adler32::Adler32;

pub fn deflate(data: &[u8], iterations: NonZeroU8) -> PngResult<Vec<u8>> {
let mut output = Vec::with_capacity(data.len());
Expand All @@ -14,3 +16,52 @@ pub fn deflate(data: &[u8], iterations: NonZeroU8) -> PngResult<Vec<u8>> {
output.shrink_to_fit();
Ok(output)
}

/// Forked from zopfli crate
pub trait Hasher {
fn update(&mut self, data: &[u8]);
}

impl Hasher for &mut Adler32 {
fn update(&mut self, data: &[u8]) {
Adler32::write(self, data)
}
}

/// A reader that wraps another reader, a hasher and an optional counter,
/// updating the hasher state and incrementing a counter of bytes read so
/// far for each block of data read.
pub struct HashingAndCountingRead<'counter, R: Read, H: Hasher> {
inner: R,
hasher: H,
bytes_read: Option<&'counter mut u32>,
}

impl<'counter, R: Read, H: Hasher> HashingAndCountingRead<'counter, R, H> {
pub fn new(inner: R, hasher: H, bytes_read: Option<&'counter mut u32>) -> Self {
Self {
inner,
hasher,
bytes_read,
}
}
}

impl<R: Read, H: Hasher> Read for HashingAndCountingRead<'_, R, H> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
match self.inner.read(buf) {
Ok(bytes_read) => {
self.hasher.update(&buf[..bytes_read]);

if let Some(total_bytes_read) = &mut self.bytes_read {
**total_bytes_read = total_bytes_read
.checked_add(bytes_read.try_into().map_err(|_| ErrorKind::Other)?)
.ok_or(ErrorKind::Other)?;
}

Ok(bytes_read)
}
Err(err) => Err(err),
}
}
}

0 comments on commit 12fa289

Please sign in to comment.