use std::io::{Seek, SeekFrom, Write};
use tiff_core::{ByteOrder, Tag, TagValue};
use crate::error::Result;
pub fn write_header<W: Write + Seek>(
sink: &mut W,
byte_order: ByteOrder,
is_bigtiff: bool,
) -> Result<u64> {
let pos = sink.stream_position()?;
sink.write_all(&byte_order.magic())?;
if is_bigtiff {
sink.write_all(&byte_order.write_u16(43))?;
sink.write_all(&byte_order.write_u16(8))?; sink.write_all(&byte_order.write_u16(0))?; sink.write_all(&byte_order.write_u64(0))?; } else {
sink.write_all(&byte_order.write_u16(42))?;
sink.write_all(&byte_order.write_u32(0))?; }
Ok(pos)
}
pub fn patch_first_ifd<W: Write + Seek>(
sink: &mut W,
header_offset: u64,
byte_order: ByteOrder,
is_bigtiff: bool,
ifd_offset: u64,
) -> Result<()> {
if is_bigtiff {
sink.seek(SeekFrom::Start(header_offset + 8))?;
sink.write_all(&byte_order.write_u64(ifd_offset))?;
} else {
sink.seek(SeekFrom::Start(header_offset + 4))?;
sink.write_all(&byte_order.write_u32(ifd_offset as u32))?;
}
Ok(())
}
#[derive(Debug)]
pub struct IfdWriteResult {
pub ifd_offset: u64,
pub next_ifd_pointer_offset: u64,
pub offsets_tag_data_offset: Option<u64>,
pub byte_counts_tag_data_offset: Option<u64>,
pub is_bigtiff: bool,
}
pub fn write_ifd<W: Write + Seek>(
sink: &mut W,
byte_order: ByteOrder,
is_bigtiff: bool,
tags: &[Tag],
offsets_tag_code: u16,
byte_counts_tag_code: u16,
_num_blocks: usize,
) -> Result<IfdWriteResult> {
let ifd_offset = sink.stream_position()?;
let entry_size: u64 = if is_bigtiff { 20 } else { 12 };
let inline_max: usize = if is_bigtiff { 8 } else { 4 };
let next_ptr_size: u64 = if is_bigtiff { 8 } else { 4 };
let count_size: u64 = if is_bigtiff { 8 } else { 2 };
if is_bigtiff {
sink.write_all(&byte_order.write_u64(tags.len() as u64))?;
} else {
sink.write_all(&byte_order.write_u16(tags.len() as u16))?;
}
let entries_total = tags.len() as u64 * entry_size;
let deferred_start = ifd_offset + count_size + entries_total + next_ptr_size;
let mut deferred_offset = deferred_start;
struct DeferredEntry {
data: Vec<u8>,
offset: u64,
}
let mut deferred_entries = Vec::new();
let mut offsets_data_offset = None;
let mut byte_counts_data_offset = None;
for tag in tags {
let encoded = tag.value.encode(byte_order);
if encoded.len() > inline_max {
if tag.code == offsets_tag_code {
offsets_data_offset = Some(deferred_offset);
} else if tag.code == byte_counts_tag_code {
byte_counts_data_offset = Some(deferred_offset);
}
let len = encoded.len() as u64;
deferred_entries.push(DeferredEntry {
data: encoded,
offset: deferred_offset,
});
deferred_offset += len;
}
}
let mut deferred_idx = 0;
for tag in tags {
sink.write_all(&byte_order.write_u16(tag.code))?;
sink.write_all(&byte_order.write_u16(tag.tag_type.to_code()))?;
if is_bigtiff {
sink.write_all(&byte_order.write_u64(tag.count))?;
} else {
sink.write_all(&byte_order.write_u32(tag.count as u32))?;
}
let encoded = tag.value.encode(byte_order);
if encoded.len() <= inline_max {
let mut inline = vec![0u8; inline_max];
inline[..encoded.len()].copy_from_slice(&encoded);
sink.write_all(&inline)?;
} else {
let offset = deferred_entries[deferred_idx].offset;
if is_bigtiff {
sink.write_all(&byte_order.write_u64(offset))?;
} else {
sink.write_all(&byte_order.write_u32(offset as u32))?;
}
deferred_idx += 1;
}
}
let next_ifd_pointer_offset = sink.stream_position()?;
if is_bigtiff {
sink.write_all(&byte_order.write_u64(0))?;
} else {
sink.write_all(&byte_order.write_u32(0))?;
}
for entry in &deferred_entries {
debug_assert_eq!(sink.stream_position()?, entry.offset);
sink.write_all(&entry.data)?;
}
Ok(IfdWriteResult {
ifd_offset,
next_ifd_pointer_offset,
offsets_tag_data_offset: offsets_data_offset,
byte_counts_tag_data_offset: byte_counts_data_offset,
is_bigtiff,
})
}
pub fn patch_block_offsets<W: Write + Seek>(
sink: &mut W,
byte_order: ByteOrder,
is_bigtiff: bool,
data_offset: u64,
offsets: &[u64],
) -> Result<()> {
sink.seek(SeekFrom::Start(data_offset))?;
for &offset in offsets {
if is_bigtiff {
sink.write_all(&byte_order.write_u64(offset))?;
} else {
if offset > u32::MAX as u64 {
return Err(crate::error::Error::ClassicOffsetOverflow { offset });
}
sink.write_all(&byte_order.write_u32(offset as u32))?;
}
}
Ok(())
}
pub fn patch_block_byte_counts<W: Write + Seek>(
sink: &mut W,
byte_order: ByteOrder,
is_bigtiff: bool,
data_offset: u64,
byte_counts: &[u64],
) -> Result<()> {
sink.seek(SeekFrom::Start(data_offset))?;
for &count in byte_counts {
if is_bigtiff {
sink.write_all(&byte_order.write_u64(count))?;
} else {
sink.write_all(&byte_order.write_u32(count as u32))?;
}
}
Ok(())
}
pub fn patch_next_ifd<W: Write + Seek>(
sink: &mut W,
byte_order: ByteOrder,
is_bigtiff: bool,
pointer_offset: u64,
next_ifd: u64,
) -> Result<()> {
sink.seek(SeekFrom::Start(pointer_offset))?;
if is_bigtiff {
sink.write_all(&byte_order.write_u64(next_ifd))?;
} else {
sink.write_all(&byte_order.write_u32(next_ifd as u32))?;
}
Ok(())
}
#[derive(Debug)]
pub struct ImageTagParams<'a> {
pub width: u32,
pub height: u32,
pub samples_per_pixel: u16,
pub bits_per_sample: u16,
pub sample_format: u16,
pub compression: u16,
pub photometric: u16,
pub predictor: u16,
pub planar_configuration: u16,
pub subfile_type: u32,
pub extra_tags: &'a [Tag],
pub offsets_tag_code: u16,
pub byte_counts_tag_code: u16,
pub num_blocks: usize,
pub layout_tags: &'a [Tag],
pub is_bigtiff: bool,
}
pub fn build_image_tags(p: &ImageTagParams<'_>) -> Vec<Tag> {
let ImageTagParams {
width,
height,
samples_per_pixel,
bits_per_sample,
sample_format,
compression,
photometric,
predictor,
planar_configuration,
subfile_type,
extra_tags,
offsets_tag_code,
byte_counts_tag_code,
num_blocks,
layout_tags,
is_bigtiff,
} = p;
let mut tags = Vec::with_capacity(16 + extra_tags.len());
if *subfile_type != 0 {
tags.push(Tag::new(
tiff_core::TAG_NEW_SUBFILE_TYPE,
TagValue::Long(vec![*subfile_type]),
));
}
tags.push(Tag::new(
tiff_core::TAG_IMAGE_WIDTH,
TagValue::Long(vec![*width]),
));
tags.push(Tag::new(
tiff_core::TAG_IMAGE_LENGTH,
TagValue::Long(vec![*height]),
));
tags.push(Tag::new(
tiff_core::TAG_BITS_PER_SAMPLE,
TagValue::Short(vec![*bits_per_sample; *samples_per_pixel as usize]),
));
tags.push(Tag::new(
tiff_core::TAG_COMPRESSION,
TagValue::Short(vec![*compression]),
));
tags.push(Tag::new(
tiff_core::TAG_PHOTOMETRIC_INTERPRETATION,
TagValue::Short(vec![*photometric]),
));
tags.push(Tag::new(
tiff_core::TAG_SAMPLES_PER_PIXEL,
TagValue::Short(vec![*samples_per_pixel]),
));
if *planar_configuration != 1 {
tags.push(Tag::new(
tiff_core::TAG_PLANAR_CONFIGURATION,
TagValue::Short(vec![*planar_configuration]),
));
}
if *predictor != 1 {
tags.push(Tag::new(
tiff_core::TAG_PREDICTOR,
TagValue::Short(vec![*predictor]),
));
}
tags.push(Tag::new(
tiff_core::TAG_SAMPLE_FORMAT,
TagValue::Short(vec![*sample_format; *samples_per_pixel as usize]),
));
for lt in *layout_tags {
tags.push(lt.clone());
}
if *is_bigtiff {
tags.push(Tag::new(
*offsets_tag_code,
TagValue::Long8(vec![0u64; *num_blocks]),
));
tags.push(Tag::new(
*byte_counts_tag_code,
TagValue::Long8(vec![0u64; *num_blocks]),
));
} else {
tags.push(Tag::new(
*offsets_tag_code,
TagValue::Long(vec![0u32; *num_blocks]),
));
tags.push(Tag::new(
*byte_counts_tag_code,
TagValue::Long(vec![0u32; *num_blocks]),
));
}
for tag in *extra_tags {
tags.push(tag.clone());
}
tags.sort_by_key(|t| t.code);
tags
}
pub fn find_inline_tag_value_offset(
ifd_offset: u64,
is_bigtiff: bool,
tags: &[Tag],
target_code: u16,
) -> Option<u64> {
let count_size: u64 = if is_bigtiff { 8 } else { 2 };
let entry_size: u64 = if is_bigtiff { 20 } else { 12 };
let value_field_offset: u64 = if is_bigtiff { 12 } else { 8 };
for (i, tag) in tags.iter().enumerate() {
if tag.code == target_code {
return Some(ifd_offset + count_size + (i as u64) * entry_size + value_field_offset);
}
}
None
}