/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 | } |