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