/rust/registry/src/index.crates.io-1949cf8c6b5b557f/png-0.18.1/src/adam7.rs
Line | Count | Source |
1 | | //! Utility functions related to handling of |
2 | | //! [the Adam7 algorithm](https://en.wikipedia.org/wiki/Adam7_algorithm). |
3 | | use core::ops::RangeTo; |
4 | | |
5 | | #[cfg(doc)] |
6 | | use crate::decoder::Reader; |
7 | | |
8 | | /// Describes which stage of |
9 | | /// [the Adam7 algorithm](https://en.wikipedia.org/wiki/Adam7_algorithm) |
10 | | /// applies to a decoded row. |
11 | | /// |
12 | | /// See also [`Reader::next_interlaced_row`]. |
13 | | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
14 | | pub struct Adam7Info { |
15 | | /// The Adam7 pass number, 1..7. |
16 | | pub(crate) pass: u8, |
17 | | /// The index of the line within this pass. |
18 | | pub(crate) line: u32, |
19 | | /// The original pixel count. |
20 | | pub(crate) width: u32, |
21 | | /// How many Adam7 samples there are. |
22 | | pub(crate) samples: u32, |
23 | | } |
24 | | |
25 | | /// The index of a bit in the image buffer. |
26 | | /// |
27 | | /// We do not use a pure `usize` to avoid overflows on 32-bit targets. |
28 | | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
29 | | struct BitPostion { |
30 | | byte: usize, |
31 | | /// [0..8) |
32 | | bit: u8, |
33 | | } |
34 | | |
35 | | impl Adam7Info { |
36 | | /// Creates a new `Adam7Info`. May panic if the arguments are out of range (e.g. if `pass` is |
37 | | /// 0 or greater than 8). |
38 | | /// |
39 | | /// * `pass` corresponds to a pass of the |
40 | | /// [the Adam7 algorithm](https://en.wikipedia.org/wiki/Adam7_algorithm) |
41 | | /// * `line` is the number of a line within a pass (starting with 0). For example, |
42 | | /// in an image of height 8, `line` can be beteween `0..4` in the 7th `pass` |
43 | | /// (those 4 interlaced rows correspond to 2nd, 4th, 6th, and 8th row of the full image). |
44 | | /// * `width` describes how many pixels are in a full row of the image. The bytes in each |
45 | | /// passline of the Adam7 are calculated from this number. |
46 | | /// |
47 | | /// Note that in typical usage, `Adam7Info`s are returned by [`Reader::next_interlaced_row`] |
48 | | /// and there is no need to create them by calling `Adam7Info::new`. `Adam7Info::new` is |
49 | | /// nevertheless exposed as a public API, because it helps to provide self-contained example |
50 | | /// usage of [`expand_interlaced_row`](crate::expand_interlaced_row). |
51 | 0 | pub fn new(pass: u8, line: u32, width: u32) -> Self { |
52 | 0 | assert!((1..=7).contains(&pass)); |
53 | 0 | assert!(width > 0); |
54 | | |
55 | 0 | let info = PassConstants::PASSES[pass as usize - 1]; |
56 | 0 | let samples = info.count_samples(width); |
57 | | |
58 | 0 | Self { |
59 | 0 | pass, |
60 | 0 | line, |
61 | 0 | width, |
62 | 0 | samples, |
63 | 0 | } |
64 | 0 | } |
65 | | |
66 | 20.3M | fn pass_constants(&self) -> PassConstants { |
67 | 20.3M | PassConstants::PASSES[self.pass as usize - 1] |
68 | 20.3M | } |
69 | | |
70 | | /// How often to repeat a pixel. |
71 | 0 | fn splat_pixel_repeat(self, idx: usize) -> u8 { |
72 | 0 | let pass = self.pass_constants(); |
73 | 0 | let x_pixel = idx as u32 * u32::from(pass.x_sampling) + u32::from(pass.x_offset); |
74 | 0 | (self.width - x_pixel).min(pass.splat_x_repeat().into()) as u8 |
75 | 0 | } |
76 | | |
77 | 0 | fn splat_line_repeat(self, height: u32) -> u8 { |
78 | 0 | let pass = self.pass_constants(); |
79 | 0 | let y_line = self.line * u32::from(pass.y_sampling) + u32::from(pass.y_offset); |
80 | 0 | (height - y_line).min(pass.splat_y_repeat().into()) as u8 |
81 | 0 | } |
82 | | } |
83 | | |
84 | | #[derive(Clone, Copy)] |
85 | | pub(crate) struct PassConstants { |
86 | | x_sampling: u8, |
87 | | x_offset: u8, |
88 | | y_sampling: u8, |
89 | | y_offset: u8, |
90 | | } |
91 | | |
92 | | impl PassConstants { |
93 | 0 | const fn splat_x_repeat(self) -> u8 { |
94 | 0 | self.x_sampling - self.x_offset |
95 | 0 | } |
96 | | |
97 | 0 | const fn splat_y_repeat(self) -> u8 { |
98 | 0 | self.y_sampling - self.y_offset |
99 | 0 | } |
100 | | |
101 | 31.8k | pub fn count_samples(self, width: u32) -> u32 { |
102 | 31.8k | width |
103 | 31.8k | .saturating_sub(u32::from(self.x_offset)) |
104 | 31.8k | .div_ceil(u32::from(self.x_sampling)) |
105 | 31.8k | } |
106 | | |
107 | 31.8k | pub fn count_lines(self, height: u32) -> u32 { |
108 | 31.8k | height |
109 | 31.8k | .saturating_sub(u32::from(self.y_offset)) |
110 | 31.8k | .div_ceil(u32::from(self.y_sampling)) |
111 | 31.8k | } |
112 | | |
113 | | /// The constants associated with each of the 7 passes. Note that it is 0-indexed while the |
114 | | /// pass number (as per specification) is 1-indexed. |
115 | | pub const PASSES: [Self; 7] = { |
116 | | // Shortens the constructor for readability, retains clear argument order below. |
117 | 0 | const fn new(x_sampling: u8, x_offset: u8, y_sampling: u8, y_offset: u8) -> PassConstants { |
118 | 0 | PassConstants { |
119 | 0 | x_sampling, |
120 | 0 | x_offset, |
121 | 0 | y_sampling, |
122 | 0 | y_offset, |
123 | 0 | } |
124 | 0 | } |
125 | | |
126 | | [ |
127 | | new(8, 0, 8, 0), |
128 | | new(8, 4, 8, 0), |
129 | | new(4, 0, 8, 4), |
130 | | new(4, 2, 4, 0), |
131 | | new(2, 0, 4, 2), |
132 | | new(2, 1, 2, 0), |
133 | | new(1, 0, 2, 1), |
134 | | ] |
135 | | }; |
136 | | } |
137 | | |
138 | | /// This iterator iterates over the different passes of an image Adam7 encoded |
139 | | /// PNG image |
140 | | /// The pattern is: |
141 | | /// 16462646 |
142 | | /// 77777777 |
143 | | /// 56565656 |
144 | | /// 77777777 |
145 | | /// 36463646 |
146 | | /// 77777777 |
147 | | /// 56565656 |
148 | | /// 77777777 |
149 | | /// |
150 | | #[derive(Clone)] |
151 | | pub(crate) struct Adam7Iterator { |
152 | | line: u32, |
153 | | lines: u32, |
154 | | line_width: u32, |
155 | | current_pass: u8, |
156 | | width: u32, |
157 | | height: u32, |
158 | | } |
159 | | |
160 | | impl Adam7Iterator { |
161 | 3.22k | pub fn new(width: u32, height: u32) -> Adam7Iterator { |
162 | 3.22k | let mut this = Adam7Iterator { |
163 | 3.22k | line: 0, |
164 | 3.22k | lines: 0, |
165 | 3.22k | line_width: 0, |
166 | 3.22k | current_pass: 1, |
167 | 3.22k | width, |
168 | 3.22k | height, |
169 | 3.22k | }; |
170 | 3.22k | this.init_pass(); |
171 | 3.22k | this |
172 | 3.22k | } |
173 | | |
174 | | /// Calculates the bounds of the current pass |
175 | 9.25k | fn init_pass(&mut self) { |
176 | 9.25k | let info = PassConstants::PASSES[self.current_pass as usize - 1]; |
177 | 9.25k | self.line_width = info.count_samples(self.width); |
178 | 9.25k | self.lines = info.count_lines(self.height); |
179 | 9.25k | self.line = 0; |
180 | 9.25k | } |
181 | | } |
182 | | |
183 | | /// Iterates over `Adam7Info`s. |
184 | | impl Iterator for Adam7Iterator { |
185 | | type Item = Adam7Info; |
186 | 20.3M | fn next(&mut self) -> Option<Self::Item> { |
187 | 20.3M | if self.line < self.lines && self.line_width > 0 { |
188 | 20.3M | let this_line = self.line; |
189 | 20.3M | self.line += 1; |
190 | 20.3M | Some(Adam7Info { |
191 | 20.3M | pass: self.current_pass, |
192 | 20.3M | line: this_line, |
193 | 20.3M | width: self.width, |
194 | 20.3M | samples: self.line_width, |
195 | 20.3M | }) |
196 | 6.25k | } else if self.current_pass < 7 { |
197 | 6.03k | self.current_pass += 1; |
198 | 6.03k | self.init_pass(); |
199 | 6.03k | self.next() |
200 | | } else { |
201 | 224 | None |
202 | | } |
203 | 20.3M | } |
204 | | } |
205 | | |
206 | | /// The algorithm to use when progressively filling pixel data from Adam7 interlaced passes. |
207 | | /// |
208 | | /// Adam7 interlacing is a technique optionally used in PNG by which only a sub-sample of pixel |
209 | | /// data is encoded in the beginning of the image data chunks, followed by progressively larger |
210 | | /// subsets of the data in subsequent passes. Therefore a 'rough image' is available after ust a |
211 | | /// very tiny fraction of the data has been read which can be advantageous for loading an image |
212 | | /// from a slow IO medium while optimizing time-to-first-meaningful-paint and then replacing the |
213 | | /// presented data as it is streamed in. |
214 | | /// |
215 | | /// There are trade-offs to make here. The strictly necessary requirement for an implementation is |
216 | | /// that the exact image is recovered after all passes are applied. However the intermediate states |
217 | | /// of the output are left to the implementation, as long as it follows the restriction of |
218 | | /// resulting in the intended image when all passes have been applied. |
219 | | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] |
220 | | #[non_exhaustive] |
221 | | pub enum Adam7Variant { |
222 | | /// This is the adam7 de-interlace we do by default. Only pixels related to the pass are |
223 | | /// written. The output buffer should not be directly used for presentation until all passes |
224 | | /// are complete. At least the invalid pixels in the buffer should be masked. However, this |
225 | | /// performs the least amount of writes and is optimal when you're only reading full frames. |
226 | | /// |
227 | | /// This corresponds to [`expand_interlaced_row`]. |
228 | | /// |
229 | | /// [`expand_interlaced_row`]: crate::expand_interlaced_row. |
230 | | #[default] |
231 | | Sparse, |
232 | | /// A variant of the Adam7 de-interlace that ensures that all pixels are initialized after each |
233 | | /// pass, and are progressively refined towards the final image. Performs more writes than the |
234 | | /// other variant as some pixels are touched repeatedly, but ensures the buffer can be used as |
235 | | /// directly as possible for presentation. |
236 | | /// |
237 | | /// This corresponds to [`splat_interlaced_row`]. |
238 | | /// |
239 | | /// [`splat_interlaced_row`]: crate::splat_interlaced_row |
240 | | Splat, |
241 | | } |
242 | | |
243 | 0 | fn subbyte_values<const N: usize>( |
244 | 0 | scanline: &[u8], |
245 | 0 | bit_pos: [u8; N], |
246 | 0 | mask: u8, |
247 | 0 | ) -> impl Iterator<Item = u8> + '_ { |
248 | 0 | (scanline.iter().copied()).flat_map(move |value| bit_pos.map(|n| (value >> n) & mask)) Unexecuted instantiation: png::adam7::subbyte_values::<2>::{closure#0}Unexecuted instantiation: png::adam7::subbyte_values::<4>::{closure#0}Unexecuted instantiation: png::adam7::subbyte_values::<8>::{closure#0}Unexecuted instantiation: png::adam7::subbyte_values::<2>::{closure#0}::{closure#0}Unexecuted instantiation: png::adam7::subbyte_values::<4>::{closure#0}::{closure#0}Unexecuted instantiation: png::adam7::subbyte_values::<8>::{closure#0}::{closure#0} |
249 | 0 | } Unexecuted instantiation: png::adam7::subbyte_values::<2> Unexecuted instantiation: png::adam7::subbyte_values::<4> Unexecuted instantiation: png::adam7::subbyte_values::<8> |
250 | | |
251 | | /// Given `row_stride`, interlace `info`, and bits-per-pixel, produce an iterator of bit positions |
252 | | /// of pixels to copy from the input scanline to the image buffer. The positions are expressed as |
253 | | /// bit offsets from position (0,0) in the frame that is currently being decoded. |
254 | | /// |
255 | | /// This should only be used with `bits_pp < 8`. |
256 | 0 | fn expand_adam7_bits( |
257 | 0 | row_stride_in_bytes: usize, |
258 | 0 | info: &Adam7Info, |
259 | 0 | bits_pp: u8, |
260 | 0 | ) -> impl Iterator<Item = BitPostion> { |
261 | 0 | debug_assert!(bits_pp == 1 || bits_pp == 2 || bits_pp == 4); |
262 | 0 | let (line_mul, line_off, samp_mul, samp_off) = { |
263 | 0 | let constants = info.pass_constants(); |
264 | 0 | ( |
265 | 0 | // Convert each to their respectively required type from u8. |
266 | 0 | usize::from(constants.y_sampling), |
267 | 0 | usize::from(constants.y_offset), |
268 | 0 | u64::from(constants.x_sampling), |
269 | 0 | u64::from(constants.x_offset), |
270 | 0 | ) |
271 | 0 | }; |
272 | | |
273 | | // the equivalent line number in progressive scan |
274 | 0 | let prog_line = line_mul * info.line as usize + line_off; |
275 | 0 | let byte_start = prog_line * row_stride_in_bytes; |
276 | | |
277 | | // In contrast to `subbyte_values` we *must* be precise with our length here. |
278 | 0 | (0..u64::from(info.samples)) |
279 | | // Bounded by u32::MAX * 8 * 4 + 16 so does not overflow `u64`. |
280 | 0 | .map(move |i| (i * samp_mul + samp_off) * u64::from(bits_pp)) |
281 | 0 | .map(move |i| BitPostion { |
282 | | // Bounded by the buffer size which already exists. |
283 | 0 | byte: byte_start + (i / 8) as usize, |
284 | 0 | bit: i as u8 % 8, |
285 | 0 | }) |
286 | 0 | } |
287 | | |
288 | 20.3M | fn expand_adam7_bytes( |
289 | 20.3M | row_stride_in_bytes: usize, |
290 | 20.3M | info: &Adam7Info, |
291 | 20.3M | bytes_pp: u8, |
292 | 20.3M | ) -> impl Iterator<Item = usize> { |
293 | 20.3M | let (line_mul, line_off, samp_mul, samp_off) = { |
294 | 20.3M | let constants = info.pass_constants(); |
295 | 20.3M | ( |
296 | 20.3M | // Convert each to their respectively required type from u8. |
297 | 20.3M | usize::from(constants.y_sampling), |
298 | 20.3M | usize::from(constants.y_offset), |
299 | 20.3M | u64::from(constants.x_sampling), |
300 | 20.3M | u64::from(constants.x_offset), |
301 | 20.3M | ) |
302 | 20.3M | }; |
303 | | |
304 | | // the equivalent line number in progressive scan |
305 | 20.3M | let prog_line = line_mul * info.line as usize + line_off; |
306 | 20.3M | let byte_start = prog_line * row_stride_in_bytes; |
307 | | |
308 | 20.3M | (0..u64::from(info.samples)) |
309 | 1.45G | .map(move |i| (i * samp_mul + samp_off) * u64::from(bytes_pp)) |
310 | | // Bounded by the allocated buffer size so must fit in `usize` |
311 | 1.45G | .map(move |i| i as usize + byte_start) |
312 | 20.3M | } |
313 | | |
314 | | /// Copies pixels from `interlaced_row` into the right location in `img`. |
315 | | /// |
316 | | /// First bytes of `img` should belong to the top-left corner of the currently decoded frame. |
317 | | /// |
318 | | /// `img_row_stride` specifies an offset in bytes between subsequent rows of `img`. |
319 | | /// This can be the width of the current frame being decoded, but this is not required - a bigger |
320 | | /// stride may be useful if the frame being decoded is a sub-region of `img`. |
321 | | /// |
322 | | /// `interlaced_row` and `interlace_info` typically come from |
323 | | /// [`Reader::next_interlaced_row`], but this is not required. In particular, before |
324 | | /// calling `expand_interlaced_row` one may need to expand the decoded row, so that its format and |
325 | | /// `bits_per_pixel` matches that of `img`. Note that in initial Adam7 passes the `interlaced_row` |
326 | | /// may contain less pixels that the width of the frame being decoded (e.g. it contains only 1/8th |
327 | | /// of pixels in the initial pass). |
328 | | /// |
329 | | /// Example: |
330 | | /// |
331 | | /// ``` |
332 | | /// use png::{expand_interlaced_row, Adam7Info}; |
333 | | /// let info = Adam7Info::new(5, 0, 8); |
334 | | /// let mut img = vec![0; 8 * 8]; |
335 | | /// let row = vec![1, 2, 3, 4]; |
336 | | /// expand_interlaced_row(&mut img, 8, &row, &info, 8); |
337 | | /// assert_eq!(&img, &[ |
338 | | /// 0, 0, 0, 0, 0, 0, 0, 0, |
339 | | /// 0, 0, 0, 0, 0, 0, 0, 0, |
340 | | /// 1, 0, 2, 0, 3, 0, 4, 0, // <= this is where the 1st line of 5s appears |
341 | | /// 0, 0, 0, 0, 0, 0, 0, 0, // in the schematic drawing of the passes at |
342 | | /// 0, 0, 0, 0, 0, 0, 0, 0, // https://en.wikipedia.org/wiki/Adam7_algorithm |
343 | | /// 0, 0, 0, 0, 0, 0, 0, 0, |
344 | | /// 0, 0, 0, 0, 0, 0, 0, 0, |
345 | | /// 0, 0, 0, 0, 0, 0, 0, 0, |
346 | | /// ]); |
347 | | /// ``` |
348 | 20.3M | pub fn expand_pass( |
349 | 20.3M | img: &mut [u8], |
350 | 20.3M | img_row_stride: usize, |
351 | 20.3M | interlaced_row: &[u8], |
352 | 20.3M | interlace_info: &Adam7Info, |
353 | 20.3M | bits_per_pixel: u8, |
354 | 20.3M | ) { |
355 | 20.3M | match bits_per_pixel { |
356 | | // Note: for 1, 2, 4 multiple runs through the iteration will access the same byte in `img` |
357 | | // so we can not iterate over `&mut u8` values. A better strategy would write multiple bit |
358 | | // groups in one go. This would then also not be as bounds-check heavy? |
359 | | 1 => { |
360 | | const BIT_POS_1: [u8; 8] = [7, 6, 5, 4, 3, 2, 1, 0]; |
361 | 0 | let bit_indices = expand_adam7_bits(img_row_stride, interlace_info, 1); |
362 | 0 | for (pos, px) in bit_indices.zip(subbyte_values(interlaced_row, BIT_POS_1, 0b1)) { |
363 | 0 | let shift = 8 - bits_per_pixel - pos.bit; |
364 | 0 | img[pos.byte] |= px << shift; |
365 | 0 | } |
366 | | } |
367 | | 2 => { |
368 | | const BIT_POS_2: [u8; 4] = [6, 4, 2, 0]; |
369 | 0 | let bit_indices = expand_adam7_bits(img_row_stride, interlace_info, 2); |
370 | | |
371 | 0 | for (pos, px) in bit_indices.zip(subbyte_values(interlaced_row, BIT_POS_2, 0b11)) { |
372 | 0 | let shift = 8 - bits_per_pixel - pos.bit; |
373 | 0 | img[pos.byte] |= px << shift; |
374 | 0 | } |
375 | | } |
376 | | 4 => { |
377 | | const BIT_POS_4: [u8; 2] = [4, 0]; |
378 | 0 | let bit_indices = expand_adam7_bits(img_row_stride, interlace_info, 4); |
379 | | |
380 | 0 | for (pos, px) in bit_indices.zip(subbyte_values(interlaced_row, BIT_POS_4, 0b1111)) { |
381 | 0 | let shift = 8 - bits_per_pixel - pos.bit; |
382 | 0 | img[pos.byte] |= px << shift; |
383 | 0 | } |
384 | | } |
385 | | // While caught by the below loop, we special case this for codegen. The effects are |
386 | | // massive when the compiler uses the constant chunk size in particular for this case where |
387 | | // no more copy_from_slice is being issued by everything happens in the register alone. |
388 | | 8 => { |
389 | 19.8M | let byte_indices = expand_adam7_bytes(img_row_stride, interlace_info, 1); |
390 | | |
391 | 749M | for (bytepos, &px) in byte_indices.zip(interlaced_row) { |
392 | 749M | img[bytepos] = px; |
393 | 749M | } |
394 | | } |
395 | | 16 => { |
396 | 118k | let byte_indices = expand_adam7_bytes(img_row_stride, interlace_info, 2); |
397 | | |
398 | 278M | for (bytepos, px) in byte_indices.zip(interlaced_row.chunks(2)) { |
399 | 278M | img[bytepos..][..2].copy_from_slice(px); |
400 | 278M | } |
401 | | } |
402 | | _ => { |
403 | 390k | debug_assert!(bits_per_pixel % 8 == 0); |
404 | 390k | let bytes_pp = bits_per_pixel / 8; |
405 | 390k | let byte_indices = expand_adam7_bytes(img_row_stride, interlace_info, bytes_pp); |
406 | | |
407 | 423M | for (bytepos, px) in byte_indices.zip(interlaced_row.chunks(bytes_pp.into())) { |
408 | 423M | img[bytepos..][..px.len()].copy_from_slice(px); |
409 | 423M | } |
410 | | } |
411 | | } |
412 | 20.3M | } |
413 | | |
414 | | /// Expand pass, but also ensure that after each pass the whole image has been initialized up to |
415 | | /// the data available. In constrast to `expand_pass` there are no holes left in the image. |
416 | | /// |
417 | | /// For instance, consider the first pass which is an eighth subsampling of the original image. |
418 | | /// Here's a side by-side of pixel data written from each of the two algorithms: |
419 | | /// |
420 | | /// ```text |
421 | | /// normal: splat: |
422 | | /// 1------- 11111111 |
423 | | /// -------- 11111111 |
424 | | /// -------- 11111111 |
425 | | /// -------- 11111111 |
426 | | /// -------- 11111111 |
427 | | /// -------- 11111111 |
428 | | /// -------- 11111111 |
429 | | /// ``` |
430 | | /// |
431 | | /// Data written in previous passes must not be modified. We 'weave' the data of passes and repeat |
432 | | /// them in the neighbouring pixels until their subsampling alignment. For details, see the |
433 | | /// `x_repeat` and `y_repeat` data. Thus the 4th pass would look like this: |
434 | | /// |
435 | | /// ```text |
436 | | /// normal: splat: |
437 | | /// --4---4- --44--44 |
438 | | /// -------- --44--44 |
439 | | /// -------- --44--44 |
440 | | /// --4---4- --44--44 |
441 | | /// -------- --44--44 |
442 | | /// -------- --44--44 |
443 | | /// -------- --44--44 |
444 | | /// ``` |
445 | | /// |
446 | 0 | pub fn expand_pass_splat( |
447 | 0 | img: &mut [u8], |
448 | 0 | img_row_stride: usize, |
449 | 0 | interlaced_row: &[u8], |
450 | 0 | interlace_info: &Adam7Info, |
451 | 0 | bits_per_pixel: u8, |
452 | 0 | ) { |
453 | 0 | fn expand_bits_to_img( |
454 | 0 | img: &mut [u8], |
455 | 0 | px: u8, |
456 | 0 | mut pos: BitPostion, |
457 | 0 | repeat: RangeTo<u8>, |
458 | 0 | bpp: u8, |
459 | 0 | ) { |
460 | 0 | let (mut into, mut tail) = img[pos.byte..].split_first_mut().unwrap(); |
461 | 0 | let mask = (1u8 << bpp) - 1; |
462 | | |
463 | 0 | for _ in 0..repeat.end { |
464 | 0 | if pos.bit >= 8 { |
465 | 0 | pos.byte += 1; |
466 | 0 | pos.bit -= 8; |
467 | 0 |
|
468 | 0 | (into, tail) = tail.split_first_mut().unwrap(); |
469 | 0 | } |
470 | | |
471 | 0 | let shift = 8 - bpp - pos.bit; |
472 | | // Preserve all other bits, but be prepared for existing bits |
473 | 0 | let pre = (*into >> shift) & mask; |
474 | 0 | *into ^= (pre ^ px) << shift; |
475 | | |
476 | 0 | pos.bit += bpp; |
477 | | } |
478 | 0 | } |
479 | | |
480 | 0 | let height = (img.len() / img_row_stride) as u32; |
481 | 0 | let y_repeat = interlace_info.splat_line_repeat(height); |
482 | 0 | debug_assert!(y_repeat > 0); |
483 | | |
484 | 0 | match bits_per_pixel { |
485 | | // Note: for 1, 2, 4 multiple runs through the iteration will access the same byte in `img` |
486 | | // so we can not iterate over `&mut u8` values. A better strategy would write multiple bit |
487 | | // groups in one go. This would then also not be as bounds-check heavy? |
488 | | 1 => { |
489 | | const BIT_POS_1: [u8; 8] = [7, 6, 5, 4, 3, 2, 1, 0]; |
490 | | |
491 | 0 | for offset in 0..y_repeat { |
492 | 0 | let bit_indices = expand_adam7_bits(img_row_stride, interlace_info, 1); |
493 | 0 | let line_offset = usize::from(offset) * img_row_stride; |
494 | | |
495 | 0 | for (idx, (mut pos, px)) in bit_indices |
496 | 0 | .zip(subbyte_values(interlaced_row, BIT_POS_1, 0b1)) |
497 | 0 | .enumerate() |
498 | | { |
499 | 0 | let x_repeat = interlace_info.splat_pixel_repeat(idx); |
500 | 0 | debug_assert!(x_repeat > 0); |
501 | 0 | pos.byte += line_offset; |
502 | 0 | expand_bits_to_img(img, px, pos, ..x_repeat, bits_per_pixel); |
503 | | } |
504 | | } |
505 | | } |
506 | | 2 => { |
507 | | const BIT_POS_2: [u8; 4] = [6, 4, 2, 0]; |
508 | | |
509 | 0 | for offset in 0..y_repeat { |
510 | 0 | let bit_indices = expand_adam7_bits(img_row_stride, interlace_info, 2); |
511 | 0 | let line_offset = usize::from(offset) * img_row_stride; |
512 | | |
513 | 0 | for (idx, (mut pos, px)) in bit_indices |
514 | 0 | .zip(subbyte_values(interlaced_row, BIT_POS_2, 0b11)) |
515 | 0 | .enumerate() |
516 | 0 | { |
517 | 0 | let x_repeat = interlace_info.splat_pixel_repeat(idx); |
518 | 0 | pos.byte += line_offset; |
519 | 0 | expand_bits_to_img(img, px, pos, ..x_repeat, bits_per_pixel); |
520 | 0 | } |
521 | | } |
522 | | } |
523 | | 4 => { |
524 | | const BIT_POS_4: [u8; 2] = [4, 0]; |
525 | | |
526 | 0 | for offset in 0..y_repeat { |
527 | 0 | let bit_indices = expand_adam7_bits(img_row_stride, interlace_info, 4); |
528 | 0 | let line_offset = usize::from(offset) * img_row_stride; |
529 | | |
530 | 0 | for (idx, (mut pos, px)) in bit_indices |
531 | 0 | .zip(subbyte_values(interlaced_row, BIT_POS_4, 0b1111)) |
532 | 0 | .enumerate() |
533 | 0 | { |
534 | 0 | let x_repeat = interlace_info.splat_pixel_repeat(idx); |
535 | 0 | pos.byte += line_offset; |
536 | 0 | expand_bits_to_img(img, px, pos, ..x_repeat, bits_per_pixel); |
537 | 0 | } |
538 | | } |
539 | | } |
540 | | // While caught by the below loop, we special case this for codegen. The effects are |
541 | | // massive when the compiler uses the constant chunk size in particular for this case where |
542 | | // no more copy_from_slice is being issued by everything happens in the register alone. |
543 | | 8 => { |
544 | 0 | for offset in 0..y_repeat { |
545 | 0 | let byte_indices = expand_adam7_bytes(img_row_stride, interlace_info, 1); |
546 | 0 | let line_offset = usize::from(offset) * img_row_stride; |
547 | | |
548 | 0 | for (idx, (bytepos, px)) in byte_indices.zip(interlaced_row).enumerate() { |
549 | 0 | let x_repeat = usize::from(interlace_info.splat_pixel_repeat(idx)); |
550 | 0 | debug_assert!(x_repeat > 0); |
551 | 0 | img[line_offset + bytepos..][..x_repeat].fill(*px); |
552 | | } |
553 | | } |
554 | | } |
555 | | _ => { |
556 | 0 | debug_assert!(bits_per_pixel % 8 == 0); |
557 | 0 | let bytes = bits_per_pixel / 8; |
558 | 0 | let chunk = usize::from(bytes); |
559 | | |
560 | 0 | for offset in 0..y_repeat { |
561 | 0 | let byte_indices = expand_adam7_bytes(img_row_stride, interlace_info, bytes); |
562 | 0 | let line_offset = usize::from(offset) * img_row_stride; |
563 | | |
564 | 0 | for (idx, (bytepos, px)) in byte_indices |
565 | 0 | .zip(interlaced_row.chunks_exact(chunk)) |
566 | 0 | .enumerate() |
567 | | { |
568 | 0 | let x_repeat = usize::from(interlace_info.splat_pixel_repeat(idx)); |
569 | 0 | let target = &mut img[line_offset + bytepos..][..chunk * x_repeat]; |
570 | | |
571 | 0 | for target in target.chunks_exact_mut(chunk) { |
572 | 0 | target.copy_from_slice(px); |
573 | 0 | } |
574 | | } |
575 | | } |
576 | | } |
577 | | } |
578 | 0 | } |
579 | | |
580 | | #[cfg(test)] |
581 | | mod tests { |
582 | | use super::*; |
583 | | |
584 | | #[test] |
585 | | fn test_adam7() { |
586 | | /* |
587 | | 1646 |
588 | | 7777 |
589 | | 5656 |
590 | | 7777 |
591 | | */ |
592 | | let it = Adam7Iterator::new(4, 4); |
593 | | let passes: Vec<_> = it.collect(); |
594 | | assert_eq!( |
595 | | &*passes, |
596 | | &[ |
597 | | Adam7Info { |
598 | | pass: 1, |
599 | | line: 0, |
600 | | samples: 1, |
601 | | width: 4, |
602 | | }, |
603 | | Adam7Info { |
604 | | pass: 4, |
605 | | line: 0, |
606 | | samples: 1, |
607 | | width: 4, |
608 | | }, |
609 | | Adam7Info { |
610 | | pass: 5, |
611 | | line: 0, |
612 | | samples: 2, |
613 | | width: 4, |
614 | | }, |
615 | | Adam7Info { |
616 | | pass: 6, |
617 | | line: 0, |
618 | | samples: 2, |
619 | | width: 4, |
620 | | }, |
621 | | Adam7Info { |
622 | | pass: 6, |
623 | | line: 1, |
624 | | samples: 2, |
625 | | width: 4, |
626 | | }, |
627 | | Adam7Info { |
628 | | pass: 7, |
629 | | line: 0, |
630 | | samples: 4, |
631 | | width: 4, |
632 | | }, |
633 | | Adam7Info { |
634 | | pass: 7, |
635 | | line: 1, |
636 | | samples: 4, |
637 | | width: 4, |
638 | | } |
639 | | ] |
640 | | ); |
641 | | } |
642 | | |
643 | | #[test] |
644 | | fn test_subbyte_pixels() { |
645 | | const BIT_POS_1: [u8; 8] = [7, 6, 5, 4, 3, 2, 1, 0]; |
646 | | |
647 | | let scanline = &[0b10101010, 0b10101010]; |
648 | | let pixels = subbyte_values(scanline, BIT_POS_1, 1).collect::<Vec<_>>(); |
649 | | |
650 | | assert_eq!(pixels.len(), 16); |
651 | | assert_eq!(pixels, [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0]); |
652 | | } |
653 | | |
654 | | #[test] |
655 | | fn test_expand_adam7_bits() { |
656 | | let width = 32; |
657 | | let bits_pp = 1; |
658 | | let stride = width / 8; |
659 | | let info = |
660 | | |pass, line, img_width| create_adam7_info_for_tests(pass, line as u32, img_width); |
661 | | |
662 | | let expected = |offset: usize, step: usize, count: usize| { |
663 | | (0..count) |
664 | | .map(move |i| step * i + offset) |
665 | | .map(|i| BitPostion { |
666 | | byte: i / 8, |
667 | | bit: (i % 8) as u8, |
668 | | }) |
669 | | .collect::<Vec<_>>() |
670 | | }; |
671 | | |
672 | | for line_no in 0..8 { |
673 | | let start = 8 * line_no * width; |
674 | | |
675 | | assert_eq!( |
676 | | expand_adam7_bits(stride, &info(1, line_no, width), bits_pp).collect::<Vec<_>>(), |
677 | | expected(start, 8, 4) |
678 | | ); |
679 | | |
680 | | let start = start + 4; |
681 | | |
682 | | assert_eq!( |
683 | | expand_adam7_bits(stride, &info(2, line_no, width), bits_pp).collect::<Vec<_>>(), |
684 | | expected(start, 8, 4) |
685 | | ); |
686 | | |
687 | | let start = (8 * line_no + 4) * width; |
688 | | |
689 | | assert_eq!( |
690 | | expand_adam7_bits(stride, &info(3, line_no, width), bits_pp).collect::<Vec<_>>(), |
691 | | expected(start, 4, 8) |
692 | | ); |
693 | | } |
694 | | |
695 | | for line_no in 0..16 { |
696 | | let start = 4 * line_no * width + 2; |
697 | | |
698 | | assert_eq!( |
699 | | expand_adam7_bits(stride, &info(4, line_no, width), bits_pp).collect::<Vec<_>>(), |
700 | | expected(start, 4, 8) |
701 | | ); |
702 | | |
703 | | let start = (4 * line_no + 2) * width; |
704 | | |
705 | | assert_eq!( |
706 | | expand_adam7_bits(stride, &info(5, line_no, width), bits_pp).collect::<Vec<_>>(), |
707 | | expected(start, 2, 16) |
708 | | ) |
709 | | } |
710 | | |
711 | | for line_no in 0..32 { |
712 | | let start = 2 * line_no * width + 1; |
713 | | |
714 | | assert_eq!( |
715 | | expand_adam7_bits(stride, &info(6, line_no, width), bits_pp).collect::<Vec<_>>(), |
716 | | expected(start, 2, 16), |
717 | | "line_no: {}", |
718 | | line_no |
719 | | ); |
720 | | |
721 | | let start = (2 * line_no + 1) * width; |
722 | | |
723 | | assert_eq!( |
724 | | expand_adam7_bits(stride, &info(7, line_no, width), bits_pp).collect::<Vec<_>>(), |
725 | | expected(start, 1, 32) |
726 | | ); |
727 | | } |
728 | | } |
729 | | |
730 | | #[test] |
731 | | fn test_expand_adam7_bits_independent_row_stride() { |
732 | | let pass = 1; |
733 | | let line_no = 1; |
734 | | let width = 32; |
735 | | let info = create_adam7_info_for_tests; |
736 | | |
737 | | { |
738 | | let stride = width; |
739 | | assert_eq!( |
740 | | expand_adam7_bytes(stride, &info(pass, line_no, width), 1).collect::<Vec<_>>(), |
741 | | [2048, 2112, 2176, 2240].map(|n| n / 8), |
742 | | ); |
743 | | } |
744 | | |
745 | | { |
746 | | let stride = 10000; |
747 | | assert_eq!( |
748 | | expand_adam7_bytes(stride, &info(pass, line_no, width), 1).collect::<Vec<_>>(), |
749 | | [640000, 640064, 640128, 640192].map(|n| n / 8), |
750 | | ); |
751 | | } |
752 | | } |
753 | | |
754 | | #[test] |
755 | | fn test_expand_pass_subbyte() { |
756 | | let mut img = [0u8; 8]; |
757 | | let width = 8; |
758 | | let stride = width / 8; |
759 | | let bits_pp = 1; |
760 | | let info = create_adam7_info_for_tests; |
761 | | |
762 | | expand_pass(&mut img, stride, &[0b10000000], &info(1, 0, width), bits_pp); |
763 | | assert_eq!(img, [0b10000000u8, 0, 0, 0, 0, 0, 0, 0]); |
764 | | |
765 | | expand_pass(&mut img, stride, &[0b10000000], &info(2, 0, width), bits_pp); |
766 | | assert_eq!(img, [0b10001000u8, 0, 0, 0, 0, 0, 0, 0]); |
767 | | |
768 | | expand_pass(&mut img, stride, &[0b11000000], &info(3, 0, width), bits_pp); |
769 | | assert_eq!(img, [0b10001000u8, 0, 0, 0, 0b10001000, 0, 0, 0]); |
770 | | |
771 | | expand_pass(&mut img, stride, &[0b11000000], &info(4, 0, width), bits_pp); |
772 | | assert_eq!(img, [0b10101010u8, 0, 0, 0, 0b10001000, 0, 0, 0]); |
773 | | |
774 | | expand_pass(&mut img, stride, &[0b11000000], &info(4, 1, width), bits_pp); |
775 | | assert_eq!(img, [0b10101010u8, 0, 0, 0, 0b10101010, 0, 0, 0]); |
776 | | |
777 | | expand_pass(&mut img, stride, &[0b11110000], &info(5, 0, width), bits_pp); |
778 | | assert_eq!(img, [0b10101010u8, 0, 0b10101010, 0, 0b10101010, 0, 0, 0]); |
779 | | |
780 | | expand_pass(&mut img, stride, &[0b11110000], &info(5, 1, width), bits_pp); |
781 | | assert_eq!( |
782 | | img, |
783 | | [0b10101010u8, 0, 0b10101010, 0, 0b10101010, 0, 0b10101010, 0] |
784 | | ); |
785 | | |
786 | | expand_pass(&mut img, stride, &[0b11110000], &info(6, 0, width), bits_pp); |
787 | | assert_eq!( |
788 | | img, |
789 | | [0b11111111u8, 0, 0b10101010, 0, 0b10101010, 0, 0b10101010, 0] |
790 | | ); |
791 | | |
792 | | expand_pass(&mut img, stride, &[0b11110000], &info(6, 1, width), bits_pp); |
793 | | assert_eq!( |
794 | | img, |
795 | | [0b11111111u8, 0, 0b11111111, 0, 0b10101010, 0, 0b10101010, 0] |
796 | | ); |
797 | | |
798 | | expand_pass(&mut img, stride, &[0b11110000], &info(6, 2, width), bits_pp); |
799 | | assert_eq!( |
800 | | img, |
801 | | [0b11111111u8, 0, 0b11111111, 0, 0b11111111, 0, 0b10101010, 0] |
802 | | ); |
803 | | |
804 | | expand_pass(&mut img, stride, &[0b11110000], &info(6, 3, width), bits_pp); |
805 | | assert_eq!( |
806 | | [0b11111111u8, 0, 0b11111111, 0, 0b11111111, 0, 0b11111111, 0], |
807 | | img |
808 | | ); |
809 | | |
810 | | expand_pass(&mut img, stride, &[0b11111111], &info(7, 0, width), bits_pp); |
811 | | assert_eq!( |
812 | | [ |
813 | | 0b11111111u8, |
814 | | 0b11111111, |
815 | | 0b11111111, |
816 | | 0, |
817 | | 0b11111111, |
818 | | 0, |
819 | | 0b11111111, |
820 | | 0 |
821 | | ], |
822 | | img |
823 | | ); |
824 | | |
825 | | expand_pass(&mut img, stride, &[0b11111111], &info(7, 1, width), bits_pp); |
826 | | assert_eq!( |
827 | | [ |
828 | | 0b11111111u8, |
829 | | 0b11111111, |
830 | | 0b11111111, |
831 | | 0b11111111, |
832 | | 0b11111111, |
833 | | 0, |
834 | | 0b11111111, |
835 | | 0 |
836 | | ], |
837 | | img |
838 | | ); |
839 | | |
840 | | expand_pass(&mut img, stride, &[0b11111111], &info(7, 2, width), bits_pp); |
841 | | assert_eq!( |
842 | | [ |
843 | | 0b11111111u8, |
844 | | 0b11111111, |
845 | | 0b11111111, |
846 | | 0b11111111, |
847 | | 0b11111111, |
848 | | 0b11111111, |
849 | | 0b11111111, |
850 | | 0 |
851 | | ], |
852 | | img |
853 | | ); |
854 | | |
855 | | expand_pass(&mut img, stride, &[0b11111111], &info(7, 3, width), bits_pp); |
856 | | assert_eq!( |
857 | | [ |
858 | | 0b11111111u8, |
859 | | 0b11111111, |
860 | | 0b11111111, |
861 | | 0b11111111, |
862 | | 0b11111111, |
863 | | 0b11111111, |
864 | | 0b11111111, |
865 | | 0b11111111 |
866 | | ], |
867 | | img |
868 | | ); |
869 | | } |
870 | | |
871 | | // We use 4bpp as representative for bit-fiddling passes bpp 1, 2, 4. The choice was made |
872 | | // because it is succinct to write in hex so one can read this and understand it. |
873 | | #[test] |
874 | | fn test_expand_pass_splat_4bpp() { |
875 | | let width = 8; |
876 | | let bits_pp = 4; |
877 | | |
878 | | let mut img = [0u8; 8]; |
879 | | let stride = width / 2; |
880 | | |
881 | | let passes: &[(&'static [u8], &'static [u8])] = &[ |
882 | | (&[0x10], &[0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11]), // pass 1, 0 |
883 | | (&[0x20], &[0x11, 0x11, 0x22, 0x22, 0x11, 0x11, 0x22, 0x22]), // pass 2, 0 |
884 | | // no third pass.. |
885 | | (&[0x4a], &[0x11, 0x44, 0x22, 0xaa, 0x11, 0x44, 0x22, 0xaa]), // pass 4, 0 |
886 | | // no fifth pass.. |
887 | | ( |
888 | | &[0x6b, 0x6b], |
889 | | &[0x16, 0x4b, 0x26, 0xab, 0x16, 0x4b, 0x26, 0xab], |
890 | | ), // pass 6, 0 |
891 | | ( |
892 | | &[0x7c, 0xc7, 0x7c, 0x7c], |
893 | | &[0x16, 0x4b, 0x26, 0xab, 0x7c, 0xc7, 0x7c, 0x7c], |
894 | | ), // pass 7, 0 |
895 | | ]; |
896 | | |
897 | | let adam7 = Adam7Iterator::new(8, 2); |
898 | | for ((data, expected), adam7_info) in passes.iter().zip(adam7) { |
899 | | expand_pass_splat(&mut img, stride, data, &adam7_info, bits_pp); |
900 | | assert_eq!(img, *expected, "{img:x?} {expected:x?} {adam7_info:?}"); |
901 | | } |
902 | | } |
903 | | |
904 | | /// Check that our different Adam7 strategies lead to the same result once all interlace lines |
905 | | /// have been applied. |
906 | | #[test] |
907 | | fn adam7_equivalence() { |
908 | | // Choose ragged sizes to cover bugs that write outside etc. |
909 | | const WIDTH: u32 = 8; |
910 | | const HEIGHT: u32 = 8; |
911 | | |
912 | | let interace_pool: Vec<_> = (0x42u8..).take(32).collect(); |
913 | | |
914 | | for &bpp in &[1u8, 2, 4, 8, 16, 24, 32][2..] { |
915 | | let bytes_of = |pix: u32| (u32::from(bpp) * pix).next_multiple_of(8) as usize / 8; |
916 | | |
917 | | let rowbytes = bytes_of(WIDTH); |
918 | | |
919 | | // In the sparse case we do not promise to override all bits |
920 | | let mut buf_sparse = vec![0x00; rowbytes * HEIGHT as usize]; |
921 | | // Whereas in the spat case we do, so we may as well set some arbitrary initial |
922 | | let mut buf_splat = vec![0xaa; rowbytes * HEIGHT as usize]; |
923 | | |
924 | | // Now execute all the iterations, then compare buffers. |
925 | | for adam7_info in Adam7Iterator::new(WIDTH, HEIGHT) { |
926 | | let adam7_bytes = bytes_of(adam7_info.samples); |
927 | | let interlace_line = &interace_pool[..adam7_bytes]; |
928 | | |
929 | | expand_pass(&mut buf_sparse, rowbytes, interlace_line, &adam7_info, bpp); |
930 | | expand_pass_splat(&mut buf_splat, rowbytes, interlace_line, &adam7_info, bpp); |
931 | | } |
932 | | |
933 | | assert_eq!( |
934 | | buf_sparse, buf_splat, |
935 | | "{buf_sparse:x?} {buf_splat:x?} bpp={bpp}" |
936 | | ); |
937 | | } |
938 | | } |
939 | | |
940 | | #[test] |
941 | | fn test_expand_pass_splat_1bpp() { |
942 | | let width = 8; |
943 | | let bits_pp = 1; |
944 | | |
945 | | let mut img = [0u8; 8]; |
946 | | let stride = 1; |
947 | | |
948 | | // Since bits do not suffice to represent the pass number in pixels we choose interlace |
949 | | // rows such that we toggle all affected bits each time. In particular the input bits that |
950 | | // must not be used are set to the inverse. |
951 | | let passes: &[(&'static [u8], &'static [u8])] = &[ |
952 | | (&[0x80], &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), // pass 1, 0 |
953 | | (&[0x7f], &[0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0]), // pass 2, 0 |
954 | | (&[0xc0], &[0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff, 0xff, 0xff]), // pass 3, 0 |
955 | | (&[0x3f], &[0xc0, 0xc0, 0xc0, 0xc0, 0xff, 0xff, 0xff, 0xff]), // pass 4, 0 |
956 | | (&[0x3f], &[0xc0, 0xc0, 0xc0, 0xc0, 0xcc, 0xcc, 0xcc, 0xcc]), // pass 4, 1 |
957 | | (&[0xf0], &[0xc0, 0xc0, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcc]), // pass 5, 0 |
958 | | (&[0xf0], &[0xc0, 0xc0, 0xff, 0xff, 0xcc, 0xcc, 0xff, 0xff]), // pass 5, 1 |
959 | | (&[0x0f], &[0x80, 0x80, 0xff, 0xff, 0xcc, 0xcc, 0xff, 0xff]), // pass 6, 0 |
960 | | (&[0x0f], &[0x80, 0x80, 0xaa, 0xaa, 0xcc, 0xcc, 0xff, 0xff]), // pass 6, 1 |
961 | | (&[0x0f], &[0x80, 0x80, 0xaa, 0xaa, 0x88, 0x88, 0xff, 0xff]), // pass 6, 2 |
962 | | (&[0x0f], &[0x80, 0x80, 0xaa, 0xaa, 0x88, 0x88, 0xaa, 0xaa]), // pass 6, 3 |
963 | | (&[0xff], &[0x80, 0xff, 0xaa, 0xaa, 0x88, 0x88, 0xaa, 0xaa]), // pass 7, 0 |
964 | | (&[0xff], &[0x80, 0xff, 0xaa, 0xff, 0x88, 0x88, 0xaa, 0xaa]), // pass 7, 1 |
965 | | (&[0xff], &[0x80, 0xff, 0xaa, 0xff, 0x88, 0xff, 0xaa, 0xaa]), // pass 7, 2 |
966 | | (&[0xff], &[0x80, 0xff, 0xaa, 0xff, 0x88, 0xff, 0xaa, 0xff]), // pass 7, 3 |
967 | | ]; |
968 | | |
969 | | let adam7 = Adam7Iterator::new(width, 8); |
970 | | for ((data, expected), adam7_info) in passes.iter().zip(adam7) { |
971 | | expand_pass_splat(&mut img, stride, data, &adam7_info, bits_pp); |
972 | | assert_eq!(img, *expected, "{img:x?} {expected:x?} {adam7_info:?}"); |
973 | | } |
974 | | } |
975 | | |
976 | | /// This test ensures that `expand_pass` works correctly on 32-bit machines, even when the indices |
977 | | /// of individual bits in the target buffer can not be expressed within a `usize`. We ensure that |
978 | | /// the output buffer size is between `usize::MAX / 8` and `isize::MAX` to trigger that condition. |
979 | | #[cfg(target_pointer_width = "32")] |
980 | | #[test] |
981 | | fn regression_overflow_adam7_bitfill() { |
982 | | fn multibyte_expand_pass_test_helper(width: usize, height: usize, bits_pp: u8) -> Vec<u8> { |
983 | | let bytes_pp = bits_pp / 8; |
984 | | let size = width * height * bytes_pp as usize; |
985 | | let mut img = vec![0u8; size]; |
986 | | let img_row_stride = width * bytes_pp as usize; |
987 | | |
988 | | for it in Adam7Iterator::new(width as u32, height as u32).into_iter() { |
989 | | if it.pass != 7 { |
990 | | continue; |
991 | | } |
992 | | |
993 | | if it.line != (width / 2) as u32 - 1 { |
994 | | continue; |
995 | | } |
996 | | |
997 | | let interlace_size = it.width * (bytes_pp as u32); |
998 | | // Ensure that expanded pixels are never empty bits. This differentiates the written bits |
999 | | // from the initial bits that are all zeroed. |
1000 | | let interlaced_row: Vec<_> = (0..interlace_size).map(|_| 0xff).collect(); |
1001 | | |
1002 | | expand_pass(&mut img, img_row_stride, &interlaced_row, &it, bits_pp); |
1003 | | } |
1004 | | |
1005 | | img |
1006 | | } |
1007 | | |
1008 | | let expanded = multibyte_expand_pass_test_helper(1 << 14, 1 << 14, 32); |
1009 | | assert_eq!(*expanded.last().unwrap(), 0xff); |
1010 | | } |
1011 | | |
1012 | | #[cfg(test)] |
1013 | | fn create_adam7_info_for_tests(pass: u8, line: u32, img_width: usize) -> Adam7Info { |
1014 | | let width = { |
1015 | | let img_height = 8; |
1016 | | Adam7Iterator::new(img_width as u32, img_height) |
1017 | | .filter(|info| info.pass == pass) |
1018 | | .map(|info| info.samples) |
1019 | | .next() |
1020 | | .unwrap() |
1021 | | }; |
1022 | | |
1023 | | Adam7Info { |
1024 | | pass, |
1025 | | line, |
1026 | | samples: width, |
1027 | | width, |
1028 | | } |
1029 | | } |
1030 | | } |