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}