Skip to main content

oxideav_webp/
meta_prefix.rs

1//! VP8L (WebP-Lossless) §5.2.3 color-cache info + §6.2.2 meta-prefix
2//! header reader + §6.2 5-prefix-code-group reader.
3//!
4//! This sits directly on top of round 104's
5//! [`crate::vp8l_prefix::PrefixCode`] reader. Where round 104 reads a
6//! *single* canonical prefix code off the wire, this module assembles
7//! the §7.3 ABNF `spatially-coded-image` and `entropy-coded-image`
8//! preambles:
9//!
10//! ```text
11//! spatially-coded-image =  color-cache-info meta-prefix data
12//! entropy-coded-image   =  color-cache-info data
13//!
14//! color-cache-info      =  %b0
15//! color-cache-info      =/ (%b1 4BIT)   ; 1 followed by color cache size
16//!
17//! meta-prefix           =  %b0 / (%b1 entropy-image)
18//!
19//! data                  =  prefix-codes lz77-coded-image
20//! prefix-codes          =  prefix-code-group *prefix-codes
21//! prefix-code-group     =  5prefix-code
22//! ```
23//!
24//! Per §5.1, "image data" appears in five **roles**: the top-level
25//! ARGB image (spatially-coded), the §6.2.2 *entropy image*, the §4.1
26//! *predictor image*, the §4.2 *color transform image*, and the §4.4
27//! *color indexing image*. The §6.2.2 meta-prefix layer is "used
28//! *only* when the image is being used in the role of an ARGB image"
29//! — the other four roles drop straight from the color-cache-info bit
30//! into a single prefix-code group + LZ77 stream.
31//!
32//! ## What this module produces
33//!
34//! [`MetaPrefixHeader::read`] consumes the `color-cache-info` bit
35//! (plus the `ReadBits(4)` `color_cache_code_bits` if set per §5.2.3),
36//! then the `meta-prefix` 1-bit flag (only for an ARGB-role read),
37//! then dispatches:
38//!
39//! * **`meta-prefix = %b0`** (single prefix-code group everywhere, or
40//!   non-ARGB role): immediately reads the `prefix-code-group`
41//!   (5 canonical prefix codes; the GREEN alphabet absorbs the
42//!   `color_cache_size` per §6.2.3) and returns a fully-built
43//!   [`MetaPrefixCodes::Single`].
44//!
45//! * **`meta-prefix = %b1 entropy-image`** (multiple groups, ARGB role
46//!   only): reads the §6.2.2 `prefix_bits = ReadBits(3) + 2` field and
47//!   computes the entropy-image dimensions, then **stops** — the
48//!   entropy image itself is an `entropy-coded-image` (i.e. another
49//!   `color-cache-info data` stream that needs the §5.2 LZ77 +
50//!   color-cache decoder this crate does not yet have). Returns
51//!   [`MetaPrefixCodes::EntropyImagePending`] with `prefix_bits`,
52//!   derived `image_width` / `image_height`, and the bit position
53//!   where the entropy image starts. The next layer's §5.2 reader can
54//!   resume from there.
55//!
56//! ## Why split the multi-group case
57//!
58//! §6.2.2 says `num_prefix_groups = max(entropy_image) + 1`. The
59//! decoder cannot read that maximum out of the entropy image without
60//! actually decoding the entropy image — a §5.2-encoded
61//! `entropy-coded-image`. Until the §5.2 LZ77 + color-cache layer
62//! lands, the multi-group case is structurally read up to the
63//! entropy-image boundary, the boundary is recorded, and the rest is
64//! left to the next round (same pattern round 99 used to stop at the
65//! first §5 transform body and round 104 used to resume at it).
66//!
67//! ## What this module does NOT do
68//!
69//! * No §5.2 LZ77 backward-reference or color-cache *symbol* decode.
70//!   Those consume symbols produced by the [`PrefixCodeGroup`] this
71//!   reader returns but live one layer up.
72//! * No actual entropy-image decode (the `num_prefix_groups` lookup).
73//! * No `oxideav-core` runtime dependency — this module compiles under
74//!   `--no-default-features`.
75
76use crate::vp8l_prefix::{PrefixCode, PrefixError};
77use crate::vp8l_stream::{BitReader, BitReaderEof};
78
79/// The five prefix codes that make up one §6.2 "prefix code group".
80///
81/// In bitstream order: green-channel + backref-length + color-cache
82/// (channel 1), red (2), blue (3), alpha (4), backref-distance (5).
83/// Each pixel is decoded with exactly one group; which group applies
84/// to which pixel block is selected by the §6.2.2 meta-prefix layer.
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct PrefixCodeGroup {
87    /// Prefix code #1: green channel + length-prefix + color-cache.
88    /// Alphabet size is `256 + 24 + color_cache_size` per §6.2.3.
89    pub green: PrefixCode,
90    /// Prefix code #2: red channel, alphabet `256`.
91    pub red: PrefixCode,
92    /// Prefix code #3: blue channel, alphabet `256`.
93    pub blue: PrefixCode,
94    /// Prefix code #4: alpha channel, alphabet `256`.
95    pub alpha: PrefixCode,
96    /// Prefix code #5: backref distance, alphabet `40`.
97    pub distance: PrefixCode,
98}
99
100impl PrefixCodeGroup {
101    /// The size-of-alphabet for prefix code #1 (green / length /
102    /// color-cache) for a stream whose color-cache holds
103    /// `color_cache_size` entries (`0` when the cache is disabled,
104    /// `1 << color_cache_code_bits` when it is enabled). Per §6.2.3:
105    /// `256 + 24 + color_cache_size`.
106    pub fn green_alphabet_size(color_cache_size: usize) -> usize {
107        256 + 24 + color_cache_size
108    }
109
110    /// Read one prefix-code group (five canonical prefix codes) from
111    /// the bitstream in §6.2 order.
112    pub fn read(
113        reader: &mut BitReader<'_>,
114        color_cache_size: usize,
115    ) -> Result<Self, MetaPrefixError> {
116        let green_alphabet = Self::green_alphabet_size(color_cache_size);
117        let green = PrefixCode::read(reader, green_alphabet)?;
118        let red = PrefixCode::read(reader, 256)?;
119        let blue = PrefixCode::read(reader, 256)?;
120        let alpha = PrefixCode::read(reader, 256)?;
121        let distance = PrefixCode::read(reader, 40)?;
122        Ok(Self {
123            green,
124            red,
125            blue,
126            alpha,
127            distance,
128        })
129    }
130}
131
132/// The role an image-data block is being read in.
133///
134/// Per §5.1 / §6.2.2: only the top-level `Argb` role carries the
135/// §6.2.2 meta-prefix layer. The other four roles (entropy / predictor
136/// / color-transform / color-indexing image, all `entropy-coded-image`
137/// in the §7.3 ABNF) drop straight from the color-cache-info bit into
138/// the single 5-code prefix-code group.
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum ImageRole {
141    /// §5.1 ARGB image (the spatially-coded top-level pixel stream).
142    /// Carries the §6.2.2 meta-prefix bit.
143    Argb,
144    /// Any of the four `entropy-coded-image` roles (entropy /
145    /// predictor / color-transform / color-indexing image). No
146    /// meta-prefix bit; single prefix-code group only.
147    EntropyCoded,
148}
149
150/// §5.2.3: range gate for `color_cache_code_bits`.
151///
152/// Spec wording is "the range of allowed values for
153/// `color_cache_code_bits` is `[1..11]`. Compliant decoders must
154/// indicate a corrupted bitstream for other values."
155pub const COLOR_CACHE_BITS_MIN: u32 = 1;
156/// See [`COLOR_CACHE_BITS_MIN`].
157pub const COLOR_CACHE_BITS_MAX: u32 = 11;
158
159/// §6.2.2 entropy image: `prefix_bits = ReadBits(3) + 2`, so the
160/// allowed range is `[2..9]`.
161pub const PREFIX_BITS_MIN: u32 = 2;
162/// See [`PREFIX_BITS_MIN`].
163pub const PREFIX_BITS_MAX: u32 = 9;
164
165/// Errors raised while reading the §5.2.3 color-cache info + §6.2.2
166/// meta-prefix header + the §6.2 prefix-code group(s).
167#[derive(Debug, Clone, PartialEq, Eq)]
168pub enum MetaPrefixError {
169    /// The bit reader hit EOF mid-field.
170    Eof(BitReaderEof),
171    /// §5.2.3: `color_cache_code_bits` was outside the `[1..11]`
172    /// allowed range.
173    InvalidColorCacheCodeBits {
174        /// The on-wire value that failed the range check.
175        value: u32,
176    },
177    /// One of the five §6.2.1 prefix codes inside a group failed to
178    /// parse.
179    Prefix(PrefixError),
180}
181
182impl From<BitReaderEof> for MetaPrefixError {
183    fn from(e: BitReaderEof) -> Self {
184        Self::Eof(e)
185    }
186}
187
188impl From<PrefixError> for MetaPrefixError {
189    fn from(e: PrefixError) -> Self {
190        match e {
191            PrefixError::Eof(eof) => Self::Eof(eof),
192            other => Self::Prefix(other),
193        }
194    }
195}
196
197impl core::fmt::Display for MetaPrefixError {
198    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
199        match self {
200            Self::Eof(e) => write!(f, "VP8L §5.2.3 / §6.2.2 meta-prefix: {e}"),
201            Self::InvalidColorCacheCodeBits { value } => write!(
202                f,
203                "VP8L §5.2.3 color_cache_code_bits = {value} is outside the [{COLOR_CACHE_BITS_MIN}..{COLOR_CACHE_BITS_MAX}] allowed range"
204            ),
205            Self::Prefix(e) => write!(f, "VP8L §6.2 prefix code in group: {e}"),
206        }
207    }
208}
209
210impl std::error::Error for MetaPrefixError {}
211
212/// §5.2.3 color-cache info: whether a color cache is present + its
213/// size (`1 << color_cache_code_bits` per §5.2.3).
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215pub struct ColorCacheInfo {
216    /// `color_cache_code_bits` read off the wire, or `0` when the
217    /// cache is disabled (`color-cache-info = %b0`). The §5.2.3 range
218    /// for the *enabled* case is `[1..11]`; `0` here is the sentinel
219    /// for "no cache" and is **never** a wire value (a 0 wire value
220    /// is rejected as out-of-range).
221    pub code_bits: u32,
222}
223
224impl ColorCacheInfo {
225    /// `true` when a color cache is active (i.e. `color-cache-info`
226    /// started with the `%b1` flag).
227    pub fn is_enabled(&self) -> bool {
228        self.code_bits != 0
229    }
230
231    /// Size of the color cache table, `1 << code_bits` when enabled,
232    /// `0` when disabled.
233    pub fn size(&self) -> usize {
234        if self.is_enabled() {
235            1usize << self.code_bits
236        } else {
237            0
238        }
239    }
240
241    /// Read a §5.2.3 `color-cache-info` field.
242    pub fn read(reader: &mut BitReader<'_>) -> Result<Self, MetaPrefixError> {
243        if reader.read_bit()? {
244            let code_bits = reader.read_bits(4)?;
245            if !(COLOR_CACHE_BITS_MIN..=COLOR_CACHE_BITS_MAX).contains(&code_bits) {
246                return Err(MetaPrefixError::InvalidColorCacheCodeBits { value: code_bits });
247            }
248            Ok(Self { code_bits })
249        } else {
250            Ok(Self { code_bits: 0 })
251        }
252    }
253}
254
255/// The decoded §6.2.2 prefix-code-group layer: either a single group
256/// that applies to the whole image, or a pending entropy-image
257/// boundary the next §5.2 reader has to resume from.
258///
259/// The `Single` variant boxes its `PrefixCodeGroup` because the group
260/// is much larger than the `EntropyImagePending` variant's handful of
261/// integers — keeping the enum compact for callers that only branch on
262/// the discriminant.
263#[derive(Debug, Clone, PartialEq, Eq)]
264pub enum MetaPrefixCodes {
265    /// `meta-prefix = %b0` (or non-ARGB role): exactly one
266    /// prefix-code group for the entire image.
267    Single {
268        /// The one prefix-code group.
269        group: Box<PrefixCodeGroup>,
270    },
271    /// `meta-prefix = %b1 entropy-image`: multiple prefix-code groups,
272    /// chosen per pixel block via an entropy image. The entropy image
273    /// itself is an `entropy-coded-image` (color-cache-info + §5.2
274    /// data) which this round cannot yet decode; the reader stops at
275    /// the entropy-image start, records `prefix_bits` +
276    /// `image_width`/`image_height` + the start bit position, and
277    /// leaves the decode to the next round.
278    EntropyImagePending {
279        /// §6.2.2 `prefix_bits = ReadBits(3) + 2`, range `[2..9]`.
280        /// `block_size = 1 << prefix_bits`.
281        prefix_bits: u8,
282        /// `DIV_ROUND_UP(image_width, 1 << prefix_bits)`.
283        image_width: u32,
284        /// `DIV_ROUND_UP(image_height, 1 << prefix_bits)`.
285        image_height: u32,
286        /// Bit position (from the start of the slice the
287        /// [`BitReader`] was constructed over) at which the entropy
288        /// image begins (i.e. just past the `prefix_bits` field).
289        entropy_image_bit_position: usize,
290    },
291}
292
293impl MetaPrefixCodes {
294    /// `true` when the read produced a single complete prefix-code
295    /// group.
296    pub fn is_single(&self) -> bool {
297        matches!(self, Self::Single { .. })
298    }
299
300    /// The single prefix-code group, when this is `Single`.
301    pub fn group(&self) -> Option<&PrefixCodeGroup> {
302        match self {
303            Self::Single { group } => Some(group),
304            Self::EntropyImagePending { .. } => None,
305        }
306    }
307}
308
309/// The combined §5.2.3 color-cache info + §6.2.2 meta-prefix +
310/// §6.2 prefix-code-group(s) header for one §5 image-data block.
311#[derive(Debug, Clone, PartialEq, Eq)]
312pub struct MetaPrefixHeader {
313    /// The §5.2.3 color-cache info bit + size.
314    pub color_cache: ColorCacheInfo,
315    /// The §6.2.2 meta-prefix dispatch result.
316    pub codes: MetaPrefixCodes,
317}
318
319/// Round-up division — matches the spec's `DIV_ROUND_UP` macro defined
320/// in §4.1.
321fn div_round_up(n: u32, d: u32) -> u32 {
322    debug_assert!(d != 0, "DIV_ROUND_UP requires non-zero divisor");
323    n.div_ceil(d)
324}
325
326impl MetaPrefixHeader {
327    /// Read the §5.2.3 + §6.2.2 + §6.2 preamble for one §5 image-data
328    /// block. `role` selects whether the §6.2.2 meta-prefix bit is
329    /// read (only for the ARGB image role); `image_width` /
330    /// `image_height` are used to derive the entropy-image dimensions
331    /// when the meta-prefix bit is set.
332    ///
333    /// The reader's bit cursor is left right at the start of the
334    /// §5.2 `data` (the LZ77-coded image) for `Single`, or right at
335    /// the start of the §6.2.2 `entropy-image` block for
336    /// `EntropyImagePending`.
337    pub fn read(
338        reader: &mut BitReader<'_>,
339        role: ImageRole,
340        image_width: u32,
341        image_height: u32,
342    ) -> Result<Self, MetaPrefixError> {
343        let color_cache = ColorCacheInfo::read(reader)?;
344
345        // §6.2.2: meta-prefix bit is present ONLY for the ARGB role.
346        let use_meta_prefix = if matches!(role, ImageRole::Argb) {
347            reader.read_bit()?
348        } else {
349            false
350        };
351
352        if !use_meta_prefix {
353            // Single group: read the 5 prefix codes directly.
354            let group = PrefixCodeGroup::read(reader, color_cache.size())?;
355            return Ok(Self {
356                color_cache,
357                codes: MetaPrefixCodes::Single {
358                    group: Box::new(group),
359                },
360            });
361        }
362
363        // Multi-group: read `prefix_bits`, derive entropy-image dims,
364        // record the boundary, and stop.
365        let raw = reader.read_bits(3)?;
366        let prefix_bits = raw + PREFIX_BITS_MIN;
367        debug_assert!((PREFIX_BITS_MIN..=PREFIX_BITS_MAX).contains(&prefix_bits));
368        let block_size = 1u32 << prefix_bits;
369        let entropy_w = div_round_up(image_width, block_size);
370        let entropy_h = div_round_up(image_height, block_size);
371        let entropy_image_bit_position = reader.bit_position();
372        Ok(Self {
373            color_cache,
374            codes: MetaPrefixCodes::EntropyImagePending {
375                prefix_bits: prefix_bits as u8,
376                image_width: entropy_w,
377                image_height: entropy_h,
378                entropy_image_bit_position,
379            },
380        })
381    }
382}
383
384#[cfg(test)]
385mod tests {
386    use super::*;
387
388    /// Tiny LSB-first bit writer for building synthetic streams.
389    struct BitWriter {
390        bytes: Vec<u8>,
391        bit_pos: usize,
392    }
393    impl BitWriter {
394        fn new() -> Self {
395            Self {
396                bytes: Vec::new(),
397                bit_pos: 0,
398            }
399        }
400        fn write_bits(&mut self, mut value: u32, n: usize) {
401            for _ in 0..n {
402                let byte_idx = self.bit_pos >> 3;
403                if byte_idx >= self.bytes.len() {
404                    self.bytes.push(0);
405                }
406                let bit = (value & 1) as u8;
407                self.bytes[byte_idx] |= bit << (self.bit_pos & 7);
408                self.bit_pos += 1;
409                value >>= 1;
410            }
411        }
412        /// Write one simple-code length-1 single-symbol prefix code
413        /// for the symbol `sym` (in the 8-bit symbol form, alphabet
414        /// `<= 256+24+cache_size`).
415        fn write_simple_single_symbol(&mut self, sym: u32) {
416            // flag = 1 (simple)
417            self.write_bits(1, 1);
418            // num_symbols - 1 = 0
419            self.write_bits(0, 1);
420            // is_first_8bits = 1
421            self.write_bits(1, 1);
422            // symbol0 in 8 bits
423            self.write_bits(sym, 8);
424        }
425        fn into_bytes(self) -> Vec<u8> {
426            self.bytes
427        }
428    }
429
430    // ---- ColorCacheInfo ----
431
432    #[test]
433    fn color_cache_info_disabled() {
434        let mut w = BitWriter::new();
435        w.write_bits(0, 1); // %b0
436        let data = w.into_bytes();
437        let mut r = BitReader::new(&data);
438        let cc = ColorCacheInfo::read(&mut r).unwrap();
439        assert!(!cc.is_enabled());
440        assert_eq!(cc.size(), 0);
441        assert_eq!(cc.code_bits, 0);
442        assert_eq!(r.bit_position(), 1);
443    }
444
445    #[test]
446    fn color_cache_info_enabled_min() {
447        let mut w = BitWriter::new();
448        w.write_bits(1, 1); // %b1
449        w.write_bits(1, 4); // code_bits = 1 (the lower bound)
450        let data = w.into_bytes();
451        let mut r = BitReader::new(&data);
452        let cc = ColorCacheInfo::read(&mut r).unwrap();
453        assert!(cc.is_enabled());
454        assert_eq!(cc.code_bits, 1);
455        assert_eq!(cc.size(), 2);
456    }
457
458    #[test]
459    fn color_cache_info_enabled_max() {
460        let mut w = BitWriter::new();
461        w.write_bits(1, 1);
462        w.write_bits(11, 4); // code_bits = 11 (the upper bound)
463        let data = w.into_bytes();
464        let mut r = BitReader::new(&data);
465        let cc = ColorCacheInfo::read(&mut r).unwrap();
466        assert_eq!(cc.code_bits, 11);
467        assert_eq!(cc.size(), 2048);
468    }
469
470    #[test]
471    fn color_cache_info_zero_code_bits_is_refused() {
472        // §5.2.3 says the enabled range is [1..11]; 0 is out of range.
473        let mut w = BitWriter::new();
474        w.write_bits(1, 1);
475        w.write_bits(0, 4);
476        let data = w.into_bytes();
477        let mut r = BitReader::new(&data);
478        match ColorCacheInfo::read(&mut r) {
479            Err(MetaPrefixError::InvalidColorCacheCodeBits { value }) => assert_eq!(value, 0),
480            other => panic!("expected InvalidColorCacheCodeBits, got {other:?}"),
481        }
482    }
483
484    #[test]
485    fn color_cache_info_twelve_code_bits_is_refused() {
486        let mut w = BitWriter::new();
487        w.write_bits(1, 1);
488        w.write_bits(12, 4);
489        let data = w.into_bytes();
490        let mut r = BitReader::new(&data);
491        match ColorCacheInfo::read(&mut r) {
492            Err(MetaPrefixError::InvalidColorCacheCodeBits { value }) => assert_eq!(value, 12),
493            other => panic!("expected InvalidColorCacheCodeBits, got {other:?}"),
494        }
495    }
496
497    // ---- PrefixCodeGroup ----
498
499    #[test]
500    fn prefix_code_group_green_alphabet_size_matches_spec() {
501        // §6.2.3: G alphabet = 256 + 24 + color_cache_size.
502        assert_eq!(PrefixCodeGroup::green_alphabet_size(0), 280);
503        assert_eq!(PrefixCodeGroup::green_alphabet_size(2), 282);
504        assert_eq!(PrefixCodeGroup::green_alphabet_size(2048), 2328);
505    }
506
507    #[test]
508    fn prefix_code_group_reads_five_simple_codes_in_order() {
509        // Bitstream order per §6.2: GREEN, RED, BLUE, ALPHA, DIST.
510        let mut w = BitWriter::new();
511        w.write_simple_single_symbol(60); // GREEN
512        w.write_simple_single_symbol(180); // RED
513        w.write_simple_single_symbol(90); // BLUE
514        w.write_simple_single_symbol(255); // ALPHA
515        w.write_simple_single_symbol(0); // DIST
516        let data = w.into_bytes();
517        let mut r = BitReader::new(&data);
518        let g = PrefixCodeGroup::read(&mut r, 0).unwrap();
519        assert_eq!(g.green.single_symbol(), Some(60));
520        assert_eq!(g.red.single_symbol(), Some(180));
521        assert_eq!(g.blue.single_symbol(), Some(90));
522        assert_eq!(g.alpha.single_symbol(), Some(255));
523        assert_eq!(g.distance.single_symbol(), Some(0));
524    }
525
526    // ---- MetaPrefixHeader (single-group / non-ARGB) ----
527
528    #[test]
529    fn entropy_coded_role_has_no_meta_prefix_bit() {
530        // For a non-ARGB role we read color-cache-info then drop
531        // straight into the group — no §6.2.2 dispatch bit.
532        // color-cache=0, then 5 single-symbol simple codes.
533        let mut w = BitWriter::new();
534        w.write_bits(0, 1);
535        for sym in [60u32, 180, 90, 255, 0] {
536            w.write_simple_single_symbol(sym);
537        }
538        let data = w.into_bytes();
539        let mut r = BitReader::new(&data);
540        let h = MetaPrefixHeader::read(&mut r, ImageRole::EntropyCoded, 1, 1).unwrap();
541        assert!(!h.color_cache.is_enabled());
542        let group = h.codes.group().expect("Single");
543        assert_eq!(group.green.single_symbol(), Some(60));
544        assert_eq!(group.distance.single_symbol(), Some(0));
545    }
546
547    #[test]
548    fn argb_role_meta_prefix_zero_reads_one_group_directly() {
549        // ARGB role, color-cache=0, meta-prefix=0, then 5 single-symbol
550        // simple codes.
551        let mut w = BitWriter::new();
552        w.write_bits(0, 1); // color-cache disabled
553        w.write_bits(0, 1); // meta-prefix = 0 → single group
554        for sym in [42u32, 200, 150, 100, 5] {
555            w.write_simple_single_symbol(sym);
556        }
557        let data = w.into_bytes();
558        let mut r = BitReader::new(&data);
559        let h = MetaPrefixHeader::read(&mut r, ImageRole::Argb, 64, 64).unwrap();
560        let group = h.codes.group().expect("Single");
561        assert_eq!(group.green.single_symbol(), Some(42));
562        assert_eq!(group.distance.single_symbol(), Some(5));
563        assert!(h.codes.is_single());
564    }
565
566    #[test]
567    fn argb_role_meta_prefix_one_stops_at_entropy_image() {
568        // ARGB role, color-cache=0, meta-prefix=1, prefix_bits = 0 + 2 = 2.
569        // image dims 32x16 → 1<<2 = 4 → 32/4=8, 16/4=4.
570        let mut w = BitWriter::new();
571        w.write_bits(0, 1); // color-cache disabled
572        w.write_bits(1, 1); // meta-prefix = 1 → multi
573        w.write_bits(0, 3); // prefix_bits raw = 0 → 2
574        let data = w.into_bytes();
575        let mut r = BitReader::new(&data);
576        let h = MetaPrefixHeader::read(&mut r, ImageRole::Argb, 32, 16).unwrap();
577        match h.codes {
578            MetaPrefixCodes::EntropyImagePending {
579                prefix_bits,
580                image_width,
581                image_height,
582                entropy_image_bit_position,
583            } => {
584                assert_eq!(prefix_bits, 2);
585                assert_eq!(image_width, 8);
586                assert_eq!(image_height, 4);
587                // 1 (cache) + 1 (meta) + 3 (prefix_bits) = 5
588                assert_eq!(entropy_image_bit_position, 5);
589            }
590            other => panic!("expected EntropyImagePending, got {other:?}"),
591        }
592    }
593
594    #[test]
595    fn argb_role_meta_prefix_one_div_round_up_rounds() {
596        // image 17x9 with prefix_bits 2 (block 4) → ceil(17/4)=5, ceil(9/4)=3.
597        let mut w = BitWriter::new();
598        w.write_bits(0, 1);
599        w.write_bits(1, 1);
600        w.write_bits(0, 3); // prefix_bits = 2
601        let data = w.into_bytes();
602        let mut r = BitReader::new(&data);
603        let h = MetaPrefixHeader::read(&mut r, ImageRole::Argb, 17, 9).unwrap();
604        match h.codes {
605            MetaPrefixCodes::EntropyImagePending {
606                image_width,
607                image_height,
608                ..
609            } => {
610                assert_eq!(image_width, 5);
611                assert_eq!(image_height, 3);
612            }
613            other => panic!("expected EntropyImagePending, got {other:?}"),
614        }
615    }
616
617    #[test]
618    fn argb_role_meta_prefix_one_max_prefix_bits() {
619        // raw = 7 → prefix_bits = 9 (the §6.2.2 upper bound).
620        let mut w = BitWriter::new();
621        w.write_bits(0, 1);
622        w.write_bits(1, 1);
623        w.write_bits(7, 3); // prefix_bits raw = 7 → 9
624        let data = w.into_bytes();
625        let mut r = BitReader::new(&data);
626        let h = MetaPrefixHeader::read(&mut r, ImageRole::Argb, 1024, 1024).unwrap();
627        match h.codes {
628            MetaPrefixCodes::EntropyImagePending {
629                prefix_bits,
630                image_width,
631                image_height,
632                ..
633            } => {
634                assert_eq!(prefix_bits, 9);
635                // ceil(1024 / 512) = 2.
636                assert_eq!(image_width, 2);
637                assert_eq!(image_height, 2);
638            }
639            other => panic!("expected EntropyImagePending, got {other:?}"),
640        }
641    }
642
643    #[test]
644    fn argb_role_color_cache_size_is_absorbed_into_green_alphabet() {
645        // ARGB role with an enabled cache; the GREEN alphabet must grow
646        // to 256 + 24 + (1 << code_bits). Build the GREEN code as a
647        // simple-code single-symbol *inside* that extended range and
648        // confirm it reads back.
649        let cache_bits: u32 = 4; // size = 16
650        let cache_size: u32 = 1 << cache_bits; // 16
651        let extended_green = 256 + 24 + cache_size - 1; // 295 — valid wire value
652        let mut w = BitWriter::new();
653        w.write_bits(1, 1); // color-cache enabled
654        w.write_bits(cache_bits, 4); // code_bits = 4
655        w.write_bits(0, 1); // meta-prefix = 0 → single group
656                            // GREEN: simple, 1 symbol, is_first_8bits = 0 → 1-bit sym field
657                            // can't reach 295. Force the 8-bit form, but 8 bits only reach
658                            // 255 — so use two symbols, first 8-bit symbol = 255, second
659                            // 8-bit symbol = (extended_green - 256) → wait, no: simple-code
660                            // symbol is a *literal* index in [0..alphabet_size). We need
661                            // the actual two-stage 1/8-bit accommodation the simple code
662                            // can only express up to value 255. For larger values we must
663                            // use the normal code length code. So instead exercise the
664                            // alphabet by reading a normal code that covers an extended
665                            // alphabet without actually emitting a wide symbol.
666                            //
667                            // Simpler check: read a GREEN code with a 2-symbol normal CLC
668                            // that emits lengths only for symbols 0 and 1, then RED/BLUE/
669                            // ALPHA/DIST as simple single-symbol codes. The point is just
670                            // that the alphabet size is propagated; the read uses the
671                            // value to size `lengths`.
672                            //
673                            // GREEN normal CLC with one symbol at length 1 (single-leaf):
674                            // flag=0 (normal), num_code_lengths=4 (=4+0), then 4 CLC
675                            // entries in kCodeLengthCodeOrder (17, 18, 0, 1). Set pos1=1
676                            // (literal len 1), everything else 0. max_symbol gate = 0
677                            // (alphabet_size). Then emit a code-18 zero-run to skip ahead
678                            // — but the simpler approach is: don't gate, and just emit a
679                            // single length-1 literal for symbol 0 plus a long code-18 run.
680                            //
681                            // Easiest test: use a *simple* GREEN code with symbol 0.
682        w.write_simple_single_symbol(0); // GREEN sym = 0
683        w.write_simple_single_symbol(10); // RED
684        w.write_simple_single_symbol(20); // BLUE
685        w.write_simple_single_symbol(30); // ALPHA
686        w.write_simple_single_symbol(0); // DIST
687        let data = w.into_bytes();
688        let mut r = BitReader::new(&data);
689        let h = MetaPrefixHeader::read(&mut r, ImageRole::Argb, 64, 64).unwrap();
690        assert!(h.color_cache.is_enabled());
691        assert_eq!(h.color_cache.size(), cache_size as usize);
692        let g = h.codes.group().expect("Single");
693        assert_eq!(g.green.single_symbol(), Some(0));
694        // Sanity: code_lengths table is sized to the *extended* alphabet,
695        // proving the cache size flowed through.
696        assert_eq!(g.green.code_lengths().len(), extended_green as usize + 1);
697    }
698
699    #[test]
700    fn truncated_color_cache_info_reports_eof() {
701        let data: [u8; 0] = [];
702        let mut r = BitReader::new(&data);
703        match ColorCacheInfo::read(&mut r) {
704            Err(MetaPrefixError::Eof(_)) => {}
705            other => panic!("expected Eof, got {other:?}"),
706        }
707    }
708
709    #[test]
710    fn truncated_meta_prefix_bit_reports_eof() {
711        // ARGB role; only the color-cache-info=0 bit is present; the
712        // meta-prefix bit read should EOF.
713        let data = [0x00u8];
714        let mut r = BitReader::new(&data);
715        // Manually position the cursor past the only valid bit.
716        // First read consumes the cache-info=0 bit; then meta-prefix
717        // tries to read the 2nd bit which is still inside the byte.
718        // To force EOF we need to exhaust the slice — seek to bit 8.
719        let h = MetaPrefixHeader::read(&mut r, ImageRole::Argb, 1, 1);
720        // color-cache-info=0 (bit 0), then meta-prefix=0 (bit 1), then
721        // GREEN code reads (which run out of bytes) — so the actual EOF
722        // comes from the prefix-code read, not the meta-prefix bit. We
723        // accept any Eof here.
724        match h {
725            Err(MetaPrefixError::Eof(_)) => {}
726            other => panic!("expected Eof, got {other:?}"),
727        }
728    }
729}