use oxideav_tiff::decode_tiff;
fn entry_short(tag: u16, value: u16) -> [u8; 12] {
let mut e = [0u8; 12];
e[0..2].copy_from_slice(&tag.to_le_bytes());
e[2..4].copy_from_slice(&3u16.to_le_bytes()); e[4..8].copy_from_slice(&1u32.to_le_bytes()); e[8..10].copy_from_slice(&value.to_le_bytes());
e
}
fn entry_long(tag: u16, value: u32) -> [u8; 12] {
let mut e = [0u8; 12];
e[0..2].copy_from_slice(&tag.to_le_bytes());
e[2..4].copy_from_slice(&4u16.to_le_bytes()); e[4..8].copy_from_slice(&1u32.to_le_bytes()); e[8..12].copy_from_slice(&value.to_le_bytes());
e
}
fn build_gray_row(
w: u32,
bits_per_sample: u16,
photometric: u16,
sample_format: u16,
strip: &[u8],
) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(b"II");
out.extend_from_slice(&42u16.to_le_bytes());
let ifd_off_pos = out.len();
out.extend_from_slice(&0u32.to_le_bytes());
let strip_off = out.len() as u32;
out.extend_from_slice(strip);
if out.len() % 2 != 0 {
out.push(0); }
let ifd_off = out.len() as u32;
out[ifd_off_pos..ifd_off_pos + 4].copy_from_slice(&ifd_off.to_le_bytes());
out.extend_from_slice(&10u16.to_le_bytes());
out.extend_from_slice(&entry_short(256, w as u16)); out.extend_from_slice(&entry_short(257, 1)); out.extend_from_slice(&entry_short(258, bits_per_sample)); out.extend_from_slice(&entry_short(259, 1)); out.extend_from_slice(&entry_short(262, photometric)); out.extend_from_slice(&entry_long(273, strip_off)); out.extend_from_slice(&entry_short(277, 1)); out.extend_from_slice(&entry_short(278, 1)); out.extend_from_slice(&entry_long(279, strip.len() as u32)); out.extend_from_slice(&entry_short(339, sample_format));
out.extend_from_slice(&0u32.to_le_bytes());
out
}
#[test]
fn signed_8bit_blackiszero_full_range_offset_binary() {
let strip = [0x80u8, 0xFF, 0x00, 0x01, 0x7F];
let bytes = build_gray_row(5, 8, 1, 2, &strip);
let d = decode_tiff(&bytes).expect("signed 8-bit grayscale must decode");
assert_eq!((d.width, d.height), (5, 1));
assert_eq!(d.frame.planes[0].data, vec![0x00u8, 0x7F, 0x80, 0x81, 0xFF]);
}
#[test]
fn signed_8bit_whiteiszero_composes_with_polarity() {
let strip = [0x80u8, 0x00, 0x7F];
let bytes = build_gray_row(3, 8, 0, 2, &strip);
let d = decode_tiff(&bytes).expect("signed 8-bit WhiteIsZero must decode");
assert_eq!(d.frame.planes[0].data, vec![0xFFu8, 0x7F, 0x00]);
}
#[test]
fn signed_16bit_blackiszero_offset_binary_le() {
let words: [i16; 3] = [-32768, 0, 32767];
let mut strip = Vec::new();
for w in words {
strip.extend_from_slice(&(w as u16).to_le_bytes());
}
let bytes = build_gray_row(3, 16, 1, 2, &strip);
let d = decode_tiff(&bytes).expect("signed 16-bit grayscale must decode");
let mut expect = Vec::new();
for v in [0x0000u16, 0x8000, 0xFFFF] {
expect.extend_from_slice(&v.to_le_bytes());
}
assert_eq!(d.frame.planes[0].data, expect);
}
#[test]
fn signed_16bit_big_endian_on_disk() {
let mut out = Vec::new();
out.extend_from_slice(b"MM");
out.extend_from_slice(&42u16.to_be_bytes());
let ifd_off_pos = out.len();
out.extend_from_slice(&0u32.to_be_bytes());
let strip_off = out.len() as u32;
out.extend_from_slice(&(-32768i16 as u16).to_be_bytes());
out.extend_from_slice(&(32767i16 as u16).to_be_bytes());
let ifd_off = out.len() as u32;
out[ifd_off_pos..ifd_off_pos + 4].copy_from_slice(&ifd_off.to_be_bytes());
let entry_short_be = |tag: u16, value: u16| -> [u8; 12] {
let mut e = [0u8; 12];
e[0..2].copy_from_slice(&tag.to_be_bytes());
e[2..4].copy_from_slice(&3u16.to_be_bytes());
e[4..8].copy_from_slice(&1u32.to_be_bytes());
e[8..10].copy_from_slice(&value.to_be_bytes());
e
};
let entry_long_be = |tag: u16, value: u32| -> [u8; 12] {
let mut e = [0u8; 12];
e[0..2].copy_from_slice(&tag.to_be_bytes());
e[2..4].copy_from_slice(&4u16.to_be_bytes());
e[4..8].copy_from_slice(&1u32.to_be_bytes());
e[8..12].copy_from_slice(&value.to_be_bytes());
e
};
out.extend_from_slice(&10u16.to_be_bytes());
out.extend_from_slice(&entry_short_be(256, 2)); out.extend_from_slice(&entry_short_be(257, 1)); out.extend_from_slice(&entry_short_be(258, 16)); out.extend_from_slice(&entry_short_be(259, 1)); out.extend_from_slice(&entry_short_be(262, 1)); out.extend_from_slice(&entry_long_be(273, strip_off)); out.extend_from_slice(&entry_short_be(277, 1)); out.extend_from_slice(&entry_short_be(278, 1)); out.extend_from_slice(&entry_long_be(279, 4)); out.extend_from_slice(&entry_short_be(339, 2)); out.extend_from_slice(&0u32.to_be_bytes());
let d = decode_tiff(&out).expect("big-endian signed 16-bit must decode");
let mut expect = Vec::new();
for v in [0x0000u16, 0xFFFF] {
expect.extend_from_slice(&v.to_le_bytes());
}
assert_eq!(d.frame.planes[0].data, expect);
}
fn expect_err_containing(bytes: &[u8], needle: &str) {
match decode_tiff(bytes) {
Ok(_) => panic!("expected an error containing {needle:?}, got Ok(..)"),
Err(e) => {
let msg = format!("{e}");
assert!(
msg.contains(needle),
"expected error to contain {needle:?}, got: {msg}"
);
}
}
}
#[test]
fn signed_rgb_is_rejected() {
let mut out = Vec::new();
out.extend_from_slice(b"II");
out.extend_from_slice(&42u16.to_le_bytes());
out.extend_from_slice(&0u32.to_le_bytes());
let strip_off = out.len() as u32; out.extend_from_slice(&[0x00u8, 0x10, 0x20]); out.push(0); let bps_off = out.len() as u32;
out.extend_from_slice(&8u16.to_le_bytes());
out.extend_from_slice(&8u16.to_le_bytes());
out.extend_from_slice(&8u16.to_le_bytes());
let ifd_off = out.len() as u32;
out[4..8].copy_from_slice(&ifd_off.to_le_bytes());
let entry_short_arr = |tag: u16, count: u32, off: u32| -> [u8; 12] {
let mut e = [0u8; 12];
e[0..2].copy_from_slice(&tag.to_le_bytes());
e[2..4].copy_from_slice(&3u16.to_le_bytes());
e[4..8].copy_from_slice(&count.to_le_bytes());
e[8..12].copy_from_slice(&off.to_le_bytes());
e
};
out.extend_from_slice(&10u16.to_le_bytes());
out.extend_from_slice(&entry_short(256, 1)); out.extend_from_slice(&entry_short(257, 1)); out.extend_from_slice(&entry_short_arr(258, 3, bps_off)); out.extend_from_slice(&entry_short(259, 1)); out.extend_from_slice(&entry_short(262, 2)); out.extend_from_slice(&entry_long(273, strip_off)); out.extend_from_slice(&entry_short(277, 3)); out.extend_from_slice(&entry_short(278, 1)); out.extend_from_slice(&entry_long(279, 3)); out.extend_from_slice(&entry_short(339, 2)); out.extend_from_slice(&0u32.to_le_bytes());
expect_err_containing(&out, "SampleFormat=2");
}
#[test]
fn signed_4bit_is_rejected() {
let strip = [0x12u8]; let bytes = build_gray_row(2, 4, 1, 2, &strip);
expect_err_containing(&bytes, "SampleFormat=2");
}
#[test]
fn signed_zero_maps_to_midpoint() {
let bytes8 = build_gray_row(1, 8, 1, 2, &[0x00u8]);
let d8 = decode_tiff(&bytes8).expect("8-bit signed zero must decode");
assert_eq!(d8.frame.planes[0].data, vec![0x80u8]);
let bytes16 = build_gray_row(1, 16, 1, 2, &0i16.to_le_bytes());
let d16 = decode_tiff(&bytes16).expect("16-bit signed zero must decode");
assert_eq!(d16.frame.planes[0].data, 0x8000u16.to_le_bytes());
}