use crate::error::{Result, TiffError as Error};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FillOrder {
MsbFirst, LsbFirst, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CcittVariant {
ModifiedHuffman,
T4OneD { eol_byte_aligned: bool },
T4TwoD { eol_byte_aligned: bool },
T6,
}
pub fn decode_ccitt(
input: &[u8],
width: u32,
rows: u32,
variant: CcittVariant,
fill: FillOrder,
) -> Result<Vec<u8>> {
let owned: Vec<u8>;
let stream: &[u8] = match fill {
FillOrder::MsbFirst => input,
FillOrder::LsbFirst => {
owned = input.iter().map(|&b| reverse_bits_u8(b)).collect();
&owned
}
};
let row_bytes = (width as usize).div_ceil(8);
let mut out = vec![0u8; row_bytes * rows as usize];
let mut bits = BitReader::new(stream);
let mut reference: Vec<u8> = vec![0u8; row_bytes];
for r in 0..rows {
let row_is_two_d: bool = match variant {
CcittVariant::ModifiedHuffman => false,
CcittVariant::T4OneD { eol_byte_aligned } => {
expect_eol(&mut bits, eol_byte_aligned)?;
false
}
CcittVariant::T4TwoD { eol_byte_aligned } => {
expect_eol(&mut bits, eol_byte_aligned)?;
let tag = bits.read_bit().ok_or_else(|| {
Error::invalid("TIFF/CCITT: stream exhausted reading T.4-2D tag bit")
})?;
tag == 0
}
CcittVariant::T6 => true,
};
let row_off = (r as usize) * row_bytes;
if row_is_two_d {
decode_two_d_row(
&mut bits,
&reference,
&mut out[row_off..row_off + row_bytes],
width as usize,
)?;
} else {
let mut x: usize = 0;
let mut color_is_white = true;
while x < width as usize {
let run = read_run(&mut bits, color_is_white)?;
if x + run > width as usize {
return Err(Error::invalid(format!(
"TIFF/CCITT: run {run} at column {x} overflows width {width}"
)));
}
if !color_is_white {
for k in 0..run {
let bit = x + k;
out[row_off + bit / 8] |= 1 << (7 - (bit % 8));
}
}
x += run;
color_is_white = !color_is_white;
}
if x != width as usize {
return Err(Error::invalid(format!(
"TIFF/CCITT: row {r} pixel count {x} != width {width}"
)));
}
}
if matches!(variant, CcittVariant::ModifiedHuffman) {
bits.align_to_byte();
}
reference.copy_from_slice(&out[row_off..row_off + row_bytes]);
}
Ok(out)
}
fn decode_two_d_row(
bits: &mut BitReader<'_>,
reference: &[u8],
out: &mut [u8],
width: usize,
) -> Result<()> {
let mut a0: usize = 0;
let mut a0_is_white = true;
while a0 < width {
let mode = next_two_d_mode(bits)?;
match mode {
TwoDMode::Pass => {
let b1 = first_change_after(reference, a0, !a0_is_white, width);
let b2 = next_change_after(reference, b1, width);
fill_run(out, a0, b2, a0_is_white);
a0 = b2;
}
TwoDMode::Horizontal => {
let run1 = read_run(bits, a0_is_white)?;
let run2 = read_run(bits, !a0_is_white)?;
let end1 = a0
.checked_add(run1)
.ok_or_else(|| Error::invalid("TIFF/CCITT: H mode run1 overflows usize"))?;
if end1 > width {
return Err(Error::invalid(format!(
"TIFF/CCITT: H mode run1 {run1} at a0={a0} overflows width {width}"
)));
}
fill_run(out, a0, end1, a0_is_white);
let end2 = end1
.checked_add(run2)
.ok_or_else(|| Error::invalid("TIFF/CCITT: H mode run2 overflows usize"))?;
if end2 > width {
return Err(Error::invalid(format!(
"TIFF/CCITT: H mode run2 {run2} at {end1} overflows width {width}"
)));
}
fill_run(out, end1, end2, !a0_is_white);
a0 = end2;
}
TwoDMode::Vertical(offset) => {
let b1 = first_change_after(reference, a0, !a0_is_white, width);
let a1_signed = (b1 as isize) + offset;
if a1_signed < a0 as isize || a1_signed > width as isize {
return Err(Error::invalid(format!(
"TIFF/CCITT: V({offset}) places a1={a1_signed} outside [a0={a0}, width={width}]"
)));
}
let a1 = a1_signed as usize;
fill_run(out, a0, a1, a0_is_white);
a0 = a1;
a0_is_white = !a0_is_white;
}
TwoDMode::Uncompressed => {
let (new_a0, new_white) =
decode_uncompressed_segment(bits, out, a0, a0_is_white, width)?;
a0 = new_a0;
a0_is_white = new_white;
}
TwoDMode::Extension1D => {
return Err(Error::invalid(
"TIFF/CCITT: 1-D extension code `000000001xxx` inside a 2-D row",
));
}
}
}
if a0 != width {
return Err(Error::invalid(format!(
"TIFF/CCITT: 2-D row finished at a0={a0}, expected {width}"
)));
}
Ok(())
}
fn decode_uncompressed_segment(
bits: &mut BitReader<'_>,
out: &mut [u8],
mut a0: usize,
a0_is_white: bool,
width: usize,
) -> Result<(usize, bool)> {
let _ = a0_is_white;
loop {
let mut zeros: usize = 0;
loop {
let bit = bits.read_bit().ok_or_else(|| {
Error::invalid("TIFF/CCITT: stream exhausted in uncompressed-mode pattern")
})?;
if bit == 1 {
break;
}
zeros += 1;
if zeros > 10 {
return Err(Error::invalid(
"TIFF/CCITT: uncompressed-mode code exceeds 11 bits (malformed)",
));
}
}
if zeros <= 4 {
let white_end = a0 + zeros;
if white_end + 1 > width {
return Err(Error::invalid(format!(
"TIFF/CCITT: uncompressed pattern overruns width {width} at a0={a0}"
)));
}
fill_run(out, white_end, white_end + 1, false);
a0 = white_end + 1;
} else if zeros == 5 {
let new_a0 = a0 + 5;
if new_a0 > width {
return Err(Error::invalid(format!(
"TIFF/CCITT: uncompressed make-up overruns width {width} at a0={a0}"
)));
}
a0 = new_a0;
} else {
let m = zeros - 6;
let new_a0 = a0 + m;
if new_a0 > width {
return Err(Error::invalid(format!(
"TIFF/CCITT: uncompressed exit trailing whites overrun width {width} at a0={a0}"
)));
}
let tag = bits.read_bit().ok_or_else(|| {
Error::invalid("TIFF/CCITT: stream exhausted reading uncompressed-mode exit tag")
})?;
return Ok((new_a0, tag == 0));
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TwoDMode {
Pass,
Horizontal,
Vertical(isize),
Uncompressed,
Extension1D,
}
fn next_two_d_mode(bits: &mut BitReader<'_>) -> Result<TwoDMode> {
let mut acc: u32 = 0;
for len in 1..=12u32 {
let b = bits.read_bit().ok_or_else(|| {
Error::invalid("TIFF/CCITT: bit stream exhausted reading 2-D mode code")
})?;
acc = (acc << 1) | (b as u32);
match (len, acc) {
(1, 0b1) => return Ok(TwoDMode::Vertical(0)),
(3, 0b001) => return Ok(TwoDMode::Horizontal),
(3, 0b010) => return Ok(TwoDMode::Vertical(-1)),
(3, 0b011) => return Ok(TwoDMode::Vertical(1)),
(4, 0b0001) => return Ok(TwoDMode::Pass),
(6, 0b000010) => return Ok(TwoDMode::Vertical(-2)),
(6, 0b000011) => return Ok(TwoDMode::Vertical(2)),
(7, 0b0000010) => return Ok(TwoDMode::Vertical(-3)),
(7, 0b0000011) => return Ok(TwoDMode::Vertical(3)),
(10, v) if (v & 0b1111111000) == 0b0000001000 => {
let xxx = (v & 0b111) as u8;
if xxx == 0b111 {
return Ok(TwoDMode::Uncompressed);
}
return Err(Error::invalid(format!(
"TIFF/CCITT: unknown 2-D extension xxx={xxx:03b}"
)));
}
(12, v) if (v & 0b111111111000) == 0b000000001000 => {
return Ok(TwoDMode::Extension1D);
}
_ => {}
}
}
Err(Error::invalid(format!(
"TIFF/CCITT: unknown 2-D mode code prefix {acc:012b}"
)))
}
fn first_change_after(reference: &[u8], from: usize, target_white: bool, width: usize) -> usize {
if from >= width {
return width;
}
let mut prev_white = if from == 0 {
true
} else {
get_bit(reference, from - 1) == 0
};
let mut k_start = from + 1;
if from == 0 {
let here_white = get_bit(reference, 0) == 0;
if here_white != prev_white && here_white == target_white {
return 0;
}
prev_white = here_white;
k_start = 1;
} else {
if from < width {
prev_white = get_bit(reference, from) == 0;
}
}
for k in k_start..width {
let here_white = get_bit(reference, k) == 0;
if here_white != prev_white && here_white == target_white {
return k;
}
prev_white = here_white;
}
width
}
fn next_change_after(reference: &[u8], from: usize, width: usize) -> usize {
if from >= width {
return width;
}
let here_white = get_bit(reference, from) == 0;
let mut i = from + 1;
while i < width && (get_bit(reference, i) == 0) == here_white {
i += 1;
}
i
}
#[inline]
fn get_bit(buf: &[u8], idx: usize) -> u8 {
(buf[idx / 8] >> (7 - (idx % 8))) & 1
}
fn fill_run(out: &mut [u8], start: usize, end: usize, is_white: bool) {
if is_white {
let _ = (start, end);
return;
}
for bit in start..end {
out[bit / 8] |= 1 << (7 - (bit % 8));
}
}
fn read_run(bits: &mut BitReader<'_>, is_white: bool) -> Result<usize> {
let mut total: usize = 0;
loop {
let (len, code) = next_code(bits, is_white)?;
match code {
Code::Terminating(n) => {
total += n;
return Ok(total);
}
Code::MakeUp(n) => {
total += n;
let _ = len;
}
Code::Eol => {
return Err(Error::invalid("TIFF/CCITT: unexpected EOL inside a run"));
}
}
}
}
#[derive(Debug, Clone, Copy)]
enum Code {
Terminating(usize),
MakeUp(usize),
Eol,
}
fn next_code(bits: &mut BitReader<'_>, is_white: bool) -> Result<(u32, Code)> {
let mut acc: u32 = 0;
for len in 1..=13u32 {
let b = bits
.read_bit()
.ok_or_else(|| Error::invalid("TIFF/CCITT: bit stream exhausted mid-code"))?;
acc = (acc << 1) | (b as u32);
if len == 12 && acc == 0b0000_0000_0001 {
return Ok((12, Code::Eol));
}
let table = if is_white { WHITE } else { BLACK };
for &(plen, pcode, run, is_makeup) in table {
if plen == len && pcode == acc {
let code = if is_makeup {
Code::MakeUp(run as usize)
} else {
Code::Terminating(run as usize)
};
return Ok((len, code));
}
}
}
Err(Error::invalid(format!(
"TIFF/CCITT: unknown {} code prefix {acc:013b}",
if is_white { "white" } else { "black" }
)))
}
fn expect_eol(bits: &mut BitReader<'_>, byte_aligned: bool) -> Result<()> {
let mut zero_run: u32 = 0;
loop {
let b = bits
.read_bit()
.ok_or_else(|| Error::invalid("TIFF/CCITT: bit stream exhausted searching for EOL"))?;
if b == 0 {
zero_run += 1;
if zero_run > 1024 {
return Err(Error::invalid("TIFF/CCITT: runaway zero-bit run, no EOL"));
}
} else {
if zero_run < 11 {
return Err(Error::invalid(format!(
"TIFF/CCITT: stray 1-bit after only {zero_run} zeros (expected EOL)"
)));
}
if byte_aligned && bits.bit_pos % 8 != 0 {
return Err(Error::invalid("TIFF/CCITT: byte-aligned EOL not aligned"));
}
return Ok(());
}
}
}
#[inline]
pub fn reverse_bits_u8(b: u8) -> u8 {
b.reverse_bits()
}
pub fn reverse_bits_in_place(buf: &mut [u8]) {
for b in buf.iter_mut() {
*b = b.reverse_bits();
}
}
pub fn encode_ccitt(
input: &[u8],
width: u32,
rows: u32,
variant: CcittVariant,
fill: FillOrder,
) -> Result<Vec<u8>> {
let row_bytes = (width as usize).div_ceil(8);
let need = row_bytes * rows as usize;
if input.len() < need {
return Err(Error::invalid(format!(
"TIFF/CCITT encode: input buffer is {} bytes, need {need} (rows={rows}, row_bytes={row_bytes})",
input.len()
)));
}
let mut reference: Vec<u8> = vec![0u8; row_bytes];
let mut bw = BitWriter::new();
for r in 0..rows {
let row_off = (r as usize) * row_bytes;
let row = &input[row_off..row_off + row_bytes];
let row_is_two_d: bool;
match variant {
CcittVariant::ModifiedHuffman => {
row_is_two_d = false;
}
CcittVariant::T4OneD { eol_byte_aligned } => {
emit_eol(&mut bw, eol_byte_aligned);
row_is_two_d = false;
}
CcittVariant::T4TwoD { eol_byte_aligned } => {
emit_eol(&mut bw, eol_byte_aligned);
if r == 0 {
bw.write(1, 1);
row_is_two_d = false;
} else {
bw.write(0, 1);
row_is_two_d = true;
}
}
CcittVariant::T6 => {
row_is_two_d = true;
}
}
if row_is_two_d {
encode_two_d_row(&mut bw, &reference, row, width as usize);
} else {
let runs = scan_runs(row, width as usize);
let mut is_white = true;
for &run in &runs {
emit_run(&mut bw, run, is_white);
is_white = !is_white;
}
if matches!(variant, CcittVariant::ModifiedHuffman) {
bw.align();
}
}
reference.copy_from_slice(row);
}
let mut out = bw.finish();
if matches!(fill, FillOrder::LsbFirst) {
reverse_bits_in_place(&mut out);
}
Ok(out)
}
fn emit_eol(bw: &mut BitWriter, byte_aligned: bool) {
if byte_aligned {
let cur = bw.bit_count % 8;
let pad = (4 + 8 - cur) % 8;
for _ in 0..pad {
bw.write(0, 1);
}
}
bw.write(0b0000_0000_0001, 12);
}
fn encode_two_d_row(bw: &mut BitWriter, reference: &[u8], row: &[u8], width: usize) {
let mut a0: usize = 0;
let mut a0_is_white = true;
while a0 < width {
let a1 = first_change_after(row, a0, !a0_is_white, width);
let a2 = next_change_after(row, a1, width);
let b1 = first_change_after(reference, a0, !a0_is_white, width);
let b2 = next_change_after(reference, b1, width);
if b2 < a1 {
bw.write(0b0001, 4);
a0 = b2;
} else {
let diff = a1 as isize - b1 as isize;
if (-3..=3).contains(&diff) {
match diff {
0 => bw.write(0b1, 1),
1 => bw.write(0b011, 3),
2 => bw.write(0b000011, 6),
3 => bw.write(0b0000011, 7),
-1 => bw.write(0b010, 3),
-2 => bw.write(0b000010, 6),
-3 => bw.write(0b0000010, 7),
_ => unreachable!("vertical diff filtered to [-3..=3]"),
}
a0 = a1;
a0_is_white = !a0_is_white;
} else {
bw.write(0b001, 3);
emit_run(bw, a1 - a0, a0_is_white);
emit_run(bw, a2 - a1, !a0_is_white);
a0 = a2;
}
}
}
}
#[cfg(test)]
fn encode_uncompressed_segment(
bw: &mut BitWriter,
row: &[u8],
a0: usize,
end: usize,
next_run_black: bool,
) {
bw.write(0b0000001111, 10);
let mut x = a0;
while x < end {
let pixel_black = get_bit(row, x) == 1;
if pixel_black {
bw.write(0b1, 1);
x += 1;
} else {
let mut w = 0usize;
while x + w < end && get_bit(row, x + w) == 0 {
w += 1;
}
let run_end = x + w;
if run_end < end {
let makeups = w / 5;
let residual = w % 5;
for _ in 0..makeups {
bw.write(0b000001, 6); }
bw.write(1, (residual + 1) as u32);
x = run_end + 1; } else {
let mut trailing = w;
while trailing > 4 {
bw.write(0b000001, 6); trailing -= 5;
}
emit_uncompressed_exit(bw, trailing, next_run_black);
return;
}
}
}
emit_uncompressed_exit(bw, 0, next_run_black);
}
#[cfg(test)]
fn emit_uncompressed_exit(bw: &mut BitWriter, m: usize, next_run_black: bool) {
debug_assert!(m <= 4, "exit-code trailing-white count must be 0..=4");
bw.write(1, (6 + m + 1) as u32);
bw.write(next_run_black as u32, 1);
}
fn scan_runs(row: &[u8], width: usize) -> Vec<usize> {
let mut runs: Vec<usize> = Vec::new();
let mut cur_color_is_white = true;
let mut cur_len: usize = 0;
for x in 0..width {
let bit = (row[x / 8] >> (7 - (x % 8))) & 1;
let pixel_is_white = bit == 0;
if pixel_is_white == cur_color_is_white {
cur_len += 1;
} else {
runs.push(cur_len);
cur_color_is_white = !cur_color_is_white;
cur_len = 1;
}
}
runs.push(cur_len);
runs
}
fn emit_run(bw: &mut BitWriter, mut run: usize, is_white: bool) {
while run >= 2624 {
let (l, c) = lookup_makeup(2560, is_white).expect("makeup 2560 in table");
bw.write(c, l);
run -= 2560;
}
if run >= 64 {
let bucket = (run / 64) * 64;
let (l, c) = lookup_makeup(bucket as u32, is_white)
.unwrap_or_else(|| panic!("missing make-up code for run bucket {bucket}"));
bw.write(c, l);
run -= bucket;
}
let (l, c) = lookup_terminating(run as u32, is_white)
.unwrap_or_else(|| panic!("missing terminating code for run {run}"));
bw.write(c, l);
}
fn lookup_terminating(run: u32, is_white: bool) -> Option<(u32, u32)> {
let table = if is_white { WHITE } else { BLACK };
for &(len, code, r, makeup) in table {
if !makeup && r == run {
return Some((len, code));
}
}
None
}
fn lookup_makeup(run: u32, is_white: bool) -> Option<(u32, u32)> {
let table = if is_white { WHITE } else { BLACK };
for &(len, code, r, makeup) in table {
if makeup && r == run {
return Some((len, code));
}
}
None
}
struct BitWriter {
buf: Vec<u8>,
bit_count: usize,
}
impl BitWriter {
fn new() -> Self {
Self {
buf: Vec::new(),
bit_count: 0,
}
}
fn write(&mut self, code: u32, nbits: u32) {
debug_assert!(nbits <= 32);
for i in (0..nbits).rev() {
let bit = ((code >> i) & 1) as u8;
let byte_idx = self.bit_count / 8;
if byte_idx >= self.buf.len() {
self.buf.push(0);
}
let bit_in_byte = 7 - (self.bit_count % 8);
self.buf[byte_idx] |= bit << bit_in_byte;
self.bit_count += 1;
}
}
fn align(&mut self) {
let rem = self.bit_count % 8;
if rem != 0 {
self.bit_count += 8 - rem;
}
}
fn finish(mut self) -> Vec<u8> {
self.align();
let want = self.bit_count / 8;
if self.buf.len() < want {
self.buf.resize(want, 0);
} else {
self.buf.truncate(want);
}
self.buf
}
}
struct BitReader<'a> {
src: &'a [u8],
bit_pos: usize, }
impl<'a> BitReader<'a> {
fn new(src: &'a [u8]) -> Self {
Self { src, bit_pos: 0 }
}
fn read_bit(&mut self) -> Option<u8> {
let byte_idx = self.bit_pos / 8;
if byte_idx >= self.src.len() {
return None;
}
let bit_in_byte = 7 - (self.bit_pos % 8);
let b = (self.src[byte_idx] >> bit_in_byte) & 1;
self.bit_pos += 1;
Some(b)
}
fn align_to_byte(&mut self) {
let rem = self.bit_pos % 8;
if rem != 0 {
self.bit_pos += 8 - rem;
}
}
}
#[rustfmt::skip]
const WHITE: &[(u32, u32, u32, bool)] = &[
(8, 0b00110101, 0, false),
(6, 0b000111, 1, false),
(4, 0b0111, 2, false),
(4, 0b1000, 3, false),
(4, 0b1011, 4, false),
(4, 0b1100, 5, false),
(4, 0b1110, 6, false),
(4, 0b1111, 7, false),
(5, 0b10011, 8, false),
(5, 0b10100, 9, false),
(5, 0b00111, 10, false),
(5, 0b01000, 11, false),
(6, 0b001000, 12, false),
(6, 0b000011, 13, false),
(6, 0b110100, 14, false),
(6, 0b110101, 15, false),
(6, 0b101010, 16, false),
(6, 0b101011, 17, false),
(7, 0b0100111, 18, false),
(7, 0b0001100, 19, false),
(7, 0b0001000, 20, false),
(7, 0b0010111, 21, false),
(7, 0b0000011, 22, false),
(7, 0b0000100, 23, false),
(7, 0b0101000, 24, false),
(7, 0b0101011, 25, false),
(7, 0b0010011, 26, false),
(7, 0b0100100, 27, false),
(7, 0b0011000, 28, false),
(8, 0b00000010, 29, false),
(8, 0b00000011, 30, false),
(8, 0b00011010, 31, false),
(8, 0b00011011, 32, false),
(8, 0b00010010, 33, false),
(8, 0b00010011, 34, false),
(8, 0b00010100, 35, false),
(8, 0b00010101, 36, false),
(8, 0b00010110, 37, false),
(8, 0b00010111, 38, false),
(8, 0b00101000, 39, false),
(8, 0b00101001, 40, false),
(8, 0b00101010, 41, false),
(8, 0b00101011, 42, false),
(8, 0b00101100, 43, false),
(8, 0b00101101, 44, false),
(8, 0b00000100, 45, false),
(8, 0b00000101, 46, false),
(8, 0b00001010, 47, false),
(8, 0b00001011, 48, false),
(8, 0b01010010, 49, false),
(8, 0b01010011, 50, false),
(8, 0b01010100, 51, false),
(8, 0b01010101, 52, false),
(8, 0b00100100, 53, false),
(8, 0b00100101, 54, false),
(8, 0b01011000, 55, false),
(8, 0b01011001, 56, false),
(8, 0b01011010, 57, false),
(8, 0b01011011, 58, false),
(8, 0b01001010, 59, false),
(8, 0b01001011, 60, false),
(8, 0b00110010, 61, false),
(8, 0b00110011, 62, false),
(8, 0b00110100, 63, false),
(5, 0b11011, 64, true),
(5, 0b10010, 128, true),
(6, 0b010111, 192, true),
(7, 0b0110111, 256, true),
(8, 0b00110110, 320, true),
(8, 0b00110111, 384, true),
(8, 0b01100100, 448, true),
(8, 0b01100101, 512, true),
(8, 0b01101000, 576, true),
(8, 0b01100111, 640, true),
(9, 0b011001100, 704, true),
(9, 0b011001101, 768, true),
(9, 0b011010010, 832, true),
(9, 0b011010011, 896, true),
(9, 0b011010100, 960, true),
(9, 0b011010101, 1024, true),
(9, 0b011010110, 1088, true),
(9, 0b011010111, 1152, true),
(9, 0b011011000, 1216, true),
(9, 0b011011001, 1280, true),
(9, 0b011011010, 1344, true),
(9, 0b011011011, 1408, true),
(9, 0b010011000, 1472, true),
(9, 0b010011001, 1536, true),
(9, 0b010011010, 1600, true),
(6, 0b011000, 1664, true),
(9, 0b010011011, 1728, true),
(11, 0b00000001000, 1792, true),
(11, 0b00000001100, 1856, true),
(11, 0b00000001101, 1920, true),
(12, 0b000000010010, 1984, true),
(12, 0b000000010011, 2048, true),
(12, 0b000000010100, 2112, true),
(12, 0b000000010101, 2176, true),
(12, 0b000000010110, 2240, true),
(12, 0b000000010111, 2304, true),
(12, 0b000000011100, 2368, true),
(12, 0b000000011101, 2432, true),
(12, 0b000000011110, 2496, true),
(12, 0b000000011111, 2560, true),
];
#[rustfmt::skip]
const BLACK: &[(u32, u32, u32, bool)] = &[
(10, 0b0000110111, 0, false),
(3, 0b010, 1, false),
(2, 0b11, 2, false),
(2, 0b10, 3, false),
(3, 0b011, 4, false),
(4, 0b0011, 5, false),
(4, 0b0010, 6, false),
(5, 0b00011, 7, false),
(6, 0b000101, 8, false),
(6, 0b000100, 9, false),
(7, 0b0000100, 10, false),
(7, 0b0000101, 11, false),
(7, 0b0000111, 12, false),
(8, 0b00000100, 13, false),
(8, 0b00000111, 14, false),
(9, 0b000011000, 15, false),
(10, 0b0000010111, 16, false),
(10, 0b0000011000, 17, false),
(10, 0b0000001000, 18, false),
(11, 0b00001100111, 19, false),
(11, 0b00001101000, 20, false),
(11, 0b00001101100, 21, false),
(11, 0b00000110111, 22, false),
(11, 0b00000101000, 23, false),
(11, 0b00000010111, 24, false),
(11, 0b00000011000, 25, false),
(12, 0b000011001010, 26, false),
(12, 0b000011001011, 27, false),
(12, 0b000011001100, 28, false),
(12, 0b000011001101, 29, false),
(12, 0b000001101000, 30, false),
(12, 0b000001101001, 31, false),
(12, 0b000001101010, 32, false),
(12, 0b000001101011, 33, false),
(12, 0b000011010010, 34, false),
(12, 0b000011010011, 35, false),
(12, 0b000011010100, 36, false),
(12, 0b000011010101, 37, false),
(12, 0b000011010110, 38, false),
(12, 0b000011010111, 39, false),
(12, 0b000001101100, 40, false),
(12, 0b000001101101, 41, false),
(12, 0b000011011010, 42, false),
(12, 0b000011011011, 43, false),
(12, 0b000001010100, 44, false),
(12, 0b000001010101, 45, false),
(12, 0b000001010110, 46, false),
(12, 0b000001010111, 47, false),
(12, 0b000001100100, 48, false),
(12, 0b000001100101, 49, false),
(12, 0b000001010010, 50, false),
(12, 0b000001010011, 51, false),
(12, 0b000000100100, 52, false),
(12, 0b000000110111, 53, false),
(12, 0b000000111000, 54, false),
(12, 0b000000100111, 55, false),
(12, 0b000000101000, 56, false),
(12, 0b000001011000, 57, false),
(12, 0b000001011001, 58, false),
(12, 0b000000101011, 59, false),
(12, 0b000000101100, 60, false),
(12, 0b000001011010, 61, false),
(12, 0b000001100110, 62, false),
(12, 0b000001100111, 63, false),
(10, 0b0000001111, 64, true),
(12, 0b000011001000, 128, true),
(12, 0b000011001001, 192, true),
(12, 0b000001011011, 256, true),
(12, 0b000000110011, 320, true),
(12, 0b000000110100, 384, true),
(12, 0b000000110101, 448, true),
(13, 0b0000001101100, 512, true),
(13, 0b0000001101101, 576, true),
(13, 0b0000001001010, 640, true),
(13, 0b0000001001011, 704, true),
(13, 0b0000001001100, 768, true),
(13, 0b0000001001101, 832, true),
(13, 0b0000001110010, 896, true),
(13, 0b0000001110011, 960, true),
(13, 0b0000001110100, 1024, true),
(13, 0b0000001110101, 1088, true),
(13, 0b0000001110110, 1152, true),
(13, 0b0000001110111, 1216, true),
(13, 0b0000001010010, 1280, true),
(13, 0b0000001010011, 1344, true),
(13, 0b0000001010100, 1408, true),
(13, 0b0000001010101, 1472, true),
(13, 0b0000001011010, 1536, true),
(13, 0b0000001011011, 1600, true),
(13, 0b0000001100100, 1664, true),
(13, 0b0000001100101, 1728, true),
(11, 0b00000001000, 1792, true),
(11, 0b00000001100, 1856, true),
(11, 0b00000001101, 1920, true),
(12, 0b000000010010, 1984, true),
(12, 0b000000010011, 2048, true),
(12, 0b000000010100, 2112, true),
(12, 0b000000010101, 2176, true),
(12, 0b000000010110, 2240, true),
(12, 0b000000010111, 2304, true),
(12, 0b000000011100, 2368, true),
(12, 0b000000011101, 2432, true),
(12, 0b000000011110, 2496, true),
(12, 0b000000011111, 2560, true),
];
#[cfg(test)]
mod tests {
use super::*;
struct BitWriter {
buf: Vec<u8>,
bit_count: u32,
}
impl BitWriter {
fn new() -> Self {
Self {
buf: Vec::new(),
bit_count: 0,
}
}
fn write(&mut self, code: u32, nbits: u32) {
for i in (0..nbits).rev() {
let bit = ((code >> i) & 1) as u8;
let byte_idx = (self.bit_count / 8) as usize;
if byte_idx >= self.buf.len() {
self.buf.push(0);
}
let bit_in_byte = 7 - (self.bit_count % 8);
self.buf[byte_idx] |= bit << bit_in_byte;
self.bit_count += 1;
}
}
fn align(&mut self) {
let rem = self.bit_count % 8;
if rem != 0 {
self.bit_count += 8 - rem;
}
}
fn finish(mut self) -> Vec<u8> {
self.align();
self.buf
}
}
#[test]
fn mh_decodes_all_white_row() {
let mut bw = BitWriter::new();
bw.write(0b10011, 5);
let bytes = bw.finish();
let got = decode_ccitt(
&bytes,
8,
1,
CcittVariant::ModifiedHuffman,
FillOrder::MsbFirst,
)
.unwrap();
assert_eq!(got, vec![0u8]);
}
#[test]
fn mh_decodes_all_black_row() {
let mut bw = BitWriter::new();
bw.write(0b00110101, 8);
bw.write(0b000101, 6);
let bytes = bw.finish();
let got = decode_ccitt(
&bytes,
8,
1,
CcittVariant::ModifiedHuffman,
FillOrder::MsbFirst,
)
.unwrap();
assert_eq!(got, vec![0xFFu8]);
}
#[test]
fn mh_decodes_alternating_8x1() {
let mut bw = BitWriter::new();
for _ in 0..4 {
bw.write(0b000111, 6); bw.write(0b010, 3); }
let bytes = bw.finish();
let got = decode_ccitt(
&bytes,
8,
1,
CcittVariant::ModifiedHuffman,
FillOrder::MsbFirst,
)
.unwrap();
assert_eq!(got, vec![0x55u8]);
}
#[test]
fn mh_makeup_plus_terminator_white_72() {
let mut bw = BitWriter::new();
bw.write(0b11011, 5); bw.write(0b10011, 5); let bytes = bw.finish();
let got = decode_ccitt(
&bytes,
72,
1,
CcittVariant::ModifiedHuffman,
FillOrder::MsbFirst,
)
.unwrap();
assert_eq!(got, vec![0u8; 9]);
}
#[test]
fn t4_1d_decodes_eol_then_row() {
let mut bw = BitWriter::new();
bw.write(0b000000000001, 12);
bw.write(0b10011, 5); let bytes = bw.finish();
let got = decode_ccitt(
&bytes,
8,
1,
CcittVariant::T4OneD {
eol_byte_aligned: false,
},
FillOrder::MsbFirst,
)
.unwrap();
assert_eq!(got, vec![0u8]);
}
#[test]
fn t4_1d_decodes_two_rows() {
let mut bw = BitWriter::new();
bw.write(0b000000000001, 12); bw.write(0b10011, 5); bw.write(0b000000000001, 12); bw.write(0b00110101, 8); bw.write(0b000101, 6); let bytes = bw.finish();
let got = decode_ccitt(
&bytes,
8,
2,
CcittVariant::T4OneD {
eol_byte_aligned: false,
},
FillOrder::MsbFirst,
)
.unwrap();
assert_eq!(got, vec![0u8, 0xFFu8]);
}
#[test]
fn empty_input_errors_out_cleanly() {
let r = decode_ccitt(
&[],
8,
1,
CcittVariant::ModifiedHuffman,
FillOrder::MsbFirst,
);
assert!(r.is_err());
}
#[test]
fn reverse_bits_u8_matches_spec_examples() {
assert_eq!(reverse_bits_u8(0x01), 0x80);
assert_eq!(reverse_bits_u8(0x80), 0x01);
assert_eq!(reverse_bits_u8(0x12), 0x48);
assert_eq!(reverse_bits_u8(0xFF), 0xFF);
assert_eq!(reverse_bits_u8(0x00), 0x00);
}
#[test]
fn reverse_bits_in_place_round_trips() {
let mut buf = [0xDE, 0xAD, 0xBE, 0xEFu8];
let orig = buf;
reverse_bits_in_place(&mut buf);
reverse_bits_in_place(&mut buf);
assert_eq!(buf, orig);
}
#[test]
fn mh_lsb_first_decodes_same_as_msb_first() {
let mut bw = BitWriter::new();
bw.write(0b00110101, 8); bw.write(0b000101, 6); let msb_bytes = bw.finish();
let lsb_bytes: Vec<u8> = msb_bytes.iter().map(|&b| reverse_bits_u8(b)).collect();
let got_msb = decode_ccitt(
&msb_bytes,
8,
1,
CcittVariant::ModifiedHuffman,
FillOrder::MsbFirst,
)
.unwrap();
let got_lsb = decode_ccitt(
&lsb_bytes,
8,
1,
CcittVariant::ModifiedHuffman,
FillOrder::LsbFirst,
)
.unwrap();
assert_eq!(got_msb, vec![0xFFu8]);
assert_eq!(got_lsb, got_msb);
}
#[test]
fn t4_1d_lsb_first_decodes_same_as_msb_first() {
let mut bw = BitWriter::new();
bw.write(0b000000000001, 12); bw.write(0b10011, 5); bw.write(0b000000000001, 12); bw.write(0b00110101, 8); bw.write(0b000101, 6); let msb_bytes = bw.finish();
let lsb_bytes: Vec<u8> = msb_bytes.iter().map(|&b| reverse_bits_u8(b)).collect();
let got_msb = decode_ccitt(
&msb_bytes,
8,
2,
CcittVariant::T4OneD {
eol_byte_aligned: false,
},
FillOrder::MsbFirst,
)
.unwrap();
let got_lsb = decode_ccitt(
&lsb_bytes,
8,
2,
CcittVariant::T4OneD {
eol_byte_aligned: false,
},
FillOrder::LsbFirst,
)
.unwrap();
assert_eq!(got_msb, vec![0u8, 0xFFu8]);
assert_eq!(got_lsb, got_msb);
}
fn pack_row_msb(pixels: &[u8]) -> Vec<u8> {
let row_bytes = pixels.len().div_ceil(8);
let mut out = vec![0u8; row_bytes];
for (i, &p) in pixels.iter().enumerate() {
if p == 1 {
out[i / 8] |= 1 << (7 - (i % 8));
}
}
out
}
fn unpack_row_msb(buf: &[u8], width: usize) -> Vec<u8> {
let mut out = Vec::with_capacity(width);
for x in 0..width {
let b = (buf[x / 8] >> (7 - (x % 8))) & 1;
out.push(b);
}
out
}
fn ccitt_roundtrip(pixels: &[u8], width: u32, rows: u32, variant: CcittVariant) {
let packed = pack_row_msb(pixels);
let encoded = encode_ccitt(&packed, width, rows, variant, FillOrder::MsbFirst).unwrap();
let decoded = decode_ccitt(&encoded, width, rows, variant, FillOrder::MsbFirst).unwrap();
assert_eq!(packed, decoded, "round-trip mismatch (variant={variant:?})");
let back = unpack_row_msb(&decoded, (width * rows) as usize);
assert_eq!(back, pixels);
}
#[test]
fn encode_mh_all_white_8x1() {
ccitt_roundtrip(&[0u8; 8], 8, 1, CcittVariant::ModifiedHuffman);
}
#[test]
fn encode_mh_all_black_8x1() {
ccitt_roundtrip(&[1u8; 8], 8, 1, CcittVariant::ModifiedHuffman);
}
#[test]
fn encode_mh_alternating_8x1() {
let row: Vec<u8> = (0..8).map(|i| (i % 2) as u8).collect();
ccitt_roundtrip(&row, 8, 1, CcittVariant::ModifiedHuffman);
}
#[test]
fn encode_mh_long_white_run_72x1() {
ccitt_roundtrip(&[0u8; 72], 72, 1, CcittVariant::ModifiedHuffman);
}
#[test]
fn encode_mh_long_black_run_72x1() {
ccitt_roundtrip(&[1u8; 72], 72, 1, CcittVariant::ModifiedHuffman);
}
#[test]
fn encode_mh_multi_row_aligns_between_rows() {
let r0 = vec![0u8; 16];
let r1 = vec![1u8; 16];
let r2: Vec<u8> = (0..16).map(|i| (i % 2) as u8).collect();
let mut all = Vec::new();
all.extend_from_slice(&r0);
all.extend_from_slice(&r1);
all.extend_from_slice(&r2);
ccitt_roundtrip(&all, 16, 3, CcittVariant::ModifiedHuffman);
}
#[test]
fn encode_mh_huge_white_run_5000x1() {
ccitt_roundtrip(&vec![0u8; 5000], 5000, 1, CcittVariant::ModifiedHuffman);
}
#[test]
fn encode_t4_1d_emits_eol_per_row() {
let pixels = vec![0u8; 16]; let packed = pack_row_msb(&pixels);
let encoded = encode_ccitt(
&packed,
8,
2,
CcittVariant::T4OneD {
eol_byte_aligned: false,
},
FillOrder::MsbFirst,
)
.unwrap();
assert_eq!(encoded[0], 0x00, "first byte of T.4 1-D stream");
assert_eq!(encoded[1] & 0xF0, 0x10, "EOL trailing 1 at bit 11");
let decoded = decode_ccitt(
&encoded,
8,
2,
CcittVariant::T4OneD {
eol_byte_aligned: false,
},
FillOrder::MsbFirst,
)
.unwrap();
assert_eq!(decoded, packed);
}
#[test]
fn encode_t4_1d_byte_aligned_eol_ends_on_byte_boundary() {
let r0: Vec<u8> = (0..16).map(|i| (i % 2) as u8).collect();
let r1 = vec![0u8; 16];
let r2 = vec![1u8; 16];
let mut all = Vec::new();
all.extend_from_slice(&r0);
all.extend_from_slice(&r1);
all.extend_from_slice(&r2);
ccitt_roundtrip(
&all,
16,
3,
CcittVariant::T4OneD {
eol_byte_aligned: true,
},
);
}
#[test]
fn encode_ccitt_lsb_first_yields_bit_reversed_stream() {
let pixels = vec![1u8; 16];
let packed = pack_row_msb(&pixels);
let msb_stream = encode_ccitt(
&packed,
16,
1,
CcittVariant::ModifiedHuffman,
FillOrder::MsbFirst,
)
.unwrap();
let lsb_stream = encode_ccitt(
&packed,
16,
1,
CcittVariant::ModifiedHuffman,
FillOrder::LsbFirst,
)
.unwrap();
let lsb_expected: Vec<u8> = msb_stream.iter().map(|&b| b.reverse_bits()).collect();
assert_eq!(lsb_stream, lsb_expected);
let decoded = decode_ccitt(
&lsb_stream,
16,
1,
CcittVariant::ModifiedHuffman,
FillOrder::LsbFirst,
)
.unwrap();
assert_eq!(decoded, packed);
}
#[test]
fn encode_buffer_too_short_errors() {
let r = encode_ccitt(
&[0u8; 5],
24,
2,
CcittVariant::ModifiedHuffman,
FillOrder::MsbFirst,
);
assert!(r.is_err());
}
#[test]
fn encode_t4_2d_solid_white_8x4() {
ccitt_roundtrip(
&[0u8; 32],
8,
4,
CcittVariant::T4TwoD {
eol_byte_aligned: false,
},
);
}
#[test]
fn encode_t4_2d_solid_black_8x4() {
ccitt_roundtrip(
&[1u8; 32],
8,
4,
CcittVariant::T4TwoD {
eol_byte_aligned: false,
},
);
}
#[test]
fn encode_t4_2d_horizontal_pattern() {
let row0: Vec<u8> = (0..8).map(|i| (i % 2) as u8).collect();
let row1: Vec<u8> = vec![1, 1, 1, 1, 1, 1, 1, 1];
let row2: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 0];
let mut all = Vec::new();
all.extend_from_slice(&row0);
all.extend_from_slice(&row1);
all.extend_from_slice(&row2);
ccitt_roundtrip(
&all,
8,
3,
CcittVariant::T4TwoD {
eol_byte_aligned: false,
},
);
}
#[test]
fn encode_t4_2d_pass_mode() {
let row0: Vec<u8> = vec![0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1];
let row1: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1];
let mut all = Vec::new();
all.extend_from_slice(&row0);
all.extend_from_slice(&row1);
ccitt_roundtrip(
&all,
16,
2,
CcittVariant::T4TwoD {
eol_byte_aligned: false,
},
);
}
#[test]
fn encode_t4_2d_byte_aligned_eol() {
let r0: Vec<u8> = (0..16).map(|i| (i % 2) as u8).collect();
let r1 = vec![0u8; 16];
let r2 = vec![1u8; 16];
let mut all = Vec::new();
all.extend_from_slice(&r0);
all.extend_from_slice(&r1);
all.extend_from_slice(&r2);
ccitt_roundtrip(
&all,
16,
3,
CcittVariant::T4TwoD {
eol_byte_aligned: true,
},
);
}
#[test]
fn encode_t6_solid_white_8x4() {
ccitt_roundtrip(&[0u8; 32], 8, 4, CcittVariant::T6);
}
#[test]
fn encode_t6_solid_black_8x4() {
ccitt_roundtrip(&[1u8; 32], 8, 4, CcittVariant::T6);
}
#[test]
fn encode_t6_diagonal_8x8() {
let mut pixels: Vec<u8> = vec![0u8; 64];
for r in 0..8 {
pixels[r * 8 + r] = 1;
}
ccitt_roundtrip(&pixels, 8, 8, CcittVariant::T6);
}
#[test]
fn encode_t6_pass_mode() {
let row0: Vec<u8> = vec![0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1];
let row1: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1];
let mut all = Vec::new();
all.extend_from_slice(&row0);
all.extend_from_slice(&row1);
ccitt_roundtrip(&all, 16, 2, CcittVariant::T6);
}
#[test]
fn encode_t6_long_runs_2624x1() {
let mut pixels: Vec<u8> = vec![0u8; 2624];
for p in pixels.iter_mut().take(2000) {
*p = 1;
}
ccitt_roundtrip(&pixels, 2624, 1, CcittVariant::T6);
}
#[test]
fn encode_t4_2d_lsb_first_matches_msb_after_bit_reverse() {
let row0: Vec<u8> = (0..16).map(|i| (i % 2) as u8).collect();
let row1: Vec<u8> = vec![1u8; 16];
let mut all = Vec::new();
all.extend_from_slice(&row0);
all.extend_from_slice(&row1);
let packed = pack_row_msb(&all);
let variant = CcittVariant::T4TwoD {
eol_byte_aligned: false,
};
let msb_stream = encode_ccitt(&packed, 16, 2, variant, FillOrder::MsbFirst).unwrap();
let lsb_stream = encode_ccitt(&packed, 16, 2, variant, FillOrder::LsbFirst).unwrap();
let lsb_expected: Vec<u8> = msb_stream.iter().map(|&b| b.reverse_bits()).collect();
assert_eq!(lsb_stream, lsb_expected);
let decoded = decode_ccitt(&lsb_stream, 16, 2, variant, FillOrder::LsbFirst).unwrap();
assert_eq!(decoded, packed);
}
fn read_two_d(bits: &[u8]) -> TwoDMode {
let mut br = BitReader::new(bits);
next_two_d_mode(&mut br).expect("decode 2-D mode")
}
#[test]
fn two_d_vertical_0_decodes_one_bit() {
let bw = {
let mut bw = BitWriter::new();
bw.write(0b1, 1);
bw.finish()
};
assert_eq!(read_two_d(&bw), TwoDMode::Vertical(0));
}
#[test]
fn two_d_horizontal_decodes_three_bits() {
let bw = {
let mut bw = BitWriter::new();
bw.write(0b001, 3);
bw.finish()
};
assert_eq!(read_two_d(&bw), TwoDMode::Horizontal);
}
#[test]
fn two_d_vr1_vl1_decode() {
let bw = {
let mut bw = BitWriter::new();
bw.write(0b011, 3);
bw.finish()
};
assert_eq!(read_two_d(&bw), TwoDMode::Vertical(1));
let bw = {
let mut bw = BitWriter::new();
bw.write(0b010, 3);
bw.finish()
};
assert_eq!(read_two_d(&bw), TwoDMode::Vertical(-1));
}
#[test]
fn two_d_pass_decodes_four_bits() {
let bw = {
let mut bw = BitWriter::new();
bw.write(0b0001, 4);
bw.finish()
};
assert_eq!(read_two_d(&bw), TwoDMode::Pass);
}
#[test]
fn two_d_vr2_vl2_vr3_vl3_decode() {
let bw = {
let mut bw = BitWriter::new();
bw.write(0b000011, 6);
bw.finish()
};
assert_eq!(read_two_d(&bw), TwoDMode::Vertical(2));
let bw = {
let mut bw = BitWriter::new();
bw.write(0b000010, 6);
bw.finish()
};
assert_eq!(read_two_d(&bw), TwoDMode::Vertical(-2));
let bw = {
let mut bw = BitWriter::new();
bw.write(0b0000011, 7);
bw.finish()
};
assert_eq!(read_two_d(&bw), TwoDMode::Vertical(3));
let bw = {
let mut bw = BitWriter::new();
bw.write(0b0000010, 7);
bw.finish()
};
assert_eq!(read_two_d(&bw), TwoDMode::Vertical(-3));
}
#[test]
fn two_d_uncompressed_extension_decodes() {
let bw = {
let mut bw = BitWriter::new();
bw.write(0b0000001111, 10);
bw.finish()
};
assert_eq!(read_two_d(&bw), TwoDMode::Uncompressed);
}
#[test]
fn t6_all_white_row_decodes_via_v0() {
let mut bw = BitWriter::new();
bw.write(0b1, 1); let bytes = bw.finish();
let got = decode_ccitt(&bytes, 8, 1, CcittVariant::T6, FillOrder::MsbFirst).unwrap();
assert_eq!(got, vec![0u8]); }
#[test]
fn t6_all_black_row_after_white_uses_horizontal() {
let mut bw = BitWriter::new();
bw.write(0b1, 1); bw.write(0b001, 3); bw.write(0b00110101, 8); bw.write(0b000101, 6); let bytes = bw.finish();
let got = decode_ccitt(&bytes, 8, 2, CcittVariant::T6, FillOrder::MsbFirst).unwrap();
assert_eq!(got, vec![0u8, 0xFFu8]);
}
#[test]
fn t6_uncompressed_mode_spec_exact_pattern_byte() {
let mut bw = BitWriter::new();
bw.write(0b0000001111, 10); bw.write(0b01, 2); bw.write(0b00001, 5); bw.write(0b0000001, 7); bw.write(0b0, 1); bw.write(0b1, 1); let bytes = bw.finish();
let got = decode_ccitt(&bytes, 8, 1, CcittVariant::T6, FillOrder::MsbFirst).unwrap();
assert_eq!(got, vec![0x42u8]);
}
#[test]
fn t6_uncompressed_make_up_long_white_run() {
let mut bw = BitWriter::new();
bw.write(0b0000001111, 10);
bw.write(0b000001, 6); bw.write(0b000001, 6); bw.write(0b0001, 4); bw.write(0b1, 1); bw.write(0b1, 1); bw.write(0b0000001, 7); bw.write(0b0, 1); let bytes = bw.finish();
let got = decode_ccitt(&bytes, 16, 1, CcittVariant::T6, FillOrder::MsbFirst).unwrap();
assert_eq!(got, vec![0x00u8, 0x07u8]);
}
#[test]
fn t6_uncompressed_exit_trailing_white_bits() {
let mut bw = BitWriter::new();
bw.write(0b0000001111, 10);
bw.write(0b1, 1); bw.write(0b000001, 6); bw.write(0b000000001, 9); bw.write(0b0, 1); let bytes = bw.finish();
let got = decode_ccitt(&bytes, 8, 1, CcittVariant::T6, FillOrder::MsbFirst).unwrap();
assert_eq!(got, vec![0x80u8]);
}
fn uncompressed_row_roundtrip(pixels: &[u8]) {
let width = pixels.len();
let packed = pack_row_msb(pixels);
let mut bw = super::BitWriter::new();
encode_uncompressed_segment(&mut bw, &packed, 0, width, false);
let bytes = bw.finish();
let got = decode_ccitt(
&bytes,
width as u32,
1,
CcittVariant::T6,
FillOrder::MsbFirst,
)
.unwrap();
let unpacked = unpack_row_msb(&got, width);
assert_eq!(unpacked, pixels, "uncompressed roundtrip mismatch");
}
#[test]
fn uncompressed_roundtrip_solid_white() {
uncompressed_row_roundtrip(&[0u8; 24]);
}
#[test]
fn uncompressed_roundtrip_solid_black() {
uncompressed_row_roundtrip(&[1u8; 24]);
}
#[test]
fn uncompressed_roundtrip_alternating() {
let row: Vec<u8> = (0..16).map(|i| (i % 2) as u8).collect();
uncompressed_row_roundtrip(&row);
}
#[test]
fn uncompressed_roundtrip_runs_of_all_residual_lengths() {
let mut row: Vec<u8> = Vec::new();
for &(w, has_black) in &[
(3usize, true),
(0, true),
(1, true),
(2, true),
(4, true),
(7, true),
(5, false),
] {
row.extend(std::iter::repeat_n(0u8, w));
if has_black {
row.push(1);
}
}
uncompressed_row_roundtrip(&row);
}
#[test]
fn uncompressed_segment_resumes_two_d_coding() {
let mut bw = BitWriter::new();
bw.write(0b1, 1); bw.write(0b0000001111, 10); bw.write(0b00001, 5); bw.write(0b01, 2); bw.write(0b0000001, 7); bw.write(0b0, 1); bw.write(0b1, 1); let bytes = bw.finish();
let got = decode_ccitt(&bytes, 8, 2, CcittVariant::T6, FillOrder::MsbFirst).unwrap();
assert_eq!(got, vec![0x00u8, 0x0Au8]);
}
#[test]
fn first_change_after_handles_start_of_row() {
let ref_line = [0u8]; assert_eq!(first_change_after(&ref_line, 0, false, 8), 8);
assert_eq!(first_change_after(&ref_line, 0, true, 8), 8);
}
#[test]
fn first_change_after_finds_mid_row_transition() {
let ref_line = [0b00001111u8];
assert_eq!(first_change_after(&ref_line, 0, false, 8), 4);
assert_eq!(first_change_after(&ref_line, 3, false, 8), 4);
assert_eq!(first_change_after(&ref_line, 4, false, 8), 8);
assert_eq!(first_change_after(&ref_line, 4, true, 8), 8);
}
}