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