use crate::vp8l_prefix::{PrefixCode, PrefixError};
use crate::vp8l_stream::{BitReader, BitReaderEof};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrefixCodeGroup {
pub green: PrefixCode,
pub red: PrefixCode,
pub blue: PrefixCode,
pub alpha: PrefixCode,
pub distance: PrefixCode,
}
impl PrefixCodeGroup {
pub fn green_alphabet_size(color_cache_size: usize) -> usize {
256 + 24 + color_cache_size
}
pub fn read(
reader: &mut BitReader<'_>,
color_cache_size: usize,
) -> Result<Self, MetaPrefixError> {
let green_alphabet = Self::green_alphabet_size(color_cache_size);
let green = PrefixCode::read(reader, green_alphabet)?;
let red = PrefixCode::read(reader, 256)?;
let blue = PrefixCode::read(reader, 256)?;
let alpha = PrefixCode::read(reader, 256)?;
let distance = PrefixCode::read(reader, 40)?;
Ok(Self {
green,
red,
blue,
alpha,
distance,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImageRole {
Argb,
EntropyCoded,
}
pub const COLOR_CACHE_BITS_MIN: u32 = 1;
pub const COLOR_CACHE_BITS_MAX: u32 = 11;
pub const PREFIX_BITS_MIN: u32 = 2;
pub const PREFIX_BITS_MAX: u32 = 9;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MetaPrefixError {
Eof(BitReaderEof),
InvalidColorCacheCodeBits {
value: u32,
},
Prefix(PrefixError),
}
impl From<BitReaderEof> for MetaPrefixError {
fn from(e: BitReaderEof) -> Self {
Self::Eof(e)
}
}
impl From<PrefixError> for MetaPrefixError {
fn from(e: PrefixError) -> Self {
match e {
PrefixError::Eof(eof) => Self::Eof(eof),
other => Self::Prefix(other),
}
}
}
impl core::fmt::Display for MetaPrefixError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Eof(e) => write!(f, "VP8L §5.2.3 / §6.2.2 meta-prefix: {e}"),
Self::InvalidColorCacheCodeBits { value } => write!(
f,
"VP8L §5.2.3 color_cache_code_bits = {value} is outside the [{COLOR_CACHE_BITS_MIN}..{COLOR_CACHE_BITS_MAX}] allowed range"
),
Self::Prefix(e) => write!(f, "VP8L §6.2 prefix code in group: {e}"),
}
}
}
impl std::error::Error for MetaPrefixError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ColorCacheInfo {
pub code_bits: u32,
}
impl ColorCacheInfo {
pub fn is_enabled(&self) -> bool {
self.code_bits != 0
}
pub fn size(&self) -> usize {
if self.is_enabled() {
1usize << self.code_bits
} else {
0
}
}
pub fn read(reader: &mut BitReader<'_>) -> Result<Self, MetaPrefixError> {
if reader.read_bit()? {
let code_bits = reader.read_bits(4)?;
if !(COLOR_CACHE_BITS_MIN..=COLOR_CACHE_BITS_MAX).contains(&code_bits) {
return Err(MetaPrefixError::InvalidColorCacheCodeBits { value: code_bits });
}
Ok(Self { code_bits })
} else {
Ok(Self { code_bits: 0 })
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MetaPrefixCodes {
Single {
group: Box<PrefixCodeGroup>,
},
EntropyImagePending {
prefix_bits: u8,
image_width: u32,
image_height: u32,
entropy_image_bit_position: usize,
},
}
impl MetaPrefixCodes {
pub fn is_single(&self) -> bool {
matches!(self, Self::Single { .. })
}
pub fn group(&self) -> Option<&PrefixCodeGroup> {
match self {
Self::Single { group } => Some(group),
Self::EntropyImagePending { .. } => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MetaPrefixHeader {
pub color_cache: ColorCacheInfo,
pub codes: MetaPrefixCodes,
}
fn div_round_up(n: u32, d: u32) -> u32 {
debug_assert!(d != 0, "DIV_ROUND_UP requires non-zero divisor");
n.div_ceil(d)
}
impl MetaPrefixHeader {
pub fn read(
reader: &mut BitReader<'_>,
role: ImageRole,
image_width: u32,
image_height: u32,
) -> Result<Self, MetaPrefixError> {
let color_cache = ColorCacheInfo::read(reader)?;
let use_meta_prefix = if matches!(role, ImageRole::Argb) {
reader.read_bit()?
} else {
false
};
if !use_meta_prefix {
let group = PrefixCodeGroup::read(reader, color_cache.size())?;
return Ok(Self {
color_cache,
codes: MetaPrefixCodes::Single {
group: Box::new(group),
},
});
}
let raw = reader.read_bits(3)?;
let prefix_bits = raw + PREFIX_BITS_MIN;
debug_assert!((PREFIX_BITS_MIN..=PREFIX_BITS_MAX).contains(&prefix_bits));
let block_size = 1u32 << prefix_bits;
let entropy_w = div_round_up(image_width, block_size);
let entropy_h = div_round_up(image_height, block_size);
let entropy_image_bit_position = reader.bit_position();
Ok(Self {
color_cache,
codes: MetaPrefixCodes::EntropyImagePending {
prefix_bits: prefix_bits as u8,
image_width: entropy_w,
image_height: entropy_h,
entropy_image_bit_position,
},
})
}
}
#[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 into_bytes(self) -> Vec<u8> {
self.bytes
}
}
#[test]
fn color_cache_info_disabled() {
let mut w = BitWriter::new();
w.write_bits(0, 1); let data = w.into_bytes();
let mut r = BitReader::new(&data);
let cc = ColorCacheInfo::read(&mut r).unwrap();
assert!(!cc.is_enabled());
assert_eq!(cc.size(), 0);
assert_eq!(cc.code_bits, 0);
assert_eq!(r.bit_position(), 1);
}
#[test]
fn color_cache_info_enabled_min() {
let mut w = BitWriter::new();
w.write_bits(1, 1); w.write_bits(1, 4); let data = w.into_bytes();
let mut r = BitReader::new(&data);
let cc = ColorCacheInfo::read(&mut r).unwrap();
assert!(cc.is_enabled());
assert_eq!(cc.code_bits, 1);
assert_eq!(cc.size(), 2);
}
#[test]
fn color_cache_info_enabled_max() {
let mut w = BitWriter::new();
w.write_bits(1, 1);
w.write_bits(11, 4); let data = w.into_bytes();
let mut r = BitReader::new(&data);
let cc = ColorCacheInfo::read(&mut r).unwrap();
assert_eq!(cc.code_bits, 11);
assert_eq!(cc.size(), 2048);
}
#[test]
fn color_cache_info_zero_code_bits_is_refused() {
let mut w = BitWriter::new();
w.write_bits(1, 1);
w.write_bits(0, 4);
let data = w.into_bytes();
let mut r = BitReader::new(&data);
match ColorCacheInfo::read(&mut r) {
Err(MetaPrefixError::InvalidColorCacheCodeBits { value }) => assert_eq!(value, 0),
other => panic!("expected InvalidColorCacheCodeBits, got {other:?}"),
}
}
#[test]
fn color_cache_info_twelve_code_bits_is_refused() {
let mut w = BitWriter::new();
w.write_bits(1, 1);
w.write_bits(12, 4);
let data = w.into_bytes();
let mut r = BitReader::new(&data);
match ColorCacheInfo::read(&mut r) {
Err(MetaPrefixError::InvalidColorCacheCodeBits { value }) => assert_eq!(value, 12),
other => panic!("expected InvalidColorCacheCodeBits, got {other:?}"),
}
}
#[test]
fn prefix_code_group_green_alphabet_size_matches_spec() {
assert_eq!(PrefixCodeGroup::green_alphabet_size(0), 280);
assert_eq!(PrefixCodeGroup::green_alphabet_size(2), 282);
assert_eq!(PrefixCodeGroup::green_alphabet_size(2048), 2328);
}
#[test]
fn prefix_code_group_reads_five_simple_codes_in_order() {
let mut w = BitWriter::new();
w.write_simple_single_symbol(60); w.write_simple_single_symbol(180); w.write_simple_single_symbol(90); w.write_simple_single_symbol(255); w.write_simple_single_symbol(0); let data = w.into_bytes();
let mut r = BitReader::new(&data);
let g = PrefixCodeGroup::read(&mut r, 0).unwrap();
assert_eq!(g.green.single_symbol(), Some(60));
assert_eq!(g.red.single_symbol(), Some(180));
assert_eq!(g.blue.single_symbol(), Some(90));
assert_eq!(g.alpha.single_symbol(), Some(255));
assert_eq!(g.distance.single_symbol(), Some(0));
}
#[test]
fn entropy_coded_role_has_no_meta_prefix_bit() {
let mut w = BitWriter::new();
w.write_bits(0, 1);
for sym in [60u32, 180, 90, 255, 0] {
w.write_simple_single_symbol(sym);
}
let data = w.into_bytes();
let mut r = BitReader::new(&data);
let h = MetaPrefixHeader::read(&mut r, ImageRole::EntropyCoded, 1, 1).unwrap();
assert!(!h.color_cache.is_enabled());
let group = h.codes.group().expect("Single");
assert_eq!(group.green.single_symbol(), Some(60));
assert_eq!(group.distance.single_symbol(), Some(0));
}
#[test]
fn argb_role_meta_prefix_zero_reads_one_group_directly() {
let mut w = BitWriter::new();
w.write_bits(0, 1); w.write_bits(0, 1); for sym in [42u32, 200, 150, 100, 5] {
w.write_simple_single_symbol(sym);
}
let data = w.into_bytes();
let mut r = BitReader::new(&data);
let h = MetaPrefixHeader::read(&mut r, ImageRole::Argb, 64, 64).unwrap();
let group = h.codes.group().expect("Single");
assert_eq!(group.green.single_symbol(), Some(42));
assert_eq!(group.distance.single_symbol(), Some(5));
assert!(h.codes.is_single());
}
#[test]
fn argb_role_meta_prefix_one_stops_at_entropy_image() {
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);
let h = MetaPrefixHeader::read(&mut r, ImageRole::Argb, 32, 16).unwrap();
match h.codes {
MetaPrefixCodes::EntropyImagePending {
prefix_bits,
image_width,
image_height,
entropy_image_bit_position,
} => {
assert_eq!(prefix_bits, 2);
assert_eq!(image_width, 8);
assert_eq!(image_height, 4);
assert_eq!(entropy_image_bit_position, 5);
}
other => panic!("expected EntropyImagePending, got {other:?}"),
}
}
#[test]
fn argb_role_meta_prefix_one_div_round_up_rounds() {
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);
let h = MetaPrefixHeader::read(&mut r, ImageRole::Argb, 17, 9).unwrap();
match h.codes {
MetaPrefixCodes::EntropyImagePending {
image_width,
image_height,
..
} => {
assert_eq!(image_width, 5);
assert_eq!(image_height, 3);
}
other => panic!("expected EntropyImagePending, got {other:?}"),
}
}
#[test]
fn argb_role_meta_prefix_one_max_prefix_bits() {
let mut w = BitWriter::new();
w.write_bits(0, 1);
w.write_bits(1, 1);
w.write_bits(7, 3); let data = w.into_bytes();
let mut r = BitReader::new(&data);
let h = MetaPrefixHeader::read(&mut r, ImageRole::Argb, 1024, 1024).unwrap();
match h.codes {
MetaPrefixCodes::EntropyImagePending {
prefix_bits,
image_width,
image_height,
..
} => {
assert_eq!(prefix_bits, 9);
assert_eq!(image_width, 2);
assert_eq!(image_height, 2);
}
other => panic!("expected EntropyImagePending, got {other:?}"),
}
}
#[test]
fn argb_role_color_cache_size_is_absorbed_into_green_alphabet() {
let cache_bits: u32 = 4; let cache_size: u32 = 1 << cache_bits; let extended_green = 256 + 24 + cache_size - 1; let mut w = BitWriter::new();
w.write_bits(1, 1); w.write_bits(cache_bits, 4); w.write_bits(0, 1); w.write_simple_single_symbol(0); w.write_simple_single_symbol(10); w.write_simple_single_symbol(20); w.write_simple_single_symbol(30); w.write_simple_single_symbol(0); let data = w.into_bytes();
let mut r = BitReader::new(&data);
let h = MetaPrefixHeader::read(&mut r, ImageRole::Argb, 64, 64).unwrap();
assert!(h.color_cache.is_enabled());
assert_eq!(h.color_cache.size(), cache_size as usize);
let g = h.codes.group().expect("Single");
assert_eq!(g.green.single_symbol(), Some(0));
assert_eq!(g.green.code_lengths().len(), extended_green as usize + 1);
}
#[test]
fn truncated_color_cache_info_reports_eof() {
let data: [u8; 0] = [];
let mut r = BitReader::new(&data);
match ColorCacheInfo::read(&mut r) {
Err(MetaPrefixError::Eof(_)) => {}
other => panic!("expected Eof, got {other:?}"),
}
}
#[test]
fn truncated_meta_prefix_bit_reports_eof() {
let data = [0x00u8];
let mut r = BitReader::new(&data);
let h = MetaPrefixHeader::read(&mut r, ImageRole::Argb, 1, 1);
match h {
Err(MetaPrefixError::Eof(_)) => {}
other => panic!("expected Eof, got {other:?}"),
}
}
}