Coverage Report

Created: 2026-03-10 07:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}