#[derive(Debug, Clone)]
pub struct BitReader<'a> {
data: &'a [u8],
bit_pos: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BitReaderEof {
pub bit_pos: usize,
pub wanted: usize,
pub available: usize,
}
impl core::fmt::Display for BitReaderEof {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"VP8L bit-reader EOF at bit {}: wanted {} bit(s), {} available",
self.bit_pos, self.wanted, self.available
)
}
}
impl std::error::Error for BitReaderEof {}
impl<'a> BitReader<'a> {
pub fn new(data: &'a [u8]) -> Self {
Self { data, bit_pos: 0 }
}
pub fn new_after_image_header(payload: &'a [u8]) -> Self {
Self {
data: payload,
bit_pos: crate::vp8l_chunk::VP8L_IMAGE_HEADER_LEN * 8,
}
}
pub fn bit_position(&self) -> usize {
self.bit_pos
}
pub fn seek_to_bit(&mut self, bit_pos: usize) {
self.bit_pos = bit_pos.min(self.data.len() * 8);
}
pub fn bits_remaining(&self) -> usize {
self.data.len() * 8 - self.bit_pos
}
pub fn read_bits(&mut self, n: usize) -> Result<u32, BitReaderEof> {
debug_assert!(n <= 32, "read_bits supports up to 32 bits");
if n == 0 {
return Ok(0);
}
let available = self.bits_remaining();
if n > available {
return Err(BitReaderEof {
bit_pos: self.bit_pos,
wanted: n,
available,
});
}
let mut value: u32 = 0;
for i in 0..n {
let byte = self.data[self.bit_pos >> 3];
let bit_in_byte = self.bit_pos & 7;
let bit = (byte >> bit_in_byte) & 1;
value |= (bit as u32) << i;
self.bit_pos += 1;
}
Ok(value)
}
pub fn read_bit(&mut self) -> Result<bool, BitReaderEof> {
Ok(self.read_bits(1)? != 0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransformType {
Predictor = 0,
Color = 1,
SubtractGreen = 2,
ColorIndexing = 3,
}
impl TransformType {
pub fn from_bits(bits: u32) -> Self {
match bits & 0x3 {
0 => Self::Predictor,
1 => Self::Color,
2 => Self::SubtractGreen,
_ => Self::ColorIndexing,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Transform {
Predictor {
size_bits: u8,
},
Color {
size_bits: u8,
},
SubtractGreen,
ColorIndexing {
color_table_size: u16,
width_bits: u8,
},
}
impl Transform {
pub fn transform_type(&self) -> TransformType {
match self {
Self::Predictor { .. } => TransformType::Predictor,
Self::Color { .. } => TransformType::Color,
Self::SubtractGreen => TransformType::SubtractGreen,
Self::ColorIndexing { .. } => TransformType::ColorIndexing,
}
}
pub fn has_entropy_body(&self) -> bool {
!matches!(self, Self::SubtractGreen)
}
}
fn color_indexing_width_bits(color_table_size: u16) -> u8 {
if color_table_size <= 2 {
3
} else if color_table_size <= 4 {
2
} else if color_table_size <= 16 {
1
} else {
0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransformListError {
Eof(BitReaderEof),
DuplicateTransform {
transform_type: TransformType,
},
}
impl From<BitReaderEof> for TransformListError {
fn from(e: BitReaderEof) -> Self {
Self::Eof(e)
}
}
impl core::fmt::Display for TransformListError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Eof(e) => write!(f, "VP8L §4 transform list: {e}"),
Self::DuplicateTransform { transform_type } => write!(
f,
"VP8L §4 transform list: {transform_type:?} transform appears more than once"
),
}
}
}
impl std::error::Error for TransformListError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransformList {
transforms: Vec<Transform>,
body_bit_position: usize,
stopped_at_entropy_body: bool,
}
impl TransformList {
pub fn read(reader: &mut BitReader<'_>) -> Result<Self, TransformListError> {
let mut transforms: Vec<Transform> = Vec::new();
let mut seen = [false; 4];
loop {
if !reader.read_bit()? {
return Ok(Self {
transforms,
body_bit_position: reader.bit_position(),
stopped_at_entropy_body: false,
});
}
let ttype = TransformType::from_bits(reader.read_bits(2)?);
let idx = ttype as usize;
if seen[idx] {
return Err(TransformListError::DuplicateTransform {
transform_type: ttype,
});
}
seen[idx] = true;
let transform = match ttype {
TransformType::Predictor => {
let size_bits = (reader.read_bits(3)? + 2) as u8;
Transform::Predictor { size_bits }
}
TransformType::Color => {
let size_bits = (reader.read_bits(3)? + 2) as u8;
Transform::Color { size_bits }
}
TransformType::SubtractGreen => Transform::SubtractGreen,
TransformType::ColorIndexing => {
let color_table_size = (reader.read_bits(8)? + 1) as u16;
Transform::ColorIndexing {
color_table_size,
width_bits: color_indexing_width_bits(color_table_size),
}
}
};
let has_body = transform.has_entropy_body();
transforms.push(transform);
if has_body {
return Ok(Self {
transforms,
body_bit_position: reader.bit_position(),
stopped_at_entropy_body: true,
});
}
}
}
pub fn transforms(&self) -> &[Transform] {
&self.transforms
}
pub fn body_bit_position(&self) -> usize {
self.body_bit_position
}
pub fn stopped_at_entropy_body(&self) -> bool {
self.stopped_at_entropy_body
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_bits_is_lsb_first() {
let data = [0xA6];
let mut r = BitReader::new(&data);
let expected = [0, 1, 1, 0, 0, 1, 0, 1];
for &e in &expected {
assert_eq!(r.read_bits(1).unwrap(), e);
}
assert_eq!(r.bit_position(), 8);
}
#[test]
fn read_bits_multi_bit_assembles_first_bit_as_lsb() {
let data = [0xA6];
let mut r = BitReader::new(&data);
assert_eq!(r.read_bits(2).unwrap(), 0b10);
let mut r2 = BitReader::new(&data);
let b0 = r2.read_bits(1).unwrap();
let b1 = r2.read_bits(1).unwrap();
assert_eq!(b0 | (b1 << 1), 0b10);
}
#[test]
fn read_bits_crosses_byte_boundary() {
let data = [0xFF, 0x01];
let mut r = BitReader::new(&data);
assert_eq!(r.read_bits(9).unwrap(), 0x1FF);
assert_eq!(r.bit_position(), 9);
}
#[test]
fn read_bits_full_u32() {
let data = [0x78, 0x56, 0x34, 0x12];
let mut r = BitReader::new(&data);
assert_eq!(r.read_bits(32).unwrap(), 0x1234_5678);
}
#[test]
fn read_bits_zero_is_noop() {
let data = [0xFF];
let mut r = BitReader::new(&data);
assert_eq!(r.read_bits(0).unwrap(), 0);
assert_eq!(r.bit_position(), 0);
}
#[test]
fn read_bits_eof_reports_position_and_demand() {
let data = [0x00]; let mut r = BitReader::new(&data);
assert_eq!(r.read_bits(4).unwrap(), 0);
match r.read_bits(8) {
Err(BitReaderEof {
bit_pos,
wanted,
available,
}) => {
assert_eq!(bit_pos, 4);
assert_eq!(wanted, 8);
assert_eq!(available, 4);
}
other => panic!("expected EOF, got {other:?}"),
}
assert_eq!(r.bit_position(), 4);
}
#[test]
fn seek_to_bit_repositions_and_clamps() {
let data = [0x00u8, 0xFF];
let mut r = BitReader::new(&data);
r.seek_to_bit(8);
assert_eq!(r.bit_position(), 8);
assert_eq!(r.read_bits(8).unwrap(), 0xFF);
r.seek_to_bit(1000);
assert_eq!(r.bit_position(), 16);
assert_eq!(r.bits_remaining(), 0);
}
#[test]
fn new_after_image_header_skips_40_bits() {
let payload = [0u8; 8];
let r = BitReader::new_after_image_header(&payload);
assert_eq!(
r.bit_position(),
crate::vp8l_chunk::VP8L_IMAGE_HEADER_LEN * 8
);
assert_eq!(r.bit_position(), 40);
}
#[test]
fn transform_type_from_bits_total() {
assert_eq!(TransformType::from_bits(0), TransformType::Predictor);
assert_eq!(TransformType::from_bits(1), TransformType::Color);
assert_eq!(TransformType::from_bits(2), TransformType::SubtractGreen);
assert_eq!(TransformType::from_bits(3), TransformType::ColorIndexing);
}
#[test]
fn color_indexing_width_bits_thresholds() {
assert_eq!(color_indexing_width_bits(1), 3);
assert_eq!(color_indexing_width_bits(2), 3);
assert_eq!(color_indexing_width_bits(3), 2);
assert_eq!(color_indexing_width_bits(4), 2);
assert_eq!(color_indexing_width_bits(5), 1);
assert_eq!(color_indexing_width_bits(16), 1);
assert_eq!(color_indexing_width_bits(17), 0);
assert_eq!(color_indexing_width_bits(256), 0);
}
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 into_bytes(self) -> Vec<u8> {
self.bytes
}
}
#[test]
fn empty_transform_list_consumes_one_zero_bit() {
let mut w = BitWriter::new();
w.write_bits(0, 1);
let data = w.into_bytes();
let mut r = BitReader::new(&data);
let list = TransformList::read(&mut r).unwrap();
assert!(list.transforms().is_empty());
assert!(!list.stopped_at_entropy_body());
assert_eq!(list.body_bit_position(), 1);
}
#[test]
fn subtract_green_only_then_terminator() {
let mut w = BitWriter::new();
w.write_bits(1, 1);
w.write_bits(2, 2);
w.write_bits(0, 1);
let data = w.into_bytes();
let mut r = BitReader::new(&data);
let list = TransformList::read(&mut r).unwrap();
assert_eq!(list.transforms(), &[Transform::SubtractGreen]);
assert!(!list.stopped_at_entropy_body());
assert_eq!(list.body_bit_position(), 4);
}
#[test]
fn predictor_stops_at_entropy_body() {
let mut w = BitWriter::new();
w.write_bits(1, 1);
w.write_bits(0, 2);
w.write_bits(7, 3); let data = w.into_bytes();
let mut r = BitReader::new(&data);
let list = TransformList::read(&mut r).unwrap();
assert_eq!(list.transforms(), &[Transform::Predictor { size_bits: 9 }]);
assert!(list.stopped_at_entropy_body());
assert_eq!(list.body_bit_position(), 6);
}
#[test]
fn color_transform_size_bits_min() {
let mut w = BitWriter::new();
w.write_bits(1, 1);
w.write_bits(1, 2);
w.write_bits(1, 3);
let data = w.into_bytes();
let mut r = BitReader::new(&data);
let list = TransformList::read(&mut r).unwrap();
assert_eq!(list.transforms(), &[Transform::Color { size_bits: 3 }]);
assert!(list.stopped_at_entropy_body());
}
#[test]
fn color_indexing_reads_table_size_and_width_bits() {
let mut w = BitWriter::new();
w.write_bits(1, 1);
w.write_bits(3, 2);
w.write_bits(0, 8); let data = w.into_bytes();
let mut r = BitReader::new(&data);
let list = TransformList::read(&mut r).unwrap();
assert_eq!(
list.transforms(),
&[Transform::ColorIndexing {
color_table_size: 1,
width_bits: 3,
}]
);
assert!(list.stopped_at_entropy_body());
assert_eq!(list.body_bit_position(), 11);
}
#[test]
fn subtract_green_then_predictor_matches_fixture_shape() {
let mut w = BitWriter::new();
w.write_bits(1, 1); w.write_bits(2, 2); w.write_bits(1, 1); w.write_bits(0, 2); w.write_bits(7, 3); let data = w.into_bytes();
let mut r = BitReader::new(&data);
let list = TransformList::read(&mut r).unwrap();
assert_eq!(
list.transforms(),
&[
Transform::SubtractGreen,
Transform::Predictor { size_bits: 9 }
]
);
assert!(list.stopped_at_entropy_body());
}
#[test]
fn duplicate_transform_is_refused() {
let mut w = BitWriter::new();
w.write_bits(1, 1);
w.write_bits(2, 2);
w.write_bits(1, 1);
w.write_bits(2, 2);
let data = w.into_bytes();
let mut r = BitReader::new(&data);
match TransformList::read(&mut r) {
Err(TransformListError::DuplicateTransform { transform_type }) => {
assert_eq!(transform_type, TransformType::SubtractGreen);
}
other => panic!("expected DuplicateTransform, got {other:?}"),
}
}
#[test]
fn truncated_transform_list_reports_eof() {
let mut w = BitWriter::new();
w.write_bits(1, 1);
let data = w.into_bytes();
let mut r = BitReader::new(&data);
r.read_bits(8).unwrap(); match TransformList::read(&mut r) {
Err(TransformListError::Eof(_)) => {}
other => panic!("expected Eof, got {other:?}"),
}
}
#[test]
fn transform_helpers_report_type_and_body() {
assert_eq!(
Transform::Predictor { size_bits: 4 }.transform_type(),
TransformType::Predictor
);
assert!(Transform::Predictor { size_bits: 4 }.has_entropy_body());
assert!(Transform::Color { size_bits: 4 }.has_entropy_body());
assert!(Transform::ColorIndexing {
color_table_size: 8,
width_bits: 1
}
.has_entropy_body());
assert!(!Transform::SubtractGreen.has_entropy_body());
}
}