125 lines
3.8 KiB
Rust
125 lines
3.8 KiB
Rust
//! A fast deflate implementation.
|
|
//!
|
|
//! This crate contains an optimized implementation of the deflate algorithm tuned to compress PNG
|
|
//! images. It is compatible with standard zlib, but make a bunch of simplifying assumptions that
|
|
//! drastically improve encoding performance:
|
|
//!
|
|
//! - Exactly one block per deflate stream.
|
|
//! - No distance codes except for run length encoding of zeros.
|
|
//! - A single fixed huffman tree trained on a large corpus of PNG images.
|
|
//! - All huffman codes are 12 bits or less.
|
|
//!
|
|
//! It also contains a fast decompressor that supports arbitrary zlib streams but does especially
|
|
//! well on streams that meet the above assumptions.
|
|
//!
|
|
//! # Inspiration
|
|
//!
|
|
//! The algorithms in this crate take inspiration from multiple sources:
|
|
//! * [fpnge](https://github.com/veluca93/fpnge)
|
|
//! * [zune-inflate](https://github.com/etemesi254/zune-image/tree/main/zune-inflate)
|
|
//! * [RealTime Data Compression blog](https://fastcompression.blogspot.com/2015/10/huffman-revisited-part-4-multi-bytes.html)
|
|
#![forbid(unsafe_code)]
|
|
#![warn(missing_docs)]
|
|
|
|
mod compress;
|
|
mod decompress;
|
|
mod huffman;
|
|
mod tables;
|
|
|
|
pub use compress::{compress_to_vec, Compressor, StoredOnlyCompressor};
|
|
pub use decompress::{
|
|
decompress_to_vec, decompress_to_vec_bounded, BoundedDecompressionError, DecompressionError,
|
|
Decompressor,
|
|
};
|
|
|
|
/// Build a length limited huffman tree.
|
|
///
|
|
/// Dynamic programming algorithm from fpnge.
|
|
#[doc(hidden)]
|
|
pub fn compute_code_lengths(
|
|
freqs: &[u64],
|
|
min_limit: &[u8],
|
|
max_limit: &[u8],
|
|
calculated_nbits: &mut [u8],
|
|
) {
|
|
debug_assert_eq!(freqs.len(), min_limit.len());
|
|
debug_assert_eq!(freqs.len(), max_limit.len());
|
|
debug_assert_eq!(freqs.len(), calculated_nbits.len());
|
|
let len = freqs.len();
|
|
|
|
for i in 0..len {
|
|
debug_assert!(min_limit[i] >= 1);
|
|
debug_assert!(min_limit[i] <= max_limit[i]);
|
|
}
|
|
|
|
let precision = *max_limit.iter().max().unwrap();
|
|
let num_patterns = 1 << precision;
|
|
|
|
let mut dynp = vec![u64::MAX; (num_patterns + 1) * (len + 1)];
|
|
let index = |sym: usize, off: usize| sym * (num_patterns + 1) + off;
|
|
|
|
dynp[index(0, 0)] = 0;
|
|
for sym in 0..len {
|
|
for bits in min_limit[sym]..=max_limit[sym] {
|
|
let off_delta = 1 << (precision - bits);
|
|
for off in 0..=num_patterns.saturating_sub(off_delta) {
|
|
dynp[index(sym + 1, off + off_delta)] = dynp[index(sym, off)]
|
|
.saturating_add(freqs[sym] * u64::from(bits))
|
|
.min(dynp[index(sym + 1, off + off_delta)]);
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut sym = len;
|
|
let mut off = num_patterns;
|
|
|
|
while sym > 0 {
|
|
sym -= 1;
|
|
assert!(off > 0);
|
|
|
|
for bits in min_limit[sym]..=max_limit[sym] {
|
|
let off_delta = 1 << (precision - bits);
|
|
if off_delta <= off
|
|
&& dynp[index(sym + 1, off)]
|
|
== dynp[index(sym, off - off_delta)]
|
|
.saturating_add(freqs[sym] * u64::from(bits))
|
|
{
|
|
off -= off_delta;
|
|
calculated_nbits[sym] = bits;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for i in 0..len {
|
|
debug_assert!(calculated_nbits[i] >= min_limit[i]);
|
|
debug_assert!(calculated_nbits[i] <= max_limit[i]);
|
|
}
|
|
}
|
|
|
|
const fn compute_codes<const NSYMS: usize>(lengths: &[u8; NSYMS]) -> Option<[u16; NSYMS]> {
|
|
let mut codes = [0u16; NSYMS];
|
|
|
|
let mut code = 0u32;
|
|
|
|
let mut len = 1;
|
|
while len <= 16 {
|
|
let mut i = 0;
|
|
while i < lengths.len() {
|
|
if lengths[i] == len {
|
|
codes[i] = (code as u16).reverse_bits() >> (16 - len);
|
|
code += 1;
|
|
}
|
|
i += 1;
|
|
}
|
|
code <<= 1;
|
|
len += 1;
|
|
}
|
|
|
|
if code == 2 << 16 {
|
|
Some(codes)
|
|
} else {
|
|
None
|
|
}
|
|
}
|