use tiff::decoder::{BufferLayoutPreference, Decoder, DecodingBuffer, DecodingResult};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let Some(image) = std::env::args_os().nth(1) else {
eprintln!("Usage: decode FILE");
return Ok(());
};
let file = std::fs::File::open(image)?;
let io = std::io::BufReader::new(file);
let mut reader = Decoder::new(io)?;
let mut data = DecodingResult::I8(vec![]);
for i in 0u32.. {
let colortype = reader.colortype()?;
let dimensions = reader.dimensions()?;
let layout = reader.read_image_to_buffer(&mut data)?;
debug_planes(i, &mut data, &layout, &colortype, dimensions);
if !reader.more_images() {
break;
}
reader.next_image()?
}
Ok(())
}
fn debug_planes(
index: u32,
data: &mut DecodingResult,
layout: &BufferLayoutPreference,
colortype: &tiff::ColorType,
(width, height): (u32, u32),
) {
let (depth, mut tupltype) = match colortype {
tiff::ColorType::Gray(_) => (1, "GRAYSCALE"),
tiff::ColorType::RGB(_) => (3, "RGB"),
tiff::ColorType::RGBA(_) => (4, "RGB_ALPHA"),
tiff::ColorType::Palette(_) => (1, "PALETTE"),
tiff::ColorType::CMYK(_) => (4, "CMYK"),
tiff::ColorType::Multiband { num_samples: 2, .. } => (2, "GRAYSCALE_ALPHA"),
_ => {
eprintln!("Unsupported color type for PAM output: {:?}", colortype);
return;
}
};
let (swidth, sheight) = if layout.planes > 1 {
(
u64::from(width) * u64::from(colortype.num_samples()),
u64::from(height),
)
} else {
(
u64::from(width),
u64::from(height) * u64::from(colortype.num_samples()),
)
};
let mut fallback_buffer;
let maxval = match data.as_buffer(0) {
DecodingBuffer::U8(buf) => match colortype.bit_depth() {
8 => u64::from(u8::MAX),
4 => {
fallback_buffer = vec![0; (swidth * sheight) as usize];
expand_4bit(buf, swidth, &mut fallback_buffer);
*data = DecodingResult::U8(fallback_buffer);
u64::from(u8::MAX)
}
2 => {
fallback_buffer = vec![0; (swidth * sheight) as usize];
expand_2bit(buf, swidth, &mut fallback_buffer);
*data = DecodingResult::U8(fallback_buffer);
u64::from(u8::MAX)
}
1 => {
fallback_buffer = vec![0; (swidth * sheight) as usize];
expand_1bit(buf, swidth, &mut fallback_buffer);
*data = DecodingResult::U8(fallback_buffer);
u64::from(u8::MAX)
}
_ => {
eprintln!(
"Unsupported bit depth for U8 buffer: {}",
colortype.bit_depth()
);
return;
}
},
DecodingBuffer::U16(_) => u64::from(u16::MAX),
DecodingBuffer::U32(_) => u64::from(u32::MAX),
DecodingBuffer::U64(_) => u64::MAX,
DecodingBuffer::I8(items) => {
items.iter_mut().for_each(|v| *v = 0.max(*v));
u64::from(i8::MAX as u8)
}
DecodingBuffer::I16(items) => {
items.iter_mut().for_each(|v| *v = 0.max(*v));
u64::from(i16::MAX as u16)
}
DecodingBuffer::I32(items) => {
items.iter_mut().for_each(|v| *v = 0.max(*v));
u64::from(i32::MAX as u32)
}
DecodingBuffer::I64(items) => {
items.iter_mut().for_each(|v| *v = 0.max(*v));
i64::MAX as u64
}
DecodingBuffer::F16(_) | DecodingBuffer::F32(_) | DecodingBuffer::F64(_) => return,
};
let byte_len = if layout.planes > 1 {
tupltype = "GRAYSCALE";
layout.plane_stride.unwrap().get()
} else {
data.as_buffer(0).byte_len()
};
let header = format!("P7\nWIDTH {width}\nHEIGHT {height}\nDEPTH {depth}\nMAXVAL {maxval}\nTUPLTYPE {tupltype}\nENDHDR\n");
for (plane, plane_data) in data
.as_buffer(0)
.as_bytes_mut()
.chunks_exact(byte_len)
.enumerate()
{
let filename = format!("image_{index:03}_plane_{plane:03}.pam");
let mut file = std::fs::File::create(filename).expect("Failed to create output file");
use std::io::Write;
file.write_all(header.as_bytes())
.expect("Failed to write PAM header");
file.write_all(plane_data)
.expect("Failed to write PAM data");
}
}
#[inline(always)]
fn bits_extract<const SCALE: u8>(val: u8, i: u8) -> u8 {
(val << (i * SCALE)) >> (8 - SCALE)
}
fn expand_1bit(from: &[u8], samples_per_padded: u64, into: &mut [u8]) {
let stride = samples_per_padded.div_ceil(8);
let from_row = from.chunks_exact(stride as usize);
let into_row = into.chunks_exact_mut(samples_per_padded as usize);
for (from_row, into_row) in from_row.zip(into_row) {
for (into, &from) in into_row.chunks_mut(8).zip(from_row) {
let data =
core::array::from_fn::<u8, 8, _>(|i| 0xff * bits_extract::<1>(from, i as u8));
into.copy_from_slice(&data[..into.len()]);
}
}
}
fn expand_2bit(from: &[u8], samples_per_padded: u64, into: &mut [u8]) {
let stride = samples_per_padded.div_ceil(4);
let from_row = from.chunks_exact(stride as usize);
let into_row = into.chunks_exact_mut(samples_per_padded as usize);
for (from_row, into_row) in from_row.zip(into_row) {
for (into, &from) in into_row.chunks_mut(4).zip(from_row) {
let data =
core::array::from_fn::<u8, 4, _>(|i| 0xaa * bits_extract::<2>(from, i as u8));
into.copy_from_slice(&data[..into.len()]);
}
}
}
fn expand_4bit(from: &[u8], samples_per_padded: u64, into: &mut [u8]) {
let stride = samples_per_padded.div_ceil(2);
let from_row = from.chunks_exact(stride as usize);
let into_row = into.chunks_exact_mut(samples_per_padded as usize);
for (from_row, into_row) in from_row.zip(into_row) {
for (into, &from) in into_row.chunks_mut(2).zip(from_row) {
let data =
core::array::from_fn::<u8, 2, _>(|i| 0x88 * bits_extract::<4>(from, i as u8));
into.copy_from_slice(&data[..into.len()]);
}
}
}