Coverage Report

Created: 2025-12-11 07:11

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