Skip to main content

flashraw_codecs/
panasonic.rs

1//! Panasonic / Leica RW2 payload codecs (RawFormat 4–8).
2//!
3//! | RawFormat | Bodies | Coding | Parallel unit |
4//! |---|---|---|---|
5//! | 4 | most RW2 bodies up to 2019 | 14 pixels per 16-byte packet, 2-bit shift + 8-bit deltas | packet (row) |
6//! | 5 | GH5S, G9 high-res | 10×12-bit or 9×14-bit LSB-packed per 16 bytes | row |
7//! | 6 | S1/S1R/S1H/S5, GH5 II | 11×14-bit or 14×12-bit, delta coded per 16 bytes | row |
8//! | 7 | S1R/S5 high-res | 9×14-bit LSB-packed per 16 bytes | row |
9//! | 8 | GH6, G9 II, S5 II, S9 ... | Huffman-coded 2×2 differences in vertical strips | strip |
10//!
11//! Formats 4 and 5 store the stream in 0x4000-byte blocks whose last `split` bytes
12//! come first on disk ([`BlockedStream`]). Format 4 reads every 16-byte packet as a
13//! 128-bit little-endian integer from its most significant bit down, and each packet
14//! carries exactly 128 bits for its 14 pixels; that makes every packet independent
15//! even though the format is defined as one sequential stream.
16//!
17//! All decoders are bit-exact, including how a short final block is padded with the
18//! previous block's bytes.
19
20use flashraw_core::par;
21use flashraw_core::{Error, Result};
22
23/// Size of the rotation blocks of formats 4 and 5.
24pub const BLOCK: usize = 0x4000;
25
26/// A format 4/5 stream: blocks of [`BLOCK`] bytes whose first `split` bytes (in
27/// stream order) are stored after the remaining `BLOCK - split` bytes.
28#[derive(Clone, Copy)]
29pub struct BlockedStream<'a> {
30    data: &'a [u8],
31    split: usize,
32}
33
34impl<'a> BlockedStream<'a> {
35    pub fn new(data: &'a [u8], split: usize) -> Result<Self> {
36        if split > BLOCK {
37            return Err(Error::corrupt("Panasonic block split beyond block size"));
38        }
39        Ok(BlockedStream { data, split })
40    }
41
42    /// Stream bytes available (a short final block is padded with stale data).
43    pub fn len(&self) -> usize {
44        self.data.len()
45    }
46
47    pub fn is_empty(&self) -> bool {
48        self.data.is_empty()
49    }
50
51    /// File offset (relative to the payload) of stream byte `i` of block `b`.
52    #[inline(always)]
53    fn file_pos(&self, b: usize, i: usize) -> usize {
54        b * BLOCK + (i + BLOCK - self.split) % BLOCK
55    }
56
57    /// Stream byte `i` of block `b`, with these semantics for a truncated final
58    /// block: bytes that were not read keep the value of the previous block's buffer.
59    fn byte(&self, mut b: usize, i: usize) -> u8 {
60        loop {
61            if let Some(&v) = self.data.get(self.file_pos(b, i)) {
62                return v;
63            }
64            if b == 0 {
65                return 0;
66            }
67            b -= 1;
68        }
69    }
70
71    /// The 16-byte packet `p` (stream bytes `16p..16p+16`).
72    #[inline(always)]
73    pub fn packet(&self, p: usize) -> [u8; 16] {
74        let b = p * 16 / BLOCK;
75        let i = p * 16 % BLOCK;
76        let start = self.file_pos(b, i);
77        // Fast path: the packet does not straddle the split or the end of the data.
78        if (b + 1) * BLOCK <= self.data.len()
79            && start + 16 <= (b + 1) * BLOCK
80            && (i >= self.split || i + 16 <= self.split)
81        {
82            let mut out = [0u8; 16];
83            out.copy_from_slice(&self.data[start..start + 16]);
84            return out;
85        }
86        std::array::from_fn(|k| self.byte(b, i + k))
87    }
88
89    /// Number of whole stream bytes needed to hold `packets` packets.
90    pub fn packets_available(&self) -> usize {
91        // Any packet inside an existing (possibly short) block can be produced.
92        self.data.len().div_ceil(BLOCK) * BLOCK / 16
93    }
94}
95
96// ---------------------------------------------------------------------------------
97// Format 4
98
99/// Pixels per format-4 packet.
100pub const V4_PIXELS: usize = 14;
101
102/// Decode one 16-byte format-4 packet into 14 pixels.
103#[inline]
104pub fn v4_packet(packet: &[u8; 16], out: &mut [u16]) {
105    // The packet is read from bit 127 of its little-endian value downwards.
106    let mut v = u128::from_le_bytes(*packet);
107    let mut take = |n: u32| -> i32 {
108        let r = (v >> (128 - n)) as i32;
109        v <<= n;
110        r
111    };
112    let mut sh = 0i32;
113    let mut pred = [0i32; 2];
114    let mut nonz = [0i32; 2];
115    for (i, o) in out.iter_mut().enumerate().take(V4_PIXELS) {
116        let c = i & 1;
117        if i % 3 == 2 {
118            sh = 4 >> (3 - take(2));
119        }
120        if nonz[c] != 0 {
121            let j = take(8);
122            if j != 0 {
123                pred[c] -= 0x80 << sh;
124                if pred[c] < 0 || sh == 4 {
125                    pred[c] &= !(-1i32 << sh);
126                }
127                pred[c] += j << sh;
128            }
129        } else {
130            nonz[c] = take(8);
131            if nonz[c] != 0 || i > 11 {
132                pred[c] = nonz[c] << 4 | take(4);
133            }
134        }
135        *o = pred[c] as u16;
136    }
137}
138
139/// Decode a format-4 image (`width * height` samples into `out`). Rows are
140/// independent when `width` is a multiple of 14 (always the case in practice);
141/// otherwise the stream is decoded sequentially.
142pub fn decode_v4(s: &BlockedStream, width: usize, height: usize, out: &mut [u16], parallel: bool) -> Result<()> {
143    check_out(out, width, height)?;
144    if width % V4_PIXELS != 0 {
145        let needed = (width * height).div_ceil(V4_PIXELS);
146        if s.packets_available() < needed {
147            return Err(Error::corrupt("truncated Panasonic data"));
148        }
149        return decode_v4_sequential(s, width, height, out);
150    }
151    decode_v4_rows(s, width, height, 0, &mut out[..width * height], parallel)
152}
153
154/// Decode rows `first_row..first_row + out.len() / width` of a format-4 image whose
155/// width is a multiple of 14 (rows are independent).
156pub fn decode_v4_rows(
157    s: &BlockedStream,
158    width: usize,
159    height: usize,
160    first_row: usize,
161    out: &mut [u16],
162    parallel: bool,
163) -> Result<()> {
164    let rows = out.len() / width.max(1);
165    if width == 0 || width % V4_PIXELS != 0 || first_row + rows > height {
166        return Err(Error::InvalidArgument("invalid Panasonic v4 row range".into()));
167    }
168    let needed = (width * height).div_ceil(V4_PIXELS);
169    if s.packets_available() < needed {
170        return Err(Error::corrupt(format!("truncated Panasonic data: {} of {needed} packets", s.packets_available())));
171    }
172    let per_row = width / V4_PIXELS;
173    par::for_each_row_chunk(&mut out[..width * rows], width, 16, parallel, |first, chunk| {
174        for (r, row) in chunk.chunks_exact_mut(width).enumerate() {
175            let base = (first_row + first + r) * per_row;
176            for (k, px) in row.chunks_exact_mut(V4_PIXELS).enumerate() {
177                v4_packet(&s.packet(base + k), px);
178            }
179        }
180    });
181    Ok(())
182}
183
184/// Bit-exact sequential reader for Panasonic packets (any width).
185pub fn decode_v4_sequential(s: &BlockedStream, width: usize, height: usize, out: &mut [u16]) -> Result<()> {
186    check_out(out, width, height)?;
187    let mut buf = vec![0u8; BLOCK + 2];
188    let mut block = 0usize;
189    let mut vpos = 0u32;
190    let mut get = |nb: u32, buf: &mut Vec<u8>| -> i32 {
191        if vpos == 0 {
192            for (i, b) in buf.iter_mut().take(BLOCK).enumerate() {
193                if let Some(&v) = s.data.get(s.file_pos(block, i)) {
194                    *b = v;
195                }
196            }
197            block += 1;
198        }
199        vpos = vpos.wrapping_sub(nb) & 0x1ffff;
200        let byte = (vpos >> 3 ^ 0x3ff0) as usize;
201        let w = buf[byte] as u32 | (buf[byte + 1] as u32) << 8;
202        (w >> (vpos & 7) & !(!0u32 << nb)) as i32
203    };
204    let (mut sh, mut pred, mut nonz) = (0i32, [0i32; 2], [0i32; 2]);
205    for row in 0..height {
206        for col in 0..width {
207            let i = col % V4_PIXELS;
208            if i == 0 {
209                pred = [0; 2];
210                nonz = [0; 2];
211            }
212            if i % 3 == 2 {
213                sh = 4 >> (3 - get(2, &mut buf));
214            }
215            let c = i & 1;
216            if nonz[c] != 0 {
217                let j = get(8, &mut buf);
218                if j != 0 {
219                    pred[c] -= 0x80 << sh;
220                    if pred[c] < 0 || sh == 4 {
221                        pred[c] &= !(-1i32 << sh);
222                    }
223                    pred[c] += j << sh;
224                }
225            } else {
226                nonz[c] = get(8, &mut buf);
227                if nonz[c] != 0 || i > 11 {
228                    pred[c] = nonz[c] << 4 | get(4, &mut buf);
229                }
230            }
231            out[row * width + col] = pred[col & 1] as u16;
232        }
233    }
234    Ok(())
235}
236
237fn check_out(out: &[u16], width: usize, height: usize) -> Result<()> {
238    if width == 0 || height == 0 || out.len() < width * height {
239        return Err(Error::InvalidArgument("Panasonic output buffer too small".into()));
240    }
241    Ok(())
242}
243
244// ---------------------------------------------------------------------------------
245// Formats 5 and 7: plain LSB-first packing of 10×12 or 9×14 bits per 16 bytes.
246
247/// Pixels per 16-byte packet for formats 5 and 7.
248pub fn packed_pixels(bits: u32) -> Option<usize> {
249    match bits {
250        12 => Some(10),
251        14 => Some(9),
252        _ => None,
253    }
254}
255
256#[inline]
257fn unpack_lsb(packet: &[u8; 16], bits: u32, out: &mut [u16]) {
258    let v = u128::from_le_bytes(*packet);
259    let mask = (1u128 << bits) - 1;
260    for (k, o) in out.iter_mut().enumerate() {
261        *o = ((v >> (bits as usize * k)) & mask) as u16;
262    }
263}
264
265/// Format 5: packets run through the blocked stream; every row starts a new packet.
266pub fn decode_v5(
267    s: &BlockedStream,
268    bits: u32,
269    width: usize,
270    height: usize,
271    out: &mut [u16],
272    parallel: bool,
273) -> Result<()> {
274    check_out(out, width, height)?;
275    decode_v5_rows(s, bits, width, height, 0, &mut out[..width * height], parallel)
276}
277
278/// Decode rows `first_row..first_row + out.len() / width` of a format-5 image.
279pub fn decode_v5_rows(
280    s: &BlockedStream,
281    bits: u32,
282    width: usize,
283    height: usize,
284    first_row: usize,
285    out: &mut [u16],
286    parallel: bool,
287) -> Result<()> {
288    let rows = out.len() / width.max(1);
289    if width == 0 || first_row + rows > height {
290        return Err(Error::InvalidArgument("invalid Panasonic v5 row range".into()));
291    }
292    let ppp = packed_pixels(bits).ok_or_else(|| Error::corrupt(format!("Panasonic v5 with {bits} bits")))?;
293    let per_row = width.div_ceil(ppp);
294    if s.packets_available() < per_row * height {
295        return Err(Error::corrupt("truncated Panasonic v5 data"));
296    }
297    par::for_each_row_chunk(&mut out[..width * rows], width, 16, parallel, |first, chunk| {
298        let mut tmp = [0u16; 10];
299        for (r, row) in chunk.chunks_exact_mut(width).enumerate() {
300            let base = (first_row + first + r) * per_row;
301            for (k, px) in row.chunks_mut(ppp).enumerate() {
302                unpack_lsb(&s.packet(base + k), bits, &mut tmp[..ppp]);
303                px.copy_from_slice(&tmp[..px.len()]);
304            }
305        }
306    });
307    Ok(())
308}
309
310/// Format 7: rows of `width / ppp` packets stored back to back (no block rotation).
311/// Only whole 16-row bands are decoded and trailing columns that do
312/// not fill a packet stay zero.
313pub fn decode_v7(data: &[u8], bits: u32, width: usize, height: usize, out: &mut [u16], parallel: bool) -> Result<()> {
314    check_out(out, width, height)?;
315    decode_v7_rows(data, bits, width, height, 0, &mut out[..width * height], parallel)
316}
317
318/// Decode rows `first_row..first_row + out.len() / width` of a format-7 image.
319pub fn decode_v7_rows(
320    data: &[u8],
321    bits: u32,
322    width: usize,
323    height: usize,
324    first_row: usize,
325    out: &mut [u16],
326    parallel: bool,
327) -> Result<()> {
328    let nrows = out.len() / width.max(1);
329    if width == 0 || first_row + nrows > height {
330        return Err(Error::InvalidArgument("invalid Panasonic v7 row range".into()));
331    }
332    let ppp = packed_pixels(bits).ok_or_else(|| Error::corrupt(format!("Panasonic v7 with {bits} bits")))?;
333    let row_bytes = width / ppp * 16;
334    let rows = height / 16 * 16;
335    if row_bytes == 0 || data.len() < rows * row_bytes {
336        return Err(Error::corrupt("truncated Panasonic v7 data"));
337    }
338    par::for_each_row_chunk(&mut out[..width * nrows], width, 16, parallel, |first, chunk| {
339        for (r, row) in chunk.chunks_exact_mut(width).enumerate() {
340            let y = first_row + first + r;
341            if y >= rows {
342                row.fill(0);
343                continue;
344            }
345            let src = &data[y * row_bytes..(y + 1) * row_bytes];
346            for (p, px) in src.chunks_exact(16).zip(row.chunks_exact_mut(ppp)) {
347                unpack_lsb(p.try_into().unwrap_or(&[0; 16]), bits, px);
348            }
349        }
350    });
351    Ok(())
352}
353
354// ---------------------------------------------------------------------------------
355// Format 6
356
357/// Decode a format-6 image: per row `width / ppb` packets of 16 bytes, each holding
358/// 11 (14-bit) or 14 (12-bit) delta-coded pixels. Only whole 16-row bands are decoded.
359pub fn decode_v6(data: &[u8], bits: u32, width: usize, height: usize, out: &mut [u16], parallel: bool) -> Result<()> {
360    check_out(out, width, height)?;
361    decode_v6_rows(data, bits, width, height, 0, &mut out[..width * height], parallel)
362}
363
364/// Decode rows `first_row..first_row + out.len() / width` of a format-6 image.
365pub fn decode_v6_rows(
366    data: &[u8],
367    bits: u32,
368    width: usize,
369    height: usize,
370    first_row: usize,
371    out: &mut [u16],
372    parallel: bool,
373) -> Result<()> {
374    let nrows = out.len() / width.max(1);
375    if width == 0 || first_row + nrows > height {
376        return Err(Error::InvalidArgument("invalid Panasonic v6 row range".into()));
377    }
378    let twelve = match bits {
379        12 => true,
380        14 => false,
381        _ => return Err(Error::corrupt(format!("Panasonic v6 with {bits} bits"))),
382    };
383    let ppb = if twelve { 14 } else { 11 };
384    let blocks = width / ppb;
385    let row_bytes = blocks * 16;
386    let rows = height / 16 * 16;
387    if row_bytes == 0 || data.len() < rows * row_bytes {
388        return Err(Error::corrupt("truncated Panasonic v6 data"));
389    }
390    par::for_each_row_chunk(&mut out[..width * nrows], width, 16, parallel, |first, chunk| {
391        for (r, row) in chunk.chunks_exact_mut(width).enumerate() {
392            let y = first_row + first + r;
393            if y >= rows {
394                row.fill(0);
395                continue;
396            }
397            let src = &data[y * row_bytes..(y + 1) * row_bytes];
398            for (p, px) in src.chunks_exact(16).zip(row.chunks_exact_mut(ppb)) {
399                v6_block(p.try_into().unwrap_or(&[0; 16]), twelve, px);
400            }
401        }
402    });
403    Ok(())
404}
405
406/// Split a 16-byte format-6 block into its fields (big-endian bit order over the
407/// byte-reversed block): pixel codes and 2-bit shift selectors interleaved.
408fn v6_fields(b: &[u8; 16], twelve: bool) -> ([u32; 18], usize) {
409    // Byte i of the reversed block.
410    let w = |i: usize| b[15 - i] as u32;
411    let mut f = [0u32; 18];
412    if twelve {
413        f[0] = (w(0) << 4) | (w(1) >> 4);
414        f[1] = ((w(1) & 0xf) << 8 | w(2)) & 0xfff;
415        f[2] = (w(3) >> 6) & 0x3;
416        f[3] = ((w(3) & 0x3f) << 2) | (w(4) >> 6);
417        f[4] = ((w(4) & 0x3f) << 2) | (w(5) >> 6);
418        f[5] = ((w(5) & 0x3f) << 2) | (w(6) >> 6);
419        f[6] = (w(6) >> 4) & 0x3;
420        f[7] = ((w(6) & 0xf) << 4) | (w(7) >> 4);
421        f[8] = ((w(7) & 0xf) << 4) | (w(8) >> 4);
422        f[9] = ((w(8) & 0xf) << 4) | (w(9) >> 4);
423        f[10] = (w(9) >> 2) & 0x3;
424        f[11] = ((w(9) & 0x3) << 6) | (w(10) >> 2);
425        f[12] = ((w(10) & 0x3) << 6) | (w(11) >> 2);
426        f[13] = ((w(11) & 0x3) << 6) | (w(12) >> 2);
427        f[14] = w(12) & 0x3;
428        f[15] = w(13);
429        f[16] = w(14);
430        f[17] = w(15);
431        (f, 18)
432    } else {
433        f[0] = (w(0) << 6) | (w(1) >> 2);
434        f[1] = (((w(1) & 0x3) << 12) | (w(2) << 4) | (w(3) >> 4)) & 0x3fff;
435        f[2] = (w(3) >> 2) & 0x3;
436        f[3] = ((w(3) & 0x3) << 8) | w(4);
437        f[4] = (w(5) << 2) | (w(6) >> 6);
438        f[5] = ((w(6) & 0x3f) << 4) | (w(7) >> 4);
439        f[6] = (w(7) >> 2) & 0x3;
440        f[7] = ((w(7) & 0x3) << 8) | w(8);
441        f[8] = ((w(9) << 2) & 0x3fc) | (w(10) >> 6);
442        f[9] = ((w(10) << 4) | (w(11) >> 4)) & 0x3ff;
443        f[10] = (w(11) >> 2) & 0x3;
444        f[11] = ((w(11) & 0x3) << 8) | w(12);
445        f[12] = (((w(13) << 2) & 0x3fc) | w(14) >> 6) & 0x3ff;
446        f[13] = ((w(14) << 4) | (w(15) >> 4)) & 0x3ff;
447        (f, 14)
448    }
449}
450
451#[inline]
452fn v6_block(b: &[u8; 16], twelve: bool, out: &mut [u16]) {
453    let (f, nf) = v6_fields(b, twelve);
454    let mut next = 0usize;
455    let mut field = || {
456        let v = if next < nf { f[next] } else { 0 };
457        next += 1;
458        v
459    };
460    let (base0, base_cmp, spix_cmp, mask) =
461        if twelve { (0x80u32, 0x800u32, 0x3fffu32, 0xfffu32) } else { (0x200, 0x2000, 0xffff, 0x3fff) };
462    let mut oddeven = [0u32; 2];
463    let mut nonzero = [0u32; 2];
464    let (mut pmul, mut pixel_base) = (0u32, 0u32);
465    for (pix, o) in out.iter_mut().enumerate() {
466        if pix % 3 == 2 {
467            let mut base = field();
468            if base == 3 {
469                base = 4;
470            }
471            pixel_base = base0 << base;
472            pmul = 1 << base;
473        }
474        let mut epixel = field();
475        let c = pix % 2;
476        if oddeven[c] != 0 {
477            epixel = epixel.wrapping_mul(pmul);
478            if pixel_base < base_cmp && nonzero[c] > pixel_base {
479                epixel = epixel.wrapping_add(nonzero[c] - pixel_base);
480            }
481            nonzero[c] = epixel;
482        } else {
483            oddeven[c] = epixel;
484            if epixel != 0 {
485                nonzero[c] = epixel;
486            } else {
487                epixel = nonzero[c];
488            }
489        }
490        let spix = epixel.wrapping_sub(0xf);
491        *o = if spix <= spix_cmp {
492            (spix & spix_cmp) as u16
493        } else {
494            // Underflow clamps to zero, overflow saturates to the mask.
495            ((((epixel.wrapping_add(0x7fff_fff1)) as i32) >> 31) as u32 & mask) as u16
496        };
497    }
498}
499
500// ---------------------------------------------------------------------------------
501// Format 8
502
503/// Format-8 parameters (RW2 tags 0x39–0x48).
504#[derive(Debug, Clone, Default)]
505pub struct V8Params {
506    /// Gamma curve: 6 (point, slope) pairs (tags 0x3A and 0x39).
507    pub gamma_points: [u32; 6],
508    pub gamma_slopes: [u32; 6],
509    /// Clip value (tag 0x3B).
510    pub clip: u32,
511    /// Initial predictions for the 2×2 block positions TL, TR, BL, BR (tags 0x3C–0x3F).
512    pub initial: [u16; 4],
513    /// Huffman codes: (code length, code) per symbol (tag 0x40); the symbol index is
514    /// the difference bit length.
515    pub codes: Vec<(u8, u16)>,
516    /// Per-symbol "shift down" (tag 0x41); zero in all known files.
517    pub shift_down: Vec<u8>,
518}
519
520/// One independently coded vertical strip.
521#[derive(Debug, Clone, Copy)]
522pub struct V8Strip<'a> {
523    pub data: &'a [u8],
524    /// Exact length of the strip's bit stream.
525    pub bits: u64,
526    pub left: u32,
527    /// Vertical position; tiles below the first row of strips exist on high-resolution
528    /// (pixel-shift) bodies.
529    pub top: u32,
530    pub width: u32,
531    pub height: u32,
532}
533
534/// Decoded symbol for each 16-bit prefix: (code length, symbol); `len == 0` = invalid.
535struct V8Lut {
536    /// Indexed by the next `bits` stream bits (the longest code length), so the table
537    /// stays small enough for L1 (4096 entries for the usual 12-bit maximum).
538    entries: Vec<(u8, u8)>,
539    bits: u32,
540}
541
542impl V8Lut {
543    fn new(codes: &[(u8, u16)]) -> Result<Self> {
544        if codes.is_empty() || codes.len() > 17 {
545            return Err(Error::corrupt("invalid Panasonic v8 Huffman table size"));
546        }
547        let bits = codes.iter().map(|&(l, _)| l as u32).filter(|&l| l <= 16).max().unwrap_or(1).max(1);
548        // Codes are matched in table order (first match wins; not necessarily a prefix code).
549        let mut entries = vec![(0u8, 0u8); 1 << bits];
550        for (v, e) in entries.iter_mut().enumerate() {
551            for (sym, &(len, code)) in codes.iter().enumerate() {
552                if len == 0 || len as u32 > bits {
553                    continue;
554                }
555                let shift = bits - len as u32;
556                if (v as u32 >> shift) == (code as u32 & ((1 << len) - 1)) {
557                    *e = (len, sym as u8);
558                    break;
559                }
560            }
561        }
562        Ok(V8Lut { entries, bits })
563    }
564}
565
566/// MSB-first reader over bytes whose bits are stored LSB first.
567struct RevBits<'a> {
568    data: &'a [u8],
569    pos: usize,
570    cache: u64,
571    bits: u32,
572}
573
574impl<'a> RevBits<'a> {
575    fn new(data: &'a [u8]) -> Self {
576        RevBits { data, pos: 0, cache: 0, bits: 0 }
577    }
578    #[inline(always)]
579    fn fill(&mut self) {
580        if self.bits > 32 {
581            return;
582        }
583        // Load 4 bytes; past the end, pad with zeros (callers check the bit budget).
584        let mut w = [0u8; 4];
585        if let Some(s) = self.data.get(self.pos..self.pos + 4) {
586            w.copy_from_slice(s);
587        } else {
588            for (k, b) in w.iter_mut().enumerate() {
589                *b = self.data.get(self.pos + k).copied().unwrap_or(0);
590            }
591        }
592        let v = u32::from_le_bytes(w).reverse_bits() as u64;
593        self.cache |= v << (32 - self.bits);
594        self.bits += 32;
595        self.pos += 4;
596    }
597    #[inline(always)]
598    fn skip(&mut self, n: u32) {
599        self.cache = if n >= 64 { 0 } else { self.cache << n };
600        self.bits -= n;
601    }
602    fn consumed(&self) -> u64 {
603        self.pos as u64 * 8 - self.bits as u64
604    }
605    /// A reader positioned `bit` bits into `data` (bits are grouped in 32-bit words).
606    fn at_bit(data: &'a [u8], bit: u64) -> Self {
607        let mut r = RevBits { data, pos: (bit / 32) as usize * 4, cache: 0, bits: 0 };
608        r.fill();
609        r.skip((bit % 32) as u32);
610        r
611    }
612}
613
614/// Format-8 decoder: Huffman-coded differences to a running 2×2 prediction.
615pub struct V8Decoder {
616    lut: V8Lut,
617    /// Per symbol: magnitude bits stored, left shift of the magnitude, offset added
618    /// for a negative difference, and the rounding offset of shifted magnitudes.
619    sym: [(u32, u32, i32, i32); 17],
620    clip: u32,
621    gamma: Option<Vec<u16>>,
622    initial: [u16; 4],
623}
624
625impl V8Decoder {
626    pub fn new(p: &V8Params) -> Result<Self> {
627        let lut = V8Lut::new(&p.codes)?;
628        let mut shift_down = [0u8; 17];
629        for (d, &s) in shift_down.iter_mut().zip(p.shift_down.iter()) {
630            *d = s.min(31);
631        }
632        let sym = std::array::from_fn(|i| {
633            let s = shift_down[i] as u32;
634            let d = (i as u32).saturating_sub(s);
635            let neg = if i > 0 { -(1i32 << i) + if s > 0 { 0 } else { 1 } } else { 0 };
636            let add = if s > 0 { 1i32 << (s - 1) } else { 0 };
637            (d, s, neg, add)
638        });
639        // Apply the tag 0x39/0x3A tone curve unless it is the identity.
640        let curve: Vec<u16> = (0..0x10000u32).map(|i| gamma_curve(p, i)).collect();
641        let gamma = if curve.iter().enumerate().all(|(i, &v)| v as usize == i) { None } else { Some(curve) };
642        Ok(V8Decoder { lut, sym, clip: p.clip, gamma, initial: p.initial })
643    }
644
645    /// Decode strips into `out` (`raw_width` samples per row). Strips decode in
646    /// parallel, each straight into its own column band of the output.
647    pub fn decode(
648        &self,
649        strips: &[V8Strip],
650        out: &mut [u16],
651        raw_width: usize,
652        raw_height: usize,
653        parallel: bool,
654    ) -> Result<()> {
655        if out.len() < raw_width * raw_height || raw_width == 0 || strips.is_empty() {
656            return Err(Error::InvalidArgument("Panasonic v8 output too small".into()));
657        }
658        // Tiles must lie inside the image, not overlap, and cover it completely.
659        let rect = |s: &V8Strip| (s.left as usize, s.top as usize, s.width as usize, s.height as usize);
660        let mut area = 0usize;
661        for (i, s) in strips.iter().enumerate() {
662            let (l, t, w, h) = rect(s);
663            if w < 2 || h < 2 || w % 2 != 0 || h % 2 != 0 || l + w > raw_width || t + h > raw_height {
664                return Err(Error::corrupt("invalid Panasonic v8 strip geometry"));
665            }
666            for o in &strips[..i] {
667                let (ol, ot, ow, oh) = rect(o);
668                if l < ol + ow && ol < l + w && t < ot + oh && ot < t + h {
669                    return Err(Error::corrupt("overlapping Panasonic v8 strips"));
670                }
671            }
672            area += w * h;
673        }
674        if area != raw_width * raw_height {
675            return Err(Error::corrupt("Panasonic v8 strips do not cover the image"));
676        }
677        // Hand every tile the matching segment of each of its output rows (disjoint borrows).
678        let mut order: Vec<usize> = (0..strips.len()).collect();
679        order.sort_by_key(|&i| (strips[i].top, strips[i].left));
680        let mut segments: Vec<Vec<&mut [u16]>> = strips.iter().map(|s| Vec::with_capacity(s.height as usize)).collect();
681        for (y, row) in out[..raw_width * raw_height].chunks_exact_mut(raw_width).enumerate() {
682            let mut rest = row;
683            let mut consumed = 0usize;
684            for &i in &order {
685                let (l, t, w, h) = rect(&strips[i]);
686                if y < t || y >= t + h {
687                    continue;
688                }
689                let (_, tail) = std::mem::take(&mut rest).split_at_mut(l - consumed);
690                let (seg, tail) = tail.split_at_mut(w);
691                segments[i].push(seg);
692                rest = tail;
693                consumed = l + w;
694            }
695        }
696        let jobs: Vec<(usize, Vec<&mut [u16]>)> = segments.into_iter().enumerate().collect();
697        let results = par::map(jobs, parallel, |(i, rows)| self.decode_strip_with(&strips[i], rows, parallel));
698        results.into_iter().collect::<Result<Vec<()>>>().map(|_| ())
699    }
700
701    /// Decode one strip into `rows` (its `height` row segments of `width` samples).
702    pub fn decode_strip(&self, s: &V8Strip, rows: Vec<&mut [u16]>) -> Result<()> {
703        self.decode_strip_with(s, rows, false)
704    }
705
706    fn decode_strip_with(&self, s: &V8Strip, rows: Vec<&mut [u16]>, parallel: bool) -> Result<()> {
707        let (w, h) = (s.width as usize, s.height as usize);
708        if w / 2 == 0 || h / 2 == 0 || rows.len() < h || rows.iter().any(|r| r.len() < w) {
709            return Err(Error::corrupt("invalid Panasonic v8 strip"));
710        }
711        if parallel && w * h >= crate::ljpeg_par::PARALLEL_MIN_SAMPLES / 4 {
712            if let Some(rows) = self.decode_strip_parallel(s, rows, None)? {
713                // Speculation could not prove the result: decode sequentially.
714                return self.decode_strip_seq(s, rows);
715            }
716            return Ok(());
717        }
718        self.decode_strip_seq(s, rows)
719    }
720
721    fn decode_strip_seq(&self, s: &V8Strip, mut rows: Vec<&mut [u16]>) -> Result<()> {
722        match &self.gamma {
723            Some(t) => self.decode_rows_seq::<true>(s, &mut rows, t),
724            None => self.decode_rows_seq::<false>(s, &mut rows, &[]),
725        }
726    }
727
728    fn decode_rows_seq<const GAMMA: bool>(&self, s: &V8Strip, rows: &mut [&mut [u16]], curve: &[u16]) -> Result<()> {
729        let (w, h) = (s.width as usize, s.height as usize);
730        let half_w = w / 2;
731        let budget = s.bits.div_ceil(64) * 64;
732        let mut bits = RevBits::new(s.data);
733        let clip = self.clip as i32;
734        let mut line_base = self.initial.map(|v| v as i32);
735        let mut err = false;
736        for pair in 0..h / 2 {
737            let mut base = line_base;
738            let (top, bottom) = rows[pair * 2..pair * 2 + 2].split_at_mut(1);
739            let (r0, r1) = (&mut *top[0], &mut *bottom[0]);
740            for bx in 0..half_w {
741                let mut blk = [0i32; 4];
742                // Values are decoded per 2x2 block in the order TL, BL, TR, BR.
743                for slot in [0usize, 2, 1, 3] {
744                    let delta = self.diff(&mut bits, &mut err);
745                    blk[slot] = (base[slot] + delta).clamp(0, clip);
746                }
747                base = blk;
748                if bx == 0 {
749                    line_base = blk;
750                }
751                let g = |v: i32| if GAMMA { curve[v as usize & 0xffff] } else { v as u16 };
752                r0[2 * bx] = g(blk[0]);
753                r0[2 * bx + 1] = g(blk[1]);
754                r1[2 * bx] = g(blk[2]);
755                r1[2 * bx + 1] = g(blk[3]);
756            }
757            if err {
758                return Err(Error::corrupt("invalid Panasonic v8 Huffman code"));
759            }
760            if bits.consumed() > budget {
761                return Err(Error::corrupt("truncated Panasonic v8 strip"));
762            }
763        }
764        Ok(())
765    }
766
767    /// Speculative parallel decode of one strip ([`crate::par_entropy`]): differences
768    /// on all threads, then the first block of every row pair is chained and row pairs
769    /// are rebuilt in parallel. Returns the rows back when the strip has to be decoded
770    /// sequentially (speculation could not prove the result, or it is corrupt).
771    fn decode_strip_parallel<'r>(
772        &self,
773        s: &V8Strip,
774        rows: Vec<&'r mut [u16]>,
775        chunk: Option<usize>,
776    ) -> Result<Option<Vec<&'r mut [u16]>>> {
777        let (w, h) = (s.width as usize, s.height as usize);
778        let (half_w, pairs) = (w / 2, h / 2);
779        let budget = s.bits.div_ceil(64) * 64;
780        let scan = V8Scan { data: s.data, dec: self, budget };
781        let Some(chunk) = chunk.or_else(|| crate::par_entropy::default_chunk(s.data.len())) else {
782            return Ok(Some(rows));
783        };
784        let Some(values) = crate::par_entropy::decode(&scan, pairs * half_w * 4, chunk) else {
785            return Ok(Some(rows));
786        };
787        let delta = |k: usize| -> i32 { (values.get(2 * k) as u32 | (values.get(2 * k + 1) as u32) << 16) as i32 };
788        let clip = self.clip as i32;
789        // Chain the first block of each row pair (its prediction comes from the pair above).
790        let mut starts = Vec::with_capacity(pairs);
791        let mut line_base = self.initial.map(|v| v as i32);
792        for pair in 0..pairs {
793            starts.push(line_base);
794            let k0 = pair * half_w * 4;
795            let mut blk = [0i32; 4];
796            for (j, slot) in [0usize, 2, 1, 3].into_iter().enumerate() {
797                blk[slot] = (line_base[slot] + delta(k0 + j)).clamp(0, clip);
798            }
799            line_base = blk;
800        }
801        let mut it = rows.into_iter();
802        let jobs: Vec<(usize, &mut [u16], &mut [u16])> =
803            (0..pairs).filter_map(|p| Some((p, it.next()?, it.next()?))).collect();
804        par::map(jobs, true, |(pair, r0, r1)| {
805            let mut buf = vec![0u16; half_w * 8];
806            values.copy_to(pair * half_w * 8, &mut buf);
807            let mut base = starts[pair];
808            for (bx, d) in buf.chunks_exact(8).enumerate() {
809                let mut blk = [0i32; 4];
810                for (j, slot) in [0usize, 2, 1, 3].into_iter().enumerate() {
811                    let delta = (d[2 * j] as u32 | (d[2 * j + 1] as u32) << 16) as i32;
812                    blk[slot] = (base[slot] + delta).clamp(0, clip);
813                }
814                base = blk;
815                let g = |v: i32| match &self.gamma {
816                    Some(t) => t[v as usize & 0xffff],
817                    None => v as u16,
818                };
819                r0[2 * bx] = g(blk[0]);
820                r0[2 * bx + 1] = g(blk[1]);
821                r1[2 * bx] = g(blk[2]);
822                r1[2 * bx + 1] = g(blk[3]);
823            }
824        });
825        Ok(None)
826    }
827
828    /// Next difference. A code of length zero (no table match) sets `err` and consumes
829    /// one bit so callers always progress.
830    #[inline(always)]
831    fn diff(&self, bits: &mut RevBits, err: &mut bool) -> i32 {
832        // One refill covers the code (<= 16 bits) and its magnitude bits (<= 16).
833        bits.fill();
834        let window = bits.cache;
835        let (len, sym) = self.lut.entries[(window >> (64 - self.lut.bits)) as usize];
836        if len == 0 {
837            *err = true;
838            bits.skip(1);
839            return 0;
840        }
841        let (d, s, neg, add) = self.sym[sym as usize];
842        let after = window << len;
843        bits.skip(len as u32 + d);
844        // The bit following the code decides the sign (JPEG-style), even when no
845        // magnitude bits are stored (shift-down >= length).
846        let positive = (after >> 63) == 1;
847        let raw = if d > 0 { (after >> (64 - d)) as i32 } else { 0 };
848        (raw << s) + if positive { 0 } else { neg } + add
849    }
850}
851
852/// A strip's stream as one 32-bit difference per symbol (two values: low, high half).
853struct V8Scan<'a> {
854    data: &'a [u8],
855    dec: &'a V8Decoder,
856    budget: u64,
857}
858
859impl crate::par_entropy::SymbolStream for V8Scan<'_> {
860    type Pump<'p>
861        = RevBits<'p>
862    where
863        Self: 'p;
864    const WIDTH: usize = 2;
865    fn cycle(&self) -> usize {
866        1
867    }
868    fn period(&self) -> usize {
869        1
870    }
871    fn total_bits(&self) -> u64 {
872        self.budget
873    }
874    fn end_check(&self, pos: u64) -> crate::par_entropy::EndCheck {
875        if pos <= self.budget { crate::par_entropy::EndCheck::Valid } else { crate::par_entropy::EndCheck::Invalid }
876    }
877    fn pump_at(&self, bit: u64) -> RevBits<'_> {
878        RevBits::at_bit(self.data, bit)
879    }
880    fn position(p: &RevBits<'_>) -> u64 {
881        p.consumed()
882    }
883    #[inline(always)]
884    fn decode(&self, p: &mut RevBits<'_>, _phase: usize, out: &mut [u16]) -> bool {
885        let mut err = false;
886        let d = self.dec.diff(p, &mut err);
887        out[0] = d as u32 as u16;
888        out[1] = (d as u32 >> 16) as u16;
889        !err
890    }
891}
892
893/// The format-8 tone curve from tags 0x39/0x3A (identity for every known file).
894fn gamma_curve(p: &V8Params, idx: u32) -> u16 {
895    let t3a = &p.gamma_points;
896    let t39 = &p.gamma_slopes;
897    let v2 = if idx & 0x10000 == 0 { idx & 0x1ffff } else { idx | 0xffff_0000 };
898    let mut v4 = v2.min(0xffff);
899    if v4 & 0x8000_0000 != 0 {
900        v4 = 0;
901    }
902    let mut seg = 0usize;
903    if v4 >= (t3a[1] & 0xffff) {
904        seg = 1;
905        if v4 >= (t3a[2] & 0xffff) {
906            seg = 2;
907            if v4 >= (t3a[3] & 0xffff) {
908                seg = 3;
909                if v4 >= (t3a[4] & 0xffff) {
910                    seg = (((v4 as u64 | 0x5_0000_0000) - (t3a[5] & 0xffff) as u64) >> 32) as usize;
911                }
912            }
913        }
914    }
915    let seg = seg.min(5);
916    let v6 = t3a[seg];
917    let v7 = t39[seg];
918    let mut v8 = v4.wrapping_sub(v6 & 0xffff);
919    let v9 = v7 & 0x1f;
920    let clip = p.clip;
921    let res: u64 = if v9 == 31 {
922        if seg == 5 { 0xffff } else { ((t3a[seg + 1] >> 16) & 0xffff) as u64 }
923    } else if v7 & 0x10 == 0 && v9 == 15 {
924        ((v6 >> 16) & 0xffff) as u64
925    } else {
926        if v7 & 0x10 == 0 {
927            if v9 != 0 {
928                v8 = (v8.wrapping_add(1 << (v9 - 1))) >> v9;
929            }
930        } else {
931            v8 = v8.wrapping_shl(v7 & 0xf);
932        }
933        v8 as u64 + ((v6 >> 16) & 0xffff) as u64
934    };
935    res.min(clip as u64) as u16
936}
937
938#[cfg(test)]
939mod tests {
940    use super::*;
941
942    fn pseudo(n: usize, seed: u64) -> Vec<u8> {
943        let mut x = seed;
944        (0..n)
945            .map(|_| {
946                x ^= x << 13;
947                x ^= x >> 7;
948                x ^= x << 17;
949                (x >> 24) as u8
950            })
951            .collect()
952    }
953
954    #[test]
955    fn blocked_stream_rotation() {
956        // Stream byte i of block 0 lives at file offset (i - split) mod BLOCK.
957        let data: Vec<u8> = (0..BLOCK).map(|i| (i % 251) as u8).collect();
958        let s = BlockedStream::new(&data, 0x2008).unwrap();
959        assert_eq!(s.packet(0)[0], data[BLOCK - 0x2008]);
960        assert_eq!(s.packet(0x2008 / 16 + 1)[0], data[8]);
961        // Packet straddling the split.
962        let p = s.packet(0x2000 / 16);
963        for (k, &v) in p.iter().enumerate() {
964            let i = 0x2000 + k;
965            assert_eq!(v, data[(i + BLOCK - 0x2008) % BLOCK]);
966        }
967    }
968
969    #[test]
970    fn v4_parallel_matches_sequential_reference() {
971        for (w, h, extra) in [(28usize, 9usize, 0usize), (14 * 37, 31, 100), (14 * 5, 4, 0)] {
972            let packets = (w * h).div_ceil(14);
973            let mut data = pseudo(packets * 16 + extra, w as u64);
974            // Truncate inside the last block to exercise the stale-buffer padding.
975            data.truncate(data.len() - extra.min(data.len() / 2));
976            let s = BlockedStream::new(&data, 0x2008).unwrap();
977            let mut a = vec![0u16; w * h];
978            let mut b = vec![0u16; w * h];
979            if decode_v4(&s, w, h, &mut a, true).is_ok() {
980                decode_v4_sequential(&s, w, h, &mut b).unwrap();
981                assert_eq!(a, b, "{w}x{h}");
982            }
983        }
984        // Width not divisible by 14 uses the sequential path.
985        let data = pseudo(4 * BLOCK, 7);
986        let s = BlockedStream::new(&data, 0x2008).unwrap();
987        let mut a = vec![0u16; 30 * 20];
988        decode_v4(&s, 30, 20, &mut a, true).unwrap();
989    }
990
991    #[test]
992    fn v4_packet_known_values() {
993        // A packet of zeros: nonz stays 0 until pixels 12/13, which then read 4 bits.
994        let mut out = [9u16; 14];
995        v4_packet(&[0; 16], &mut out);
996        assert_eq!(out, [0; 14]);
997        // First pixel: 8-bit nonzero (0x12), then 4 bits (0x3) -> 0x123.
998        let mut v: u128 = 0x12 << 120 | 0x3 << 116;
999        v |= 0x01 << 108; // pixel 1: nonz = 1, low = 0
1000        let mut out = [0u16; 14];
1001        v4_packet(&v.to_le_bytes(), &mut out);
1002        assert_eq!(out[0], 0x123);
1003        assert_eq!(out[1], 0x010);
1004    }
1005
1006    #[test]
1007    fn v5_v7_unpack_roundtrip() {
1008        for bits in [12u32, 14] {
1009            let ppp = packed_pixels(bits).unwrap();
1010            let (w, h) = (ppp * 3, 16);
1011            let vals: Vec<u16> = (0..w * h).map(|i| ((i * 2654435761) >> 7) as u16 & ((1 << bits) - 1)).collect();
1012            let mut data = Vec::new();
1013            for row in vals.chunks(w) {
1014                for px in row.chunks(ppp) {
1015                    let mut v: u128 = 0;
1016                    for (k, &p) in px.iter().enumerate() {
1017                        v |= (p as u128) << (bits as usize * k);
1018                    }
1019                    data.extend_from_slice(&v.to_le_bytes());
1020                }
1021            }
1022            let mut out = vec![0u16; w * h];
1023            decode_v7(&data, bits, w, h, &mut out, true).unwrap();
1024            assert_eq!(out, vals);
1025            // Format 5 with no rotation (split = 0) is the same packing.
1026            let s = BlockedStream::new(&data, 0).unwrap();
1027            let mut out5 = vec![0u16; w * h];
1028            decode_v5(&s, bits, w, h, &mut out5, false).unwrap();
1029            assert_eq!(out5, vals);
1030            assert!(decode_v7(&data[..data.len() / 2], bits, w, h, &mut out, true).is_err());
1031        }
1032    }
1033
1034    #[test]
1035    fn v6_does_not_panic_and_rejects_truncation() {
1036        for bits in [12u32, 14] {
1037            let (w, h) = (154usize, 32usize);
1038            let ppb = if bits == 12 { 14 } else { 11 };
1039            let data = pseudo(w / ppb * 16 * h, bits as u64);
1040            let mut out = vec![0u16; w * h];
1041            decode_v6(&data, bits, w, h, &mut out, true).unwrap();
1042            // Random input may saturate to the 16-bit (14-bit mode) or 14-bit (12-bit mode) mask.
1043            assert!(out.iter().all(|&v| (v as u32) < if bits == 12 { 1 << 14 } else { 1 << 16 }));
1044            assert!(decode_v6(&data[..100], bits, w, h, &mut out, true).is_err());
1045        }
1046    }
1047
1048    /// Reference format-8 encoder for tests: JPEG-style categories with codes
1049    /// assigned by symbol index.
1050    fn v8_encode(vals: &[u16], w: usize, h: usize, codes: &[(u8, u16)], init: [u16; 4]) -> Vec<u8> {
1051        let mut bitsv: Vec<bool> = Vec::new();
1052        let put = |v: u32, n: u32, bitsv: &mut Vec<bool>| {
1053            for k in (0..n).rev() {
1054                bitsv.push(v >> k & 1 == 1);
1055            }
1056        };
1057        let mut line_base = init.map(|v| v as i32);
1058        for pair in 0..h / 2 {
1059            let mut base = line_base;
1060            for bx in 0..w / 2 {
1061                let blk = [
1062                    vals[pair * 2 * w + 2 * bx] as i32,
1063                    vals[pair * 2 * w + 2 * bx + 1] as i32,
1064                    vals[(pair * 2 + 1) * w + 2 * bx] as i32,
1065                    vals[(pair * 2 + 1) * w + 2 * bx + 1] as i32,
1066                ];
1067                for slot in [0usize, 2, 1, 3] {
1068                    let d = blk[slot] - base[slot];
1069                    let cat = if d == 0 { 0 } else { 32 - d.unsigned_abs().leading_zeros() };
1070                    let (len, code) = codes[cat as usize];
1071                    put(code as u32, len as u32, &mut bitsv);
1072                    if cat > 0 {
1073                        let m = if d > 0 { d as u32 } else { (d + (1 << cat) - 1) as u32 };
1074                        put(m, cat, &mut bitsv);
1075                    }
1076                }
1077                base = blk;
1078                if bx == 0 {
1079                    line_base = blk;
1080                }
1081            }
1082        }
1083        let mut bytes = vec![0u8; bitsv.len().div_ceil(8) + 8];
1084        for (i, &b) in bitsv.iter().enumerate() {
1085            if b {
1086                // LSB-first within each byte.
1087                bytes[i / 8] |= 1 << (i % 8);
1088            }
1089        }
1090        bytes
1091    }
1092
1093    #[test]
1094    fn v8_roundtrip() {
1095        // Codes from a real file (lengths/codes per category 0..=16).
1096        let codes: Vec<(u8, u16)> = vec![
1097            (10, 1022),
1098            (11, 2046),
1099            (8, 254),
1100            (9, 510),
1101            (7, 126),
1102            (4, 14),
1103            (4, 12),
1104            (3, 4),
1105            (3, 2),
1106            (2, 0),
1107            (3, 3),
1108            (3, 5),
1109            (4, 13),
1110            (5, 30),
1111            (6, 62),
1112            (12, 4094),
1113            (12, 4095),
1114        ];
1115        let (w, h) = (24usize, 10usize);
1116        let vals: Vec<u16> = (0..w * h).map(|i| ((i * 7919) % 65536) as u16).collect();
1117        let data = v8_encode(&vals, w, h, &codes, [100, 200, 300, 400]);
1118        let p = V8Params {
1119            gamma_points: [0; 6],
1120            gamma_slopes: [65536; 6],
1121            clip: 65535,
1122            initial: [100, 200, 300, 400],
1123            codes: codes.clone(),
1124            shift_down: vec![0; 17],
1125        };
1126        let dec = V8Decoder::new(&p).unwrap();
1127        assert!(dec.gamma.is_none());
1128        let strip =
1129            V8Strip { data: &data, bits: (data.len() * 8) as u64, left: 0, top: 0, width: w as u32, height: h as u32 };
1130        let mut out = vec![0u16; w * h];
1131        dec.decode(&[strip], &mut out, w, h, true).unwrap();
1132        assert_eq!(out, vals);
1133        // Truncated strip.
1134        let short = V8Strip { data: &data[..data.len() / 3], bits: (data.len() / 3 * 8) as u64, ..strip };
1135        assert!(dec.decode(&[short], &mut out, w, h, true).is_err());
1136        // Speculative parallel strip decoding gives the same values for any chunking.
1137        for chunk in [3usize, 17, 64, 1 << 20] {
1138            let mut par = vec![0u16; w * h];
1139            let rows: Vec<&mut [u16]> = par.chunks_exact_mut(w).collect();
1140            assert!(dec.decode_strip_parallel(&strip, rows, Some(chunk)).unwrap().is_none(), "chunk {chunk}");
1141            assert_eq!(par, vals, "chunk {chunk}");
1142        }
1143    }
1144}