Skip to main content

oxideav_webp/
vp8l_decode.rs

1//! VP8L (WebP-Lossless) §5.2 LZ77 backward-reference + §5.2.3
2//! color-cache per-pixel ARGB decode loop.
3//!
4//! This is the layer that sits directly on top of round 106's
5//! [`crate::meta_prefix::PrefixCodeGroup`] reader. Round 106 assembled
6//! the five canonical prefix codes one §5 image-data block opens with;
7//! this module runs the §6.2.3 per-pixel decode loop that *consumes*
8//! symbols from that group, producing a fully-decoded ARGB pixel
9//! buffer.
10//!
11//! ## The §6.2.3 per-pixel dispatch
12//!
13//! For each pixel position `(x, y)` in scan-line order, the decoder
14//! reads one symbol `S` from prefix code #1 (the GREEN / length /
15//! color-cache alphabet, sized `256 + 24 + color_cache_size` per
16//! §6.2.3). `S` is interpreted by range:
17//!
18//! * **`S < 256`** — §5.2.1 prefix-coded literal. `S` is the green
19//!   channel; red, blue, and alpha are read from prefix codes #2, #3,
20//!   and #4. The emitted pixel is `ARGB = (alpha << 24) | (red << 16)
21//!   | (green << 8) | blue`.
22//! * **`256 <= S < 256 + 24`** — §5.2.2 LZ77 backward reference.
23//!   `S - 256` is a *length prefix code*; the §5.2.2 prefix → value
24//!   formula plus extra bits yields the copy length `L`. A *distance
25//!   prefix code* is then read from prefix code #5, decoded the same
26//!   way into a raw `distance_code`, and mapped through the §5.2.2
27//!   distance map into a scan-line distance `D`. `L` pixels are copied
28//!   from `D` pixels back.
29//! * **`S >= 256 + 24`** — §5.2.3 color-cache code. `S - (256 + 24)`
30//!   indexes the color cache; the cached ARGB is emitted.
31//!
32//! Every emitted pixel — literal, copied, or cache-resolved — is
33//! inserted into the color cache (when enabled) in stream order, per
34//! §5.2.3 ("the state of the color cache is maintained by inserting
35//! every pixel ... into the cache in the order they appear in the
36//! stream").
37//!
38//! ## §5.2.2 LZ77 prefix coding
39//!
40//! Length and distance both use the same prefix → value transform
41//! (§5.2.2):
42//!
43//! ```text
44//! if (prefix_code < 4) return prefix_code + 1;
45//! int extra_bits = (prefix_code - 2) >> 1;
46//! int offset = (2 + (prefix_code & 1)) << extra_bits;
47//! return offset + ReadBits(extra_bits) + 1;
48//! ```
49//!
50//! The §5.2.2 *distance map* maps the 120 smallest distance codes onto
51//! a close neighborhood of the current pixel; larger codes denote a
52//! scan-line distance offset by 120:
53//!
54//! ```text
55//! (xi, yi) = distance_map[distance_code - 1]
56//! dist = xi + yi * image_width
57//! if (dist < 1) dist = 1
58//! ```
59//!
60//! (for `distance_code > 120`, `dist = distance_code - 120`).
61//!
62//! ## What this module does NOT do
63//!
64//! * No §4 inverse-transform application — this decoder produces the
65//!   raw entropy-decoded ARGB stream, *before* any predictor /
66//!   color / subtract-green / color-indexing inverse pass. (Those
67//!   transforms operate on the buffer this loop produces.)
68//! * No `oxideav-core` runtime dependency — this module compiles under
69//!   `--no-default-features`.
70//!
71//! ## §6.2.2 multi-group (entropy-image) path
72//!
73//! [`decode_argb`] is the full ARGB-role decode: it consumes the
74//! round-106 [`crate::meta_prefix::MetaPrefixHeader`] result and, when
75//! the meta-prefix bit selected multiple groups, decodes the §6.2.2
76//! *entropy image* (itself a §5 `entropy-coded-image` of size
77//! `DIV_ROUND_UP(width, 1<<prefix_bits)` ×
78//! `DIV_ROUND_UP(height, 1<<prefix_bits)`), derives
79//! `num_prefix_groups = max(entropy image) + 1`, reads that many
80//! [`PrefixCodeGroup`]s, and runs the per-pixel §6.2.3 loop selecting a
81//! group per block via
82//! `meta_index[(y >> prefix_bits) * block_width + (x >> prefix_bits)]`.
83//! The single-group case (meta-prefix bit zero, or a non-ARGB role)
84//! degrades to the same primitives with one group used everywhere.
85//! Per §6.2.2 the per-pixel meta-prefix code is the red+green channels
86//! of the entropy-image pixel: `(entropy_pixel >> 8) & 0xffff`.
87
88use crate::meta_prefix::{
89    ImageRole, MetaPrefixCodes, MetaPrefixError, MetaPrefixHeader, PrefixCodeGroup,
90    PREFIX_BITS_MAX, PREFIX_BITS_MIN,
91};
92use crate::vp8l_prefix::PrefixError;
93use crate::vp8l_stream::{BitReader, BitReaderEof};
94
95/// §5.2.2: the number of "short" distance codes reserved for the close
96/// neighborhood. Codes `1..=120` map through [`DISTANCE_MAP`]; codes
97/// `> 120` denote a scan-line distance offset by 120.
98pub const NUM_DISTANCE_MAP_CODES: usize = 120;
99
100/// §5.2.2 the maximum number of *length* prefix codes that are
101/// meaningful (`256..256+24`). The GREEN alphabet reserves 24 slots
102/// for these.
103pub const NUM_LENGTH_PREFIX_CODES: usize = 24;
104
105/// §5.2.2 distance map: the `(xi, yi)` neighbor offset for distance
106/// codes `1..=120` (indexed `distance_code - 1`). Transcribed from the
107/// spec's §5.2.2 "Distance Mapping" listing.
108pub const DISTANCE_MAP: [(i32, i32); NUM_DISTANCE_MAP_CODES] = [
109    (0, 1),
110    (1, 0),
111    (1, 1),
112    (-1, 1),
113    (0, 2),
114    (2, 0),
115    (1, 2),
116    (-1, 2),
117    (2, 1),
118    (-2, 1),
119    (2, 2),
120    (-2, 2),
121    (0, 3),
122    (3, 0),
123    (1, 3),
124    (-1, 3),
125    (3, 1),
126    (-3, 1),
127    (2, 3),
128    (-2, 3),
129    (3, 2),
130    (-3, 2),
131    (0, 4),
132    (4, 0),
133    (1, 4),
134    (-1, 4),
135    (4, 1),
136    (-4, 1),
137    (3, 3),
138    (-3, 3),
139    (2, 4),
140    (-2, 4),
141    (4, 2),
142    (-4, 2),
143    (0, 5),
144    (3, 4),
145    (-3, 4),
146    (4, 3),
147    (-4, 3),
148    (5, 0),
149    (1, 5),
150    (-1, 5),
151    (5, 1),
152    (-5, 1),
153    (2, 5),
154    (-2, 5),
155    (5, 2),
156    (-5, 2),
157    (4, 4),
158    (-4, 4),
159    (3, 5),
160    (-3, 5),
161    (5, 3),
162    (-5, 3),
163    (0, 6),
164    (6, 0),
165    (1, 6),
166    (-1, 6),
167    (6, 1),
168    (-6, 1),
169    (2, 6),
170    (-2, 6),
171    (6, 2),
172    (-6, 2),
173    (4, 5),
174    (-4, 5),
175    (5, 4),
176    (-5, 4),
177    (3, 6),
178    (-3, 6),
179    (6, 3),
180    (-6, 3),
181    (0, 7),
182    (7, 0),
183    (1, 7),
184    (-1, 7),
185    (5, 5),
186    (-5, 5),
187    (7, 1),
188    (-7, 1),
189    (4, 6),
190    (-4, 6),
191    (6, 4),
192    (-6, 4),
193    (2, 7),
194    (-2, 7),
195    (7, 2),
196    (-7, 2),
197    (3, 7),
198    (-3, 7),
199    (7, 3),
200    (-7, 3),
201    (5, 6),
202    (-5, 6),
203    (6, 5),
204    (-6, 5),
205    (8, 0),
206    (4, 7),
207    (-4, 7),
208    (7, 4),
209    (-7, 4),
210    (8, 1),
211    (8, 2),
212    (6, 6),
213    (-6, 6),
214    (8, 3),
215    (5, 7),
216    (-5, 7),
217    (7, 5),
218    (-7, 5),
219    (8, 4),
220    (6, 7),
221    (-6, 7),
222    (7, 6),
223    (-7, 6),
224    (8, 5),
225    (7, 7),
226    (-7, 7),
227    (8, 6),
228    (8, 7),
229];
230
231/// §5.2.3 color-cache hash multiplier. Specified verbatim in §5.2.3:
232/// "Colors are looked up by indexing them by `(0x1e35a7bd * color) >>
233/// (32 - color_cache_code_bits)`."
234pub const COLOR_CACHE_HASH_MULTIPLIER: u32 = 0x1e35_a7bd;
235
236/// Eager-reservation ceiling for the §5.2 / §6.2.2 per-pixel ARGB
237/// decode buffers, in pixels.
238///
239/// The §3.4 image-header's 14-bit `width - 1` / `height - 1` fields can
240/// declare up to `16384 × 16384 ≈ 2.7e8` pixels — a 1 GiB `Vec<u32>` —
241/// while the carrying §2.6 `VP8L` chunk is only a few dozen bytes. The
242/// declared `width * height` therefore cannot be trusted to pre-size the
243/// output buffer: a tiny truncated chunk that declares a huge canvas
244/// would force an eager multi-hundred-MiB allocation *before* the decode
245/// loop reads a single symbol and discovers the stream is exhausted.
246///
247/// The decode loop is self-terminating — every symbol read goes through
248/// the EOF-checked [`crate::vp8l_stream::BitReader`], and a truncated
249/// stream raises [`DecodeError::Eof`] long before `width * height` pixels
250/// are emitted — so the full `total_pixels` reservation is only an
251/// allocation *optimization*, not a correctness requirement. Capping the
252/// initial reservation at this ceiling keeps that optimization for
253/// normally-sized images while letting the `Vec` grow on demand for a
254/// legitimately large one, so a malformed header can no longer drive an
255/// unbounded eager allocation. `1 << 22 = 4_194_304` pixels (16 MiB of
256/// `u32`) comfortably covers any realistic single still image without
257/// trusting the header's worst case.
258const MAX_EAGER_PIXEL_RESERVATION: usize = 1 << 22;
259
260/// Initial `Vec<u32>` capacity for a `total_pixels`-sized decode buffer,
261/// capped at [`MAX_EAGER_PIXEL_RESERVATION`] so a §3.4-header-declared but
262/// unbacked pixel count cannot force an unbounded eager allocation. The
263/// `Vec` still grows to `total_pixels` if the bitstream legitimately
264/// produces them.
265fn eager_pixel_capacity(total_pixels: usize) -> usize {
266    total_pixels.min(MAX_EAGER_PIXEL_RESERVATION)
267}
268
269/// Errors raised while decoding the §5.2 per-pixel ARGB stream.
270#[derive(Debug, Clone, PartialEq, Eq)]
271pub enum DecodeError {
272    /// The bit reader hit EOF mid-symbol or mid-extra-bits.
273    Eof(BitReaderEof),
274    /// A prefix-code symbol read failed.
275    Prefix(PrefixError),
276    /// §6.2.3: the GREEN symbol `S` was at or beyond the alphabet size
277    /// `256 + 24 + color_cache_size`.
278    GreenSymbolOutOfRange {
279        /// The offending symbol value.
280        symbol: usize,
281        /// The GREEN alphabet size.
282        alphabet_size: usize,
283    },
284    /// §5.2.3: a color-cache code resolved to an index outside the
285    /// cache, or a color-cache code was read for a stream with no
286    /// color cache.
287    ColorCacheIndexOutOfRange {
288        /// The resolved cache index.
289        index: usize,
290        /// The configured cache size (`0` when the cache is disabled).
291        cache_size: usize,
292    },
293    /// §5.2.2: a backward-reference copy reached back beyond the start
294    /// of the already-decoded pixels.
295    BackwardReferenceUnderflow {
296        /// The pixel position the copy was attempted at.
297        position: usize,
298        /// The scan-line distance `D` that underflowed.
299        distance: usize,
300    },
301    /// §5.2.2: a backward-reference run would write past the end of the
302    /// image (more pixels than remain).
303    BackwardReferenceOverflow {
304        /// The pixel position the copy started at.
305        position: usize,
306        /// The copy length `L`.
307        length: usize,
308        /// The total number of pixels in the image.
309        total_pixels: usize,
310    },
311    /// §5.2.3 / §6.2.2: reading the color-cache info, meta-prefix bit,
312    /// the entropy-image header, or one of the per-group prefix-code
313    /// groups failed.
314    MetaPrefix(MetaPrefixError),
315    /// §6.2.2: the entropy image's `prefix_image_width` /
316    /// `prefix_image_height` were zero (a degenerate header that yields
317    /// no blocks to select groups for).
318    EmptyEntropyImage {
319        /// The derived entropy-image width.
320        prefix_image_width: u32,
321        /// The derived entropy-image height.
322        prefix_image_height: u32,
323    },
324    /// §6.2.2: a pixel's block selected a meta-prefix code at or beyond
325    /// `num_prefix_groups` — i.e. an index larger than the maximum the
326    /// entropy image was supposed to contain. This can only arise from a
327    /// logic error in the caller, but is checked rather than panicking.
328    MetaPrefixIndexOutOfRange {
329        /// The selected meta-prefix code.
330        meta_prefix_code: usize,
331        /// The number of prefix-code groups that were read.
332        num_prefix_groups: usize,
333    },
334    /// §4 / §7.2: a transform type appeared more than once in the
335    /// `optional-transform` list. The spec mandates each transform is
336    /// used at most once.
337    DuplicateTransform,
338}
339
340impl From<BitReaderEof> for DecodeError {
341    fn from(e: BitReaderEof) -> Self {
342        Self::Eof(e)
343    }
344}
345
346impl From<PrefixError> for DecodeError {
347    fn from(e: PrefixError) -> Self {
348        match e {
349            PrefixError::Eof(eof) => Self::Eof(eof),
350            other => Self::Prefix(other),
351        }
352    }
353}
354
355impl From<MetaPrefixError> for DecodeError {
356    fn from(e: MetaPrefixError) -> Self {
357        match e {
358            MetaPrefixError::Eof(eof) => Self::Eof(eof),
359            other => Self::MetaPrefix(other),
360        }
361    }
362}
363
364impl core::fmt::Display for DecodeError {
365    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
366        match self {
367            Self::Eof(e) => write!(f, "VP8L §5.2 image decode: {e}"),
368            Self::Prefix(e) => write!(f, "VP8L §5.2 image decode prefix code: {e}"),
369            Self::GreenSymbolOutOfRange {
370                symbol,
371                alphabet_size,
372            } => write!(
373                f,
374                "VP8L §6.2.3 image decode: GREEN symbol {symbol} out of range for alphabet size {alphabet_size}"
375            ),
376            Self::ColorCacheIndexOutOfRange { index, cache_size } => write!(
377                f,
378                "VP8L §5.2.3 image decode: color-cache index {index} out of range for cache size {cache_size}"
379            ),
380            Self::BackwardReferenceUnderflow { position, distance } => write!(
381                f,
382                "VP8L §5.2.2 image decode: backward reference distance {distance} underflows at pixel {position}"
383            ),
384            Self::BackwardReferenceOverflow {
385                position,
386                length,
387                total_pixels,
388            } => write!(
389                f,
390                "VP8L §5.2.2 image decode: backward reference length {length} at pixel {position} overruns the {total_pixels}-pixel image"
391            ),
392            Self::MetaPrefix(e) => write!(f, "VP8L §6.2.2 image decode meta-prefix: {e}"),
393            Self::EmptyEntropyImage {
394                prefix_image_width,
395                prefix_image_height,
396            } => write!(
397                f,
398                "VP8L §6.2.2 image decode: degenerate entropy image of size {prefix_image_width}x{prefix_image_height}"
399            ),
400            Self::MetaPrefixIndexOutOfRange {
401                meta_prefix_code,
402                num_prefix_groups,
403            } => write!(
404                f,
405                "VP8L §6.2.2 image decode: meta-prefix code {meta_prefix_code} out of range for {num_prefix_groups} prefix-code group(s)"
406            ),
407            Self::DuplicateTransform => write!(
408                f,
409                "VP8L §4 transform list: a transform type appears more than once"
410            ),
411        }
412    }
413}
414
415impl std::error::Error for DecodeError {}
416
417/// §5.2.2: turn a length/distance *prefix code* plus its extra bits
418/// into the decoded value.
419///
420/// The transform is shared between length and distance per the
421/// §5.2.2 pseudocode:
422///
423/// ```text
424/// if (prefix_code < 4) return prefix_code + 1;
425/// int extra_bits = (prefix_code - 2) >> 1;
426/// int offset = (2 + (prefix_code & 1)) << extra_bits;
427/// return offset + ReadBits(extra_bits) + 1;
428/// ```
429pub fn read_lz77_value(reader: &mut BitReader<'_>, prefix_code: u32) -> Result<u32, BitReaderEof> {
430    if prefix_code < 4 {
431        return Ok(prefix_code + 1);
432    }
433    let extra_bits = (prefix_code - 2) >> 1;
434    let offset = (2 + (prefix_code & 1)) << extra_bits;
435    let extra = reader.read_bits(extra_bits as usize)?;
436    Ok(offset + extra + 1)
437}
438
439/// §5.2.3 color cache: a fixed-size array of recently-emitted ARGB
440/// colors, indexed by a multiplicative hash of the color.
441#[derive(Debug, Clone, PartialEq, Eq)]
442pub struct ColorCache {
443    /// `color_cache_code_bits`; the table holds `1 << bits` entries.
444    code_bits: u32,
445    entries: Vec<u32>,
446}
447
448impl ColorCache {
449    /// Create a color cache holding `1 << code_bits` zero-initialized
450    /// ARGB entries (§5.2.3: "all entries in all color cache values are
451    /// set to zero").
452    pub fn new(code_bits: u32) -> Self {
453        let size = 1usize << code_bits;
454        Self {
455            code_bits,
456            entries: vec![0u32; size],
457        }
458    }
459
460    /// The number of entries in the cache (`1 << code_bits`).
461    pub fn size(&self) -> usize {
462        self.entries.len()
463    }
464
465    /// §5.2.3 hash: `(0x1e35a7bd * argb) >> (32 - code_bits)`.
466    pub fn hash(&self, argb: u32) -> usize {
467        (COLOR_CACHE_HASH_MULTIPLIER.wrapping_mul(argb) >> (32 - self.code_bits)) as usize
468    }
469
470    /// Insert `argb` into the cache at its hashed slot (§5.2.3: every
471    /// emitted pixel is inserted in stream order).
472    pub fn insert(&mut self, argb: u32) {
473        let idx = self.hash(argb);
474        self.entries[idx] = argb;
475    }
476
477    /// Look up the ARGB color stored at `index` (a resolved
478    /// color-cache code, `S - (256 + 24)`).
479    pub fn lookup(&self, index: usize) -> Option<u32> {
480        self.entries.get(index).copied()
481    }
482}
483
484/// The result of a §5.2 single-group ARGB decode: the raw,
485/// pre-inverse-transform ARGB pixel buffer in scan-line order.
486#[derive(Debug, Clone, PartialEq, Eq)]
487pub struct DecodedImage {
488    width: u32,
489    height: u32,
490    /// `width * height` ARGB pixels, scan-line order. Each pixel is
491    /// `(alpha << 24) | (red << 16) | (green << 8) | blue`.
492    pixels: Vec<u32>,
493}
494
495impl DecodedImage {
496    /// Image width in pixels.
497    pub fn width(&self) -> u32 {
498        self.width
499    }
500
501    /// Image height in pixels.
502    pub fn height(&self) -> u32 {
503        self.height
504    }
505
506    /// The decoded ARGB pixels in scan-line order.
507    pub fn pixels(&self) -> &[u32] {
508        &self.pixels
509    }
510
511    /// The decoded ARGB pixels in scan-line order, mutably. Used by the
512    /// §4 inverse-transform passes ([`crate::vp8l_transform`]), which
513    /// rewrite the buffer in place.
514    pub fn pixels_mut(&mut self) -> &mut [u32] {
515        &mut self.pixels
516    }
517
518    /// Construct a [`DecodedImage`] from explicit dimensions and a
519    /// scan-line-order ARGB buffer. Used by the §4 color-indexing
520    /// inverse transform, which replaces the (subsampled) decoded buffer
521    /// with a freshly-sized palette-lookup buffer.
522    pub fn from_parts(width: u32, height: u32, pixels: Vec<u32>) -> Self {
523        Self {
524            width,
525            height,
526            pixels,
527        }
528    }
529}
530
531/// §6.2.3 GREEN-channel symbol-type tag, split out of the integer
532/// dispatch so it can be unit-tested in isolation.
533#[derive(Debug, Clone, Copy, PartialEq, Eq)]
534pub enum GreenSymbol {
535    /// `S < 256`: a §5.2.1 prefix-coded literal; the value is the green
536    /// channel.
537    Literal {
538        /// The green channel value (`S`).
539        green: u8,
540    },
541    /// `256 <= S < 256 + 24`: a §5.2.2 LZ77 length prefix code; the
542    /// value is `S - 256`.
543    LengthPrefix {
544        /// The length prefix code (`S - 256`, range `0..24`).
545        prefix_code: u32,
546    },
547    /// `S >= 256 + 24`: a §5.2.3 color-cache code; the value is the
548    /// cache index `S - (256 + 24)`.
549    ColorCache {
550        /// The cache index (`S - 280`).
551        index: usize,
552    },
553}
554
555impl GreenSymbol {
556    /// §6.2.3: classify a GREEN symbol `S` by range. `alphabet_size` is
557    /// the GREEN alphabet size (`256 + 24 + color_cache_size`); a
558    /// symbol at or beyond it is invalid.
559    pub fn classify(symbol: usize, alphabet_size: usize) -> Result<Self, DecodeError> {
560        if symbol >= alphabet_size {
561            return Err(DecodeError::GreenSymbolOutOfRange {
562                symbol,
563                alphabet_size,
564            });
565        }
566        if symbol < 256 {
567            Ok(Self::Literal {
568                green: symbol as u8,
569            })
570        } else if symbol < 256 + NUM_LENGTH_PREFIX_CODES {
571            Ok(Self::LengthPrefix {
572                prefix_code: (symbol - 256) as u32,
573            })
574        } else {
575            Ok(Self::ColorCache {
576                index: symbol - (256 + NUM_LENGTH_PREFIX_CODES),
577            })
578        }
579    }
580}
581
582/// §5.2.2: convert a raw `distance_code` to a scan-line pixel distance
583/// `D`, given the image width.
584///
585/// Codes `1..=120` index the §5.2.2 distance map; codes `> 120` denote
586/// a scan-line distance offset by 120. The result is clamped to a
587/// minimum of 1 per the spec's `if (dist < 1) dist = 1`.
588pub fn distance_code_to_pixel_distance(distance_code: u32, image_width: u32) -> usize {
589    if distance_code > NUM_DISTANCE_MAP_CODES as u32 {
590        return (distance_code - NUM_DISTANCE_MAP_CODES as u32) as usize;
591    }
592    // distance_code is in 1..=120 here.
593    let (xi, yi) = DISTANCE_MAP[(distance_code - 1) as usize];
594    let dist = xi + yi * image_width as i32;
595    if dist < 1 {
596        1
597    } else {
598        dist as usize
599    }
600}
601
602/// §5.2.2: assemble a single backward-reference run into the decoded
603/// pixel buffer.
604///
605/// `pixels` holds the already-emitted scan-line-order ARGB pixels; the
606/// run starts at `position == pixels.len()`. `length` is the §5.2.2
607/// length `L` and `dist` is the §5.2.2 scan-line pixel distance `D`
608/// (as produced by [`distance_code_to_pixel_distance`]). `total_pixels`
609/// is the image's pixel count, used for the overrun guard.
610///
611/// Two §5.2.2 carrier invariants are enforced **before** any byte is
612/// written, so a refused run leaves `pixels` untouched:
613///
614/// * **Underflow** (`dist > position`): the copy source `position -
615///   dist` would reach back past the start of the decoded pixels.
616///   Refused with [`DecodeError::BackwardReferenceUnderflow`].
617/// * **Overflow** (`position + length > total_pixels`): the run would
618///   write more pixels than the image holds. Refused with
619///   [`DecodeError::BackwardReferenceOverflow`].
620///
621/// # Precondition
622///
623/// `dist` must be `>= 1`. Per §5.2.2 the scan-line pixel distance `D`
624/// is always positive — [`distance_code_to_pixel_distance`] applies the
625/// §5.2.2 `if (dist < 1) dist = 1` clamp before this function is reached
626/// — so a real §5.2 decode never passes `dist == 0`. A `dist` of `0`
627/// would attempt to read the not-yet-written pixel at `position` and
628/// panic; the underflow guard (`dist > position`) does not cover it.
629///
630/// On success exactly `length` pixels are appended. The copy is the
631/// standard byte-for-byte LZ77 walk: a run with `dist < length`
632/// overlaps the pixels it is itself emitting, so the just-copied
633/// pixels repeat — each source index is read *after* the preceding
634/// appends, never from a pre-snapshot. The indices of the newly
635/// appended pixels are returned as `position..position + length` so the
636/// caller can replay them into the §5.2.3 color cache in stream order
637/// (every emitted pixel — including a back-reference copy — is
638/// re-inserted per §5.2.3).
639pub fn apply_backward_reference(
640    pixels: &mut Vec<u32>,
641    length: usize,
642    dist: usize,
643    total_pixels: usize,
644) -> Result<core::ops::Range<usize>, DecodeError> {
645    let position = pixels.len();
646    if dist > position {
647        return Err(DecodeError::BackwardReferenceUnderflow {
648            position,
649            distance: dist,
650        });
651    }
652    if position + length > total_pixels {
653        return Err(DecodeError::BackwardReferenceOverflow {
654            position,
655            length,
656            total_pixels,
657        });
658    }
659    let src_start = position - dist;
660    for i in 0..length {
661        let argb = pixels[src_start + i];
662        pixels.push(argb);
663    }
664    Ok(position..position + length)
665}
666
667/// Assemble an ARGB pixel from its four channels (§6.2.3 / §5.2.1):
668/// `(alpha << 24) | (red << 16) | (green << 8) | blue`.
669fn pack_argb(green: u8, red: u8, blue: u8, alpha: u8) -> u32 {
670    ((alpha as u32) << 24) | ((red as u32) << 16) | ((green as u32) << 8) | (blue as u32)
671}
672
673/// Decode the single §6.2.3 symbol at the current position using
674/// `group`, appending the emitted pixel(s) to `pixels` and (when
675/// present) maintaining `color_cache` in stream order.
676///
677/// This is the per-symbol core shared by the single-group
678/// [`decode_image`] loop and the multi-group [`decode_argb`] loop. The
679/// only difference between the two callers is *which* group they pass
680/// for the symbol at the current pixel; the literal / LZ77 / color-cache
681/// dispatch below is identical (the spec switches the group per pixel in
682/// §6.2.2 but the §6.2.3 decode of each pixel is role-independent).
683///
684/// `width` is the image width (needed for the §5.2.2 distance map) and
685/// `total_pixels` is its pixel count (needed for the overrun check).
686/// `alphabet_size` and `cache_size` are derived once by the caller.
687#[allow(clippy::too_many_arguments)]
688fn decode_one_symbol(
689    reader: &mut BitReader<'_>,
690    group: &PrefixCodeGroup,
691    color_cache: &mut Option<ColorCache>,
692    pixels: &mut Vec<u32>,
693    width: u32,
694    total_pixels: usize,
695    alphabet_size: usize,
696    cache_size: usize,
697) -> Result<(), DecodeError> {
698    let s = group.green.read_symbol(reader)? as usize;
699    match GreenSymbol::classify(s, alphabet_size)? {
700        GreenSymbol::Literal { green } => {
701            // §5.2.1: green came from prefix #1 (already read as S);
702            // read red / blue / alpha from prefix #2 / #3 / #4.
703            let red = group.red.read_symbol(reader)? as u8;
704            let blue = group.blue.read_symbol(reader)? as u8;
705            let alpha = group.alpha.read_symbol(reader)? as u8;
706            let argb = pack_argb(green, red, blue, alpha);
707            pixels.push(argb);
708            if let Some(cache) = color_cache.as_mut() {
709                cache.insert(argb);
710            }
711        }
712        GreenSymbol::LengthPrefix { prefix_code } => {
713            // §5.2.2: length L from the length prefix code + extra.
714            let length = read_lz77_value(reader, prefix_code)? as usize;
715            // Distance: prefix code #5 → raw distance_code → map.
716            let dist_prefix = group.distance.read_symbol(reader)? as u32;
717            let distance_code = read_lz77_value(reader, dist_prefix)?;
718            let dist = distance_code_to_pixel_distance(distance_code, width);
719            // §5.2.2: copy L pixels from `position - dist` (bounds checked
720            // inside). The copy is byte-for-byte scan-line, and
721            // overlapping copies (dist < length) repeat the just-copied
722            // pixels, which is the standard LZ77 behaviour.
723            let emitted = apply_backward_reference(pixels, length, dist, total_pixels)?;
724            if let Some(cache) = color_cache.as_mut() {
725                // §5.2.3: every emitted pixel (even a back-reference copy)
726                // is re-inserted in stream order.
727                for i in emitted {
728                    cache.insert(pixels[i]);
729                }
730            }
731        }
732        GreenSymbol::ColorCache { index } => {
733            // §5.2.3: resolve the cache index to its ARGB.
734            let cache = color_cache
735                .as_mut()
736                .ok_or(DecodeError::ColorCacheIndexOutOfRange {
737                    index,
738                    cache_size: 0,
739                })?;
740            let argb = cache
741                .lookup(index)
742                .ok_or(DecodeError::ColorCacheIndexOutOfRange { index, cache_size })?;
743            pixels.push(argb);
744            // §5.2.3: every emitted pixel (even a cache hit) is
745            // re-inserted in stream order.
746            cache.insert(argb);
747        }
748    }
749    Ok(())
750}
751
752/// Decode one §5.2 entropy-coded ARGB image with a single
753/// [`PrefixCodeGroup`].
754///
755/// `reader` must be positioned at the start of the §5.2 `data`
756/// (the `lz77-coded-image`) — i.e. right after the color-cache info,
757/// meta-prefix bit, and the prefix-code group the round-106
758/// [`crate::meta_prefix::MetaPrefixHeader`] reader consumed.
759///
760/// `color_cache` is `Some` when the §5.2.3 color cache is enabled for
761/// this stream (its `code_bits` matching the
762/// [`crate::meta_prefix::ColorCacheInfo`]), `None` otherwise. The
763/// cache size feeds the GREEN alphabet size (`256 + 24 +
764/// color_cache_size`).
765///
766/// Returns a [`DecodedImage`] holding `width * height` ARGB pixels in
767/// scan-line order, before any §4 inverse transform.
768pub fn decode_image(
769    reader: &mut BitReader<'_>,
770    group: &PrefixCodeGroup,
771    mut color_cache: Option<ColorCache>,
772    width: u32,
773    height: u32,
774) -> Result<DecodedImage, DecodeError> {
775    let cache_size = color_cache.as_ref().map(|c| c.size()).unwrap_or(0);
776    let alphabet_size = PrefixCodeGroup::green_alphabet_size(cache_size);
777    let total_pixels = (width as usize) * (height as usize);
778    // §3.4 dimensions can declare far more pixels than the §2.6 chunk
779    // can back; cap the eager reservation so a malformed header cannot
780    // force an unbounded allocation before the EOF-checked decode loop
781    // runs. The `Vec` still grows to `total_pixels` on demand.
782    let mut pixels: Vec<u32> = Vec::with_capacity(eager_pixel_capacity(total_pixels));
783
784    while pixels.len() < total_pixels {
785        decode_one_symbol(
786            reader,
787            group,
788            &mut color_cache,
789            &mut pixels,
790            width,
791            total_pixels,
792            alphabet_size,
793            cache_size,
794        )?;
795    }
796
797    Ok(DecodedImage {
798        width,
799        height,
800        pixels,
801    })
802}
803
804/// Errors raised by [`MetaPrefixIndex::from_parts`] when the supplied
805/// parts cannot describe a §6.2.2 entropy-image index.
806#[derive(Debug, Clone, PartialEq, Eq)]
807pub enum MetaPrefixIndexError {
808    /// §6.2.2: `prefix_bits` is outside `[2, 9]`. The on-wire field is
809    /// `prefix_bits = ReadBits(3) + 2`, so a 3-bit read can only reach
810    /// that window.
811    InvalidPrefixBits {
812        /// The rejected `prefix_bits` value.
813        prefix_bits: u8,
814    },
815    /// §6.2.2: the index covers zero blocks (`block_width` or
816    /// `block_height` is zero). The dimensions derive from
817    /// `DIV_ROUND_UP(image_dim, 1 << prefix_bits)` of an ARGB image
818    /// whose dimensions are at least 1, so each is always at least 1.
819    EmptyIndex {
820        /// The rejected block-grid width.
821        block_width: u32,
822        /// The rejected block-grid height.
823        block_height: u32,
824    },
825    /// §6.2.2: `meta_codes` does not hold exactly `block_width *
826    /// block_height` entries — one meta-prefix code per entropy-image
827    /// block, in scan-line order.
828    CodeCountMismatch {
829        /// The block-grid width.
830        block_width: u32,
831        /// The block-grid height.
832        block_height: u32,
833        /// `block_width * block_height`.
834        expected: u64,
835        /// `meta_codes.len()`.
836        got: usize,
837    },
838}
839
840impl core::fmt::Display for MetaPrefixIndexError {
841    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
842        match self {
843            Self::InvalidPrefixBits { prefix_bits } => write!(
844                f,
845                "VP8L §6.2.2 meta-prefix index: prefix_bits = {prefix_bits} is outside the [{PREFIX_BITS_MIN}..{PREFIX_BITS_MAX}] window of `ReadBits(3) + 2`"
846            ),
847            Self::EmptyIndex {
848                block_width,
849                block_height,
850            } => write!(
851                f,
852                "VP8L §6.2.2 meta-prefix index: degenerate {block_width}x{block_height} block grid covers zero blocks"
853            ),
854            Self::CodeCountMismatch {
855                block_width,
856                block_height,
857                expected,
858                got,
859            } => write!(
860                f,
861                "VP8L §6.2.2 meta-prefix index: {got} meta-prefix code(s) supplied for a {block_width}x{block_height} block grid ({expected} expected)"
862            ),
863        }
864    }
865}
866
867impl std::error::Error for MetaPrefixIndexError {}
868
869/// §6.2.2 per-pixel meta-prefix index: the decoded entropy image folded
870/// into the one number each pixel block needs — its meta-prefix code,
871/// i.e. the index into the array of [`PrefixCodeGroup`]s.
872///
873/// The entropy image is a sub-resolution image of size
874/// `prefix_image_width × prefix_image_height` where each pixel covers a
875/// `(1 << prefix_bits) × (1 << prefix_bits)` block of the full ARGB
876/// image. Per §6.2.2 the meta-prefix code for a block is the red+green
877/// channels of the corresponding entropy-image pixel:
878/// `(entropy_pixel >> 8) & 0xffff`.
879#[derive(Debug, Clone, PartialEq, Eq)]
880pub struct MetaPrefixIndex {
881    /// §6.2.2 `prefix_bits` (`ReadBits(3) + 2`); a block is
882    /// `(1 << prefix_bits)` pixels wide and tall.
883    prefix_bits: u8,
884    /// `DIV_ROUND_UP(image_width, 1 << prefix_bits)`.
885    block_width: u32,
886    /// `DIV_ROUND_UP(image_height, 1 << prefix_bits)`.
887    block_height: u32,
888    /// `block_width * block_height` meta-prefix codes, scan-line order.
889    meta_codes: Vec<u16>,
890}
891
892impl MetaPrefixIndex {
893    /// Build a §6.2.2 meta-prefix index directly from its parts.
894    ///
895    /// [`decode_entropy_image`] produces this index by decoding the
896    /// §6.2.2 entropy image off a bitstream; this constructor is the
897    /// standalone equivalent for callers that already hold the decoded
898    /// per-block meta-prefix codes (`meta_codes` in scan-line order,
899    /// each entry the `(entropy_pixel >> 8) & 0xffff` red+green fold of
900    /// one entropy-image pixel). It validates the §6.2.2 carrier
901    /// invariants the bitstream path establishes by construction, in
902    /// this order (the first violated invariant is reported):
903    ///
904    /// 1. `prefix_bits ∈ [2, 9]` — the on-wire field is
905    ///    `prefix_bits = ReadBits(3) + 2` (§6.2.2), so only that window
906    ///    is reachable from a bitstream.
907    /// 2. `block_width >= 1 && block_height >= 1` — §6.2.2 derives the
908    ///    entropy-image dimensions as
909    ///    `DIV_ROUND_UP(image_width, 1 << prefix_bits)` (resp. height)
910    ///    of an ARGB image whose dimensions are at least 1, so a
911    ///    zero-block grid cannot arise.
912    /// 3. `meta_codes.len() == block_width * block_height` — one
913    ///    meta-prefix code per entropy-image block.
914    ///
915    /// On success, [`MetaPrefixIndex::meta_code_for`] resolves the
916    /// §6.2.2 group selection for any pixel `(x, y)` of an image whose
917    /// dimensions satisfy the `DIV_ROUND_UP` derivation above — i.e.
918    /// `x < block_width << prefix_bits` and
919    /// `y < block_height << prefix_bits`. Querying outside that covered
920    /// area is a caller logic error (the §6.2.2 position formula only
921    /// defines blocks inside the grid).
922    pub fn from_parts(
923        prefix_bits: u8,
924        block_width: u32,
925        block_height: u32,
926        meta_codes: Vec<u16>,
927    ) -> Result<Self, MetaPrefixIndexError> {
928        if !(PREFIX_BITS_MIN..=PREFIX_BITS_MAX).contains(&(prefix_bits as u32)) {
929            return Err(MetaPrefixIndexError::InvalidPrefixBits { prefix_bits });
930        }
931        if block_width == 0 || block_height == 0 {
932            return Err(MetaPrefixIndexError::EmptyIndex {
933                block_width,
934                block_height,
935            });
936        }
937        let expected = block_width as u64 * block_height as u64;
938        if meta_codes.len() as u64 != expected {
939            return Err(MetaPrefixIndexError::CodeCountMismatch {
940                block_width,
941                block_height,
942                expected,
943                got: meta_codes.len(),
944            });
945        }
946        Ok(Self {
947            prefix_bits,
948            block_width,
949            block_height,
950            meta_codes,
951        })
952    }
953
954    /// §6.2.2 `prefix_bits`.
955    pub fn prefix_bits(&self) -> u8 {
956        self.prefix_bits
957    }
958
959    /// The entropy-image width in blocks (`prefix_image_width`).
960    pub fn block_width(&self) -> u32 {
961        self.block_width
962    }
963
964    /// The entropy-image height in blocks (`prefix_image_height`).
965    pub fn block_height(&self) -> u32 {
966        self.block_height
967    }
968
969    /// The per-block meta-prefix codes in scan-line order.
970    pub fn meta_codes(&self) -> &[u16] {
971        &self.meta_codes
972    }
973
974    /// §6.2.2 `num_prefix_groups = max(entropy image) + 1`. With no
975    /// blocks this is `0`; callers reject that as a degenerate header.
976    pub fn num_prefix_groups(&self) -> usize {
977        self.meta_codes
978            .iter()
979            .copied()
980            .max()
981            .map(|m| m as usize + 1)
982            .unwrap_or(0)
983    }
984
985    /// §6.2.2 group selection for pixel `(x, y)`:
986    /// `meta_index[(y >> prefix_bits) * block_width + (x >> prefix_bits)]`.
987    pub fn meta_code_for(&self, x: u32, y: u32) -> u16 {
988        let bx = x >> self.prefix_bits;
989        let by = y >> self.prefix_bits;
990        let position = (by * self.block_width + bx) as usize;
991        self.meta_codes[position]
992    }
993}
994
995/// Decode a §7.3 `entropy-coded-image` to a [`DecodedImage`].
996///
997/// An `entropy-coded-image = color-cache-info data` (§7.3 ABNF): a
998/// §5.2.3 `color-cache-info` bit followed by a single 5-code prefix-code
999/// group (no §6.2.2 meta-prefix layer) and the §5.2 LZ77 / color-cache
1000/// data. This is the shape of the §6.2.2 entropy image, the §4.1
1001/// predictor image, the §4.2 color-transform image, and the §4.4
1002/// color-indexing color table — every image-data role *except* the
1003/// top-level §5.1 ARGB image.
1004///
1005/// `reader` must be positioned at the start of the block (the
1006/// `color-cache-info` bit). On return the reader sits just past the last
1007/// pixel of the `width * height` image. Use this to decode a §4
1008/// transform's sub-resolution body, then resume reading the next
1009/// transform (or the main image) from the same reader.
1010pub fn decode_entropy_coded_image(
1011    reader: &mut BitReader<'_>,
1012    width: u32,
1013    height: u32,
1014) -> Result<DecodedImage, DecodeError> {
1015    if width == 0 || height == 0 {
1016        return Err(DecodeError::EmptyEntropyImage {
1017            prefix_image_width: width,
1018            prefix_image_height: height,
1019        });
1020    }
1021
1022    // The image is an entropy-coded-image: color-cache-info + one
1023    // prefix-code group, no §6.2.2 meta-prefix bit. The round-106
1024    // reader handles exactly that for the `EntropyCoded` role.
1025    let header = MetaPrefixHeader::read(reader, ImageRole::EntropyCoded, width, height)?;
1026    let group = match &header.codes {
1027        MetaPrefixCodes::Single { group } => group.as_ref(),
1028        // EntropyImagePending is impossible for the EntropyCoded role
1029        // (no meta-prefix bit is read), but guard rather than panic.
1030        MetaPrefixCodes::EntropyImagePending { .. } => {
1031            return Err(DecodeError::EmptyEntropyImage {
1032                prefix_image_width: width,
1033                prefix_image_height: height,
1034            });
1035        }
1036    };
1037    let cache = header
1038        .color_cache
1039        .is_enabled()
1040        .then(|| ColorCache::new(header.color_cache.code_bits));
1041
1042    decode_image(reader, group, cache, width, height)
1043}
1044
1045/// Decode the §6.2.2 *entropy image* into a [`MetaPrefixIndex`].
1046///
1047/// The entropy image is itself an `entropy-coded-image` (§7.3 ABNF):
1048/// a §5.2.3 `color-cache-info` bit followed by a single prefix-code
1049/// group and the §5.2 LZ77 / color-cache data. `reader` must be
1050/// positioned at the start of that block — i.e. at the
1051/// `entropy_image_bit_position` the round-106
1052/// [`MetaPrefixHeader`] recorded for an
1053/// [`MetaPrefixCodes::EntropyImagePending`] result. On return the
1054/// reader sits at the start of the main ARGB image's prefix-code
1055/// groups.
1056///
1057/// `prefix_bits`, `prefix_image_width`, and `prefix_image_height` are
1058/// the dimensions the round-106 header already derived.
1059pub fn decode_entropy_image(
1060    reader: &mut BitReader<'_>,
1061    prefix_bits: u8,
1062    prefix_image_width: u32,
1063    prefix_image_height: u32,
1064) -> Result<MetaPrefixIndex, DecodeError> {
1065    let decoded = decode_entropy_coded_image(reader, prefix_image_width, prefix_image_height)
1066        .map_err(|e| match e {
1067            // Map a degenerate-size rejection back onto the
1068            // entropy-image-specific error to preserve the public
1069            // contract of this function.
1070            DecodeError::EmptyEntropyImage { .. } => DecodeError::EmptyEntropyImage {
1071                prefix_image_width,
1072                prefix_image_height,
1073            },
1074            other => other,
1075        })?;
1076
1077    // §6.2.2: meta_prefix_code = (entropy_pixel >> 8) & 0xffff — the
1078    // red+green channels of each block's entropy-image pixel.
1079    let meta_codes: Vec<u16> = decoded
1080        .pixels()
1081        .iter()
1082        .map(|&argb| ((argb >> 8) & 0xffff) as u16)
1083        .collect();
1084
1085    Ok(MetaPrefixIndex {
1086        prefix_bits,
1087        block_width: prefix_image_width,
1088        block_height: prefix_image_height,
1089        meta_codes,
1090    })
1091}
1092
1093/// Decode a full §5.1 ARGB-role image — the §6.2.2 multi-group path.
1094///
1095/// `reader` must be positioned at the start of the ARGB image's
1096/// `spatially-coded-image`: the §5.2.3 `color-cache-info` bit, then the
1097/// §6.2.2 meta-prefix bit. This function reads the round-106
1098/// [`MetaPrefixHeader`] for the [`ImageRole::Argb`] role and dispatches:
1099///
1100/// * **single group** (`meta-prefix = %b0`): runs the §6.2.3 decode
1101///   loop with the one group everywhere (identical to [`decode_image`]).
1102/// * **multiple groups** (`meta-prefix = %b1`): decodes the §6.2.2
1103///   entropy image via [`decode_entropy_image`], computes
1104///   `num_prefix_groups = max(entropy image) + 1`, reads that many
1105///   [`PrefixCodeGroup`]s, then runs the §6.2.3 loop selecting a group
1106///   per pixel block via
1107///   [`MetaPrefixIndex::meta_code_for`].
1108///
1109/// Returns a [`DecodedImage`] of `width * height` ARGB pixels in
1110/// scan-line order, before any §4 inverse transform.
1111pub fn decode_argb(
1112    reader: &mut BitReader<'_>,
1113    width: u32,
1114    height: u32,
1115) -> Result<DecodedImage, DecodeError> {
1116    let header = MetaPrefixHeader::read(reader, ImageRole::Argb, width, height)?;
1117    let color_cache_size = header.color_cache.size();
1118    let cache_code_bits = header.color_cache.code_bits;
1119    let cache_enabled = header.color_cache.is_enabled();
1120
1121    match header.codes {
1122        MetaPrefixCodes::Single { group } => {
1123            let cache = cache_enabled.then(|| ColorCache::new(cache_code_bits));
1124            decode_image(reader, &group, cache, width, height)
1125        }
1126        MetaPrefixCodes::EntropyImagePending {
1127            prefix_bits,
1128            image_width: prefix_image_width,
1129            image_height: prefix_image_height,
1130            ..
1131        } => {
1132            // The reader is already positioned at the entropy image
1133            // start (just past the `prefix_bits` field).
1134            let index =
1135                decode_entropy_image(reader, prefix_bits, prefix_image_width, prefix_image_height)?;
1136            let num_prefix_groups = index.num_prefix_groups();
1137            if num_prefix_groups == 0 {
1138                return Err(DecodeError::EmptyEntropyImage {
1139                    prefix_image_width,
1140                    prefix_image_height,
1141                });
1142            }
1143
1144            // §6.2.2: read num_prefix_groups prefix-code groups, each
1145            // sized against the *main* ARGB color cache.
1146            let mut groups: Vec<PrefixCodeGroup> = Vec::with_capacity(num_prefix_groups);
1147            for _ in 0..num_prefix_groups {
1148                groups.push(PrefixCodeGroup::read(reader, color_cache_size)?);
1149            }
1150
1151            decode_argb_multi_group(
1152                reader,
1153                &groups,
1154                &index,
1155                cache_enabled,
1156                cache_code_bits,
1157                width,
1158                height,
1159            )
1160        }
1161    }
1162}
1163
1164/// §6.2.2 / §6.2.3 multi-group decode loop: like [`decode_image`] but
1165/// the group used for each pixel is selected by the entropy image.
1166///
1167/// `groups` holds the `num_prefix_groups` prefix-code groups, `index`
1168/// the decoded entropy image. For each pixel `(x, y)` in scan-line
1169/// order the meta-prefix code
1170/// `index.meta_code_for(x, y)` indexes `groups`; that group decodes the
1171/// pixel via the shared §6.2.3 [`decode_one_symbol`] core. A single
1172/// color cache is maintained across the whole image in stream order
1173/// (§5.2.3), independent of which group emitted the pixel.
1174#[allow(clippy::too_many_arguments)]
1175fn decode_argb_multi_group(
1176    reader: &mut BitReader<'_>,
1177    groups: &[PrefixCodeGroup],
1178    index: &MetaPrefixIndex,
1179    cache_enabled: bool,
1180    cache_code_bits: u32,
1181    width: u32,
1182    height: u32,
1183) -> Result<DecodedImage, DecodeError> {
1184    let cache_size = if cache_enabled {
1185        1usize << cache_code_bits
1186    } else {
1187        0
1188    };
1189    let alphabet_size = PrefixCodeGroup::green_alphabet_size(cache_size);
1190    let total_pixels = (width as usize) * (height as usize);
1191    let mut color_cache = cache_enabled.then(|| ColorCache::new(cache_code_bits));
1192    // See `decode_image`: cap the eager reservation against a malformed
1193    // §3.4 header so an unbacked pixel count cannot drive an unbounded
1194    // allocation; the `Vec` still grows to `total_pixels` on demand.
1195    let mut pixels: Vec<u32> = Vec::with_capacity(eager_pixel_capacity(total_pixels));
1196
1197    // The current pixel's (x, y) is derived from `pixels.len()` — the
1198    // decode loop fills in scan-line order, and an LZ77 backref pushes
1199    // multiple pixels at once but always lands the cursor back on the
1200    // next undefined pixel. Group selection happens once per *symbol*,
1201    // keyed by the position of the next pixel to emit (the spec selects
1202    // the group for the pixel the decoder is about to decode).
1203    while pixels.len() < total_pixels {
1204        let position = pixels.len();
1205        let x = (position % width as usize) as u32;
1206        let y = (position / width as usize) as u32;
1207        let meta_code = index.meta_code_for(x, y) as usize;
1208        let group = groups
1209            .get(meta_code)
1210            .ok_or(DecodeError::MetaPrefixIndexOutOfRange {
1211                meta_prefix_code: meta_code,
1212                num_prefix_groups: groups.len(),
1213            })?;
1214        decode_one_symbol(
1215            reader,
1216            group,
1217            &mut color_cache,
1218            &mut pixels,
1219            width,
1220            total_pixels,
1221            alphabet_size,
1222            cache_size,
1223        )?;
1224    }
1225
1226    Ok(DecodedImage {
1227        width,
1228        height,
1229        pixels,
1230    })
1231}
1232
1233#[cfg(test)]
1234mod tests {
1235    use super::*;
1236
1237    #[test]
1238    fn eager_pixel_capacity_is_capped_for_a_huge_declared_pixel_count() {
1239        // A §3.4 header can declare up to 16384 × 16384 ≈ 2.7e8 pixels;
1240        // the eager reservation must be clamped to the ceiling so a
1241        // malformed header cannot drive an unbounded allocation before
1242        // the EOF-checked decode loop runs.
1243        let huge = 16_384usize * 16_384usize;
1244        assert_eq!(eager_pixel_capacity(huge), MAX_EAGER_PIXEL_RESERVATION);
1245        assert!(eager_pixel_capacity(huge) < huge);
1246    }
1247
1248    #[test]
1249    fn eager_pixel_capacity_is_exact_for_a_realistic_image() {
1250        // A normally-sized image still pre-sizes its buffer exactly — the
1251        // cap is only reached by canvases far larger than any real still.
1252        let small = 128usize * 128usize;
1253        assert_eq!(eager_pixel_capacity(small), small);
1254        assert!(small < MAX_EAGER_PIXEL_RESERVATION);
1255    }
1256
1257    /// Tiny LSB-first bit writer for building synthetic §5.2 streams.
1258    struct BitWriter {
1259        bytes: Vec<u8>,
1260        bit_pos: usize,
1261    }
1262    impl BitWriter {
1263        fn new() -> Self {
1264            Self {
1265                bytes: Vec::new(),
1266                bit_pos: 0,
1267            }
1268        }
1269        fn write_bits(&mut self, mut value: u32, n: usize) {
1270            for _ in 0..n {
1271                let byte_idx = self.bit_pos >> 3;
1272                if byte_idx >= self.bytes.len() {
1273                    self.bytes.push(0);
1274                }
1275                let bit = (value & 1) as u8;
1276                self.bytes[byte_idx] |= bit << (self.bit_pos & 7);
1277                self.bit_pos += 1;
1278                value >>= 1;
1279            }
1280        }
1281        /// Emit a §6.2.1 simple-code single-symbol code (length-1 leaf)
1282        /// for `sym` in the 8-bit form.
1283        fn write_simple_single_symbol(&mut self, sym: u32) {
1284            self.write_bits(1, 1); // simple flag
1285            self.write_bits(0, 1); // num_symbols - 1 = 0
1286            self.write_bits(1, 1); // is_first_8bits = 1
1287            self.write_bits(sym, 8); // symbol0
1288        }
1289        /// Emit a §6.2.1 simple-code two-symbol code, both at length 1.
1290        /// Canonical: the smaller value reads as bit 0, the larger as
1291        /// bit 1.
1292        fn write_simple_two_symbols(&mut self, sym0: u32, sym1: u32) {
1293            self.write_bits(1, 1); // simple flag
1294            self.write_bits(1, 1); // num_symbols - 1 = 1 → 2 symbols
1295            self.write_bits(1, 1); // is_first_8bits = 1
1296            self.write_bits(sym0, 8);
1297            self.write_bits(sym1, 8);
1298        }
1299        fn into_bytes(self) -> Vec<u8> {
1300            self.bytes
1301        }
1302        /// Bit count written so far. Used by the bit-prefix property
1303        /// test to know the exact stream length; the trailing
1304        /// `bytes.len() * 8 - bit_len()` bits of the last byte are
1305        /// zero-padded.
1306        fn bit_len(&self) -> usize {
1307            self.bit_pos
1308        }
1309    }
1310
1311    // ---- §5.2.2 LZ77 prefix-value transform ----
1312
1313    #[test]
1314    fn lz77_value_small_codes_are_identity_plus_one() {
1315        // prefix_code < 4 → prefix_code + 1, no extra bits.
1316        let data: [u8; 0] = [];
1317        let mut r = BitReader::new(&data);
1318        assert_eq!(read_lz77_value(&mut r, 0).unwrap(), 1);
1319        assert_eq!(read_lz77_value(&mut r, 1).unwrap(), 2);
1320        assert_eq!(read_lz77_value(&mut r, 2).unwrap(), 3);
1321        assert_eq!(read_lz77_value(&mut r, 3).unwrap(), 4);
1322        assert_eq!(r.bit_position(), 0); // no bits consumed
1323    }
1324
1325    #[test]
1326    fn lz77_value_prefix_4_reads_one_extra_bit() {
1327        // prefix_code = 4 → extra_bits = (4-2)>>1 = 1; offset =
1328        // (2 + 0) << 1 = 4; value = 4 + ReadBits(1) + 1.
1329        // The spec table says prefix 4 covers value range 5..6.
1330        let mut w = BitWriter::new();
1331        w.write_bits(0, 1); // extra bit = 0 → value 5
1332        w.write_bits(1, 1); // extra bit = 1 → value 6
1333        let data = w.into_bytes();
1334        let mut r = BitReader::new(&data);
1335        assert_eq!(read_lz77_value(&mut r, 4).unwrap(), 5);
1336        assert_eq!(read_lz77_value(&mut r, 4).unwrap(), 6);
1337    }
1338
1339    #[test]
1340    fn lz77_value_prefix_5_covers_7_to_8() {
1341        // prefix 5 → extra_bits = 1; offset = (2 + 1) << 1 = 6;
1342        // value = 6 + ReadBits(1) + 1 → 7 or 8.
1343        let mut w = BitWriter::new();
1344        w.write_bits(0, 1);
1345        w.write_bits(1, 1);
1346        let data = w.into_bytes();
1347        let mut r = BitReader::new(&data);
1348        assert_eq!(read_lz77_value(&mut r, 5).unwrap(), 7);
1349        assert_eq!(read_lz77_value(&mut r, 5).unwrap(), 8);
1350    }
1351
1352    #[test]
1353    fn lz77_value_prefix_6_covers_9_to_12() {
1354        // prefix 6 → extra_bits = 2; offset = (2 + 0) << 2 = 8;
1355        // value = 8 + ReadBits(2) + 1 → 9..12.
1356        let mut w = BitWriter::new();
1357        for extra in 0u32..4 {
1358            w.write_bits(extra, 2);
1359        }
1360        let data = w.into_bytes();
1361        let mut r = BitReader::new(&data);
1362        for expected in 9u32..=12 {
1363            assert_eq!(read_lz77_value(&mut r, 6).unwrap(), expected);
1364        }
1365    }
1366
1367    #[test]
1368    fn lz77_value_prefix_23_max_length_4096() {
1369        // The spec note: max backward-reference length is 4096; prefix
1370        // 23 covers 3072..4096 with 10 extra bits.
1371        // prefix 23 → extra_bits = (23-2)>>1 = 10; offset =
1372        // (2 + 1) << 10 = 3072; max value = 3072 + 1023 + 1 = 4096.
1373        let mut w = BitWriter::new();
1374        w.write_bits(0, 10); // min → 3073
1375        w.write_bits(1023, 10); // max → 4096
1376        let data = w.into_bytes();
1377        let mut r = BitReader::new(&data);
1378        assert_eq!(read_lz77_value(&mut r, 23).unwrap(), 3073);
1379        assert_eq!(read_lz77_value(&mut r, 23).unwrap(), 4096);
1380    }
1381
1382    // ---- §5.2.2 distance map ----
1383
1384    #[test]
1385    fn distance_map_has_120_entries() {
1386        assert_eq!(DISTANCE_MAP.len(), 120);
1387        assert_eq!(NUM_DISTANCE_MAP_CODES, 120);
1388    }
1389
1390    #[test]
1391    fn distance_map_first_entries_match_spec_examples() {
1392        // §5.2.2: "distance code 1 indicates an offset of (0, 1)".
1393        assert_eq!(DISTANCE_MAP[0], (0, 1));
1394        // "distance code 3 indicates the top-left pixel" → (1, 1).
1395        assert_eq!(DISTANCE_MAP[2], (1, 1));
1396        // Last two entries from the listing.
1397        assert_eq!(DISTANCE_MAP[118], (8, 6));
1398        assert_eq!(DISTANCE_MAP[119], (8, 7));
1399    }
1400
1401    #[test]
1402    fn distance_code_1_is_pixel_above() {
1403        // (0, 1) over a width-W image → dist = 0 + 1*W = W (the pixel
1404        // directly above the current one).
1405        assert_eq!(distance_code_to_pixel_distance(1, 16), 16);
1406        assert_eq!(distance_code_to_pixel_distance(1, 1), 1);
1407    }
1408
1409    #[test]
1410    fn distance_code_2_is_pixel_to_left() {
1411        // (1, 0) → dist = 1 + 0*W = 1, the pixel immediately left.
1412        assert_eq!(distance_code_to_pixel_distance(2, 100), 1);
1413    }
1414
1415    #[test]
1416    fn distance_code_above_120_offsets_by_120() {
1417        // §5.2.2: codes > 120 denote scan-line distance offset by 120.
1418        assert_eq!(distance_code_to_pixel_distance(121, 64), 1);
1419        assert_eq!(distance_code_to_pixel_distance(200, 64), 80);
1420    }
1421
1422    #[test]
1423    fn distance_clamps_to_one_for_negative_offsets() {
1424        // A neighbor whose (xi, yi) gives dist < 1 clamps to 1.
1425        // (-1, 1) with width 1 → -1 + 1 = 0 → clamps to 1.
1426        // distance_code 4 maps to (-1, 1).
1427        assert_eq!(DISTANCE_MAP[3], (-1, 1));
1428        assert_eq!(distance_code_to_pixel_distance(4, 1), 1);
1429    }
1430
1431    // ---- §5.2.2 backward-reference assembler ----
1432
1433    #[test]
1434    fn backward_reference_simple_copy() {
1435        // Copy 3 pixels from distance 3 (no overlap): the run reproduces
1436        // the three source pixels verbatim, in order.
1437        let mut pixels = vec![0xAA, 0xBB, 0xCC];
1438        let r = apply_backward_reference(&mut pixels, 3, 3, 10).unwrap();
1439        assert_eq!(r, 3..6);
1440        assert_eq!(pixels, vec![0xAA, 0xBB, 0xCC, 0xAA, 0xBB, 0xCC]);
1441    }
1442
1443    #[test]
1444    fn backward_reference_overlapping_run_repeats() {
1445        // dist=1, length=4: the LZ77 self-overlap repeats the single
1446        // source pixel — each read happens after the preceding push.
1447        let mut pixels = vec![0x11];
1448        let r = apply_backward_reference(&mut pixels, 4, 1, 10).unwrap();
1449        assert_eq!(r, 1..5);
1450        assert_eq!(pixels, vec![0x11, 0x11, 0x11, 0x11, 0x11]);
1451    }
1452
1453    #[test]
1454    fn backward_reference_overlapping_dist_two() {
1455        // dist=2, length=4 over [A, B]: produces A B A B (each new pixel
1456        // read from two-back, including the freshly-appended ones).
1457        let mut pixels = vec![0xA, 0xB];
1458        apply_backward_reference(&mut pixels, 4, 2, 10).unwrap();
1459        assert_eq!(pixels, vec![0xA, 0xB, 0xA, 0xB, 0xA, 0xB]);
1460    }
1461
1462    #[test]
1463    fn backward_reference_underflow_leaves_buffer_untouched() {
1464        // dist > position: the source would reach before pixel 0.
1465        let mut pixels = vec![0x1, 0x2];
1466        let err = apply_backward_reference(&mut pixels, 1, 3, 10).unwrap_err();
1467        match err {
1468            DecodeError::BackwardReferenceUnderflow { position, distance } => {
1469                assert_eq!(position, 2);
1470                assert_eq!(distance, 3);
1471            }
1472            other => panic!("expected underflow, got {other:?}"),
1473        }
1474        // No partial write: the buffer is exactly as it was.
1475        assert_eq!(pixels, vec![0x1, 0x2]);
1476    }
1477
1478    #[test]
1479    fn backward_reference_overflow_leaves_buffer_untouched() {
1480        // position(2) + length(5) = 7 > total_pixels(4).
1481        let mut pixels = vec![0x1, 0x2];
1482        let err = apply_backward_reference(&mut pixels, 5, 1, 4).unwrap_err();
1483        match err {
1484            DecodeError::BackwardReferenceOverflow {
1485                position,
1486                length,
1487                total_pixels,
1488            } => {
1489                assert_eq!(position, 2);
1490                assert_eq!(length, 5);
1491                assert_eq!(total_pixels, 4);
1492            }
1493            other => panic!("expected overflow, got {other:?}"),
1494        }
1495        assert_eq!(pixels, vec![0x1, 0x2]);
1496    }
1497
1498    #[test]
1499    fn backward_reference_dist_equal_position_is_allowed() {
1500        // dist == position copies from index 0 forward — the boundary
1501        // case (dist > position underflows, dist == position is fine).
1502        let mut pixels = vec![0x7, 0x8];
1503        let r = apply_backward_reference(&mut pixels, 2, 2, 10).unwrap();
1504        assert_eq!(r, 2..4);
1505        assert_eq!(pixels, vec![0x7, 0x8, 0x7, 0x8]);
1506    }
1507
1508    #[test]
1509    fn backward_reference_zero_length_appends_nothing() {
1510        // A zero-length run is a no-op that still passes the guards.
1511        let mut pixels = vec![0x5];
1512        let r = apply_backward_reference(&mut pixels, 0, 1, 10).unwrap();
1513        assert_eq!(r, 1..1);
1514        assert_eq!(pixels, vec![0x5]);
1515    }
1516
1517    #[test]
1518    fn backward_reference_fill_to_exact_capacity() {
1519        // position + length == total_pixels is the exact-fit boundary
1520        // and must succeed (overflow guard is strict `>`).
1521        let mut pixels = vec![0xF0, 0xF1];
1522        apply_backward_reference(&mut pixels, 2, 2, 4).unwrap();
1523        assert_eq!(pixels.len(), 4);
1524    }
1525
1526    // ---- §6.2.3 GREEN symbol classification ----
1527
1528    #[test]
1529    fn green_symbol_literal_range() {
1530        match GreenSymbol::classify(0, 280).unwrap() {
1531            GreenSymbol::Literal { green } => assert_eq!(green, 0),
1532            other => panic!("expected Literal, got {other:?}"),
1533        }
1534        match GreenSymbol::classify(255, 280).unwrap() {
1535            GreenSymbol::Literal { green } => assert_eq!(green, 255),
1536            other => panic!("expected Literal, got {other:?}"),
1537        }
1538    }
1539
1540    #[test]
1541    fn green_symbol_length_prefix_range() {
1542        // 256 → length prefix 0; 279 → length prefix 23.
1543        match GreenSymbol::classify(256, 280).unwrap() {
1544            GreenSymbol::LengthPrefix { prefix_code } => assert_eq!(prefix_code, 0),
1545            other => panic!("expected LengthPrefix, got {other:?}"),
1546        }
1547        match GreenSymbol::classify(279, 280).unwrap() {
1548            GreenSymbol::LengthPrefix { prefix_code } => assert_eq!(prefix_code, 23),
1549            other => panic!("expected LengthPrefix, got {other:?}"),
1550        }
1551    }
1552
1553    #[test]
1554    fn green_symbol_color_cache_range() {
1555        // With a cache of size 16, alphabet = 256 + 24 + 16 = 296.
1556        // Symbol 280 → cache index 0; 295 → cache index 15.
1557        match GreenSymbol::classify(280, 296).unwrap() {
1558            GreenSymbol::ColorCache { index } => assert_eq!(index, 0),
1559            other => panic!("expected ColorCache, got {other:?}"),
1560        }
1561        match GreenSymbol::classify(295, 296).unwrap() {
1562            GreenSymbol::ColorCache { index } => assert_eq!(index, 15),
1563            other => panic!("expected ColorCache, got {other:?}"),
1564        }
1565    }
1566
1567    #[test]
1568    fn green_symbol_out_of_range_is_refused() {
1569        match GreenSymbol::classify(280, 280) {
1570            Err(DecodeError::GreenSymbolOutOfRange {
1571                symbol,
1572                alphabet_size,
1573            }) => {
1574                assert_eq!(symbol, 280);
1575                assert_eq!(alphabet_size, 280);
1576            }
1577            other => panic!("expected GreenSymbolOutOfRange, got {other:?}"),
1578        }
1579    }
1580
1581    // ---- §5.2.3 color cache ----
1582
1583    #[test]
1584    fn color_cache_hash_matches_spec_formula() {
1585        let cache = ColorCache::new(4); // code_bits = 4, size 16
1586        let argb = 0xFF_12_34_56u32;
1587        let expected = (COLOR_CACHE_HASH_MULTIPLIER.wrapping_mul(argb) >> (32 - 4)) as usize;
1588        assert_eq!(cache.hash(argb), expected);
1589        assert!(cache.hash(argb) < 16);
1590    }
1591
1592    #[test]
1593    fn color_cache_insert_then_lookup_round_trips() {
1594        let mut cache = ColorCache::new(8);
1595        let argb = 0xDE_AD_BE_EFu32;
1596        cache.insert(argb);
1597        let idx = cache.hash(argb);
1598        assert_eq!(cache.lookup(idx), Some(argb));
1599    }
1600
1601    #[test]
1602    fn color_cache_starts_zeroed() {
1603        let cache = ColorCache::new(2);
1604        for i in 0..cache.size() {
1605            assert_eq!(cache.lookup(i), Some(0));
1606        }
1607    }
1608
1609    // ---- §5.2 full decode loop ----
1610
1611    /// Build a single prefix-code group from synthetic simple codes so
1612    /// the §5.2 decode loop has a known group to consume. The GREEN
1613    /// code is supplied by the caller (it varies per test); RED, BLUE,
1614    /// ALPHA, DIST default to single-symbol codes the caller picks.
1615    fn group_with_codes(
1616        green_writer: impl Fn(&mut BitWriter),
1617        red: u32,
1618        blue: u32,
1619        alpha: u32,
1620        dist: u32,
1621        cache_size: usize,
1622    ) -> PrefixCodeGroup {
1623        let mut w = BitWriter::new();
1624        green_writer(&mut w);
1625        w.write_simple_single_symbol(red);
1626        w.write_simple_single_symbol(blue);
1627        w.write_simple_single_symbol(alpha);
1628        w.write_simple_single_symbol(dist);
1629        let data = w.into_bytes();
1630        let mut r = BitReader::new(&data);
1631        PrefixCodeGroup::read(&mut r, cache_size).unwrap()
1632    }
1633
1634    #[test]
1635    fn decode_literal_only_2x1_image() {
1636        // A 2x1 image of two literal pixels. GREEN code carries two
1637        // symbols (green values 10 and 20); RED/BLUE/ALPHA single.
1638        // No color cache.
1639        let group = group_with_codes(
1640            |w| w.write_simple_two_symbols(10, 20),
1641            0xAA, // red
1642            0xBB, // blue
1643            0xCC, // alpha
1644            0,    // dist (unused)
1645            0,
1646        );
1647        // Image stream: two GREEN symbols (10 → bit 0, 20 → bit 1).
1648        // Each literal also reads RED/BLUE/ALPHA which are single-leaf
1649        // (no bits). So just two GREEN bits.
1650        let mut w = BitWriter::new();
1651        w.write_bits(0, 1); // GREEN = 10
1652        w.write_bits(1, 1); // GREEN = 20
1653        let data = w.into_bytes();
1654        let mut r = BitReader::new(&data);
1655        let img = decode_image(&mut r, &group, None, 2, 1).unwrap();
1656        assert_eq!(img.width(), 2);
1657        assert_eq!(img.height(), 1);
1658        assert_eq!(
1659            img.pixels(),
1660            &[
1661                pack_argb(10, 0xAA, 0xBB, 0xCC),
1662                pack_argb(20, 0xAA, 0xBB, 0xCC)
1663            ]
1664        );
1665    }
1666
1667    #[test]
1668    fn decode_single_literal_pixel() {
1669        // 1x1 image; GREEN single-symbol = 0x42.
1670        let group = group_with_codes(
1671            |w| w.write_simple_single_symbol(0x42),
1672            0x10,
1673            0x20,
1674            0x30,
1675            0,
1676            0,
1677        );
1678        // GREEN is single-leaf → no bits; RED/BLUE/ALPHA single → no
1679        // bits. So the whole image stream is empty.
1680        let data: [u8; 0] = [];
1681        let mut r = BitReader::new(&data);
1682        let img = decode_image(&mut r, &group, None, 1, 1).unwrap();
1683        assert_eq!(img.pixels(), &[pack_argb(0x42, 0x10, 0x20, 0x30)]);
1684    }
1685
1686    #[test]
1687    fn decode_length_distance_back_reference() {
1688        // 4x1 image. First emit one literal (green=5), then a backward
1689        // reference of length 3 distance 1 that copies the literal
1690        // three times (LZ77 self-overlap fills the row).
1691        //
1692        // GREEN code must carry literal 5 AND length-prefix symbol
1693        // 256 (= length prefix code 0 → length 1)... but we need
1694        // length 3, so use length prefix code 2 (symbol 258) → value 3.
1695        // So GREEN alphabet must hold symbols {5, 258}. Use a two-symbol
1696        // simple code (5 and 258); but simple codes only reach 255.
1697        // Instead drive GREEN with a normal code? Simpler: use a
1698        // two-symbol simple code over {5, 258}? 258 > 255 → not
1699        // expressible in a simple code. So use the GREEN single-symbol
1700        // trick twice won't work either.
1701        //
1702        // Build GREEN from an explicit code-length table with symbols 5
1703        // and 258 both at length 1 (a complete 2-leaf tree). Canonical:
1704        // smaller value (5) → bit 0, larger (258) → bit 1.
1705        let green = {
1706            let mut lengths = vec![0u8; PrefixCodeGroup::green_alphabet_size(0)];
1707            lengths[5] = 1;
1708            lengths[258] = 1;
1709            crate::vp8l_prefix::PrefixCode::from_code_lengths(lengths).unwrap()
1710        };
1711        let red = {
1712            let mut l = vec![0u8; 256];
1713            l[0xAA] = 1;
1714            crate::vp8l_prefix::PrefixCode::from_code_lengths(l).unwrap()
1715        };
1716        let blue = {
1717            let mut l = vec![0u8; 256];
1718            l[0xBB] = 1;
1719            crate::vp8l_prefix::PrefixCode::from_code_lengths(l).unwrap()
1720        };
1721        let alpha = {
1722            let mut l = vec![0u8; 256];
1723            l[0xCC] = 1;
1724            crate::vp8l_prefix::PrefixCode::from_code_lengths(l).unwrap()
1725        };
1726        // Distance code #5: single symbol = distance prefix code 1.
1727        // distance prefix 1 → distance_code value = 2 → distance map
1728        // entry 2 = (1,0) → pixel distance 1.
1729        let dist = {
1730            let mut l = vec![0u8; 40];
1731            l[1] = 1;
1732            crate::vp8l_prefix::PrefixCode::from_code_lengths(l).unwrap()
1733        };
1734        let group = PrefixCodeGroup {
1735            green,
1736            red,
1737            blue,
1738            alpha,
1739            distance: dist,
1740        };
1741
1742        // Stream:
1743        //  pixel0 literal: GREEN bit 0 (→ sym 5). RED/BLUE/ALPHA single
1744        //                  (no bits).
1745        //  then a length code: GREEN bit 1 (→ sym 258 → length prefix
1746        //                  code 2 → value 3). LENGTH read_lz77_value(2)
1747        //                  consumes no extra bits.
1748        //                  DIST: single-leaf (no bits) → distance prefix
1749        //                  1 → read_lz77_value(1) → value 2 (no extra) →
1750        //                  distance_map[1] = (1,0) → dist 1.
1751        let mut w = BitWriter::new();
1752        w.write_bits(0, 1); // GREEN = 5 (literal)
1753        w.write_bits(1, 1); // GREEN = 258 (length prefix code 2)
1754        let data = w.into_bytes();
1755        let mut r = BitReader::new(&data);
1756        let img = decode_image(&mut r, &group, None, 4, 1).unwrap();
1757        let p = pack_argb(5, 0xAA, 0xBB, 0xCC);
1758        // literal then 3 copies of the same pixel (dist 1).
1759        assert_eq!(img.pixels(), &[p, p, p, p]);
1760    }
1761
1762    #[test]
1763    fn decode_color_cache_hit() {
1764        // 2x1 image with a color cache. Pixel 0 is a literal; pixel 1
1765        // is a color-cache code that resolves to pixel 0's ARGB.
1766        let cache_bits = 4u32;
1767        let cache_size = 1usize << cache_bits; // 16
1768        let alphabet = PrefixCodeGroup::green_alphabet_size(cache_size); // 296
1769
1770        // Determine the literal pixel's ARGB and its cache slot.
1771        let green_val = 0x11u8;
1772        let red_val = 0x22u8;
1773        let blue_val = 0x33u8;
1774        let alpha_val = 0x44u8;
1775        let argb = pack_argb(green_val, red_val, blue_val, alpha_val);
1776        let probe = ColorCache::new(cache_bits);
1777        let slot = probe.hash(argb);
1778        let cache_symbol = 256 + NUM_LENGTH_PREFIX_CODES + slot; // 280 + slot
1779
1780        // GREEN code carries literal `green_val` AND the color-cache
1781        // symbol. Both at length 1 (2-leaf complete tree). Canonical:
1782        // smaller value → bit 0, larger → bit 1.
1783        let green = {
1784            let mut lengths = vec![0u8; alphabet];
1785            lengths[green_val as usize] = 1;
1786            lengths[cache_symbol] = 1;
1787            crate::vp8l_prefix::PrefixCode::from_code_lengths(lengths).unwrap()
1788        };
1789        let mk_single = |sym: usize, size: usize| {
1790            let mut l = vec![0u8; size];
1791            l[sym] = 1;
1792            crate::vp8l_prefix::PrefixCode::from_code_lengths(l).unwrap()
1793        };
1794        let group = PrefixCodeGroup {
1795            green,
1796            red: mk_single(red_val as usize, 256),
1797            blue: mk_single(blue_val as usize, 256),
1798            alpha: mk_single(alpha_val as usize, 256),
1799            distance: mk_single(0, 40),
1800        };
1801
1802        // Stream: pixel0 GREEN bit (literal green_val < cache_symbol →
1803        // bit 0). pixel1 GREEN bit (cache_symbol → bit 1).
1804        let mut w = BitWriter::new();
1805        w.write_bits(0, 1); // GREEN = green_val literal
1806        w.write_bits(1, 1); // GREEN = cache_symbol
1807        let data = w.into_bytes();
1808        let mut r = BitReader::new(&data);
1809        let img = decode_image(&mut r, &group, Some(ColorCache::new(cache_bits)), 2, 1).unwrap();
1810        assert_eq!(img.pixels(), &[argb, argb]);
1811    }
1812
1813    #[test]
1814    fn decode_backward_reference_underflow_is_refused() {
1815        // A length code at pixel 0 (nothing decoded yet) with distance
1816        // 1 underflows.
1817        let green = {
1818            let mut lengths = vec![0u8; PrefixCodeGroup::green_alphabet_size(0)];
1819            lengths[256] = 1; // length prefix code 0 → length 1
1820            crate::vp8l_prefix::PrefixCode::from_code_lengths(lengths).unwrap()
1821        };
1822        let mk_single = |sym: usize, size: usize| {
1823            let mut l = vec![0u8; size];
1824            l[sym] = 1;
1825            crate::vp8l_prefix::PrefixCode::from_code_lengths(l).unwrap()
1826        };
1827        let group = PrefixCodeGroup {
1828            green,
1829            red: mk_single(0, 256),
1830            blue: mk_single(0, 256),
1831            alpha: mk_single(0, 256),
1832            distance: mk_single(1, 40), // dist prefix 1 → value 2 → (1,0) → dist 1
1833        };
1834        // GREEN single-leaf (no bits) → length symbol 256 → length 1.
1835        // LENGTH read_lz77_value(0) → 1 (no extra). DIST single-leaf →
1836        // prefix 1 → value 2 → dist 1. At position 0, dist 1 > 0 →
1837        // underflow.
1838        let data: [u8; 0] = [];
1839        let mut r = BitReader::new(&data);
1840        match decode_image(&mut r, &group, None, 4, 1) {
1841            Err(DecodeError::BackwardReferenceUnderflow { position, distance }) => {
1842                assert_eq!(position, 0);
1843                assert_eq!(distance, 1);
1844            }
1845            other => panic!("expected BackwardReferenceUnderflow, got {other:?}"),
1846        }
1847    }
1848
1849    #[test]
1850    fn decode_color_cache_code_without_cache_is_refused() {
1851        // A GREEN symbol in the color-cache range when no cache exists
1852        // would itself be out of range (alphabet excludes it). Confirm
1853        // classify rejects a cache symbol against the no-cache alphabet.
1854        let alphabet = PrefixCodeGroup::green_alphabet_size(0); // 280
1855        match GreenSymbol::classify(280, alphabet) {
1856            Err(DecodeError::GreenSymbolOutOfRange { .. }) => {}
1857            other => panic!("expected GreenSymbolOutOfRange, got {other:?}"),
1858        }
1859    }
1860
1861    // ---- §6.2.2 entropy-image multi-group path ----
1862
1863    impl BitWriter {
1864        /// Emit a §6.2 prefix-code group of five single-symbol simple
1865        /// codes (GREEN / RED / BLUE / ALPHA / DIST), each carrying no
1866        /// data bits in the §5.2 stream.
1867        fn write_single_symbol_group(&mut self, g: u32, r: u32, b: u32, a: u32, d: u32) {
1868            self.write_simple_single_symbol(g);
1869            self.write_simple_single_symbol(r);
1870            self.write_simple_single_symbol(b);
1871            self.write_simple_single_symbol(a);
1872            self.write_simple_single_symbol(d);
1873        }
1874    }
1875
1876    #[test]
1877    fn meta_prefix_index_helpers_match_spec() {
1878        // Build the index directly to exercise the §6.2.2 derivations.
1879        let index = MetaPrefixIndex {
1880            prefix_bits: 2, // block size 4
1881            block_width: 2,
1882            block_height: 1,
1883            meta_codes: vec![0, 1],
1884        };
1885        // num_prefix_groups = max(0, 1) + 1 = 2.
1886        assert_eq!(index.num_prefix_groups(), 2);
1887        // §6.2.2 selection: position = (y>>2)*2 + (x>>2).
1888        // Pixels 0..3 → block 0 → code 0; 4..7 → block 1 → code 1.
1889        assert_eq!(index.meta_code_for(0, 0), 0);
1890        assert_eq!(index.meta_code_for(3, 0), 0);
1891        assert_eq!(index.meta_code_for(4, 0), 1);
1892        assert_eq!(index.meta_code_for(7, 0), 1);
1893    }
1894
1895    #[test]
1896    fn meta_prefix_index_num_groups_uses_max_not_count() {
1897        // A 2-block entropy image whose codes are {0, 5} → 6 groups
1898        // (max + 1), not 2.
1899        let index = MetaPrefixIndex {
1900            prefix_bits: 2,
1901            block_width: 2,
1902            block_height: 1,
1903            meta_codes: vec![0, 5],
1904        };
1905        assert_eq!(index.num_prefix_groups(), 6);
1906    }
1907
1908    #[test]
1909    fn meta_prefix_index_from_parts_accepts_valid_parts() {
1910        // 2x3 block grid at prefix_bits 4 (16-pixel blocks), 6 codes in
1911        // scan-line order.
1912        let index = MetaPrefixIndex::from_parts(4, 2, 3, vec![0, 1, 2, 3, 4, 5]).unwrap();
1913        assert_eq!(index.prefix_bits(), 4);
1914        assert_eq!(index.block_width(), 2);
1915        assert_eq!(index.block_height(), 3);
1916        assert_eq!(index.meta_codes(), &[0, 1, 2, 3, 4, 5]);
1917        // §6.2.2 num_prefix_groups = max(entropy image) + 1.
1918        assert_eq!(index.num_prefix_groups(), 6);
1919        // §6.2.2 selection: position = (y>>4)*2 + (x>>4). Pixel (17, 33)
1920        // → block (1, 2) → position 5.
1921        assert_eq!(index.meta_code_for(17, 33), 5);
1922        // It equals the literally-constructed value (the bitstream path
1923        // builds the same struct).
1924        assert_eq!(
1925            index,
1926            MetaPrefixIndex {
1927                prefix_bits: 4,
1928                block_width: 2,
1929                block_height: 3,
1930                meta_codes: vec![0, 1, 2, 3, 4, 5],
1931            }
1932        );
1933    }
1934
1935    #[test]
1936    fn meta_prefix_index_from_parts_accepts_prefix_bits_window_ends() {
1937        // §6.2.2 `prefix_bits = ReadBits(3) + 2` → [2, 9] inclusive.
1938        for pb in 2u8..=9 {
1939            assert!(MetaPrefixIndex::from_parts(pb, 1, 1, vec![0]).is_ok());
1940        }
1941    }
1942
1943    #[test]
1944    fn meta_prefix_index_from_parts_rejects_prefix_bits_out_of_window() {
1945        // Just outside the §6.2.2 `ReadBits(3) + 2` window on each side.
1946        for pb in [0u8, 1, 10, 255] {
1947            match MetaPrefixIndex::from_parts(pb, 1, 1, vec![0]) {
1948                Err(MetaPrefixIndexError::InvalidPrefixBits { prefix_bits }) => {
1949                    assert_eq!(prefix_bits, pb);
1950                }
1951                other => panic!("expected InvalidPrefixBits for {pb}, got {other:?}"),
1952            }
1953        }
1954    }
1955
1956    #[test]
1957    fn meta_prefix_index_from_parts_rejects_zero_block_grid() {
1958        for (bw, bh) in [(0u32, 1u32), (1, 0), (0, 0)] {
1959            match MetaPrefixIndex::from_parts(2, bw, bh, vec![]) {
1960                Err(MetaPrefixIndexError::EmptyIndex {
1961                    block_width,
1962                    block_height,
1963                }) => {
1964                    assert_eq!((block_width, block_height), (bw, bh));
1965                }
1966                other => panic!("expected EmptyIndex for {bw}x{bh}, got {other:?}"),
1967            }
1968        }
1969    }
1970
1971    #[test]
1972    fn meta_prefix_index_from_parts_rejects_code_count_mismatch() {
1973        // 2x2 grid wants 4 codes; supply 3 and 5.
1974        for got in [3usize, 5] {
1975            match MetaPrefixIndex::from_parts(2, 2, 2, vec![0; got]) {
1976                Err(MetaPrefixIndexError::CodeCountMismatch {
1977                    block_width,
1978                    block_height,
1979                    expected,
1980                    got: g,
1981                }) => {
1982                    assert_eq!((block_width, block_height), (2, 2));
1983                    assert_eq!(expected, 4);
1984                    assert_eq!(g, got);
1985                }
1986                other => panic!("expected CodeCountMismatch for {got} codes, got {other:?}"),
1987            }
1988        }
1989    }
1990
1991    #[test]
1992    fn meta_prefix_index_from_parts_invariant_order() {
1993        // The §6.2.2 checks run prefix_bits → grid → count: a part set
1994        // violating all three reports InvalidPrefixBits, and one
1995        // violating grid + count reports EmptyIndex.
1996        assert!(matches!(
1997            MetaPrefixIndex::from_parts(1, 0, 0, vec![0]),
1998            Err(MetaPrefixIndexError::InvalidPrefixBits { prefix_bits: 1 })
1999        ));
2000        assert!(matches!(
2001            MetaPrefixIndex::from_parts(2, 0, 1, vec![0]),
2002            Err(MetaPrefixIndexError::EmptyIndex { .. })
2003        ));
2004    }
2005
2006    #[test]
2007    fn meta_prefix_index_from_parts_round_trips_decoded_index() {
2008        // Rebuilding from the accessors of a bitstream-decoded index
2009        // reproduces it exactly.
2010        let mut w = BitWriter::new();
2011        w.write_bits(0, 1); // entropy-image color-cache-info = disabled
2012        w.write_simple_two_symbols(0, 1); // GREEN: 0 → bit 0, 1 → bit 1
2013        w.write_simple_single_symbol(0); // RED  = 0
2014        w.write_simple_single_symbol(0); // BLUE = 0
2015        w.write_simple_single_symbol(0); // ALPHA= 0
2016        w.write_simple_single_symbol(0); // DIST = 0
2017        w.write_bits(0, 1); // entropy pixel 0 GREEN = 0
2018        w.write_bits(1, 1); // entropy pixel 1 GREEN = 1
2019        let data = w.into_bytes();
2020        let mut r = BitReader::new(&data);
2021        let index = decode_entropy_image(&mut r, 2, 2, 1).unwrap();
2022        let rebuilt = MetaPrefixIndex::from_parts(
2023            index.prefix_bits(),
2024            index.block_width(),
2025            index.block_height(),
2026            index.meta_codes().to_vec(),
2027        )
2028        .unwrap();
2029        assert_eq!(rebuilt, index);
2030    }
2031
2032    #[test]
2033    fn decode_entropy_image_extracts_red_green_meta_codes() {
2034        // A 2x1 entropy image. Pixel 0: green=0, red=0 → meta-code 0.
2035        // Pixel 1: green=1, red=0 → meta-code 1. (meta = (red<<8)|green.)
2036        let mut w = BitWriter::new();
2037        w.write_bits(0, 1); // entropy-image color-cache-info = disabled
2038                            // GREEN code carries greens {0, 1}; RED/BLUE/ALPHA/DIST single 0.
2039        w.write_simple_two_symbols(0, 1); // GREEN: 0 → bit 0, 1 → bit 1
2040        w.write_simple_single_symbol(0); // RED  = 0
2041        w.write_simple_single_symbol(0); // BLUE = 0
2042        w.write_simple_single_symbol(0); // ALPHA= 0
2043        w.write_simple_single_symbol(0); // DIST = 0
2044        w.write_bits(0, 1); // entropy pixel 0 GREEN = 0
2045        w.write_bits(1, 1); // entropy pixel 1 GREEN = 1
2046        let data = w.into_bytes();
2047        let mut r = BitReader::new(&data);
2048        let index = decode_entropy_image(&mut r, 2, 2, 1).unwrap();
2049        assert_eq!(index.prefix_bits(), 2);
2050        assert_eq!(index.block_width(), 2);
2051        assert_eq!(index.block_height(), 1);
2052        assert_eq!(index.meta_codes(), &[0, 1]);
2053        assert_eq!(index.num_prefix_groups(), 2);
2054    }
2055
2056    #[test]
2057    fn decode_entropy_image_high_meta_code_uses_red_channel() {
2058        // A 1x1 entropy image with green=2, red=3 → meta = (3<<8)|2 = 770.
2059        let mut w = BitWriter::new();
2060        w.write_bits(0, 1); // color-cache disabled
2061        w.write_simple_single_symbol(2); // GREEN = 2 (single leaf, no data bit)
2062        w.write_simple_single_symbol(3); // RED   = 3
2063        w.write_simple_single_symbol(0); // BLUE
2064        w.write_simple_single_symbol(0); // ALPHA
2065        w.write_simple_single_symbol(0); // DIST
2066        let data = w.into_bytes();
2067        let mut r = BitReader::new(&data);
2068        let index = decode_entropy_image(&mut r, 2, 1, 1).unwrap();
2069        assert_eq!(index.meta_codes(), &[(3u16 << 8) | 2]);
2070        assert_eq!(index.num_prefix_groups(), 771);
2071    }
2072
2073    #[test]
2074    fn decode_argb_two_groups_select_per_block() {
2075        // An 8x1 ARGB image, prefix_bits = 2 (block size 4) → 2 blocks.
2076        // Block 0 (pixels 0..3) uses group 0 (green 100); block 1 (4..7)
2077        // uses group 1 (green 200). Both groups single-symbol → main
2078        // image data stream consumes no bits.
2079        let mut w = BitWriter::new();
2080        // --- spatially-coded-image header (ARGB role) ---
2081        w.write_bits(0, 1); // main color-cache-info = disabled
2082        w.write_bits(1, 1); // meta-prefix = 1 → multiple groups
2083        w.write_bits(0, 3); // prefix_bits raw = 0 → 2 (block 4)
2084                            // --- entropy image (2x1 entropy-coded-image) ---
2085        w.write_bits(0, 1); // entropy color-cache-info = disabled
2086        w.write_simple_two_symbols(0, 1); // GREEN {0,1}
2087        w.write_simple_single_symbol(0); // RED
2088        w.write_simple_single_symbol(0); // BLUE
2089        w.write_simple_single_symbol(0); // ALPHA
2090        w.write_simple_single_symbol(0); // DIST
2091        w.write_bits(0, 1); // entropy pixel 0 GREEN = 0 → meta 0
2092        w.write_bits(1, 1); // entropy pixel 1 GREEN = 1 → meta 1
2093                            // --- num_prefix_groups = 2 prefix-code groups ---
2094        w.write_single_symbol_group(100, 0x10, 0x20, 0x30, 0); // group 0
2095        w.write_single_symbol_group(200, 0x40, 0x50, 0x60, 0); // group 1
2096                                                               // --- main image data: 8 single-symbol literals, no bits ---
2097        let data = w.into_bytes();
2098        let mut r = BitReader::new(&data);
2099        let img = decode_argb(&mut r, 8, 1).unwrap();
2100        assert_eq!(img.width(), 8);
2101        assert_eq!(img.height(), 1);
2102        let g0 = pack_argb(100, 0x10, 0x20, 0x30);
2103        let g1 = pack_argb(200, 0x40, 0x50, 0x60);
2104        assert_eq!(
2105            img.pixels(),
2106            &[g0, g0, g0, g0, g1, g1, g1, g1],
2107            "first 4 pixels use group 0, last 4 use group 1"
2108        );
2109    }
2110
2111    #[test]
2112    fn decode_argb_single_group_meta_prefix_zero() {
2113        // ARGB role, meta-prefix = 0 → one group everywhere. A 2x1 image
2114        // with greens 7 and 8 via a two-symbol GREEN code.
2115        let mut w = BitWriter::new();
2116        w.write_bits(0, 1); // color-cache disabled
2117        w.write_bits(0, 1); // meta-prefix = 0 → single group
2118        w.write_simple_two_symbols(7, 8); // GREEN {7, 8}
2119        w.write_simple_single_symbol(0xAA); // RED
2120        w.write_simple_single_symbol(0xBB); // BLUE
2121        w.write_simple_single_symbol(0xCC); // ALPHA
2122        w.write_simple_single_symbol(0); // DIST
2123        w.write_bits(0, 1); // pixel0 GREEN = 7
2124        w.write_bits(1, 1); // pixel1 GREEN = 8
2125        let data = w.into_bytes();
2126        let mut r = BitReader::new(&data);
2127        let img = decode_argb(&mut r, 2, 1).unwrap();
2128        assert_eq!(
2129            img.pixels(),
2130            &[
2131                pack_argb(7, 0xAA, 0xBB, 0xCC),
2132                pack_argb(8, 0xAA, 0xBB, 0xCC)
2133            ]
2134        );
2135    }
2136
2137    #[test]
2138    fn decode_argb_single_group_matches_decode_image() {
2139        // The single-group decode_argb result must match a direct
2140        // decode_image with the same group + stream tail.
2141        let mut header = BitWriter::new();
2142        header.write_bits(0, 1); // color-cache disabled
2143        header.write_bits(0, 1); // meta-prefix = 0
2144        header.write_single_symbol_group(0x42, 0x10, 0x20, 0x30, 0);
2145        let data = header.into_bytes();
2146        let mut r = BitReader::new(&data);
2147        let img = decode_argb(&mut r, 1, 1).unwrap();
2148        assert_eq!(img.pixels(), &[pack_argb(0x42, 0x10, 0x20, 0x30)]);
2149    }
2150
2151    #[test]
2152    fn decode_argb_multi_group_with_color_cache_round_2x2() {
2153        // A 8x1 ARGB image with a color cache enabled. Two groups; group
2154        // 0 emits literal green=50, group 1 emits literal green=60. The
2155        // cache is maintained across blocks in stream order; this checks
2156        // the multi-group loop threads a single cache through both
2157        // groups without panicking and yields the expected literals.
2158        let cache_bits = 4u32; // size 16
2159        let cache_size = 1usize << cache_bits;
2160        let mut w = BitWriter::new();
2161        w.write_bits(1, 1); // main color-cache-info = enabled
2162        w.write_bits(cache_bits, 4); // code_bits = 4
2163        w.write_bits(1, 1); // meta-prefix = 1 → multi
2164        w.write_bits(0, 3); // prefix_bits → 2 (block 4)
2165                            // entropy image 2x1, codes {0, 1}, no cache.
2166        w.write_bits(0, 1);
2167        w.write_simple_two_symbols(0, 1);
2168        w.write_simple_single_symbol(0);
2169        w.write_simple_single_symbol(0);
2170        w.write_simple_single_symbol(0);
2171        w.write_simple_single_symbol(0);
2172        w.write_bits(0, 1); // entropy pixel 0 → meta 0
2173        w.write_bits(1, 1); // entropy pixel 1 → meta 1
2174                            // 2 prefix-code groups, sized against the main cache.
2175        w.write_single_symbol_group(50, 0x11, 0x22, 0x33, 0);
2176        w.write_single_symbol_group(60, 0x44, 0x55, 0x66, 0);
2177        let data = w.into_bytes();
2178        let mut r = BitReader::new(&data);
2179        let img = decode_argb(&mut r, 8, 1).unwrap();
2180        let g0 = pack_argb(50, 0x11, 0x22, 0x33);
2181        let g1 = pack_argb(60, 0x44, 0x55, 0x66);
2182        assert_eq!(img.pixels(), &[g0, g0, g0, g0, g1, g1, g1, g1]);
2183        // Sanity: cache was sized for the alphabet (so the group GREEN
2184        // reads used the extended alphabet without overruns).
2185        assert_eq!(
2186            PrefixCodeGroup::green_alphabet_size(cache_size),
2187            256 + 24 + cache_size
2188        );
2189    }
2190
2191    #[test]
2192    fn decode_entropy_image_zero_dim_is_refused() {
2193        let data = [0u8; 4];
2194        let mut r = BitReader::new(&data);
2195        match decode_entropy_image(&mut r, 2, 0, 1) {
2196            Err(DecodeError::EmptyEntropyImage {
2197                prefix_image_width,
2198                prefix_image_height,
2199            }) => {
2200                assert_eq!(prefix_image_width, 0);
2201                assert_eq!(prefix_image_height, 1);
2202            }
2203            other => panic!("expected EmptyEntropyImage, got {other:?}"),
2204        }
2205    }
2206
2207    // ---- §5.2 / §6.2.2 malformed-input property tests for decode_argb ----
2208    //
2209    // The full ARGB-role decode_argb pipeline is the public surface
2210    // attackers and corrupt files reach. Each of the four cooperating
2211    // sub-stages (§5.2.3 color-cache info, §6.2.2 meta-prefix header,
2212    // §6.2.2 entropy image, per-group §6.2 prefix code groups, §6.2.3
2213    // main pixel loop) reads from the same BitReader and must respond
2214    // to a truncated stream with a structured DecodeError — never a
2215    // panic, never an `Ok` with an under-filled image. These tests pin
2216    // that contract on each boundary.
2217    //
2218    // The property at the end is exhaustive: every byte-prefix of a
2219    // valid 8x1 multi-group stream that doesn't span all stages must
2220    // produce an `Err`. That covers every off-by-one truncation point
2221    // any stage could be reading at.
2222
2223    /// Build the exact byte buffer that the round-106
2224    /// `decode_argb_two_groups_select_per_block` test uses. Factored so
2225    /// the truncation-prefix tests can re-use it without copy/paste.
2226    fn build_valid_two_group_8x1_stream() -> Vec<u8> {
2227        build_valid_two_group_8x1_stream_with_bit_len().0
2228    }
2229
2230    /// Same as [`build_valid_two_group_8x1_stream`] but also returns the
2231    /// number of bits actually written to the buffer. The trailing
2232    /// `bytes.len() * 8 - bit_len` bits of the last byte are zero pad.
2233    /// Used by the round-165 bit-prefix property test to slice the
2234    /// stream at every bit boundary, not only every byte boundary.
2235    fn build_valid_two_group_8x1_stream_with_bit_len() -> (Vec<u8>, usize) {
2236        let mut w = BitWriter::new();
2237        // spatially-coded-image header (ARGB role)
2238        w.write_bits(0, 1); // §5.2.3 main color-cache-info = disabled
2239        w.write_bits(1, 1); // §6.2.2 meta-prefix = 1 → multiple groups
2240        w.write_bits(0, 3); // §6.2.2 prefix_bits raw = 0 → 2 (block 4)
2241                            // §6.2.2 entropy image (2x1 entropy-coded-image)
2242        w.write_bits(0, 1); // entropy color-cache-info = disabled
2243        w.write_simple_two_symbols(0, 1); // GREEN {0,1}
2244        w.write_simple_single_symbol(0); // RED
2245        w.write_simple_single_symbol(0); // BLUE
2246        w.write_simple_single_symbol(0); // ALPHA
2247        w.write_simple_single_symbol(0); // DIST
2248        w.write_bits(0, 1); // entropy pixel 0 GREEN = 0 → meta 0
2249        w.write_bits(1, 1); // entropy pixel 1 GREEN = 1 → meta 1
2250                            // num_prefix_groups = 2 prefix-code groups
2251        w.write_single_symbol_group(100, 0x10, 0x20, 0x30, 0);
2252        w.write_single_symbol_group(200, 0x40, 0x50, 0x60, 0);
2253        // main image data: 8 single-symbol literals, no bits.
2254        let bit_len = w.bit_len();
2255        (w.into_bytes(), bit_len)
2256    }
2257
2258    #[test]
2259    fn decode_argb_two_groups_baseline_decodes_clean() {
2260        // Sanity: the helper produces a stream that round-trips. The
2261        // truncation tests below all assume this baseline is valid.
2262        let data = build_valid_two_group_8x1_stream();
2263        let mut r = BitReader::new(&data);
2264        assert!(decode_argb(&mut r, 8, 1).is_ok());
2265    }
2266
2267    #[test]
2268    fn decode_argb_empty_input_reports_eof() {
2269        // Zero bytes can't even satisfy the first 1-bit color-cache-info
2270        // read. decode_argb must return Eof, not panic, not Ok.
2271        let data: [u8; 0] = [];
2272        let mut r = BitReader::new(&data);
2273        match decode_argb(&mut r, 1, 1) {
2274            Err(DecodeError::Eof(_)) | Err(DecodeError::MetaPrefix(_)) => {}
2275            other => panic!("expected Eof or MetaPrefix, got {other:?}"),
2276        }
2277    }
2278
2279    #[test]
2280    fn decode_argb_truncated_after_meta_prefix_header_reports_eof() {
2281        // Just enough bits to land past the meta-prefix-header phase (5
2282        // bits: color-cache=0, meta-prefix=1, prefix_bits=000). The
2283        // entropy-image read should then EOF on the entropy
2284        // color-cache-info bit.
2285        let mut w = BitWriter::new();
2286        w.write_bits(0, 1);
2287        w.write_bits(1, 1);
2288        w.write_bits(0, 3);
2289        let data = w.into_bytes();
2290        let mut r = BitReader::new(&data);
2291        match decode_argb(&mut r, 8, 1) {
2292            Err(DecodeError::Eof(_)) | Err(DecodeError::MetaPrefix(_)) => {}
2293            other => panic!("expected Eof / MetaPrefix, got {other:?}"),
2294        }
2295    }
2296
2297    #[test]
2298    fn decode_argb_truncated_mid_per_group_prefix_reports_eof() {
2299        // Build a valid stream, then chop it to a length that lands
2300        // *inside* the per-group prefix-code section (after entropy
2301        // image, before the second group's tables are complete). The
2302        // per-group PrefixCodeGroup::read must surface an EOF rather
2303        // than a wrong-shape success.
2304        let full = build_valid_two_group_8x1_stream();
2305        // The full stream is ~10 bytes; truncating to 6 lands inside
2306        // group 0's GREEN/RED tables on this layout.
2307        assert!(
2308            full.len() > 6,
2309            "stream layout changed; rechoose truncation point"
2310        );
2311        let truncated = &full[..6];
2312        let mut r = BitReader::new(truncated);
2313        match decode_argb(&mut r, 8, 1) {
2314            Err(DecodeError::Eof(_))
2315            | Err(DecodeError::MetaPrefix(_))
2316            | Err(DecodeError::Prefix(_)) => {}
2317            other => panic!("expected Eof / MetaPrefix / Prefix, got {other:?}"),
2318        }
2319    }
2320
2321    #[test]
2322    fn decode_argb_every_byte_prefix_of_valid_stream_is_safe() {
2323        // The strong property: for every byte-prefix shorter than the
2324        // full valid stream, decode_argb on an 8x1 image must either
2325        // return Err, or — if the bit cursor happened to land at a
2326        // valid stage boundary and the remaining symbols are
2327        // single-leaf no-bit reads (so EOF isn't tripped) — return an
2328        // image of the requested size. It must NOT panic and must NOT
2329        // return a successfully-decoded image with the wrong pixel
2330        // count.
2331        let full = build_valid_two_group_8x1_stream();
2332        for len in 0..full.len() {
2333            let prefix = &full[..len];
2334            let mut r = BitReader::new(prefix);
2335            let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2336                decode_argb(&mut r, 8, 1)
2337            }));
2338            match result {
2339                Ok(Ok(img)) => {
2340                    assert_eq!(
2341                        img.pixels().len(),
2342                        8,
2343                        "len={len}: Ok decode produced wrong pixel count"
2344                    );
2345                }
2346                Ok(Err(_)) => {
2347                    // Any structured DecodeError is fine — it's the
2348                    // contract we want.
2349                }
2350                Err(_) => panic!("len={len}: decode_argb panicked on truncated input"),
2351            }
2352        }
2353    }
2354
2355    #[test]
2356    fn decode_argb_oversize_meta_prefix_bits_is_refused() {
2357        // §6.2.2 prefix_bits raw is 3 bits → derived prefix_bits is
2358        // raw + 2, capped at 9. Some malformed encoders may emit the
2359        // maximum raw value (7 → derived 9) on a tiny canvas; if the
2360        // resulting entropy image is degenerate, decode_argb must
2361        // surface EmptyEntropyImage rather than wedge.
2362        let mut w = BitWriter::new();
2363        w.write_bits(0, 1); // color-cache disabled
2364        w.write_bits(1, 1); // meta-prefix = 1 → multiple groups
2365        w.write_bits(7, 3); // prefix_bits raw 7 → derived 9 → block 512
2366                            // For a 1x1 image with block 512, the entropy image
2367                            // dimensions are (1+511)/512 = 1, so we still need
2368                            // a valid entropy image. Make it minimal: one
2369                            // single-meta-code pixel = 0.
2370        w.write_bits(0, 1); // entropy color-cache disabled
2371        w.write_simple_single_symbol(0); // GREEN single 0
2372        w.write_simple_single_symbol(0); // RED
2373        w.write_simple_single_symbol(0); // BLUE
2374        w.write_simple_single_symbol(0); // ALPHA
2375        w.write_simple_single_symbol(0); // DIST
2376                                         // One prefix-code group (max = 0 → count = 1).
2377        w.write_single_symbol_group(0x77, 0, 0, 0, 0);
2378        let data = w.into_bytes();
2379        let mut r = BitReader::new(&data);
2380        // The point of this test is "doesn't panic / doesn't loop"; a
2381        // clean Ok with the right pixel count is also acceptable for
2382        // this corner, and any structured Err is also acceptable.
2383        if let Ok(img) = decode_argb(&mut r, 1, 1) {
2384            assert_eq!(img.pixels().len(), 1);
2385        }
2386    }
2387
2388    // ---- Round 165: §5.2 / §6.2.2 bit-prefix property test ----
2389    //
2390    // The round-164 property iterates byte-prefixes of the valid 8x1
2391    // multi-group stream. The §5.2 / §6.2.2 stages all consume sub-byte
2392    // bit fields (a §5.2.3 color-cache-info is a single bit; the
2393    // §6.2.2 prefix_bits field is three bits; §6.2.1 simple-code
2394    // symbols read 1-8 bits each), so a byte-prefix only samples the
2395    // truncation lattice at every 8 bits — most stage-boundary
2396    // truncation points sit *inside* a byte and are invisible to the
2397    // r164 property.
2398    //
2399    // This round-165 property tightens the granularity to a single
2400    // bit. For every truncation point `bit_len ∈ 0..=full_bits`, build
2401    // a buffer of `ceil(bit_len / 8)` bytes whose last byte's upper
2402    // `(8 - bit_len % 8) & 7` bits are zero-masked, then run the same
2403    // catch_unwind-protected decode_argb assertion: no panic, and any
2404    // `Ok` must carry exactly the requested pixel count. The
2405    // zero-padding tail is part of the property — the decoder may
2406    // legitimately read zero bits past the encoder's stop point and
2407    // either succeed (if the trailing zeros parse as a valid
2408    // single-leaf symbol stream) or EOF cleanly.
2409    //
2410    // Pure additive coverage: the decoder source is unchanged; this
2411    // pins the structured-error contract at 8x finer resolution than
2412    // r164.
2413
2414    /// Truncate `data` (LSB-first bitstream) to its first `bit_len`
2415    /// bits, returning a fresh byte vector. The returned buffer has
2416    /// length `ceil(bit_len / 8)`; if `bit_len` is not a byte multiple,
2417    /// the upper `(8 - bit_len % 8)` bits of the last byte are masked
2418    /// to zero so the buffer cleanly represents the first `bit_len`
2419    /// bits of `data` followed by zero pad to the byte boundary.
2420    fn truncate_to_bit_prefix(data: &[u8], bit_len: usize) -> Vec<u8> {
2421        let byte_len = bit_len.div_ceil(8);
2422        assert!(
2423            byte_len <= data.len(),
2424            "bit_len {bit_len} exceeds source ({} bits)",
2425            data.len() * 8
2426        );
2427        let mut out = data[..byte_len].to_vec();
2428        let leftover = bit_len & 7;
2429        if leftover != 0 && !out.is_empty() {
2430            // Keep the low `leftover` bits of the last byte, zero the rest.
2431            let mask = (1u8 << leftover) - 1;
2432            let last = out.len() - 1;
2433            out[last] &= mask;
2434        }
2435        out
2436    }
2437
2438    #[test]
2439    fn truncate_to_bit_prefix_round_trips_a_known_byte() {
2440        // 0b1011_0101 = 0xB5. Bit 0 (LSB of LSB-first) = 1, bit 1 = 0,
2441        // bit 2 = 1, bit 3 = 0, bit 4 = 1, bit 5 = 1, bit 6 = 0,
2442        // bit 7 = 1.
2443        let src = [0xB5u8];
2444        // 0-bit prefix: empty buffer.
2445        assert!(truncate_to_bit_prefix(&src, 0).is_empty());
2446        // 1-bit prefix: only bit 0 = 1, byte = 0x01.
2447        assert_eq!(truncate_to_bit_prefix(&src, 1), vec![0x01]);
2448        // 4-bit prefix: 0b0101 = 0x05.
2449        assert_eq!(truncate_to_bit_prefix(&src, 4), vec![0x05]);
2450        // 8-bit prefix: full byte.
2451        assert_eq!(truncate_to_bit_prefix(&src, 8), vec![0xB5]);
2452    }
2453
2454    #[test]
2455    fn decode_argb_every_bit_prefix_of_valid_stream_is_safe() {
2456        // The round-165 strong property: for every bit-prefix
2457        // `bit_len ∈ 0..=full_bits` of a valid 8x1 multi-group stream
2458        // (zero-padded to the next byte boundary as the natural
2459        // representation of a partial bit count), decode_argb must
2460        // either return Err, or — when the trailing zero pad happens
2461        // to parse as a valid continuation of the stream — return an
2462        // image of exactly 8 pixels. It must NOT panic.
2463        //
2464        // Granularity is 1 bit (8x tighter than r164's byte property);
2465        // the §5.2.3 / §6.2.2 stages all read sub-byte fields, so this
2466        // catches truncation seams the byte property cannot see.
2467        let (full, full_bits) = build_valid_two_group_8x1_stream_with_bit_len();
2468        // Sanity: a full-bit-length decode succeeds (the baseline test
2469        // checks this too, but we re-assert here so a regression in
2470        // the helper is caught locally).
2471        {
2472            let mut r = BitReader::new(&full);
2473            assert!(
2474                decode_argb(&mut r, 8, 1).is_ok(),
2475                "full {full_bits}-bit stream must decode"
2476            );
2477        }
2478        // Iterate every bit cut from 0 up to and including the full
2479        // bit length. The 0-bit prefix mirrors the empty-input EOF
2480        // contract; the full-bits prefix mirrors the baseline decode.
2481        for bit_len in 0..=full_bits {
2482            let prefix = truncate_to_bit_prefix(&full, bit_len);
2483            let mut r = BitReader::new(&prefix);
2484            let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2485                decode_argb(&mut r, 8, 1)
2486            }));
2487            match result {
2488                Ok(Ok(img)) => {
2489                    assert_eq!(
2490                        img.pixels().len(),
2491                        8,
2492                        "bit_len={bit_len}: Ok decode produced wrong pixel count"
2493                    );
2494                }
2495                Ok(Err(_)) => {
2496                    // Any structured DecodeError is acceptable — that's
2497                    // the contract under test.
2498                }
2499                Err(_) => {
2500                    panic!("bit_len={bit_len}: decode_argb panicked on truncated input")
2501                }
2502            }
2503        }
2504    }
2505
2506    #[test]
2507    fn decode_argb_bit_prefix_covers_every_sub_byte_seam() {
2508        // Coverage sanity for the round-165 property above: confirm
2509        // the prefix sweep actually visits at least one bit position
2510        // inside every byte of the stream (i.e. covers all sub-byte
2511        // boundaries the byte-prefix property would skip past). With
2512        // the iteration `0..=full_bits` and `full_bits > 8`, every
2513        // bit boundary in `[0, full_bits]` is hit by construction;
2514        // this test just pins that arithmetic so a future refactor
2515        // that switched the bound (e.g. to `step_by(8)`) is caught.
2516        let (_full, full_bits) = build_valid_two_group_8x1_stream_with_bit_len();
2517        // The fixture currently writes about ~80 bits (5-bit header +
2518        // ~19-bit GREEN simple-two + 4×11-bit single-symbol + 2-bit
2519        // entropy pixels + 2×~55-bit single-symbol groups); allow a
2520        // generous lower bound so the test doesn't lock the exact
2521        // layout but still asserts non-trivial coverage.
2522        assert!(
2523            full_bits >= 60,
2524            "fixture bit-length unexpectedly short ({full_bits} bits); \
2525             round-165 property loses its multi-stage coverage if the \
2526             helper stops writing the entropy image / per-group tables"
2527        );
2528        // And the byte buffer must be at least `ceil(full_bits / 8)`
2529        // bytes — otherwise truncate_to_bit_prefix's len assertion
2530        // would fail on the largest bit prefix.
2531        let byte_len_needed = full_bits.div_ceil(8);
2532        assert!(
2533            byte_len_needed * 8 >= full_bits,
2534            "byte length ({byte_len_needed}) cannot cover bit length ({full_bits})"
2535        );
2536    }
2537}