#![no_std]
#![forbid(unsafe_code)]
#![forbid(missing_docs)]
extern crate alloc;
use crate::bit_reader::BitReader;
use crate::decode::{EOFB, Mode};
use alloc::vec;
use alloc::vec::Vec;
mod bit_reader;
mod decode;
mod state_machine;
pub type Result<T> = core::result::Result<T, DecodeError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DecodeError {
UnexpectedEof,
InvalidCode,
LineLengthMismatch,
Overflow,
}
impl core::fmt::Display for DecodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::UnexpectedEof => write!(f, "unexpected end of input"),
Self::InvalidCode => write!(f, "invalid CCITT code sequence"),
Self::LineLengthMismatch => write!(f, "scanline length mismatch"),
Self::Overflow => write!(f, "arithmetic overflow in position calculation"),
}
}
}
impl core::error::Error for DecodeError {}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum EncodingMode {
Group4,
Group3_1D,
Group3_2D {
k: u32,
},
}
#[derive(Copy, Clone, Debug)]
pub struct DecodeSettings {
pub columns: u32,
pub rows: u32,
pub end_of_block: bool,
pub end_of_line: bool,
pub rows_are_byte_aligned: bool,
pub encoding: EncodingMode,
pub invert_black: bool,
}
pub fn split_run(written: u32, count: u32) -> (u32, u32, u32) {
let to_boundary = (8 - (written % 8)) % 8;
let prefix = count.min(to_boundary);
let rest = count - prefix;
(prefix, rest / 8, rest % 8)
}
pub trait Decoder {
fn push_pixels(&mut self, white: bool, count: u32);
fn next_line(&mut self);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Color {
White,
Black,
}
impl Color {
#[inline(always)]
fn opposite(self) -> Self {
match self {
Self::White => Self::Black,
Self::Black => Self::White,
}
}
#[inline(always)]
fn is_white(self) -> bool {
matches!(self, Self::White)
}
}
#[derive(Clone, Copy)]
struct ColorChange {
idx: u32,
color: Color,
}
pub fn decode(data: &[u8], decoder: &mut impl Decoder, ctx: &mut DecoderContext) -> Result<usize> {
ctx.reset();
let mut reader = BitReader::new(data);
match ctx.settings.encoding {
EncodingMode::Group4 => decode_group4(ctx, &mut reader, decoder)?,
EncodingMode::Group3_1D => decode_group3_1d(ctx, &mut reader, decoder)?,
EncodingMode::Group3_2D { .. } => decode_group3_2d(ctx, &mut reader, decoder)?,
}
reader.align();
Ok(reader.byte_pos())
}
fn decode_group3_1d(
ctx: &mut DecoderContext,
reader: &mut BitReader<'_>,
decoder: &mut impl Decoder,
) -> Result<()> {
let _ = reader.read_eol_if_available();
loop {
decode_1d_line(ctx, reader, decoder)?;
ctx.next_line(reader, decoder)?;
if group3_check_eob(ctx, reader) {
break;
}
}
Ok(())
}
fn decode_group3_2d(
ctx: &mut DecoderContext,
reader: &mut BitReader<'_>,
decoder: &mut impl Decoder,
) -> Result<()> {
let _ = reader.read_eol_if_available();
loop {
let tag_bit = reader.read_bit()?;
if tag_bit == 1 {
decode_1d_line(ctx, reader, decoder)?;
} else {
decode_2d_line(ctx, reader, decoder)?;
}
ctx.next_line(reader, decoder)?;
if group3_check_eob(ctx, reader) {
break;
}
}
Ok(())
}
fn group3_check_eob(ctx: &mut DecoderContext, reader: &mut BitReader<'_>) -> bool {
let eol_count = reader.read_eol_if_available();
if ctx.settings.end_of_block && eol_count >= 6 {
return true;
}
if ctx.decoded_rows == ctx.settings.rows || reader.at_end() {
return true;
}
false
}
fn decode_group4(
ctx: &mut DecoderContext,
reader: &mut BitReader<'_>,
decoder: &mut impl Decoder,
) -> Result<()> {
loop {
if ctx.settings.end_of_block && reader.peak_bits(24) == Ok(EOFB) {
reader.read_bits(24)?;
break;
}
if ctx.decoded_rows == ctx.settings.rows || reader.at_end() {
break;
}
decode_2d_line(ctx, reader, decoder)?;
ctx.next_line(reader, decoder)?;
}
Ok(())
}
#[inline(always)]
fn decode_1d_line(
ctx: &mut DecoderContext,
reader: &mut BitReader<'_>,
decoder: &mut impl Decoder,
) -> Result<()> {
while !ctx.at_eol() {
let run_length = reader.decode_run(ctx.color)?;
ctx.push_pixels(decoder, run_length);
ctx.color = ctx.color.opposite();
}
Ok(())
}
#[inline(always)]
fn decode_2d_line(
ctx: &mut DecoderContext,
reader: &mut BitReader<'_>,
decoder: &mut impl Decoder,
) -> Result<()> {
while !ctx.at_eol() {
let mode = reader.decode_mode()?;
match mode {
Mode::Pass => {
ctx.push_pixels(decoder, ctx.b2() - ctx.a0().unwrap_or(0));
ctx.update_b();
}
Mode::Vertical(i) => {
let b1 = ctx.b1();
let a1 = if i >= 0 {
b1.checked_add(i as u32).ok_or(DecodeError::Overflow)?
} else {
b1.checked_sub((-i) as u32).ok_or(DecodeError::Overflow)?
};
let a0 = ctx.a0().unwrap_or(0);
ctx.push_pixels(decoder, a1.checked_sub(a0).ok_or(DecodeError::Overflow)?);
ctx.color = ctx.color.opposite();
ctx.update_b();
}
Mode::Horizontal => {
let a0a1 = reader.decode_run(ctx.color)?;
ctx.push_pixels(decoder, a0a1);
ctx.color = ctx.color.opposite();
let a1a2 = reader.decode_run(ctx.color)?;
ctx.push_pixels(decoder, a1a2);
ctx.color = ctx.color.opposite();
ctx.update_b();
}
}
}
Ok(())
}
pub struct DecoderContext {
ref_changes: Vec<ColorChange>,
ref_pos: u32,
b1_idx: u32,
coding_changes: Vec<ColorChange>,
pixels_decoded: u32,
line_width: u32,
color: Color,
decoded_rows: u32,
settings: DecodeSettings,
invert_black: bool,
}
impl DecoderContext {
pub fn new(settings: DecodeSettings) -> Self {
Self {
ref_changes: vec![],
ref_pos: 0,
b1_idx: 0,
coding_changes: Vec::new(),
pixels_decoded: 0,
line_width: settings.columns,
color: Color::White,
decoded_rows: 0,
settings,
invert_black: settings.invert_black,
}
}
fn reset(&mut self) {
self.ref_changes.clear();
self.ref_pos = 0;
self.b1_idx = 0;
self.coding_changes.clear();
self.pixels_decoded = 0;
self.line_width = self.settings.columns;
self.color = Color::White;
self.decoded_rows = 0;
self.invert_black = self.settings.invert_black;
}
#[inline(always)]
fn a0(&self) -> Option<u32> {
if self.pixels_decoded == 0 {
None
} else {
Some(self.pixels_decoded)
}
}
#[inline(always)]
fn b1(&self) -> u32 {
self.ref_changes
.get(self.b1_idx as usize)
.map_or(self.line_width, |c| c.idx)
}
#[inline(always)]
fn b2(&self) -> u32 {
self.ref_changes
.get(self.b1_idx as usize + 1)
.map_or(self.line_width, |c| c.idx)
}
#[inline(always)]
fn update_b(&mut self) {
let target_color = self.color.opposite();
let min_idx = self.a0().map_or(0, |a| a + 1);
self.b1_idx = self.line_width;
for i in self.ref_pos..self.ref_changes.len() as u32 {
let change = &self.ref_changes[i as usize];
if change.idx < min_idx {
self.ref_pos = i + 1;
continue;
}
if change.color == target_color {
self.b1_idx = i;
break;
}
}
}
#[inline(always)]
fn push_pixels(&mut self, decoder: &mut impl Decoder, count: u32) {
let count = count.min(self.line_width - self.pixels_decoded);
let white = self.color.is_white() ^ self.invert_black;
decoder.push_pixels(white, count);
if count > 0 {
let is_change = self
.coding_changes
.last()
.map_or(!self.color.is_white(), |last| last.color != self.color);
if is_change {
self.coding_changes.push(ColorChange {
idx: self.pixels_decoded,
color: self.color,
});
}
self.pixels_decoded += count;
}
}
#[inline(always)]
fn at_eol(&self) -> bool {
self.a0().unwrap_or(0) == self.line_width
}
#[inline(always)]
fn next_line(&mut self, reader: &mut BitReader<'_>, decoder: &mut impl Decoder) -> Result<()> {
if self.pixels_decoded != self.settings.columns {
return Err(DecodeError::LineLengthMismatch);
}
core::mem::swap(&mut self.ref_changes, &mut self.coding_changes);
self.coding_changes.clear();
self.pixels_decoded = 0;
self.ref_pos = 0;
self.b1_idx = 0;
self.color = Color::White;
self.decoded_rows += 1;
decoder.next_line();
if self.settings.rows_are_byte_aligned {
reader.align();
}
self.update_b();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
struct PixelCollector {
rows: Vec<Vec<bool>>,
current: Vec<bool>,
}
impl PixelCollector {
fn new() -> Self {
Self {
rows: Vec::new(),
current: Vec::new(),
}
}
}
impl Decoder for PixelCollector {
fn push_pixels(&mut self, white: bool, count: u32) {
for _ in 0..count {
self.current.push(white);
}
}
fn next_line(&mut self) {
self.rows.push(core::mem::take(&mut self.current));
}
}
fn g3_1d(columns: u32, rows: u32) -> DecodeSettings {
DecodeSettings {
columns,
rows,
end_of_block: false,
end_of_line: false,
rows_are_byte_aligned: false,
encoding: EncodingMode::Group3_1D,
invert_black: false,
}
}
fn g4(columns: u32, rows: u32) -> DecodeSettings {
DecodeSettings {
columns,
rows,
end_of_block: false,
end_of_line: false,
rows_are_byte_aligned: false,
encoding: EncodingMode::Group4,
invert_black: false,
}
}
const W: bool = true;
const B: bool = false;
#[test]
fn g3_1d_all_white() {
let mut sink = PixelCollector::new();
decode(&[0xB0], &mut sink, &mut DecoderContext::new(g3_1d(4, 1))).unwrap();
assert_eq!(sink.rows, [[W, W, W, W]]);
}
#[test]
fn g3_1d_white_then_black() {
let mut sink = PixelCollector::new();
decode(&[0x7C], &mut sink, &mut DecoderContext::new(g3_1d(4, 1))).unwrap();
assert_eq!(sink.rows, [[W, W, B, B]]);
}
#[test]
fn g3_1d_all_black() {
let mut sink = PixelCollector::new();
decode(
&[0x35, 0x30],
&mut sink,
&mut DecoderContext::new(g3_1d(4, 1)),
)
.unwrap();
assert_eq!(sink.rows, [[B, B, B, B]]);
}
#[test]
fn g3_1d_two_rows() {
let mut sink = PixelCollector::new();
decode(&[0xBB], &mut sink, &mut DecoderContext::new(g3_1d(4, 2))).unwrap();
assert_eq!(sink.rows, [[W, W, W, W], [W, W, W, W]]);
}
#[test]
fn group4_all_white() {
let mut sink = PixelCollector::new();
decode(&[0x80], &mut sink, &mut DecoderContext::new(g4(4, 1))).unwrap();
assert_eq!(sink.rows, [[W, W, W, W]]);
}
#[test]
fn invert_black_flag() {
let mut sink = PixelCollector::new();
let settings = DecodeSettings {
invert_black: true,
..g3_1d(1, 1)
};
decode(&[0x1C], &mut sink, &mut DecoderContext::new(settings)).unwrap();
assert_eq!(sink.rows, [[B]]);
}
#[test]
fn empty_data_returns_unexpected_eof() {
let mut sink = PixelCollector::new();
let err = decode(&[], &mut sink, &mut DecoderContext::new(g3_1d(4, 1))).unwrap_err();
assert_eq!(err, DecodeError::UnexpectedEof);
}
}
#[cfg(test)]
mod split_run_tests {
use super::split_run;
#[test]
fn the_parts_always_reconstruct_the_run() {
for written in 0..32u32 {
for count in 0..200u32 {
let (prefix, bytes, tail) = split_run(written, count);
assert_eq!(
prefix + bytes * 8 + tail,
count,
"written={written} count={count} split into {prefix}+{bytes}*8+{tail}"
);
assert!(tail < 8, "tail {tail} should have been a whole byte");
assert!(
prefix == count || (written + prefix) % 8 == 0,
"written={written} count={count}: prefix {prefix} left the writer unaligned"
);
}
}
}
#[test]
fn an_aligned_writer_gets_no_prefix() {
for written in [0u32, 8, 16, 800] {
assert_eq!(split_run(written, 24), (0, 3, 0), "written={written}");
}
}
}