use crate::decoders::CcittParams;
use crate::error::{Error, Result};
pub fn decompress_ccitt(data: &[u8], params: &CcittParams) -> Result<Vec<u8>> {
if params.columns == 0 {
return Err(Error::Decode("CCITT decompression requires /Columns parameter".to_string()));
}
let width = params.columns as u16;
let height_opt = params.rows.map(|h| h as u16);
log::debug!(
"CCITT decompression: {} bytes, {}x{} pixels, K={}, BlackIs1={}",
data.len(),
params.columns,
params.rows.unwrap_or(0),
params.k,
params.black_is_1
);
if params.is_group_3() {
log::debug!("CCITT Group 3 decompression requested (K={})", params.k);
} else {
log::debug!("CCITT Group 4 decompression requested");
}
match decompress_with_fax(data, width, height_opt, params) {
Ok(mut output) => {
let rows = output.len() / (width as usize).div_ceil(8);
log::debug!(
"CCITT decompressed: {} bytes -> {} bytes ({} rows)",
data.len(),
output.len(),
rows
);
if params.black_is_1 {
invert_bilevel_pixels(&mut output);
}
log::trace!("CCITT decompression successful!");
Ok(output)
},
Err(e) => {
log::warn!(
"CCITT decompression failed: {}x{} pixels, {} bytes: {}",
params.columns,
params.rows.unwrap_or(0),
data.len(),
e
);
log::trace!(
"Check /DecodeParms: /EndOfLine={}, /EncodedByteAlign={}, /EndOfBlock={}",
params.end_of_line,
params.encoded_byte_align,
params.end_of_block
);
let expected_bytes = height_opt.unwrap_or(1) as usize * (width as usize).div_ceil(8);
log::trace!("Returning {} bytes of white pixels as fallback", expected_bytes);
Ok(vec![0; expected_bytes])
},
}
}
fn decompress_with_fax(
data: &[u8],
width: u16,
height: Option<u16>,
params: &CcittParams,
) -> Result<Vec<u8>> {
let width_usize = width as usize;
log::debug!(
"Attempting CCITT decompression with fax crate: width={}, height={:?}, data_len={}, K={}",
width,
height,
data.len(),
params.k
);
match try_decode_with_fax(data, width_usize, height, params) {
Ok(output) if !output.is_empty() => {
return Ok(output);
},
Ok(_empty) => {
log::debug!("First attempt returned no data, trying with leading zeros stripped");
},
Err(e) => {
log::debug!("First attempt failed: {}, trying with leading zeros stripped", e);
},
}
let trimmed_data = data
.iter()
.skip_while(|b| **b == 0)
.copied()
.collect::<Vec<_>>();
if trimmed_data.len() < data.len() && !trimmed_data.is_empty() {
log::debug!(
"Stripped {} leading zero bytes ({} -> {}), attempting decompression",
data.len() - trimmed_data.len(),
data.len(),
trimmed_data.len()
);
log::debug!(
"Data after stripping zeros, first 32 bytes: {}",
trimmed_data
.iter()
.take(32)
.map(|b| format!("{:02x}", b))
.collect::<Vec<_>>()
.join(" ")
);
match try_decode_with_fax(&trimmed_data, width_usize, height, params) {
Ok(output) if !output.is_empty() => {
log::trace!("Successfully decompressed after stripping leading zeros!");
return Ok(output);
},
Ok(_) => {
log::debug!("Strip attempt also returned no data");
},
Err(e) => {
log::debug!("Strip attempt also failed: {}", e);
},
}
}
Err(Error::Decode(
"CCITT decompression failed: fax decoder returned no output".to_string(),
))
}
fn try_decode_with_fax(
data: &[u8],
width: usize,
height: Option<u16>,
params: &CcittParams,
) -> Result<Vec<u8>> {
use fax::decoder;
let mut output_rows = Vec::new();
let bytes_per_row = width.div_ceil(8);
let bytes_iter = data.iter().copied();
let success = if params.is_group_4() {
log::debug!("Using Group 4 (T.6) decoder");
decoder::decode_g4(bytes_iter, width as u16, height, |transitions: &[u16]| {
let row_bytes = transitions_to_bytes(transitions, width);
output_rows.push(row_bytes);
})
} else {
log::debug!("Using Group 3 (T.4) decoder");
decoder::decode_g3(bytes_iter, |transitions: &[u16]| {
let row_bytes = transitions_to_bytes(transitions, width);
output_rows.push(row_bytes);
})
};
if success.is_some() && !output_rows.is_empty() {
let output = output_rows.into_iter().flatten().collect::<Vec<u8>>();
log::debug!(
"CCITT decompression successful: {} bytes input -> {} bytes output ({} rows)",
data.len(),
output.len(),
output.len() / bytes_per_row
);
Ok(output)
} else if success.is_some() {
log::debug!("CCITT decoder returned success but no rows produced");
Ok(Vec::new())
} else {
log::warn!("CCITT fax decoder returned None");
Err(Error::Decode("CCITT fax decoder failed".to_string()))
}
}
fn transitions_to_bytes(transitions: &[u16], width: usize) -> Vec<u8> {
let bytes_per_row = width.div_ceil(8);
let mut row_bytes = vec![0u8; bytes_per_row];
let mut is_black = false; let mut start_pos = 0u16;
for &transition_pos in transitions {
let transition_pos = transition_pos as usize;
if is_black {
for pixel_idx in start_pos as usize..transition_pos.min(width) {
let byte_idx = pixel_idx / 8;
let bit_idx = 7 - (pixel_idx % 8);
row_bytes[byte_idx] |= 1 << bit_idx;
}
}
is_black = !is_black;
start_pos = transition_pos as u16;
}
if is_black && (start_pos as usize) < width {
for pixel_idx in (start_pos as usize)..width {
let byte_idx = pixel_idx / 8;
let bit_idx = 7 - (pixel_idx % 8);
row_bytes[byte_idx] |= 1 << bit_idx;
}
}
row_bytes
}
#[deprecated(
since = "0.1.5",
note = "Use decompress_ccitt with CcittParams instead"
)]
pub fn decompress_ccitt_group4(data: &[u8], width: u32, height: u32) -> Result<Vec<u8>> {
let params = CcittParams {
columns: width,
rows: Some(height),
..Default::default()
};
decompress_ccitt(data, ¶ms)
}
fn invert_bilevel_pixels(data: &mut [u8]) {
for byte in data.iter_mut() {
*byte = !*byte;
}
}
pub fn bilevel_to_grayscale(bilevel_data: &[u8], width: u32, height: u32) -> Vec<u8> {
let width = width as usize;
let height = height as usize;
let mut grayscale = Vec::with_capacity(width * height);
for row_idx in 0..height {
let row_start = row_idx * width.div_ceil(8);
for col_idx in 0..width {
let byte_idx = row_start + (col_idx / 8);
if byte_idx < bilevel_data.len() {
let bit_pos = 7 - (col_idx % 8);
let bit = (bilevel_data[byte_idx] >> bit_pos) & 1;
grayscale.push(if bit == 0 { 0xFF } else { 0x00 });
} else {
grayscale.push(0xFF);
}
}
}
grayscale
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bilevel_to_grayscale() {
let bilevel = vec![0b10000001];
let grayscale = bilevel_to_grayscale(&bilevel, 8, 1);
assert_eq!(grayscale.len(), 8);
assert_eq!(grayscale[0], 0x00, "Pixel 0 should be black");
assert_eq!(grayscale[1], 0xFF, "Pixel 1 should be white");
assert_eq!(grayscale[7], 0x00, "Pixel 7 should be black");
}
#[test]
fn test_bilevel_to_grayscale_padding() {
let bilevel = vec![0b10000001];
let grayscale = bilevel_to_grayscale(&bilevel, 5, 1);
assert_eq!(grayscale.len(), 5);
assert_eq!(grayscale[0], 0x00); assert_eq!(grayscale[1], 0xFF); assert_eq!(grayscale[4], 0xFF); }
#[test]
fn test_transitions_to_bytes() {
let transitions = vec![2, 5, 7];
let row = transitions_to_bytes(&transitions, 8);
assert_eq!(row.len(), 1);
assert_eq!(row[0], 0b00111001);
}
}