Coverage Report

Created: 2026-11-11 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/zopfli-0.8.4/src/blocksplitter.rs
Line
Count
Source
1
use alloc::vec::Vec;
2
3
#[cfg(feature = "std")]
4
use log::{debug, log_enabled};
5
6
use crate::{cache::NoCache, deflate::calculate_block_size_auto_type, lz77::Lz77Store};
7
8
/// Finds minimum of function `f(i)` where `i` is of type `usize`, `f(i)` is of type
9
/// `f64`, `i` is in range `start-end` (excluding `end`).
10
/// Returns the index to the minimum and the minimum value.
11
0
fn find_minimum<F: Fn(usize) -> f64>(f: F, start: usize, end: usize) -> (usize, f64) {
12
0
    if end - start < 1024 {
13
0
        let mut best = f64::INFINITY;
14
0
        let mut result = start;
15
0
        for i in start..end {
16
0
            let v = f(i);
17
0
            if v < best {
18
0
                best = v;
19
0
                result = i;
20
0
            }
21
        }
22
0
        (result, best)
23
    } else {
24
        /* Try to find minimum faster by recursively checking multiple points. */
25
0
        let mut start = start;
26
0
        let mut end = end;
27
        const NUM: usize = 9; /* Good value: 9. ?!?!?!?! */
28
0
        let mut p = [0; NUM];
29
0
        let mut vp = [0.0; NUM];
30
0
        let mut lastbest = f64::INFINITY;
31
0
        let mut pos = start;
32
33
0
        while end - start > NUM {
34
0
            let mut besti = 0;
35
0
            let mut best = f64::INFINITY;
36
0
            let multiplier = (end - start) / (NUM + 1);
37
0
            for i in 0..NUM {
38
0
                p[i] = start + (i + 1) * multiplier;
39
0
                vp[i] = f(p[i]);
40
0
                if vp[i] < best {
41
0
                    best = vp[i];
42
0
                    besti = i;
43
0
                }
44
            }
45
0
            if best > lastbest {
46
0
                break;
47
0
            }
48
49
0
            start = if besti == 0 { start } else { p[besti - 1] };
50
0
            end = if besti == NUM - 1 { end } else { p[besti + 1] };
51
52
0
            pos = p[besti];
53
0
            lastbest = best;
54
        }
55
0
        (pos, lastbest)
56
    }
57
0
}
58
59
/// Returns estimated cost of a block in bits.  It includes the size to encode the
60
/// tree and the size to encode all literal, length and distance symbols and their
61
/// extra bits.
62
///
63
/// litlens: lz77 lit/lengths
64
/// dists: ll77 distances
65
/// lstart: start of block
66
/// lend: end of block (not inclusive)
67
0
fn estimate_cost(lz77: &Lz77Store, lstart: usize, lend: usize) -> f64 {
68
0
    calculate_block_size_auto_type(lz77, lstart, lend)
69
0
}
70
71
/// Finds next block to try to split, the largest of the available ones.
72
/// The largest is chosen to make sure that if only a limited amount of blocks is
73
/// requested, their sizes are spread evenly.
74
/// lz77size: the size of the LL77 data, which is the size of the done array here.
75
/// done: array indicating which blocks starting at that position are no longer
76
///     splittable (splitting them increases rather than decreases cost).
77
/// splitpoints: the splitpoints found so far.
78
/// npoints: the amount of splitpoints found so far.
79
/// lstart: output variable, giving start of block.
80
/// lend: output variable, giving end of block.
81
/// returns 1 if a block was found, 0 if no block found (all are done).
82
0
fn find_largest_splittable_block(
83
0
    lz77size: usize,
84
0
    done: &[u8],
85
0
    splitpoints: &[usize],
86
0
) -> Option<(usize, usize)> {
87
0
    let mut longest = 0;
88
0
    let mut found = None;
89
90
0
    let mut last = 0;
91
92
0
    for &item in splitpoints {
93
0
        if done[last] == 0 && item - last > longest {
94
0
            found = Some((last, item));
95
0
            longest = item - last;
96
0
        }
97
0
        last = item;
98
    }
99
100
0
    let end = lz77size - 1;
101
0
    if done[last] == 0 && end - last > longest {
102
0
        found = Some((last, end));
103
0
    }
104
105
0
    found
106
0
}
107
108
/// Prints the block split points as decimal and hex values in the terminal.
109
#[inline]
110
0
fn print_block_split_points(lz77: &Lz77Store, lz77splitpoints: &[usize]) {
111
0
    if !log_enabled!(log::Level::Debug) {
112
0
        return;
113
0
    }
114
115
0
    let nlz77points = lz77splitpoints.len();
116
0
    let mut splitpoints = Vec::with_capacity(nlz77points);
117
118
    /* The input is given as lz77 indices, but we want to see the uncompressed
119
    index values. */
120
0
    let mut pos = 0;
121
0
    if nlz77points > 0 {
122
0
        for (i, item) in lz77.litlens.iter().enumerate() {
123
0
            let length = item.size();
124
0
            if lz77splitpoints[splitpoints.len()] == i {
125
0
                splitpoints.push(pos);
126
0
                if splitpoints.len() == nlz77points {
127
0
                    break;
128
0
                }
129
0
            }
130
0
            pos += length;
131
        }
132
0
    }
133
0
    debug_assert_eq!(splitpoints.len(), nlz77points);
134
135
0
    debug!(
136
0
        "block split points: {} (hex: {})",
137
0
        splitpoints
138
0
            .iter()
139
0
            .map(|&sp| format!("{sp}"))
140
0
            .collect::<Vec<_>>()
141
0
            .join(" "),
142
0
        splitpoints
143
0
            .iter()
144
0
            .map(|&sp| format!("{sp:x}"))
145
0
            .collect::<Vec<_>>()
146
0
            .join(" ")
147
    );
148
0
}
149
150
/// Does blocksplitting on LZ77 data.
151
/// The output splitpoints are indices in the LZ77 data.
152
/// maxblocks: set a limit to the amount of blocks. Set to 0 to mean no limit.
153
0
pub fn blocksplit_lz77(lz77: &Lz77Store, maxblocks: u16, splitpoints: &mut Vec<usize>) {
154
0
    if lz77.size() < 10 {
155
0
        return; /* This code fails on tiny files. */
156
0
    }
157
158
0
    let mut numblocks = 1u32;
159
0
    let mut done = vec![0; lz77.size()];
160
0
    let mut lstart = 0;
161
0
    let mut lend = lz77.size();
162
163
0
    while maxblocks != 0 && numblocks < u32::from(maxblocks) {
164
0
        debug_assert!(lstart < lend);
165
0
        let find_minimum_result = find_minimum(
166
0
            |i| estimate_cost(lz77, lstart, i) + estimate_cost(lz77, i, lend),
167
0
            lstart + 1,
168
0
            lend,
169
        );
170
0
        let llpos = find_minimum_result.0;
171
0
        let splitcost = find_minimum_result.1;
172
173
0
        debug_assert!(llpos > lstart);
174
0
        debug_assert!(llpos < lend);
175
176
0
        let origcost = estimate_cost(lz77, lstart, lend);
177
178
0
        if splitcost > origcost || llpos == lstart + 1 || llpos == lend {
179
0
            done[lstart] = 1;
180
0
        } else {
181
0
            splitpoints.push(llpos);
182
0
            splitpoints.sort_unstable();
183
0
            numblocks += 1;
184
0
        }
185
186
        // If `find_largest_splittable_block` returns `None`, no further split will
187
        // likely reduce compression.
188
0
        let is_finished = find_largest_splittable_block(lz77.size(), &done, splitpoints)
189
0
            .is_none_or(|(start, end)| {
190
0
                lstart = start;
191
0
                lend = end;
192
0
                lend - lstart < 10
193
0
            });
194
195
0
        if is_finished {
196
0
            break;
197
0
        }
198
    }
199
200
0
    print_block_split_points(lz77, splitpoints);
201
0
}
202
203
/// Does blocksplitting on uncompressed data.
204
/// The output splitpoints are indices in the uncompressed bytes.
205
///
206
/// options: general program options.
207
/// in: uncompressed input data
208
/// instart: where to start splitting
209
/// inend: where to end splitting (not inclusive)
210
/// maxblocks: maximum amount of blocks to split into, or 0 for no limit
211
/// splitpoints: dynamic array to put the resulting split point coordinates into.
212
///   The coordinates are indices in the input array.
213
/// npoints: pointer to amount of splitpoints, for the dynamic array. The amount of
214
///   blocks is the amount of splitpoitns + 1.
215
0
pub fn blocksplit(
216
0
    in_data: &[u8],
217
0
    instart: usize,
218
0
    inend: usize,
219
0
    maxblocks: u16,
220
0
    splitpoints: &mut Vec<usize>,
221
0
) {
222
0
    splitpoints.clear();
223
0
    let mut store = Lz77Store::new();
224
225
    /* Unintuitively, Using a simple LZ77 method here instead of lz77_optimal
226
    results in better blocks. */
227
0
    {
228
0
        store.greedy(&mut NoCache, in_data, instart, inend);
229
0
    }
230
231
0
    let mut lz77splitpoints = Vec::with_capacity(maxblocks as usize);
232
0
    blocksplit_lz77(&store, maxblocks, &mut lz77splitpoints);
233
234
0
    let nlz77points = lz77splitpoints.len();
235
236
    /* Convert LZ77 positions to positions in the uncompressed input. */
237
0
    let mut pos = instart;
238
0
    if nlz77points > 0 {
239
0
        for (i, item) in store.litlens.iter().enumerate() {
240
0
            let length = item.size();
241
0
            if lz77splitpoints[splitpoints.len()] == i {
242
0
                splitpoints.push(pos);
243
0
                if splitpoints.len() == nlz77points {
244
0
                    break;
245
0
                }
246
0
            }
247
0
            pos += length;
248
        }
249
0
    }
250
0
    debug_assert_eq!(splitpoints.len(), nlz77points);
251
0
}