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}