use crate::meta_prefix::{
ImageRole, MetaPrefixCodes, MetaPrefixError, MetaPrefixHeader, PrefixCodeGroup,
};
use crate::vp8l_prefix::PrefixError;
use crate::vp8l_stream::{BitReader, BitReaderEof};
pub const NUM_DISTANCE_MAP_CODES: usize = 120;
pub const NUM_LENGTH_PREFIX_CODES: usize = 24;
pub const DISTANCE_MAP: [(i32, i32); NUM_DISTANCE_MAP_CODES] = [
(0, 1),
(1, 0),
(1, 1),
(-1, 1),
(0, 2),
(2, 0),
(1, 2),
(-1, 2),
(2, 1),
(-2, 1),
(2, 2),
(-2, 2),
(0, 3),
(3, 0),
(1, 3),
(-1, 3),
(3, 1),
(-3, 1),
(2, 3),
(-2, 3),
(3, 2),
(-3, 2),
(0, 4),
(4, 0),
(1, 4),
(-1, 4),
(4, 1),
(-4, 1),
(3, 3),
(-3, 3),
(2, 4),
(-2, 4),
(4, 2),
(-4, 2),
(0, 5),
(3, 4),
(-3, 4),
(4, 3),
(-4, 3),
(5, 0),
(1, 5),
(-1, 5),
(5, 1),
(-5, 1),
(2, 5),
(-2, 5),
(5, 2),
(-5, 2),
(4, 4),
(-4, 4),
(3, 5),
(-3, 5),
(5, 3),
(-5, 3),
(0, 6),
(6, 0),
(1, 6),
(-1, 6),
(6, 1),
(-6, 1),
(2, 6),
(-2, 6),
(6, 2),
(-6, 2),
(4, 5),
(-4, 5),
(5, 4),
(-5, 4),
(3, 6),
(-3, 6),
(6, 3),
(-6, 3),
(0, 7),
(7, 0),
(1, 7),
(-1, 7),
(5, 5),
(-5, 5),
(7, 1),
(-7, 1),
(4, 6),
(-4, 6),
(6, 4),
(-6, 4),
(2, 7),
(-2, 7),
(7, 2),
(-7, 2),
(3, 7),
(-3, 7),
(7, 3),
(-7, 3),
(5, 6),
(-5, 6),
(6, 5),
(-6, 5),
(8, 0),
(4, 7),
(-4, 7),
(7, 4),
(-7, 4),
(8, 1),
(8, 2),
(6, 6),
(-6, 6),
(8, 3),
(5, 7),
(-5, 7),
(7, 5),
(-7, 5),
(8, 4),
(6, 7),
(-6, 7),
(7, 6),
(-7, 6),
(8, 5),
(7, 7),
(-7, 7),
(8, 6),
(8, 7),
];
pub const COLOR_CACHE_HASH_MULTIPLIER: u32 = 0x1e35_a7bd;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DecodeError {
Eof(BitReaderEof),
Prefix(PrefixError),
GreenSymbolOutOfRange {
symbol: usize,
alphabet_size: usize,
},
ColorCacheIndexOutOfRange {
index: usize,
cache_size: usize,
},
BackwardReferenceUnderflow {
position: usize,
distance: usize,
},
BackwardReferenceOverflow {
position: usize,
length: usize,
total_pixels: usize,
},
MetaPrefix(MetaPrefixError),
EmptyEntropyImage {
prefix_image_width: u32,
prefix_image_height: u32,
},
MetaPrefixIndexOutOfRange {
meta_prefix_code: usize,
num_prefix_groups: usize,
},
DuplicateTransform,
}
impl From<BitReaderEof> for DecodeError {
fn from(e: BitReaderEof) -> Self {
Self::Eof(e)
}
}
impl From<PrefixError> for DecodeError {
fn from(e: PrefixError) -> Self {
match e {
PrefixError::Eof(eof) => Self::Eof(eof),
other => Self::Prefix(other),
}
}
}
impl From<MetaPrefixError> for DecodeError {
fn from(e: MetaPrefixError) -> Self {
match e {
MetaPrefixError::Eof(eof) => Self::Eof(eof),
other => Self::MetaPrefix(other),
}
}
}
impl core::fmt::Display for DecodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Eof(e) => write!(f, "VP8L §5.2 image decode: {e}"),
Self::Prefix(e) => write!(f, "VP8L §5.2 image decode prefix code: {e}"),
Self::GreenSymbolOutOfRange {
symbol,
alphabet_size,
} => write!(
f,
"VP8L §6.2.3 image decode: GREEN symbol {symbol} out of range for alphabet size {alphabet_size}"
),
Self::ColorCacheIndexOutOfRange { index, cache_size } => write!(
f,
"VP8L §5.2.3 image decode: color-cache index {index} out of range for cache size {cache_size}"
),
Self::BackwardReferenceUnderflow { position, distance } => write!(
f,
"VP8L §5.2.2 image decode: backward reference distance {distance} underflows at pixel {position}"
),
Self::BackwardReferenceOverflow {
position,
length,
total_pixels,
} => write!(
f,
"VP8L §5.2.2 image decode: backward reference length {length} at pixel {position} overruns the {total_pixels}-pixel image"
),
Self::MetaPrefix(e) => write!(f, "VP8L §6.2.2 image decode meta-prefix: {e}"),
Self::EmptyEntropyImage {
prefix_image_width,
prefix_image_height,
} => write!(
f,
"VP8L §6.2.2 image decode: degenerate entropy image of size {prefix_image_width}x{prefix_image_height}"
),
Self::MetaPrefixIndexOutOfRange {
meta_prefix_code,
num_prefix_groups,
} => write!(
f,
"VP8L §6.2.2 image decode: meta-prefix code {meta_prefix_code} out of range for {num_prefix_groups} prefix-code group(s)"
),
Self::DuplicateTransform => write!(
f,
"VP8L §4 transform list: a transform type appears more than once"
),
}
}
}
impl std::error::Error for DecodeError {}
pub fn read_lz77_value(reader: &mut BitReader<'_>, prefix_code: u32) -> Result<u32, BitReaderEof> {
if prefix_code < 4 {
return Ok(prefix_code + 1);
}
let extra_bits = (prefix_code - 2) >> 1;
let offset = (2 + (prefix_code & 1)) << extra_bits;
let extra = reader.read_bits(extra_bits as usize)?;
Ok(offset + extra + 1)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ColorCache {
code_bits: u32,
entries: Vec<u32>,
}
impl ColorCache {
pub fn new(code_bits: u32) -> Self {
let size = 1usize << code_bits;
Self {
code_bits,
entries: vec![0u32; size],
}
}
pub fn size(&self) -> usize {
self.entries.len()
}
pub fn hash(&self, argb: u32) -> usize {
(COLOR_CACHE_HASH_MULTIPLIER.wrapping_mul(argb) >> (32 - self.code_bits)) as usize
}
pub fn insert(&mut self, argb: u32) {
let idx = self.hash(argb);
self.entries[idx] = argb;
}
pub fn lookup(&self, index: usize) -> Option<u32> {
self.entries.get(index).copied()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecodedImage {
width: u32,
height: u32,
pixels: Vec<u32>,
}
impl DecodedImage {
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn pixels(&self) -> &[u32] {
&self.pixels
}
pub fn pixels_mut(&mut self) -> &mut [u32] {
&mut self.pixels
}
pub fn from_parts(width: u32, height: u32, pixels: Vec<u32>) -> Self {
Self {
width,
height,
pixels,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GreenSymbol {
Literal {
green: u8,
},
LengthPrefix {
prefix_code: u32,
},
ColorCache {
index: usize,
},
}
impl GreenSymbol {
pub fn classify(symbol: usize, alphabet_size: usize) -> Result<Self, DecodeError> {
if symbol >= alphabet_size {
return Err(DecodeError::GreenSymbolOutOfRange {
symbol,
alphabet_size,
});
}
if symbol < 256 {
Ok(Self::Literal {
green: symbol as u8,
})
} else if symbol < 256 + NUM_LENGTH_PREFIX_CODES {
Ok(Self::LengthPrefix {
prefix_code: (symbol - 256) as u32,
})
} else {
Ok(Self::ColorCache {
index: symbol - (256 + NUM_LENGTH_PREFIX_CODES),
})
}
}
}
pub fn distance_code_to_pixel_distance(distance_code: u32, image_width: u32) -> usize {
if distance_code > NUM_DISTANCE_MAP_CODES as u32 {
return (distance_code - NUM_DISTANCE_MAP_CODES as u32) as usize;
}
let (xi, yi) = DISTANCE_MAP[(distance_code - 1) as usize];
let dist = xi + yi * image_width as i32;
if dist < 1 {
1
} else {
dist as usize
}
}
fn pack_argb(green: u8, red: u8, blue: u8, alpha: u8) -> u32 {
((alpha as u32) << 24) | ((red as u32) << 16) | ((green as u32) << 8) | (blue as u32)
}
#[allow(clippy::too_many_arguments)]
fn decode_one_symbol(
reader: &mut BitReader<'_>,
group: &PrefixCodeGroup,
color_cache: &mut Option<ColorCache>,
pixels: &mut Vec<u32>,
width: u32,
total_pixels: usize,
alphabet_size: usize,
cache_size: usize,
) -> Result<(), DecodeError> {
let s = group.green.read_symbol(reader)? as usize;
match GreenSymbol::classify(s, alphabet_size)? {
GreenSymbol::Literal { green } => {
let red = group.red.read_symbol(reader)? as u8;
let blue = group.blue.read_symbol(reader)? as u8;
let alpha = group.alpha.read_symbol(reader)? as u8;
let argb = pack_argb(green, red, blue, alpha);
pixels.push(argb);
if let Some(cache) = color_cache.as_mut() {
cache.insert(argb);
}
}
GreenSymbol::LengthPrefix { prefix_code } => {
let length = read_lz77_value(reader, prefix_code)? as usize;
let dist_prefix = group.distance.read_symbol(reader)? as u32;
let distance_code = read_lz77_value(reader, dist_prefix)?;
let dist = distance_code_to_pixel_distance(distance_code, width);
let position = pixels.len();
if dist > position {
return Err(DecodeError::BackwardReferenceUnderflow {
position,
distance: dist,
});
}
if position + length > total_pixels {
return Err(DecodeError::BackwardReferenceOverflow {
position,
length,
total_pixels,
});
}
let src_start = position - dist;
for i in 0..length {
let argb = pixels[src_start + i];
pixels.push(argb);
if let Some(cache) = color_cache.as_mut() {
cache.insert(argb);
}
}
}
GreenSymbol::ColorCache { index } => {
let cache = color_cache
.as_mut()
.ok_or(DecodeError::ColorCacheIndexOutOfRange {
index,
cache_size: 0,
})?;
let argb = cache
.lookup(index)
.ok_or(DecodeError::ColorCacheIndexOutOfRange { index, cache_size })?;
pixels.push(argb);
cache.insert(argb);
}
}
Ok(())
}
pub fn decode_image(
reader: &mut BitReader<'_>,
group: &PrefixCodeGroup,
mut color_cache: Option<ColorCache>,
width: u32,
height: u32,
) -> Result<DecodedImage, DecodeError> {
let cache_size = color_cache.as_ref().map(|c| c.size()).unwrap_or(0);
let alphabet_size = PrefixCodeGroup::green_alphabet_size(cache_size);
let total_pixels = (width as usize) * (height as usize);
let mut pixels: Vec<u32> = Vec::with_capacity(total_pixels);
while pixels.len() < total_pixels {
decode_one_symbol(
reader,
group,
&mut color_cache,
&mut pixels,
width,
total_pixels,
alphabet_size,
cache_size,
)?;
}
Ok(DecodedImage {
width,
height,
pixels,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MetaPrefixIndex {
prefix_bits: u8,
block_width: u32,
block_height: u32,
meta_codes: Vec<u16>,
}
impl MetaPrefixIndex {
pub fn prefix_bits(&self) -> u8 {
self.prefix_bits
}
pub fn block_width(&self) -> u32 {
self.block_width
}
pub fn block_height(&self) -> u32 {
self.block_height
}
pub fn meta_codes(&self) -> &[u16] {
&self.meta_codes
}
pub fn num_prefix_groups(&self) -> usize {
self.meta_codes
.iter()
.copied()
.max()
.map(|m| m as usize + 1)
.unwrap_or(0)
}
pub fn meta_code_for(&self, x: u32, y: u32) -> u16 {
let bx = x >> self.prefix_bits;
let by = y >> self.prefix_bits;
let position = (by * self.block_width + bx) as usize;
self.meta_codes[position]
}
}
pub fn decode_entropy_coded_image(
reader: &mut BitReader<'_>,
width: u32,
height: u32,
) -> Result<DecodedImage, DecodeError> {
if width == 0 || height == 0 {
return Err(DecodeError::EmptyEntropyImage {
prefix_image_width: width,
prefix_image_height: height,
});
}
let header = MetaPrefixHeader::read(reader, ImageRole::EntropyCoded, width, height)?;
let group = match &header.codes {
MetaPrefixCodes::Single { group } => group.as_ref(),
MetaPrefixCodes::EntropyImagePending { .. } => {
return Err(DecodeError::EmptyEntropyImage {
prefix_image_width: width,
prefix_image_height: height,
});
}
};
let cache = header
.color_cache
.is_enabled()
.then(|| ColorCache::new(header.color_cache.code_bits));
decode_image(reader, group, cache, width, height)
}
pub fn decode_entropy_image(
reader: &mut BitReader<'_>,
prefix_bits: u8,
prefix_image_width: u32,
prefix_image_height: u32,
) -> Result<MetaPrefixIndex, DecodeError> {
let decoded = decode_entropy_coded_image(reader, prefix_image_width, prefix_image_height)
.map_err(|e| match e {
DecodeError::EmptyEntropyImage { .. } => DecodeError::EmptyEntropyImage {
prefix_image_width,
prefix_image_height,
},
other => other,
})?;
let meta_codes: Vec<u16> = decoded
.pixels()
.iter()
.map(|&argb| ((argb >> 8) & 0xffff) as u16)
.collect();
Ok(MetaPrefixIndex {
prefix_bits,
block_width: prefix_image_width,
block_height: prefix_image_height,
meta_codes,
})
}
pub fn decode_argb(
reader: &mut BitReader<'_>,
width: u32,
height: u32,
) -> Result<DecodedImage, DecodeError> {
let header = MetaPrefixHeader::read(reader, ImageRole::Argb, width, height)?;
let color_cache_size = header.color_cache.size();
let cache_code_bits = header.color_cache.code_bits;
let cache_enabled = header.color_cache.is_enabled();
match header.codes {
MetaPrefixCodes::Single { group } => {
let cache = cache_enabled.then(|| ColorCache::new(cache_code_bits));
decode_image(reader, &group, cache, width, height)
}
MetaPrefixCodes::EntropyImagePending {
prefix_bits,
image_width: prefix_image_width,
image_height: prefix_image_height,
..
} => {
let index =
decode_entropy_image(reader, prefix_bits, prefix_image_width, prefix_image_height)?;
let num_prefix_groups = index.num_prefix_groups();
if num_prefix_groups == 0 {
return Err(DecodeError::EmptyEntropyImage {
prefix_image_width,
prefix_image_height,
});
}
let mut groups: Vec<PrefixCodeGroup> = Vec::with_capacity(num_prefix_groups);
for _ in 0..num_prefix_groups {
groups.push(PrefixCodeGroup::read(reader, color_cache_size)?);
}
decode_argb_multi_group(
reader,
&groups,
&index,
cache_enabled,
cache_code_bits,
width,
height,
)
}
}
}
#[allow(clippy::too_many_arguments)]
fn decode_argb_multi_group(
reader: &mut BitReader<'_>,
groups: &[PrefixCodeGroup],
index: &MetaPrefixIndex,
cache_enabled: bool,
cache_code_bits: u32,
width: u32,
height: u32,
) -> Result<DecodedImage, DecodeError> {
let cache_size = if cache_enabled {
1usize << cache_code_bits
} else {
0
};
let alphabet_size = PrefixCodeGroup::green_alphabet_size(cache_size);
let total_pixels = (width as usize) * (height as usize);
let mut color_cache = cache_enabled.then(|| ColorCache::new(cache_code_bits));
let mut pixels: Vec<u32> = Vec::with_capacity(total_pixels);
while pixels.len() < total_pixels {
let position = pixels.len();
let x = (position % width as usize) as u32;
let y = (position / width as usize) as u32;
let meta_code = index.meta_code_for(x, y) as usize;
let group = groups
.get(meta_code)
.ok_or(DecodeError::MetaPrefixIndexOutOfRange {
meta_prefix_code: meta_code,
num_prefix_groups: groups.len(),
})?;
decode_one_symbol(
reader,
group,
&mut color_cache,
&mut pixels,
width,
total_pixels,
alphabet_size,
cache_size,
)?;
}
Ok(DecodedImage {
width,
height,
pixels,
})
}
#[cfg(test)]
mod tests {
use super::*;
struct BitWriter {
bytes: Vec<u8>,
bit_pos: usize,
}
impl BitWriter {
fn new() -> Self {
Self {
bytes: Vec::new(),
bit_pos: 0,
}
}
fn write_bits(&mut self, mut value: u32, n: usize) {
for _ in 0..n {
let byte_idx = self.bit_pos >> 3;
if byte_idx >= self.bytes.len() {
self.bytes.push(0);
}
let bit = (value & 1) as u8;
self.bytes[byte_idx] |= bit << (self.bit_pos & 7);
self.bit_pos += 1;
value >>= 1;
}
}
fn write_simple_single_symbol(&mut self, sym: u32) {
self.write_bits(1, 1); self.write_bits(0, 1); self.write_bits(1, 1); self.write_bits(sym, 8); }
fn write_simple_two_symbols(&mut self, sym0: u32, sym1: u32) {
self.write_bits(1, 1); self.write_bits(1, 1); self.write_bits(1, 1); self.write_bits(sym0, 8);
self.write_bits(sym1, 8);
}
fn into_bytes(self) -> Vec<u8> {
self.bytes
}
fn bit_len(&self) -> usize {
self.bit_pos
}
}
#[test]
fn lz77_value_small_codes_are_identity_plus_one() {
let data: [u8; 0] = [];
let mut r = BitReader::new(&data);
assert_eq!(read_lz77_value(&mut r, 0).unwrap(), 1);
assert_eq!(read_lz77_value(&mut r, 1).unwrap(), 2);
assert_eq!(read_lz77_value(&mut r, 2).unwrap(), 3);
assert_eq!(read_lz77_value(&mut r, 3).unwrap(), 4);
assert_eq!(r.bit_position(), 0); }
#[test]
fn lz77_value_prefix_4_reads_one_extra_bit() {
let mut w = BitWriter::new();
w.write_bits(0, 1); w.write_bits(1, 1); let data = w.into_bytes();
let mut r = BitReader::new(&data);
assert_eq!(read_lz77_value(&mut r, 4).unwrap(), 5);
assert_eq!(read_lz77_value(&mut r, 4).unwrap(), 6);
}
#[test]
fn lz77_value_prefix_5_covers_7_to_8() {
let mut w = BitWriter::new();
w.write_bits(0, 1);
w.write_bits(1, 1);
let data = w.into_bytes();
let mut r = BitReader::new(&data);
assert_eq!(read_lz77_value(&mut r, 5).unwrap(), 7);
assert_eq!(read_lz77_value(&mut r, 5).unwrap(), 8);
}
#[test]
fn lz77_value_prefix_6_covers_9_to_12() {
let mut w = BitWriter::new();
for extra in 0u32..4 {
w.write_bits(extra, 2);
}
let data = w.into_bytes();
let mut r = BitReader::new(&data);
for expected in 9u32..=12 {
assert_eq!(read_lz77_value(&mut r, 6).unwrap(), expected);
}
}
#[test]
fn lz77_value_prefix_23_max_length_4096() {
let mut w = BitWriter::new();
w.write_bits(0, 10); w.write_bits(1023, 10); let data = w.into_bytes();
let mut r = BitReader::new(&data);
assert_eq!(read_lz77_value(&mut r, 23).unwrap(), 3073);
assert_eq!(read_lz77_value(&mut r, 23).unwrap(), 4096);
}
#[test]
fn distance_map_has_120_entries() {
assert_eq!(DISTANCE_MAP.len(), 120);
assert_eq!(NUM_DISTANCE_MAP_CODES, 120);
}
#[test]
fn distance_map_first_entries_match_spec_examples() {
assert_eq!(DISTANCE_MAP[0], (0, 1));
assert_eq!(DISTANCE_MAP[2], (1, 1));
assert_eq!(DISTANCE_MAP[118], (8, 6));
assert_eq!(DISTANCE_MAP[119], (8, 7));
}
#[test]
fn distance_code_1_is_pixel_above() {
assert_eq!(distance_code_to_pixel_distance(1, 16), 16);
assert_eq!(distance_code_to_pixel_distance(1, 1), 1);
}
#[test]
fn distance_code_2_is_pixel_to_left() {
assert_eq!(distance_code_to_pixel_distance(2, 100), 1);
}
#[test]
fn distance_code_above_120_offsets_by_120() {
assert_eq!(distance_code_to_pixel_distance(121, 64), 1);
assert_eq!(distance_code_to_pixel_distance(200, 64), 80);
}
#[test]
fn distance_clamps_to_one_for_negative_offsets() {
assert_eq!(DISTANCE_MAP[3], (-1, 1));
assert_eq!(distance_code_to_pixel_distance(4, 1), 1);
}
#[test]
fn green_symbol_literal_range() {
match GreenSymbol::classify(0, 280).unwrap() {
GreenSymbol::Literal { green } => assert_eq!(green, 0),
other => panic!("expected Literal, got {other:?}"),
}
match GreenSymbol::classify(255, 280).unwrap() {
GreenSymbol::Literal { green } => assert_eq!(green, 255),
other => panic!("expected Literal, got {other:?}"),
}
}
#[test]
fn green_symbol_length_prefix_range() {
match GreenSymbol::classify(256, 280).unwrap() {
GreenSymbol::LengthPrefix { prefix_code } => assert_eq!(prefix_code, 0),
other => panic!("expected LengthPrefix, got {other:?}"),
}
match GreenSymbol::classify(279, 280).unwrap() {
GreenSymbol::LengthPrefix { prefix_code } => assert_eq!(prefix_code, 23),
other => panic!("expected LengthPrefix, got {other:?}"),
}
}
#[test]
fn green_symbol_color_cache_range() {
match GreenSymbol::classify(280, 296).unwrap() {
GreenSymbol::ColorCache { index } => assert_eq!(index, 0),
other => panic!("expected ColorCache, got {other:?}"),
}
match GreenSymbol::classify(295, 296).unwrap() {
GreenSymbol::ColorCache { index } => assert_eq!(index, 15),
other => panic!("expected ColorCache, got {other:?}"),
}
}
#[test]
fn green_symbol_out_of_range_is_refused() {
match GreenSymbol::classify(280, 280) {
Err(DecodeError::GreenSymbolOutOfRange {
symbol,
alphabet_size,
}) => {
assert_eq!(symbol, 280);
assert_eq!(alphabet_size, 280);
}
other => panic!("expected GreenSymbolOutOfRange, got {other:?}"),
}
}
#[test]
fn color_cache_hash_matches_spec_formula() {
let cache = ColorCache::new(4); let argb = 0xFF_12_34_56u32;
let expected = (COLOR_CACHE_HASH_MULTIPLIER.wrapping_mul(argb) >> (32 - 4)) as usize;
assert_eq!(cache.hash(argb), expected);
assert!(cache.hash(argb) < 16);
}
#[test]
fn color_cache_insert_then_lookup_round_trips() {
let mut cache = ColorCache::new(8);
let argb = 0xDE_AD_BE_EFu32;
cache.insert(argb);
let idx = cache.hash(argb);
assert_eq!(cache.lookup(idx), Some(argb));
}
#[test]
fn color_cache_starts_zeroed() {
let cache = ColorCache::new(2);
for i in 0..cache.size() {
assert_eq!(cache.lookup(i), Some(0));
}
}
fn group_with_codes(
green_writer: impl Fn(&mut BitWriter),
red: u32,
blue: u32,
alpha: u32,
dist: u32,
cache_size: usize,
) -> PrefixCodeGroup {
let mut w = BitWriter::new();
green_writer(&mut w);
w.write_simple_single_symbol(red);
w.write_simple_single_symbol(blue);
w.write_simple_single_symbol(alpha);
w.write_simple_single_symbol(dist);
let data = w.into_bytes();
let mut r = BitReader::new(&data);
PrefixCodeGroup::read(&mut r, cache_size).unwrap()
}
#[test]
fn decode_literal_only_2x1_image() {
let group = group_with_codes(
|w| w.write_simple_two_symbols(10, 20),
0xAA, 0xBB, 0xCC, 0, 0,
);
let mut w = BitWriter::new();
w.write_bits(0, 1); w.write_bits(1, 1); let data = w.into_bytes();
let mut r = BitReader::new(&data);
let img = decode_image(&mut r, &group, None, 2, 1).unwrap();
assert_eq!(img.width(), 2);
assert_eq!(img.height(), 1);
assert_eq!(
img.pixels(),
&[
pack_argb(10, 0xAA, 0xBB, 0xCC),
pack_argb(20, 0xAA, 0xBB, 0xCC)
]
);
}
#[test]
fn decode_single_literal_pixel() {
let group = group_with_codes(
|w| w.write_simple_single_symbol(0x42),
0x10,
0x20,
0x30,
0,
0,
);
let data: [u8; 0] = [];
let mut r = BitReader::new(&data);
let img = decode_image(&mut r, &group, None, 1, 1).unwrap();
assert_eq!(img.pixels(), &[pack_argb(0x42, 0x10, 0x20, 0x30)]);
}
#[test]
fn decode_length_distance_back_reference() {
let green = {
let mut lengths = vec![0u8; PrefixCodeGroup::green_alphabet_size(0)];
lengths[5] = 1;
lengths[258] = 1;
crate::vp8l_prefix::PrefixCode::from_code_lengths(lengths).unwrap()
};
let red = {
let mut l = vec![0u8; 256];
l[0xAA] = 1;
crate::vp8l_prefix::PrefixCode::from_code_lengths(l).unwrap()
};
let blue = {
let mut l = vec![0u8; 256];
l[0xBB] = 1;
crate::vp8l_prefix::PrefixCode::from_code_lengths(l).unwrap()
};
let alpha = {
let mut l = vec![0u8; 256];
l[0xCC] = 1;
crate::vp8l_prefix::PrefixCode::from_code_lengths(l).unwrap()
};
let dist = {
let mut l = vec![0u8; 40];
l[1] = 1;
crate::vp8l_prefix::PrefixCode::from_code_lengths(l).unwrap()
};
let group = PrefixCodeGroup {
green,
red,
blue,
alpha,
distance: dist,
};
let mut w = BitWriter::new();
w.write_bits(0, 1); w.write_bits(1, 1); let data = w.into_bytes();
let mut r = BitReader::new(&data);
let img = decode_image(&mut r, &group, None, 4, 1).unwrap();
let p = pack_argb(5, 0xAA, 0xBB, 0xCC);
assert_eq!(img.pixels(), &[p, p, p, p]);
}
#[test]
fn decode_color_cache_hit() {
let cache_bits = 4u32;
let cache_size = 1usize << cache_bits; let alphabet = PrefixCodeGroup::green_alphabet_size(cache_size);
let green_val = 0x11u8;
let red_val = 0x22u8;
let blue_val = 0x33u8;
let alpha_val = 0x44u8;
let argb = pack_argb(green_val, red_val, blue_val, alpha_val);
let probe = ColorCache::new(cache_bits);
let slot = probe.hash(argb);
let cache_symbol = 256 + NUM_LENGTH_PREFIX_CODES + slot;
let green = {
let mut lengths = vec![0u8; alphabet];
lengths[green_val as usize] = 1;
lengths[cache_symbol] = 1;
crate::vp8l_prefix::PrefixCode::from_code_lengths(lengths).unwrap()
};
let mk_single = |sym: usize, size: usize| {
let mut l = vec![0u8; size];
l[sym] = 1;
crate::vp8l_prefix::PrefixCode::from_code_lengths(l).unwrap()
};
let group = PrefixCodeGroup {
green,
red: mk_single(red_val as usize, 256),
blue: mk_single(blue_val as usize, 256),
alpha: mk_single(alpha_val as usize, 256),
distance: mk_single(0, 40),
};
let mut w = BitWriter::new();
w.write_bits(0, 1); w.write_bits(1, 1); let data = w.into_bytes();
let mut r = BitReader::new(&data);
let img = decode_image(&mut r, &group, Some(ColorCache::new(cache_bits)), 2, 1).unwrap();
assert_eq!(img.pixels(), &[argb, argb]);
}
#[test]
fn decode_backward_reference_underflow_is_refused() {
let green = {
let mut lengths = vec![0u8; PrefixCodeGroup::green_alphabet_size(0)];
lengths[256] = 1; crate::vp8l_prefix::PrefixCode::from_code_lengths(lengths).unwrap()
};
let mk_single = |sym: usize, size: usize| {
let mut l = vec![0u8; size];
l[sym] = 1;
crate::vp8l_prefix::PrefixCode::from_code_lengths(l).unwrap()
};
let group = PrefixCodeGroup {
green,
red: mk_single(0, 256),
blue: mk_single(0, 256),
alpha: mk_single(0, 256),
distance: mk_single(1, 40), };
let data: [u8; 0] = [];
let mut r = BitReader::new(&data);
match decode_image(&mut r, &group, None, 4, 1) {
Err(DecodeError::BackwardReferenceUnderflow { position, distance }) => {
assert_eq!(position, 0);
assert_eq!(distance, 1);
}
other => panic!("expected BackwardReferenceUnderflow, got {other:?}"),
}
}
#[test]
fn decode_color_cache_code_without_cache_is_refused() {
let alphabet = PrefixCodeGroup::green_alphabet_size(0); match GreenSymbol::classify(280, alphabet) {
Err(DecodeError::GreenSymbolOutOfRange { .. }) => {}
other => panic!("expected GreenSymbolOutOfRange, got {other:?}"),
}
}
impl BitWriter {
fn write_single_symbol_group(&mut self, g: u32, r: u32, b: u32, a: u32, d: u32) {
self.write_simple_single_symbol(g);
self.write_simple_single_symbol(r);
self.write_simple_single_symbol(b);
self.write_simple_single_symbol(a);
self.write_simple_single_symbol(d);
}
}
#[test]
fn meta_prefix_index_helpers_match_spec() {
let index = MetaPrefixIndex {
prefix_bits: 2, block_width: 2,
block_height: 1,
meta_codes: vec![0, 1],
};
assert_eq!(index.num_prefix_groups(), 2);
assert_eq!(index.meta_code_for(0, 0), 0);
assert_eq!(index.meta_code_for(3, 0), 0);
assert_eq!(index.meta_code_for(4, 0), 1);
assert_eq!(index.meta_code_for(7, 0), 1);
}
#[test]
fn meta_prefix_index_num_groups_uses_max_not_count() {
let index = MetaPrefixIndex {
prefix_bits: 2,
block_width: 2,
block_height: 1,
meta_codes: vec![0, 5],
};
assert_eq!(index.num_prefix_groups(), 6);
}
#[test]
fn decode_entropy_image_extracts_red_green_meta_codes() {
let mut w = BitWriter::new();
w.write_bits(0, 1); w.write_simple_two_symbols(0, 1); w.write_simple_single_symbol(0); w.write_simple_single_symbol(0); w.write_simple_single_symbol(0); w.write_simple_single_symbol(0); w.write_bits(0, 1); w.write_bits(1, 1); let data = w.into_bytes();
let mut r = BitReader::new(&data);
let index = decode_entropy_image(&mut r, 2, 2, 1).unwrap();
assert_eq!(index.prefix_bits(), 2);
assert_eq!(index.block_width(), 2);
assert_eq!(index.block_height(), 1);
assert_eq!(index.meta_codes(), &[0, 1]);
assert_eq!(index.num_prefix_groups(), 2);
}
#[test]
fn decode_entropy_image_high_meta_code_uses_red_channel() {
let mut w = BitWriter::new();
w.write_bits(0, 1); w.write_simple_single_symbol(2); w.write_simple_single_symbol(3); w.write_simple_single_symbol(0); w.write_simple_single_symbol(0); w.write_simple_single_symbol(0); let data = w.into_bytes();
let mut r = BitReader::new(&data);
let index = decode_entropy_image(&mut r, 2, 1, 1).unwrap();
assert_eq!(index.meta_codes(), &[(3u16 << 8) | 2]);
assert_eq!(index.num_prefix_groups(), 771);
}
#[test]
fn decode_argb_two_groups_select_per_block() {
let mut w = BitWriter::new();
w.write_bits(0, 1); w.write_bits(1, 1); w.write_bits(0, 3); w.write_bits(0, 1); w.write_simple_two_symbols(0, 1); w.write_simple_single_symbol(0); w.write_simple_single_symbol(0); w.write_simple_single_symbol(0); w.write_simple_single_symbol(0); w.write_bits(0, 1); w.write_bits(1, 1); w.write_single_symbol_group(100, 0x10, 0x20, 0x30, 0); w.write_single_symbol_group(200, 0x40, 0x50, 0x60, 0); let data = w.into_bytes();
let mut r = BitReader::new(&data);
let img = decode_argb(&mut r, 8, 1).unwrap();
assert_eq!(img.width(), 8);
assert_eq!(img.height(), 1);
let g0 = pack_argb(100, 0x10, 0x20, 0x30);
let g1 = pack_argb(200, 0x40, 0x50, 0x60);
assert_eq!(
img.pixels(),
&[g0, g0, g0, g0, g1, g1, g1, g1],
"first 4 pixels use group 0, last 4 use group 1"
);
}
#[test]
fn decode_argb_single_group_meta_prefix_zero() {
let mut w = BitWriter::new();
w.write_bits(0, 1); w.write_bits(0, 1); w.write_simple_two_symbols(7, 8); w.write_simple_single_symbol(0xAA); w.write_simple_single_symbol(0xBB); w.write_simple_single_symbol(0xCC); w.write_simple_single_symbol(0); w.write_bits(0, 1); w.write_bits(1, 1); let data = w.into_bytes();
let mut r = BitReader::new(&data);
let img = decode_argb(&mut r, 2, 1).unwrap();
assert_eq!(
img.pixels(),
&[
pack_argb(7, 0xAA, 0xBB, 0xCC),
pack_argb(8, 0xAA, 0xBB, 0xCC)
]
);
}
#[test]
fn decode_argb_single_group_matches_decode_image() {
let mut header = BitWriter::new();
header.write_bits(0, 1); header.write_bits(0, 1); header.write_single_symbol_group(0x42, 0x10, 0x20, 0x30, 0);
let data = header.into_bytes();
let mut r = BitReader::new(&data);
let img = decode_argb(&mut r, 1, 1).unwrap();
assert_eq!(img.pixels(), &[pack_argb(0x42, 0x10, 0x20, 0x30)]);
}
#[test]
fn decode_argb_multi_group_with_color_cache_round_2x2() {
let cache_bits = 4u32; let cache_size = 1usize << cache_bits;
let mut w = BitWriter::new();
w.write_bits(1, 1); w.write_bits(cache_bits, 4); w.write_bits(1, 1); w.write_bits(0, 3); w.write_bits(0, 1);
w.write_simple_two_symbols(0, 1);
w.write_simple_single_symbol(0);
w.write_simple_single_symbol(0);
w.write_simple_single_symbol(0);
w.write_simple_single_symbol(0);
w.write_bits(0, 1); w.write_bits(1, 1); w.write_single_symbol_group(50, 0x11, 0x22, 0x33, 0);
w.write_single_symbol_group(60, 0x44, 0x55, 0x66, 0);
let data = w.into_bytes();
let mut r = BitReader::new(&data);
let img = decode_argb(&mut r, 8, 1).unwrap();
let g0 = pack_argb(50, 0x11, 0x22, 0x33);
let g1 = pack_argb(60, 0x44, 0x55, 0x66);
assert_eq!(img.pixels(), &[g0, g0, g0, g0, g1, g1, g1, g1]);
assert_eq!(
PrefixCodeGroup::green_alphabet_size(cache_size),
256 + 24 + cache_size
);
}
#[test]
fn decode_entropy_image_zero_dim_is_refused() {
let data = [0u8; 4];
let mut r = BitReader::new(&data);
match decode_entropy_image(&mut r, 2, 0, 1) {
Err(DecodeError::EmptyEntropyImage {
prefix_image_width,
prefix_image_height,
}) => {
assert_eq!(prefix_image_width, 0);
assert_eq!(prefix_image_height, 1);
}
other => panic!("expected EmptyEntropyImage, got {other:?}"),
}
}
fn build_valid_two_group_8x1_stream() -> Vec<u8> {
build_valid_two_group_8x1_stream_with_bit_len().0
}
fn build_valid_two_group_8x1_stream_with_bit_len() -> (Vec<u8>, usize) {
let mut w = BitWriter::new();
w.write_bits(0, 1); w.write_bits(1, 1); w.write_bits(0, 3); w.write_bits(0, 1); w.write_simple_two_symbols(0, 1); w.write_simple_single_symbol(0); w.write_simple_single_symbol(0); w.write_simple_single_symbol(0); w.write_simple_single_symbol(0); w.write_bits(0, 1); w.write_bits(1, 1); w.write_single_symbol_group(100, 0x10, 0x20, 0x30, 0);
w.write_single_symbol_group(200, 0x40, 0x50, 0x60, 0);
let bit_len = w.bit_len();
(w.into_bytes(), bit_len)
}
#[test]
fn decode_argb_two_groups_baseline_decodes_clean() {
let data = build_valid_two_group_8x1_stream();
let mut r = BitReader::new(&data);
assert!(decode_argb(&mut r, 8, 1).is_ok());
}
#[test]
fn decode_argb_empty_input_reports_eof() {
let data: [u8; 0] = [];
let mut r = BitReader::new(&data);
match decode_argb(&mut r, 1, 1) {
Err(DecodeError::Eof(_)) | Err(DecodeError::MetaPrefix(_)) => {}
other => panic!("expected Eof or MetaPrefix, got {other:?}"),
}
}
#[test]
fn decode_argb_truncated_after_meta_prefix_header_reports_eof() {
let mut w = BitWriter::new();
w.write_bits(0, 1);
w.write_bits(1, 1);
w.write_bits(0, 3);
let data = w.into_bytes();
let mut r = BitReader::new(&data);
match decode_argb(&mut r, 8, 1) {
Err(DecodeError::Eof(_)) | Err(DecodeError::MetaPrefix(_)) => {}
other => panic!("expected Eof / MetaPrefix, got {other:?}"),
}
}
#[test]
fn decode_argb_truncated_mid_per_group_prefix_reports_eof() {
let full = build_valid_two_group_8x1_stream();
assert!(
full.len() > 6,
"stream layout changed; rechoose truncation point"
);
let truncated = &full[..6];
let mut r = BitReader::new(truncated);
match decode_argb(&mut r, 8, 1) {
Err(DecodeError::Eof(_))
| Err(DecodeError::MetaPrefix(_))
| Err(DecodeError::Prefix(_)) => {}
other => panic!("expected Eof / MetaPrefix / Prefix, got {other:?}"),
}
}
#[test]
fn decode_argb_every_byte_prefix_of_valid_stream_is_safe() {
let full = build_valid_two_group_8x1_stream();
for len in 0..full.len() {
let prefix = &full[..len];
let mut r = BitReader::new(prefix);
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
decode_argb(&mut r, 8, 1)
}));
match result {
Ok(Ok(img)) => {
assert_eq!(
img.pixels().len(),
8,
"len={len}: Ok decode produced wrong pixel count"
);
}
Ok(Err(_)) => {
}
Err(_) => panic!("len={len}: decode_argb panicked on truncated input"),
}
}
}
#[test]
fn decode_argb_oversize_meta_prefix_bits_is_refused() {
let mut w = BitWriter::new();
w.write_bits(0, 1); w.write_bits(1, 1); w.write_bits(7, 3); w.write_bits(0, 1); w.write_simple_single_symbol(0); w.write_simple_single_symbol(0); w.write_simple_single_symbol(0); w.write_simple_single_symbol(0); w.write_simple_single_symbol(0); w.write_single_symbol_group(0x77, 0, 0, 0, 0);
let data = w.into_bytes();
let mut r = BitReader::new(&data);
if let Ok(img) = decode_argb(&mut r, 1, 1) {
assert_eq!(img.pixels().len(), 1);
}
}
fn truncate_to_bit_prefix(data: &[u8], bit_len: usize) -> Vec<u8> {
let byte_len = bit_len.div_ceil(8);
assert!(
byte_len <= data.len(),
"bit_len {bit_len} exceeds source ({} bits)",
data.len() * 8
);
let mut out = data[..byte_len].to_vec();
let leftover = bit_len & 7;
if leftover != 0 && !out.is_empty() {
let mask = (1u8 << leftover) - 1;
let last = out.len() - 1;
out[last] &= mask;
}
out
}
#[test]
fn truncate_to_bit_prefix_round_trips_a_known_byte() {
let src = [0xB5u8];
assert!(truncate_to_bit_prefix(&src, 0).is_empty());
assert_eq!(truncate_to_bit_prefix(&src, 1), vec![0x01]);
assert_eq!(truncate_to_bit_prefix(&src, 4), vec![0x05]);
assert_eq!(truncate_to_bit_prefix(&src, 8), vec![0xB5]);
}
#[test]
fn decode_argb_every_bit_prefix_of_valid_stream_is_safe() {
let (full, full_bits) = build_valid_two_group_8x1_stream_with_bit_len();
{
let mut r = BitReader::new(&full);
assert!(
decode_argb(&mut r, 8, 1).is_ok(),
"full {full_bits}-bit stream must decode"
);
}
for bit_len in 0..=full_bits {
let prefix = truncate_to_bit_prefix(&full, bit_len);
let mut r = BitReader::new(&prefix);
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
decode_argb(&mut r, 8, 1)
}));
match result {
Ok(Ok(img)) => {
assert_eq!(
img.pixels().len(),
8,
"bit_len={bit_len}: Ok decode produced wrong pixel count"
);
}
Ok(Err(_)) => {
}
Err(_) => {
panic!("bit_len={bit_len}: decode_argb panicked on truncated input")
}
}
}
}
#[test]
fn decode_argb_bit_prefix_covers_every_sub_byte_seam() {
let (_full, full_bits) = build_valid_two_group_8x1_stream_with_bit_len();
assert!(
full_bits >= 60,
"fixture bit-length unexpectedly short ({full_bits} bits); \
round-165 property loses its multi-stage coverage if the \
helper stops writing the entropy image / per-group tables"
);
let byte_len_needed = full_bits.div_ceil(8);
assert!(
byte_len_needed * 8 >= full_bits,
"byte length ({byte_len_needed}) cannot cover bit length ({full_bits})"
);
}
}