use alloc::vec::Vec;
use core::{cmp, iter};
#[cfg(feature = "std")]
use log::{debug, log_enabled};
use enough::Stop;
use crate::{
Error, Options, Write,
blocksplitter::{blocksplit, blocksplit_lz77},
cache::ZopfliLongestMatchCache,
iter::ToFlagLastIterator,
katajainen::{HuffmanScratch, length_limited_code_lengths, length_limited_code_lengths_into},
lz77::{LitLen, Lz77Store},
squeeze::{lz77_optimal, lz77_optimal_fixed},
stop_to_error,
symbols::{
get_dist_extra_bits, get_dist_extra_bits_value, get_dist_symbol,
get_dist_symbol_extra_bits, get_length_extra_bits, get_length_extra_bits_value,
get_length_symbol, get_length_symbol_extra_bits,
},
tree::lengths_to_symbols,
util::{ZOPFLI_NUM_D, ZOPFLI_NUM_LL, ZOPFLI_WINDOW_SIZE},
};
#[derive(Debug)]
pub struct CompressResult<W> {
pub(crate) inner: W,
fully_optimized: bool,
}
impl<W> CompressResult<W> {
#[must_use]
pub fn fully_optimized(&self) -> bool {
self.fully_optimized
}
#[must_use]
pub fn into_inner(self) -> W {
self.inner
}
}
pub struct DeflateEncoder<W: Write, S: Stop = enough::Unstoppable> {
options: Options,
have_chunk: bool,
chunk_start: usize,
window_and_chunk: Vec<u8>,
bitwise_writer: Option<BitwiseWriter<W>>,
stop: S,
fully_optimized: bool,
}
impl<W: Write> DeflateEncoder<W> {
pub fn new(options: Options, sink: W) -> Self {
Self {
options,
have_chunk: false,
chunk_start: 0,
window_and_chunk: Vec::with_capacity(ZOPFLI_WINDOW_SIZE),
bitwise_writer: Some(BitwiseWriter::new(sink)),
stop: enough::Unstoppable,
fully_optimized: true,
}
}
#[cfg(feature = "std")]
pub fn new_buffered(options: Options, sink: W) -> std::io::BufWriter<Self> {
std::io::BufWriter::with_capacity(
crate::util::ZOPFLI_MASTER_BLOCK_SIZE,
Self::new(options, sink),
)
}
}
impl<W: Write, S: Stop> DeflateEncoder<W, S> {
pub fn with_stop(options: Options, sink: W, stop: S) -> Self {
Self {
options,
have_chunk: false,
chunk_start: 0,
window_and_chunk: Vec::with_capacity(ZOPFLI_WINDOW_SIZE),
bitwise_writer: Some(BitwiseWriter::new(sink)),
stop,
fully_optimized: true,
}
}
#[cfg(feature = "std")]
pub fn with_stop_buffered(options: Options, sink: W, stop: S) -> std::io::BufWriter<Self> {
std::io::BufWriter::with_capacity(
crate::util::ZOPFLI_MASTER_BLOCK_SIZE,
Self::with_stop(options, sink, stop),
)
}
pub fn finish(mut self) -> Result<CompressResult<W>, Error> {
self.__finish().map(|sink| CompressResult {
inner: sink.unwrap(),
fully_optimized: self.fully_optimized,
})
}
#[inline]
fn compress_chunk(&mut self, is_last: bool) -> Result<(), Error> {
let fo = deflate_part(
&self.options,
self.options.block_type,
is_last,
&self.window_and_chunk,
self.chunk_start,
self.window_and_chunk.len(),
self.bitwise_writer.as_mut().unwrap(),
&self.stop,
)?;
self.fully_optimized &= fo;
Ok(())
}
fn set_chunk(&mut self, chunk: &[u8]) {
self.window_and_chunk.drain(
..self
.window_and_chunk
.len()
.saturating_sub(ZOPFLI_WINDOW_SIZE),
);
self.chunk_start = self.window_and_chunk.len();
self.window_and_chunk.extend_from_slice(chunk);
self.have_chunk = true;
}
fn __finish(&mut self) -> Result<Option<W>, Error> {
if self.bitwise_writer.is_none() {
return Ok(None);
}
self.compress_chunk(true)?;
let mut bitwise_writer = self.bitwise_writer.take().unwrap();
bitwise_writer.finish_partial_bits()?;
Ok(Some(bitwise_writer.out))
}
pub fn get_ref(&self) -> &W {
&self.bitwise_writer.as_ref().unwrap().out
}
pub fn get_mut(&mut self) -> &mut W {
&mut self.bitwise_writer.as_mut().unwrap().out
}
}
impl<W: Write, S: Stop> core::fmt::Debug for DeflateEncoder<W, S> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DeflateEncoder")
.field("options", &self.options)
.field("have_chunk", &self.have_chunk)
.field("fully_optimized", &self.fully_optimized)
.finish_non_exhaustive()
}
}
impl<W: Write, S: Stop> Write for DeflateEncoder<W, S> {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
if self.have_chunk {
self.compress_chunk(false)?;
}
self.set_chunk(buf);
Ok(buf.len())
}
fn flush(&mut self) -> Result<(), Error> {
self.bitwise_writer.as_mut().unwrap().out.flush()
}
}
impl<W: Write, S: Stop> Drop for DeflateEncoder<W, S> {
fn drop(&mut self) {
self.__finish().ok();
}
}
#[cfg(all(doc, feature = "std"))]
impl<W: crate::io::Write, S: Stop> std::io::Write for DeflateEncoder<W, S> {
fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
unimplemented!()
}
fn flush(&mut self) -> std::io::Result<()> {
unimplemented!()
}
}
#[allow(clippy::too_many_arguments)]
fn deflate_part<W: Write>(
options: &Options,
btype: BlockType,
final_block: bool,
in_data: &[u8],
instart: usize,
inend: usize,
bitwise_writer: &mut BitwiseWriter<W>,
stop: &dyn Stop,
) -> Result<bool, Error> {
match btype {
BlockType::Uncompressed => {
add_non_compressed_block(final_block, in_data, instart, inend, bitwise_writer)?;
Ok(true)
}
BlockType::Fixed => {
let mut store = Lz77Store::with_capacity(inend - instart);
lz77_optimal_fixed(
&mut ZopfliLongestMatchCache::new(inend - instart),
in_data,
instart,
inend,
&mut store,
);
add_lz77_block(
btype,
final_block,
in_data,
&store,
0,
store.size(),
0,
options.enhanced,
bitwise_writer,
)?;
Ok(true)
}
BlockType::Dynamic => blocksplit_attempt(
options,
final_block,
in_data,
instart,
inend,
bitwise_writer,
stop,
),
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone, Debug)]
#[cfg_attr(all(test, feature = "std"), derive(proptest_derive::Arbitrary))]
#[derive(Default)]
pub enum BlockType {
Uncompressed,
Fixed,
#[default]
Dynamic,
}
const FIXED_TREE_LL: [u32; ZOPFLI_NUM_LL] = {
let mut ll = [0u32; ZOPFLI_NUM_LL];
let mut i = 0;
while i < 144 {
ll[i] = 8;
i += 1;
}
while i < 256 {
ll[i] = 9;
i += 1;
}
while i < 280 {
ll[i] = 7;
i += 1;
}
while i < 288 {
ll[i] = 8;
i += 1;
}
ll
};
const FIXED_TREE_D: [u32; ZOPFLI_NUM_D] = [5; ZOPFLI_NUM_D];
pub(crate) fn optimize_huffman_for_rle(counts: &mut [usize]) {
let mut length = counts.len();
loop {
if length == 0 {
return;
}
if counts[length - 1] != 0 {
break;
}
length -= 1;
}
let mut good_for_rle = [false; ZOPFLI_NUM_LL];
let mut symbol = counts[0];
let mut stride = 0;
for (i, &count) in counts.iter().enumerate().take(length) {
if count == symbol {
stride += 1;
} else {
if (symbol == 0 && stride >= 5) || (symbol != 0 && stride >= 7) {
for k in 0..stride {
good_for_rle[i - k - 1] = true;
}
}
stride = 1;
symbol = count;
}
}
stride = 0;
let mut limit = counts[0];
let mut sum = 0;
for i in 0..=length {
if i == length || good_for_rle[i] || (counts[i] as i32 - limit as i32).abs() >= 4 {
if stride >= 4 || (stride >= 3 && sum == 0) {
let count = if sum == 0 {
0
} else {
cmp::max((sum + stride / 2) / stride, 1)
};
set_counts_to_count(counts, count, i, stride);
}
stride = 0;
sum = 0;
if length > 2 && i < length - 3 {
limit = (counts[i] + counts[i + 1] + counts[i + 2] + counts[i + 3] + 2) / 4;
} else if i < length {
limit = counts[i];
} else {
limit = 0;
}
}
stride += 1;
if i != length {
sum += counts[i];
}
}
}
fn optimize_huffman_for_rle_brotli(counts: &mut [usize]) {
let mut n = counts.len();
while n > 0 && counts[n - 1] == 0 {
n -= 1;
}
if n == 0 {
return;
}
let mut good_for_rle = [false; ZOPFLI_NUM_LL];
{
let mut i = 0;
while i < n {
if counts[i] == 0 {
let start = i;
while i < n && counts[i] == 0 {
i += 1;
}
if i - start >= 5 {
good_for_rle[start..i].fill(true);
}
} else {
i += 1;
}
}
}
{
let mut i = 0;
while i < n {
if counts[i] != 0 {
let start = i;
let mut sum = 0u64;
while i < n && counts[i] != 0 {
sum += counts[i] as u64;
i += 1;
}
let run_len = i - start;
if run_len >= 7 {
let avg = sum / run_len as u64;
let limit = (avg * 1240) >> 10;
let all_similar = counts[start..i]
.iter()
.all(|&c| (c as u64).abs_diff(avg) <= limit);
if all_similar {
good_for_rle[start..i].fill(true);
}
}
} else {
i += 1;
}
}
}
{
let mut i = 0;
while i < n {
if good_for_rle[i] {
let start = i;
let mut sum = 0u64;
while i < n && good_for_rle[i] {
sum += counts[i] as u64;
i += 1;
}
let run_len = (i - start) as u64;
let avg = sum.div_ceil(run_len) as usize;
counts[start..i].fill(avg);
} else {
i += 1;
}
}
}
}
fn patch_distance_codes_for_buggy_decoders(d_lengths: &mut [u32]) {
let num_dist_codes = d_lengths
.iter()
.take(30)
.filter(|&&d_length| d_length != 0)
.count();
match num_dist_codes {
0 => {
d_lengths[0] = 1;
d_lengths[1] = 1;
}
1 => {
let index = usize::from(d_lengths[0] != 0);
d_lengths[index] = 1;
}
_ => {} }
}
fn calculate_block_symbol_size_small(
ll_lengths: &[u32],
d_lengths: &[u32],
lz77: &Lz77Store,
lstart: usize,
lend: usize,
) -> usize {
let mut result = 0;
debug_assert!(lend == lstart || lend - 1 < lz77.size());
for &item in &lz77.litlens[lstart..lend] {
match item {
LitLen::Literal(litlens_i) => {
debug_assert!(litlens_i < 259);
result += ll_lengths[litlens_i as usize];
}
LitLen::LengthDist(litlens_i, dists_i) => {
debug_assert!(litlens_i < 259);
let ll_symbol = get_length_symbol(litlens_i as usize);
let d_symbol = get_dist_symbol(dists_i) as usize;
result += ll_lengths[ll_symbol];
result += d_lengths[d_symbol];
result += get_length_symbol_extra_bits(ll_symbol);
result += get_dist_symbol_extra_bits(d_symbol);
}
}
}
result += ll_lengths[256]; result as usize
}
fn calculate_block_symbol_size_given_counts(
ll_counts: &[usize],
d_counts: &[usize],
ll_lengths: &[u32],
d_lengths: &[u32],
lz77: &Lz77Store,
lstart: usize,
lend: usize,
) -> usize {
if lstart + ZOPFLI_NUM_LL * 3 > lend {
calculate_block_symbol_size_small(ll_lengths, d_lengths, lz77, lstart, lend)
} else {
let mut result = 0;
for i in 0..256 {
result += ll_lengths[i] * ll_counts[i] as u32;
}
for i in 257..286 {
result += ll_lengths[i] * ll_counts[i] as u32;
result += (get_length_symbol_extra_bits(i) as usize * ll_counts[i]) as u32;
}
for i in 0..30 {
result += d_lengths[i] * d_counts[i] as u32;
result += (get_dist_symbol_extra_bits(i) as usize * d_counts[i]) as u32;
}
result += ll_lengths[256]; result as usize
}
}
fn calculate_block_symbol_size(
ll_lengths: &[u32],
d_lengths: &[u32],
lz77: &Lz77Store,
lstart: usize,
lend: usize,
) -> usize {
if lstart + ZOPFLI_NUM_LL * 3 > lend {
calculate_block_symbol_size_small(ll_lengths, d_lengths, lz77, lstart, lend)
} else {
let (ll_counts, d_counts) = lz77.get_histogram(lstart, lend);
calculate_block_symbol_size_given_counts(
&ll_counts, &d_counts, ll_lengths, d_lengths, lz77, lstart, lend,
)
}
}
#[allow(clippy::too_many_arguments)]
fn encode_tree_no_output_with_scratch(
ll_lengths: &[u32],
d_lengths: &[u32],
use_16: bool,
use_17: bool,
use_18: bool,
fuse_7: bool,
fuse_8: bool,
scratch: &mut HuffmanScratch,
) -> usize {
let mut hlit = 29;
let mut hdist = 29;
let mut clcounts = [0; 19];
let order = [
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
];
let mut result_size = 0;
while hlit > 0 && ll_lengths[257 + hlit - 1] == 0 {
hlit -= 1;
}
while hdist > 0 && d_lengths[1 + hdist - 1] == 0 {
hdist -= 1;
}
let hlit2 = hlit + 257;
let lld_total = hlit2 + hdist + 1;
let mut i = 0;
while i < lld_total {
let symbol = if i < hlit2 {
ll_lengths[i]
} else {
d_lengths[i - hlit2]
} as u8;
let mut count = 1;
if use_16 || (symbol == 0 && (use_17 || use_18)) {
let mut j = i + 1;
let mut symbol_calc = if j < hlit2 {
ll_lengths[j]
} else {
d_lengths[j - hlit2]
} as u8;
while j < lld_total && symbol == symbol_calc {
count += 1;
j += 1;
symbol_calc = if j < hlit2 {
ll_lengths[j]
} else {
d_lengths[j - hlit2]
} as u8;
}
}
i += count - 1;
if symbol == 0 && count >= 3 {
if use_18 {
while count >= 11 {
let count2 = if count > 138 { 138 } else { count };
clcounts[18] += 1;
count -= count2;
}
}
if use_17 {
while count >= 3 {
let count2 = if count > 10 { 10 } else { count };
clcounts[17] += 1;
count -= count2;
}
}
}
if use_16 && count >= 4 {
count -= 1;
clcounts[symbol as usize] += 1;
if fuse_7 && count == 6 {
clcounts[16] += 2;
count = 0;
} else if fuse_8 && count == 7 {
clcounts[16] += 2;
count = 0;
} else {
while count >= 3 {
let count2 = if count > 6 { 6 } else { count };
clcounts[16] += 1;
count -= count2;
}
}
}
clcounts[symbol as usize] += count;
while count > 0 {
count -= 1;
}
i += 1;
}
let mut clcl = [0u32; 19];
length_limited_code_lengths_into(&clcounts, 7, scratch, &mut clcl);
let mut hclen = 15;
while hclen > 0 && clcounts[order[hclen + 4 - 1]] == 0 {
hclen -= 1;
}
result_size += 14;
result_size += (hclen + 4) * 3;
for i in 0..19 {
result_size += clcl[i] as usize * clcounts[i];
}
result_size += clcounts[16] * 2;
result_size += clcounts[17] * 3;
result_size += clcounts[18] * 7;
result_size
}
fn encode_tree_no_output(
ll_lengths: &[u32],
d_lengths: &[u32],
use_16: bool,
use_17: bool,
use_18: bool,
fuse_7: bool,
fuse_8: bool,
) -> usize {
encode_tree_no_output_with_scratch(
ll_lengths,
d_lengths,
use_16,
use_17,
use_18,
fuse_7,
fuse_8,
&mut HuffmanScratch::new(),
)
}
fn calculate_tree_size_with_scratch(
ll_lengths: &[u32],
d_lengths: &[u32],
enhanced: bool,
scratch: &mut HuffmanScratch,
) -> usize {
static TRUTH_TABLE: [(bool, bool, bool); 8] = [
(false, false, false),
(true, false, false),
(false, true, false),
(true, true, false),
(false, false, true),
(true, false, true),
(false, true, true),
(true, true, true),
];
if enhanced {
let mut best = usize::MAX;
for &(use_16, use_17, use_18) in &TRUTH_TABLE {
let fuse_options: &[(bool, bool)] = if use_16 {
&[(false, false), (true, false), (false, true), (true, true)]
} else {
&[(false, false)]
};
for &(fuse_7, fuse_8) in fuse_options {
let size = encode_tree_no_output_with_scratch(
ll_lengths, d_lengths, use_16, use_17, use_18, fuse_7, fuse_8, scratch,
);
if size < best {
best = size;
}
}
}
best
} else {
TRUTH_TABLE
.iter()
.map(|&(use_16, use_17, use_18)| {
encode_tree_no_output_with_scratch(
ll_lengths, d_lengths, use_16, use_17, use_18, false, false, scratch,
)
})
.min()
.unwrap_or(0)
}
}
#[allow(clippy::too_many_arguments)]
fn encode_tree<W: Write>(
ll_lengths: &[u32],
d_lengths: &[u32],
use_16: bool,
use_17: bool,
use_18: bool,
fuse_7: bool,
fuse_8: bool,
bitwise_writer: &mut BitwiseWriter<W>,
) -> Result<usize, Error> {
let mut hlit = 29;
let mut hdist = 29;
let mut clcounts = [0; 19];
let order = [
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
];
let mut result_size = 0;
let mut rle = vec![];
let mut rle_bits = vec![];
while hlit > 0 && ll_lengths[257 + hlit - 1] == 0 {
hlit -= 1;
}
while hdist > 0 && d_lengths[1 + hdist - 1] == 0 {
hdist -= 1;
}
let hlit2 = hlit + 257;
let lld_total = hlit2 + hdist + 1;
let mut i = 0;
while i < lld_total {
let symbol = if i < hlit2 {
ll_lengths[i]
} else {
d_lengths[i - hlit2]
} as u8;
let mut count = 1;
if use_16 || (symbol == 0 && (use_17 || use_18)) {
let mut j = i + 1;
let mut symbol_calc = if j < hlit2 {
ll_lengths[j]
} else {
d_lengths[j - hlit2]
} as u8;
while j < lld_total && symbol == symbol_calc {
count += 1;
j += 1;
symbol_calc = if j < hlit2 {
ll_lengths[j]
} else {
d_lengths[j - hlit2]
} as u8;
}
}
i += count - 1;
if symbol == 0 && count >= 3 {
if use_18 {
while count >= 11 {
let count2 = if count > 138 { 138 } else { count };
rle.push(18);
rle_bits.push(count2 - 11);
clcounts[18] += 1;
count -= count2;
}
}
if use_17 {
while count >= 3 {
let count2 = if count > 10 { 10 } else { count };
rle.push(17);
rle_bits.push(count2 - 3);
clcounts[17] += 1;
count -= count2;
}
}
}
if use_16 && count >= 4 {
count -= 1;
clcounts[symbol as usize] += 1;
rle.push(symbol);
rle_bits.push(0);
if fuse_7 && count == 6 {
rle.push(16);
rle_bits.push(0); rle.push(16);
rle_bits.push(0); clcounts[16] += 2;
count = 0;
} else if fuse_8 && count == 7 {
rle.push(16);
rle_bits.push(1); rle.push(16);
rle_bits.push(0); clcounts[16] += 2;
count = 0;
} else {
while count >= 3 {
let count2 = if count > 6 { 6 } else { count };
rle.push(16);
rle_bits.push(count2 - 3);
clcounts[16] += 1;
count -= count2;
}
}
}
clcounts[symbol as usize] += count;
while count > 0 {
rle.push(symbol);
rle_bits.push(0);
count -= 1;
}
i += 1;
}
let clcl = length_limited_code_lengths(&clcounts, 7);
let clsymbols = lengths_to_symbols(&clcl, 7);
let mut hclen = 15;
while hclen > 0 && clcounts[order[hclen + 4 - 1]] == 0 {
hclen -= 1;
}
bitwise_writer.add_bits(hlit as u32, 5)?;
bitwise_writer.add_bits(hdist as u32, 5)?;
bitwise_writer.add_bits(hclen as u32, 4)?;
for &item in order.iter().take(hclen + 4) {
bitwise_writer.add_bits(clcl[item], 3)?;
}
for i in 0..rle.len() {
let rle_i = rle[i] as usize;
let rle_bits_i = rle_bits[i] as u32;
let sym = clsymbols[rle_i];
bitwise_writer.add_huffman_bits(sym, clcl[rle_i])?;
if rle_i == 16 {
bitwise_writer.add_bits(rle_bits_i, 2)?;
} else if rle_i == 17 {
bitwise_writer.add_bits(rle_bits_i, 3)?;
} else if rle_i == 18 {
bitwise_writer.add_bits(rle_bits_i, 7)?;
}
}
result_size += 14;
result_size += (hclen + 4) * 3;
for i in 0..19 {
result_size += clcl[i] as usize * clcounts[i];
}
result_size += clcounts[16] * 2;
result_size += clcounts[17] * 3;
result_size += clcounts[18] * 7;
Ok(result_size)
}
fn add_dynamic_tree<W: Write>(
ll_lengths: &[u32],
d_lengths: &[u32],
enhanced: bool,
bitwise_writer: &mut BitwiseWriter<W>,
) -> Result<(), Error> {
let mut best_use_16 = false;
let mut best_use_17 = false;
let mut best_use_18 = false;
let mut best_fuse_7 = false;
let mut best_fuse_8 = false;
let mut bestsize = usize::MAX;
for i in 0..8u8 {
let use_16 = i & 1 > 0;
let use_17 = i & 2 > 0;
let use_18 = i & 4 > 0;
let fuse_options: &[(bool, bool)] = if enhanced && use_16 {
&[(false, false), (true, false), (false, true), (true, true)]
} else {
&[(false, false)]
};
for &(fuse_7, fuse_8) in fuse_options {
let size = encode_tree_no_output(
ll_lengths, d_lengths, use_16, use_17, use_18, fuse_7, fuse_8,
);
if size < bestsize {
bestsize = size;
best_use_16 = use_16;
best_use_17 = use_17;
best_use_18 = use_18;
best_fuse_7 = fuse_7;
best_fuse_8 = fuse_8;
}
}
}
encode_tree(
ll_lengths,
d_lengths,
best_use_16,
best_use_17,
best_use_18,
best_fuse_7,
best_fuse_8,
bitwise_writer,
)
.map(|_| ())
}
#[allow(clippy::too_many_arguments)] fn add_lz77_block<W: Write>(
btype: BlockType,
final_block: bool,
in_data: &[u8],
lz77: &Lz77Store,
lstart: usize,
lend: usize,
expected_data_size: usize,
enhanced: bool,
bitwise_writer: &mut BitwiseWriter<W>,
) -> Result<(), Error> {
if btype == BlockType::Uncompressed {
let length = lz77.get_byte_range(lstart, lend);
let pos = if lstart == lend {
0
} else {
lz77.pos[lstart] as usize
};
let end = pos + length;
return add_non_compressed_block(final_block, in_data, pos, end, bitwise_writer);
}
bitwise_writer.add_bit(u8::from(final_block))?;
let (ll_lengths, d_lengths) = match btype {
BlockType::Uncompressed => unreachable!(),
BlockType::Fixed => {
bitwise_writer.add_bit(1)?;
bitwise_writer.add_bit(0)?;
(FIXED_TREE_LL.to_vec(), FIXED_TREE_D.to_vec())
}
BlockType::Dynamic => {
bitwise_writer.add_bit(0)?;
bitwise_writer.add_bit(1)?;
let (_, ll_lengths, d_lengths) = get_dynamic_lengths(lz77, lstart, lend, enhanced);
let _detect_tree_size = bitwise_writer.bytes_written();
add_dynamic_tree(&ll_lengths, &d_lengths, enhanced, bitwise_writer)?;
debug!(
"treesize: {}",
bitwise_writer.bytes_written() - _detect_tree_size
);
(ll_lengths, d_lengths)
}
};
let ll_symbols = lengths_to_symbols(&ll_lengths, 15);
let d_symbols = lengths_to_symbols(&d_lengths, 15);
let detect_block_size = bitwise_writer.bytes_written();
add_lz77_data(
lz77,
lstart,
lend,
expected_data_size,
&ll_symbols,
&ll_lengths,
&d_symbols,
&d_lengths,
bitwise_writer,
)?;
bitwise_writer.add_huffman_bits(ll_symbols[256], ll_lengths[256])?;
if log_enabled!(log::Level::Debug) {
let _uncompressed_size = lz77.litlens[lstart..lend]
.iter()
.fold(0, |acc, &x| acc + x.size());
let _compressed_size = bitwise_writer.bytes_written() - detect_block_size;
debug!(
"compressed block size: {} ({}k) (unc: {})",
_compressed_size,
_compressed_size / 1024,
_uncompressed_size
);
}
Ok(())
}
pub fn calculate_block_size(
lz77: &Lz77Store,
lstart: usize,
lend: usize,
btype: BlockType,
enhanced: bool,
) -> f64 {
match btype {
BlockType::Uncompressed => {
let length = lz77.get_byte_range(lstart, lend);
let rem = length % 65535;
let blocks = length / 65535 + usize::from(rem > 0);
(blocks * 5 * 8 + length * 8) as f64
}
BlockType::Fixed => {
let mut result = 3.0;
result += calculate_block_symbol_size(&FIXED_TREE_LL, &FIXED_TREE_D, lz77, lstart, lend)
as f64;
result
}
BlockType::Dynamic => get_dynamic_lengths(lz77, lstart, lend, enhanced).0 + 3.0,
}
}
#[allow(clippy::too_many_arguments)]
fn try_optimize_huffman_for_rle_with_scratch(
lz77: &Lz77Store,
lstart: usize,
lend: usize,
ll_counts: &[usize; ZOPFLI_NUM_LL],
d_counts: &[usize; ZOPFLI_NUM_D],
ll_lengths: &[u32; ZOPFLI_NUM_LL],
d_lengths: &[u32; ZOPFLI_NUM_D],
enhanced: bool,
scratch: &mut HuffmanScratch,
) -> (f64, [u32; ZOPFLI_NUM_LL], [u32; ZOPFLI_NUM_D]) {
let try_strategy = |ll_c: &[usize; ZOPFLI_NUM_LL],
d_c: &[usize; ZOPFLI_NUM_D],
max_bits: usize,
scratch: &mut HuffmanScratch|
-> (usize, [u32; ZOPFLI_NUM_LL], [u32; ZOPFLI_NUM_D]) {
let mut ll_lens = [0u32; ZOPFLI_NUM_LL];
let mut d_lens = [0u32; ZOPFLI_NUM_D];
length_limited_code_lengths_into(ll_c, max_bits, scratch, &mut ll_lens);
length_limited_code_lengths_into(d_c, max_bits, scratch, &mut d_lens);
patch_distance_codes_for_buggy_decoders(&mut d_lens[..]);
let tree = calculate_tree_size_with_scratch(&ll_lens, &d_lens, enhanced, scratch);
let data = calculate_block_symbol_size_given_counts(
ll_counts, d_counts, &ll_lens, &d_lens, lz77, lstart, lend,
);
(tree + data, ll_lens, d_lens)
};
let mut ll_counts_a = *ll_counts;
let mut d_counts_a = *d_counts;
optimize_huffman_for_rle(&mut ll_counts_a);
optimize_huffman_for_rle(&mut d_counts_a);
let (cost_a, ll_a, d_a) = try_strategy(&ll_counts_a, &d_counts_a, 15, scratch);
let treesize_raw = calculate_tree_size_with_scratch(ll_lengths, d_lengths, enhanced, scratch);
let datasize_raw = calculate_block_symbol_size_given_counts(
ll_counts, d_counts, ll_lengths, d_lengths, lz77, lstart, lend,
);
let cost_raw = treesize_raw + datasize_raw;
let (mut best_cost, mut best_ll, mut best_d) = if cost_a < cost_raw {
(cost_a, ll_a, d_a)
} else {
(cost_raw, *ll_lengths, *d_lengths)
};
if enhanced {
let mut ll_counts_b = *ll_counts;
let mut d_counts_b = *d_counts;
optimize_huffman_for_rle_brotli(&mut ll_counts_b);
optimize_huffman_for_rle_brotli(&mut d_counts_b);
let (cost_b, ll_b, d_b) = try_strategy(&ll_counts_b, &d_counts_b, 15, scratch);
if cost_b < best_cost {
best_cost = cost_b;
best_ll = ll_b;
best_d = d_b;
}
let (sweep_ll_c, sweep_d_c) = if best_cost == cost_a {
(ll_counts_a, d_counts_a)
} else if best_cost == cost_b {
(ll_counts_b, d_counts_b)
} else {
(*ll_counts, *d_counts)
};
let mut prev_cost = best_cost;
for max_bits in (9..=14).rev() {
let (cost, ll, d) = try_strategy(&sweep_ll_c, &sweep_d_c, max_bits, scratch);
if cost < best_cost {
best_cost = cost;
best_ll = ll;
best_d = d;
}
if cost > prev_cost {
break; }
prev_cost = cost;
}
}
(best_cost as f64, best_ll, best_d)
}
fn get_dynamic_lengths_with_scratch(
lz77: &Lz77Store,
lstart: usize,
lend: usize,
enhanced: bool,
scratch: &mut HuffmanScratch,
) -> (f64, [u32; ZOPFLI_NUM_LL], [u32; ZOPFLI_NUM_D]) {
let (mut ll_counts, d_counts) = lz77.get_histogram(lstart, lend);
ll_counts[256] = 1;
let mut ll_lengths = [0u32; ZOPFLI_NUM_LL];
let mut d_lengths = [0u32; ZOPFLI_NUM_D];
length_limited_code_lengths_into(&ll_counts, 15, scratch, &mut ll_lengths);
length_limited_code_lengths_into(&d_counts, 15, scratch, &mut d_lengths);
patch_distance_codes_for_buggy_decoders(&mut d_lengths[..]);
try_optimize_huffman_for_rle_with_scratch(
lz77,
lstart,
lend,
&ll_counts,
&d_counts,
&ll_lengths,
&d_lengths,
enhanced,
scratch,
)
}
fn get_dynamic_lengths(
lz77: &Lz77Store,
lstart: usize,
lend: usize,
enhanced: bool,
) -> (f64, Vec<u32>, Vec<u32>) {
let mut scratch = HuffmanScratch::new();
let (cost, ll, d) =
get_dynamic_lengths_with_scratch(lz77, lstart, lend, enhanced, &mut scratch);
(cost, ll.to_vec(), d.to_vec())
}
#[allow(clippy::too_many_arguments)] fn add_lz77_data<W: Write>(
lz77: &Lz77Store,
lstart: usize,
lend: usize,
expected_data_size: usize,
ll_symbols: &[u32],
ll_lengths: &[u32],
d_symbols: &[u32],
d_lengths: &[u32],
bitwise_writer: &mut BitwiseWriter<W>,
) -> Result<(), Error> {
let mut testlength = 0;
for &item in &lz77.litlens[lstart..lend] {
match item {
LitLen::Literal(lit) => {
let litlen = lit as usize;
debug_assert!(litlen < 256);
debug_assert!(ll_lengths[litlen] > 0);
bitwise_writer.add_huffman_bits(ll_symbols[litlen], ll_lengths[litlen])?;
testlength += 1;
}
LitLen::LengthDist(len, dist) => {
let litlen = len as usize;
assert!((3..=288).contains(&litlen)); let lls = get_length_symbol(litlen);
let ds = get_dist_symbol(dist) as usize;
debug_assert!(ll_lengths[lls] > 0);
debug_assert!(d_lengths[ds] > 0);
bitwise_writer.add_huffman_bits(ll_symbols[lls], ll_lengths[lls])?;
bitwise_writer.add_bits(
get_length_extra_bits_value(litlen),
get_length_extra_bits(litlen),
)?;
bitwise_writer.add_huffman_bits(d_symbols[ds], d_lengths[ds])?;
bitwise_writer.add_bits(
u32::from(get_dist_extra_bits_value(dist)),
get_dist_extra_bits(dist),
)?;
testlength += litlen;
}
}
}
debug_assert!(expected_data_size == 0 || testlength == expected_data_size);
Ok(())
}
#[allow(clippy::too_many_arguments)] fn add_lz77_block_auto_type<W: Write>(
final_block: bool,
in_data: &[u8],
lz77: &Lz77Store,
lstart: usize,
lend: usize,
expected_data_size: usize,
enhanced: bool,
bitwise_writer: &mut BitwiseWriter<W>,
) -> Result<(), Error> {
let uncompressedcost =
calculate_block_size(lz77, lstart, lend, BlockType::Uncompressed, enhanced);
let mut fixedcost = calculate_block_size(lz77, lstart, lend, BlockType::Fixed, enhanced);
let dyncost = calculate_block_size(lz77, lstart, lend, BlockType::Dynamic, enhanced);
let expensivefixed = (lz77.size() < 1000) || fixedcost <= dyncost * 1.1;
let mut fixedstore = Lz77Store::new();
if lstart == lend {
bitwise_writer.add_bits(u32::from(final_block), 1)?;
bitwise_writer.add_bits(1, 2)?;
bitwise_writer.add_bits(0, 7)?;
return Ok(());
}
if expensivefixed {
let instart = lz77.pos[lstart] as usize;
let inend = instart + lz77.get_byte_range(lstart, lend);
fixedstore = Lz77Store::with_capacity(inend - instart);
lz77_optimal_fixed(
&mut ZopfliLongestMatchCache::new(inend - instart),
in_data,
instart,
inend,
&mut fixedstore,
);
fixedcost = calculate_block_size(
&fixedstore,
0,
fixedstore.size(),
BlockType::Fixed,
enhanced,
);
}
if uncompressedcost <= fixedcost && uncompressedcost <= dyncost {
add_lz77_block(
BlockType::Uncompressed,
final_block,
in_data,
lz77,
lstart,
lend,
expected_data_size,
enhanced,
bitwise_writer,
)
} else if fixedcost <= dyncost {
if expensivefixed {
add_lz77_block(
BlockType::Fixed,
final_block,
in_data,
&fixedstore,
0,
fixedstore.size(),
expected_data_size,
enhanced,
bitwise_writer,
)
} else {
add_lz77_block(
BlockType::Fixed,
final_block,
in_data,
lz77,
lstart,
lend,
expected_data_size,
enhanced,
bitwise_writer,
)
}
} else {
add_lz77_block(
BlockType::Dynamic,
final_block,
in_data,
lz77,
lstart,
lend,
expected_data_size,
enhanced,
bitwise_writer,
)
}
}
pub(crate) fn calculate_block_size_dynamic_with_scratch(
lz77: &Lz77Store,
lstart: usize,
lend: usize,
enhanced: bool,
scratch: &mut HuffmanScratch,
) -> f64 {
get_dynamic_lengths_with_scratch(lz77, lstart, lend, enhanced, scratch).0 + 3.0
}
pub(crate) fn calculate_block_cost_from_frequencies(
ll_counts: &[usize; ZOPFLI_NUM_LL],
d_counts: &[usize; ZOPFLI_NUM_D],
enhanced: bool,
scratch: &mut HuffmanScratch,
) -> f64 {
let mut ll_lengths = [0u32; ZOPFLI_NUM_LL];
let mut d_lengths = [0u32; ZOPFLI_NUM_D];
length_limited_code_lengths_into(ll_counts, 15, scratch, &mut ll_lengths);
length_limited_code_lengths_into(d_counts, 15, scratch, &mut d_lengths);
patch_distance_codes_for_buggy_decoders(&mut d_lengths[..]);
let try_strategy = |ll_c: &[usize; ZOPFLI_NUM_LL],
d_c: &[usize; ZOPFLI_NUM_D],
max_bits: usize,
scratch: &mut HuffmanScratch|
-> (usize, [u32; ZOPFLI_NUM_LL], [u32; ZOPFLI_NUM_D]) {
let mut ll_lens = [0u32; ZOPFLI_NUM_LL];
let mut d_lens = [0u32; ZOPFLI_NUM_D];
length_limited_code_lengths_into(ll_c, max_bits, scratch, &mut ll_lens);
length_limited_code_lengths_into(d_c, max_bits, scratch, &mut d_lens);
patch_distance_codes_for_buggy_decoders(&mut d_lens[..]);
let tree = calculate_tree_size_with_scratch(&ll_lens, &d_lens, enhanced, scratch);
let data = block_symbol_cost_from_counts(ll_counts, d_counts, &ll_lens, &d_lens);
(tree + data, ll_lens, d_lens)
};
let mut ll_counts_a = *ll_counts;
let mut d_counts_a = *d_counts;
optimize_huffman_for_rle(&mut ll_counts_a);
optimize_huffman_for_rle(&mut d_counts_a);
let (cost_a, _ll_a, _d_a) = try_strategy(&ll_counts_a, &d_counts_a, 15, scratch);
let treesize_raw = calculate_tree_size_with_scratch(&ll_lengths, &d_lengths, enhanced, scratch);
let datasize_raw = block_symbol_cost_from_counts(ll_counts, d_counts, &ll_lengths, &d_lengths);
let cost_raw = treesize_raw + datasize_raw;
let mut best_cost = cost_a.min(cost_raw);
if enhanced {
let mut ll_counts_b = *ll_counts;
let mut d_counts_b = *d_counts;
optimize_huffman_for_rle_brotli(&mut ll_counts_b);
optimize_huffman_for_rle_brotli(&mut d_counts_b);
let (cost_b, _, _) = try_strategy(&ll_counts_b, &d_counts_b, 15, scratch);
if cost_b < best_cost {
best_cost = cost_b;
}
let (sweep_ll_c, sweep_d_c) = if best_cost == cost_a {
(ll_counts_a, d_counts_a)
} else if best_cost == cost_b {
(ll_counts_b, d_counts_b)
} else {
(*ll_counts, *d_counts)
};
let mut prev_cost = best_cost;
for max_bits in (9..=14).rev() {
let (cost, _, _) = try_strategy(&sweep_ll_c, &sweep_d_c, max_bits, scratch);
if cost < best_cost {
best_cost = cost;
}
if cost > prev_cost {
break;
}
prev_cost = cost;
}
}
best_cost as f64 + 3.0
}
fn block_symbol_cost_from_counts(
ll_counts: &[usize; ZOPFLI_NUM_LL],
d_counts: &[usize; ZOPFLI_NUM_D],
ll_lengths: &[u32],
d_lengths: &[u32],
) -> usize {
let mut result = 0u32;
for i in 0..256 {
result += ll_lengths[i] * ll_counts[i] as u32;
}
for i in 257..286 {
result += ll_lengths[i] * ll_counts[i] as u32;
result += (get_length_symbol_extra_bits(i) as usize * ll_counts[i]) as u32;
}
for i in 0..30 {
result += d_lengths[i] * d_counts[i] as u32;
result += (get_dist_symbol_extra_bits(i) as usize * d_counts[i]) as u32;
}
result += ll_lengths[256]; result as usize
}
pub fn calculate_block_size_auto_type_with_scratch(
lz77: &Lz77Store,
lstart: usize,
lend: usize,
enhanced: bool,
scratch: &mut HuffmanScratch,
) -> f64 {
let uncompressedcost =
calculate_block_size(lz77, lstart, lend, BlockType::Uncompressed, enhanced);
let fixedcost = if lz77.size() > 1000 {
uncompressedcost
} else {
calculate_block_size(lz77, lstart, lend, BlockType::Fixed, enhanced)
};
let dyncost = calculate_block_size_dynamic_with_scratch(lz77, lstart, lend, enhanced, scratch);
uncompressedcost.min(fixedcost).min(dyncost)
}
fn add_all_blocks<W: Write>(
splitpoints: &[usize],
lz77: &Lz77Store,
final_block: bool,
in_data: &[u8],
enhanced: bool,
bitwise_writer: &mut BitwiseWriter<W>,
) -> Result<(), Error> {
let mut last = 0;
for &item in splitpoints {
add_lz77_block_auto_type(
false,
in_data,
lz77,
last,
item,
0,
enhanced,
bitwise_writer,
)?;
last = item;
}
add_lz77_block_auto_type(
final_block,
in_data,
lz77,
last,
lz77.size(),
0,
enhanced,
bitwise_writer,
)
}
fn blocksplit_attempt<W: Write>(
options: &Options,
final_block: bool,
in_data: &[u8],
instart: usize,
inend: usize,
bitwise_writer: &mut BitwiseWriter<W>,
stop: &dyn Stop,
) -> Result<bool, Error> {
let mut totalcost = 0.0;
let mut lz77 = Lz77Store::with_capacity(inend - instart);
let mut splitpoints_uncompressed = Vec::with_capacity(options.maximum_block_splits as usize);
blocksplit(
in_data,
instart,
inend,
options.maximum_block_splits,
&mut splitpoints_uncompressed,
);
let npoints = splitpoints_uncompressed.len();
let mut splitpoints = Vec::with_capacity(npoints);
let mut ranges = Vec::with_capacity(npoints + 1);
let mut last = instart;
for &item in &splitpoints_uncompressed {
ranges.push((last, item));
last = item;
}
ranges.push((last, inend));
let mut scratch = HuffmanScratch::new();
let mut fully_optimized = true;
#[cfg(feature = "parallel")]
{
use rayon::prelude::*;
let results: Vec<(Lz77Store, bool)> = ranges
.par_iter()
.map(|&(start, end)| {
lz77_optimal(
&mut ZopfliLongestMatchCache::new(end - start),
in_data,
start,
end,
options.iteration_count.get(),
options.iterations_without_improvement.get(),
options.enhanced,
stop,
)
})
.collect::<Result<Vec<_>, _>>()
.map_err(stop_to_error)?;
for (i, (store, fo)) in results.iter().enumerate() {
fully_optimized &= fo;
totalcost += calculate_block_size_auto_type_with_scratch(
store,
0,
store.size(),
options.enhanced,
&mut scratch,
);
debug_assert!(instart == inend || store.size() > 0);
for (&litlens, &pos) in store.litlens.iter().zip(store.pos.iter()) {
lz77.append_store_item(litlens, pos as usize);
}
if i < results.len() - 1 {
splitpoints.push(lz77.size());
}
}
}
#[cfg(not(feature = "parallel"))]
{
let nranges = ranges.len();
for (i, &(start, end)) in ranges.iter().enumerate() {
let (store, fo) = lz77_optimal(
&mut ZopfliLongestMatchCache::new(end - start),
in_data,
start,
end,
options.iteration_count.get(),
options.iterations_without_improvement.get(),
options.enhanced,
stop,
)
.map_err(stop_to_error)?;
fully_optimized &= fo;
totalcost += calculate_block_size_auto_type_with_scratch(
&store,
0,
store.size(),
options.enhanced,
&mut scratch,
);
debug_assert!(instart == inend || store.size() > 0);
for (&litlens, &pos) in store.litlens.iter().zip(store.pos.iter()) {
lz77.append_store_item(litlens, pos as usize);
}
if i < nranges - 1 {
splitpoints.push(lz77.size());
}
}
}
if npoints > 1 {
let mut splitpoints2 = Vec::with_capacity(splitpoints_uncompressed.len());
let mut totalcost2 = 0.0;
blocksplit_lz77(&lz77, options.maximum_block_splits, &mut splitpoints2);
let mut last = 0;
for &item in &splitpoints2 {
totalcost2 += calculate_block_size_auto_type_with_scratch(
&lz77,
last,
item,
options.enhanced,
&mut scratch,
);
last = item;
}
totalcost2 += calculate_block_size_auto_type_with_scratch(
&lz77,
last,
lz77.size(),
options.enhanced,
&mut scratch,
);
if totalcost2 < totalcost {
splitpoints = splitpoints2;
}
}
add_all_blocks(
&splitpoints,
&lz77,
final_block,
in_data,
options.enhanced,
bitwise_writer,
)?;
Ok(fully_optimized)
}
fn add_non_compressed_block<W: Write>(
final_block: bool,
in_data: &[u8],
instart: usize,
inend: usize,
bitwise_writer: &mut BitwiseWriter<W>,
) -> Result<(), Error> {
let in_data = &in_data[instart..inend];
let in_data_chunks = in_data.chunks(65535).size_hint().0;
for (chunk, is_final) in in_data
.chunks(65535)
.flag_last()
.chain(iter::once((&[][..], true)))
.take(if final_block {
cmp::max(in_data_chunks, 1)
} else {
in_data_chunks
})
{
let blocksize = chunk.len();
let nlen = !blocksize;
bitwise_writer.add_bit(u8::from(final_block && is_final))?;
bitwise_writer.add_bit(0)?;
bitwise_writer.add_bit(0)?;
bitwise_writer.finish_partial_bits()?;
bitwise_writer.add_byte((blocksize % 256) as u8)?;
bitwise_writer.add_byte(((blocksize / 256) % 256) as u8)?;
bitwise_writer.add_byte((nlen % 256) as u8)?;
bitwise_writer.add_byte(((nlen / 256) % 256) as u8)?;
bitwise_writer.add_bytes(chunk)?;
}
Ok(())
}
struct BitwiseWriter<W> {
buf: u64,
bp: u32,
len: usize,
out: W,
}
impl<W: Write> BitwiseWriter<W> {
const fn new(out: W) -> Self {
Self {
buf: 0,
bp: 0,
len: 0,
out,
}
}
const fn bytes_written(&self) -> usize {
self.len + if self.bp > 0 { 1 } else { 0 }
}
fn add_byte(&mut self, byte: u8) -> Result<(), Error> {
self.add_bytes(&[byte])
}
fn add_bytes(&mut self, bytes: &[u8]) -> Result<(), Error> {
self.len += bytes.len();
self.out.write_all(bytes)
}
fn add_bit(&mut self, bit: u8) -> Result<(), Error> {
self.add_bits(u32::from(bit), 1)
}
fn add_bits(&mut self, symbol: u32, length: u32) -> Result<(), Error> {
self.buf |= (symbol as u64) << self.bp;
self.bp += length;
self.flush_bits()
}
fn add_huffman_bits(&mut self, symbol: u32, length: u32) -> Result<(), Error> {
let reversed = symbol.reverse_bits() >> (32 - length);
self.add_bits(reversed, length)
}
fn flush_bits(&mut self) -> Result<(), Error> {
let full_bytes = (self.bp / 8) as usize;
if full_bytes > 0 {
let bytes = self.buf.to_le_bytes();
self.out.write_all(&bytes[..full_bytes])?;
self.len += full_bytes;
let shift = (full_bytes as u32) * 8;
self.buf >>= shift;
self.bp -= shift;
}
Ok(())
}
fn finish_partial_bits(&mut self) -> Result<(), Error> {
if self.bp != 0 {
let byte = (self.buf & 0xFF) as u8;
self.add_bytes(&[byte])?;
self.buf = 0;
self.bp = 0;
}
Ok(())
}
}
fn set_counts_to_count(counts: &mut [usize], count: usize, i: usize, stride: usize) {
for c in &mut counts[(i - stride)..i] {
*c = count;
}
}
#[cfg(test)]
mod test {
use miniz_oxide::inflate;
use super::*;
#[test]
fn test_set_counts_to_count() {
let mut counts = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let count = 100;
let i = 8;
let stride = 5;
set_counts_to_count(&mut counts, count, i, stride);
assert_eq!(counts, vec![0, 1, 2, 100, 100, 100, 100, 100, 8, 9]);
}
#[test]
fn weird_encoder_write_size_combinations_works() {
let mut compressed_data = vec![];
let default_options = Options::default();
let mut encoder = DeflateEncoder::new(default_options, &mut compressed_data);
encoder.write_all(&[0]).unwrap();
encoder.write_all(&[]).unwrap();
encoder.write_all(&[1, 2]).unwrap();
encoder.write_all(&[]).unwrap();
encoder.write_all(&[]).unwrap();
encoder.write_all(&[3]).unwrap();
encoder.write_all(&[4]).unwrap();
encoder.finish().unwrap();
let decompressed_data = inflate::decompress_to_vec(&compressed_data)
.expect("Could not inflate compressed stream");
assert_eq!(
&[0, 1, 2, 3, 4][..],
decompressed_data,
"Decompressed data should match input data"
);
}
}