#![allow(clippy::inline_always)]
use crate::error::{HuffmanFailure, JpegError};
const ACC_BITS: u8 = 64;
const REFILL_THRESHOLD: u8 = 56;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct BitReaderSnapshot {
pub(crate) pos: usize,
pub(crate) acc: u64,
pub(crate) bits: u8,
}
pub(crate) struct BitReader<'a> {
bytes: &'a [u8],
pos: usize,
acc: u64,
bits: u8,
marker: Option<u8>,
}
impl<'a> BitReader<'a> {
pub(crate) fn new(bytes: &'a [u8]) -> Self {
Self {
bytes,
pos: 0,
acc: 0,
bits: 0,
marker: None,
}
}
#[inline(always)]
pub(crate) fn ensure_bits(&mut self, n: u8) -> Result<(), JpegError> {
while self.bits < n {
if !self.refill_one_byte() {
if self.bits >= n {
return Ok(());
}
return Err(JpegError::HuffmanDecode {
mcu: 0,
reason: HuffmanFailure::TableExhausted,
});
}
}
Ok(())
}
#[inline(always)]
pub(crate) fn ensure_bits_padded(&mut self, n: u8) -> Result<(), JpegError> {
let mut refilled = false;
while self.bits < n {
if !self.refill_one_byte() {
if self.marker.is_none() {
return Err(JpegError::HuffmanDecode {
mcu: 0,
reason: HuffmanFailure::TableExhausted,
});
}
while self.bits < n {
self.acc |= 1u64 << (ACC_BITS - 1 - self.bits);
self.bits += 1;
}
return Ok(());
}
refilled = true;
}
if refilled {
self.refill_to_threshold();
}
Ok(())
}
#[inline(always)]
fn refill_one_byte(&mut self) -> bool {
if self.marker.is_some() || self.pos >= self.bytes.len() {
return false;
}
let b = self.bytes[self.pos];
if b == 0xFF {
if self.pos + 1 >= self.bytes.len() {
return false;
}
let next = self.bytes[self.pos + 1];
if next == 0x00 {
self.push_byte(0xFF);
self.pos += 2;
true
} else {
self.marker = Some(next);
false
}
} else {
self.push_byte(b);
self.pos += 1;
true
}
}
#[inline(always)]
fn push_byte(&mut self, b: u8) {
let shift = ACC_BITS - 8 - self.bits;
self.acc |= u64::from(b) << shift;
self.bits += 8;
}
#[inline(always)]
pub(crate) fn peek_bits(&self, n: u8) -> u32 {
debug_assert!(n <= 32, "peek_bits({n}) exceeds u32");
debug_assert!(
n <= self.bits,
"peek_bits({n}) with only {} buffered",
self.bits
);
if n == 0 {
0
} else {
(self.acc >> (ACC_BITS - n)) as u32
}
}
#[inline(always)]
pub(crate) fn consume_bits(&mut self, n: u8) {
debug_assert!(
n <= self.bits,
"consume_bits({n}) with only {} buffered",
self.bits
);
self.acc <<= n;
self.bits -= n;
}
pub(crate) fn read_bits(&mut self, n: u8) -> Result<u32, JpegError> {
self.ensure_bits(n)?;
let v = self.peek_bits(n);
self.consume_bits(n);
self.refill_to_threshold();
Ok(v)
}
fn refill_to_threshold(&mut self) {
while self.bits < REFILL_THRESHOLD && self.refill_one_byte() {}
}
#[inline(always)]
pub(crate) fn receive_extend(&mut self, ssss: u8) -> Result<i32, JpegError> {
if ssss == 0 {
return Ok(0);
}
self.ensure_bits(ssss)?;
let v = self.peek_bits(ssss) as i32;
self.consume_bits(ssss);
let threshold = 1i32 << (ssss - 1);
Ok(if v < threshold {
v + ((-1i32) << ssss) + 1
} else {
v
})
}
pub(crate) fn take_marker(&mut self) -> Option<u8> {
let m = self.marker.take()?;
self.pos += 2;
Some(m)
}
pub(crate) fn position(&self) -> usize {
self.pos
}
pub(crate) fn snapshot(&self) -> BitReaderSnapshot {
BitReaderSnapshot {
pos: self.pos,
acc: self.acc,
bits: self.bits,
}
}
pub(crate) fn reset_at_restart(&mut self) {
self.acc = 0;
self.bits = 0;
}
pub(crate) fn from_snapshot(bytes: &'a [u8], snapshot: BitReaderSnapshot) -> Self {
Self {
bytes,
pos: snapshot.pos,
acc: snapshot.acc,
bits: snapshot.bits,
marker: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reads_bits_in_msb_first_order() {
let data = [0b1011_0010u8, 0b0110_0100];
let mut br = BitReader::new(&data);
assert_eq!(br.read_bits(1).unwrap(), 0b1);
assert_eq!(br.read_bits(3).unwrap(), 0b011);
assert_eq!(br.read_bits(8).unwrap(), 0b0010_0110);
assert_eq!(br.read_bits(2).unwrap(), 0b01);
assert_eq!(br.read_bits(2).unwrap(), 0b00);
}
#[test]
fn unstuffs_ff00_sequence_as_single_ff_data_byte() {
let data = [0xFFu8, 0x00, 0x55];
let mut br = BitReader::new(&data);
assert_eq!(br.read_bits(8).unwrap(), 0xFF);
assert_eq!(br.read_bits(8).unwrap(), 0x55);
}
#[test]
fn stops_at_rst_marker_and_exposes_code() {
let data = [0x42u8, 0xFF, 0xD3, 0x99];
let mut br = BitReader::new(&data);
assert_eq!(br.read_bits(8).unwrap(), 0x42);
let err = br.read_bits(8).unwrap_err();
assert!(matches!(err, JpegError::HuffmanDecode { .. }));
assert_eq!(br.take_marker(), Some(0xD3));
}
#[test]
fn stops_at_eoi_marker() {
let data = [0x11u8, 0x22, 0xFF, 0xD9];
let mut br = BitReader::new(&data);
assert_eq!(br.read_bits(8).unwrap(), 0x11);
assert_eq!(br.read_bits(8).unwrap(), 0x22);
let err = br.read_bits(8).unwrap_err();
assert!(matches!(err, JpegError::HuffmanDecode { .. }));
assert_eq!(br.take_marker(), Some(0xD9));
}
#[test]
fn padded_huffman_lookahead_prefetches_reservoir_without_consuming_bits() {
let data = [0x12u8, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
let mut br = BitReader::new(&data);
br.ensure_bits_padded(12).unwrap();
let snapshot = br.snapshot();
assert!(
snapshot.bits >= 56,
"expected full hot-path reservoir, got {} bits",
snapshot.bits
);
assert_eq!(br.peek_bits(16), 0x1234);
}
#[test]
fn peek_does_not_advance_cursor() {
let data = [0xAB, 0xCD];
let mut br = BitReader::new(&data);
br.ensure_bits(16).unwrap();
assert_eq!(br.peek_bits(8), 0xAB);
assert_eq!(br.peek_bits(8), 0xAB);
br.consume_bits(4);
assert_eq!(br.peek_bits(8), 0xBC);
}
#[test]
fn receive_extend_matches_t81_f_2_2_1() {
for (raw, ssss, expected) in [
(0b010u16, 3u8, -5i32),
(0b000u16, 3u8, -7i32),
(0b111u16, 3u8, 7i32),
(0b100u16, 3u8, 4i32),
(0b0u16, 1u8, -1i32),
(0b1u16, 1u8, 1i32),
] {
let data = [(raw << (8 - ssss)) as u8];
let mut br = BitReader::new(&data);
let got = br.receive_extend(ssss).unwrap();
assert_eq!(got, expected, "ssss={ssss} raw={raw:b}");
}
}
#[test]
fn refills_across_many_bytes_without_losing_bits() {
let data = [0xAAu8; 12];
let mut br = BitReader::new(&data);
for i in 0..96 {
let bit = br.read_bits(1).unwrap();
let expected = u32::from(i % 2 == 0);
assert_eq!(bit, expected, "bit {i}");
}
}
#[test]
fn reports_huffman_failure_on_truncated_scan() {
let data = [0x55u8];
let mut br = BitReader::new(&data);
let _ = br.read_bits(8).unwrap();
let err = br.read_bits(1).unwrap_err();
assert!(matches!(
err,
JpegError::HuffmanDecode {
reason: HuffmanFailure::TableExhausted,
..
}
));
}
#[test]
fn snapshot_roundtrips_reader_state() {
let data = [0xABu8, 0xCD, 0xEF];
let mut br = BitReader::new(&data);
assert_eq!(br.read_bits(5).unwrap(), 0b10101);
let snapshot = br.snapshot();
let expected = br.read_bits(7).unwrap();
let mut restored = BitReader::from_snapshot(&data, snapshot);
assert_eq!(restored.read_bits(7).unwrap(), expected);
}
}