use crate::{ByteOrder, Ifd, TiffFile, Variant};
fn even(n: usize) -> usize {
n + (n & 1)
}
fn ifd_size(ifd: &Ifd, variant: Variant) -> usize {
variant.count_size() + ifd.fields().len() * variant.entry_size() + variant.offset_size()
}
fn put_offset(out: &mut [u8], pos: usize, v: u64, order: ByteOrder, variant: Variant) {
match variant {
Variant::Classic => out[pos..pos + 4].copy_from_slice(&order.pack_u32(v as u32)),
#[cfg(feature = "bigtiff")]
Variant::Big => out[pos..pos + 8].copy_from_slice(&order.pack_u64(v)),
}
}
#[must_use]
pub fn write(file: &TiffFile) -> Vec<u8> {
let order = file.order;
let variant = file.variant;
let entry_size = variant.entry_size();
let offset_size = variant.offset_size();
let inline = variant.inline_threshold();
let mut cursor = even(variant.header_size());
let mut ifd_offsets: Vec<u64> = Vec::with_capacity(file.ifds.len());
for ifd in &file.ifds {
ifd_offsets.push(cursor as u64);
cursor = even(cursor + ifd_size(ifd, variant));
}
let mut value_offsets: Vec<Vec<u64>> = Vec::with_capacity(file.ifds.len());
let mut pool: Vec<(usize, Vec<u8>)> = Vec::new();
for ifd in &file.ifds {
let mut offs = Vec::with_capacity(ifd.fields().len());
for field in ifd.fields() {
let bytes = field.value.encode(order);
if bytes.len() <= inline {
offs.push(0);
} else {
cursor = even(cursor);
offs.push(cursor as u64);
pool.push((cursor, bytes.clone()));
cursor += bytes.len();
}
}
value_offsets.push(offs);
}
let mut out = vec![0u8; cursor];
out[0..2].copy_from_slice(match order {
ByteOrder::LittleEndian => b"II",
ByteOrder::BigEndian => b"MM",
});
out[2..4].copy_from_slice(&order.pack_u16(variant.magic()));
let first = ifd_offsets.first().copied().unwrap_or(0);
match variant {
Variant::Classic => out[4..8].copy_from_slice(&order.pack_u32(first as u32)),
#[cfg(feature = "bigtiff")]
Variant::Big => {
out[4..6].copy_from_slice(&order.pack_u16(8));
out[8..16].copy_from_slice(&order.pack_u64(first));
}
}
for (idx, ifd) in file.ifds.iter().enumerate() {
let mut pos = ifd_offsets[idx] as usize;
let n = ifd.fields().len();
match variant {
Variant::Classic => out[pos..pos + 2].copy_from_slice(&order.pack_u16(n as u16)),
#[cfg(feature = "bigtiff")]
Variant::Big => out[pos..pos + 8].copy_from_slice(&order.pack_u64(n as u64)),
}
pos += variant.count_size();
for (field, &voff) in ifd.fields().iter().zip(&value_offsets[idx]) {
let bytes = field.value.encode(order);
out[pos..pos + 2].copy_from_slice(&order.pack_u16(field.tag));
out[pos + 2..pos + 4].copy_from_slice(&order.pack_u16(field.value.field_type().code()));
put_offset(
&mut out,
pos + 4,
field.value.count() as u64,
order,
variant,
);
let value_pos = pos + 4 + offset_size;
if bytes.len() <= inline {
out[value_pos..value_pos + bytes.len()].copy_from_slice(&bytes);
} else {
put_offset(&mut out, value_pos, voff, order, variant);
}
pos += entry_size;
}
let next = file.ifds.get(idx + 1).map_or(0, |_| ifd_offsets[idx + 1]);
put_offset(&mut out, pos, next, order, variant);
}
for (offset, bytes) in pool {
out[offset..offset + bytes.len()].copy_from_slice(&bytes);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Value, read, read_header};
fn sample_ifd() -> Ifd {
let mut ifd = Ifd::new();
ifd.set(256, Value::Short(vec![640]));
ifd.set(257, Value::Long(vec![480]));
ifd.set(258, Value::Short(vec![8, 8, 8])); ifd.set(282, Value::Rational(vec![(300, 1)])); ifd.set(72, Value::Ascii("gamut-tiff".to_owned())); ifd
}
fn roundtrip(order: ByteOrder, variant: Variant) {
let file = TiffFile {
order,
variant,
ifds: vec![sample_ifd()],
};
let bytes = write(&file);
let parsed = read(&bytes).expect("read back");
assert_eq!(parsed, file);
}
fn multi_ifd_roundtrip(variant: Variant) {
let mut second = Ifd::new();
second.set(256, Value::Short(vec![1]));
second.set(257, Value::Short(vec![1]));
let file = TiffFile {
order: ByteOrder::LittleEndian,
variant,
ifds: vec![sample_ifd(), second],
};
let bytes = write(&file);
let parsed = read(&bytes).expect("read back");
assert_eq!(parsed.ifds.len(), 2);
assert_eq!(parsed, file);
}
#[test]
fn classic_single_ifd_roundtrips_both_orders() {
roundtrip(ByteOrder::LittleEndian, Variant::Classic);
roundtrip(ByteOrder::BigEndian, Variant::Classic);
}
#[test]
fn classic_multi_ifd_chain_roundtrips() {
multi_ifd_roundtrip(Variant::Classic);
}
#[test]
fn value_offsets_are_even() {
let bytes = write(&TiffFile {
order: ByteOrder::LittleEndian,
variant: Variant::Classic,
ifds: vec![sample_ifd()],
});
assert_eq!(&bytes[0..2], b"II");
assert_eq!(read_header(&bytes).expect("header").2, 8);
}
#[cfg(feature = "bigtiff")]
#[test]
fn bigtiff_roundtrips_and_inline_threshold() {
roundtrip(ByteOrder::LittleEndian, Variant::Big);
roundtrip(ByteOrder::BigEndian, Variant::Big);
multi_ifd_roundtrip(Variant::Big);
let bytes = write(&TiffFile {
order: ByteOrder::LittleEndian,
variant: Variant::Big,
ifds: vec![sample_ifd()],
});
assert_eq!(&bytes[0..2], b"II");
let (order, variant, first) = read_header(&bytes).expect("header");
assert_eq!(order, ByteOrder::LittleEndian);
assert_eq!(variant, Variant::Big);
assert_eq!(bytes[2], 0x2b); assert_eq!(first, 16); let parsed = read(&bytes).expect("read back");
assert_eq!(parsed.variant, Variant::Big);
assert_eq!(
parsed.ifds[0].get(282),
Some(&Value::Rational(vec![(300, 1)]))
);
}
}