use std::io::{Seek, SeekFrom, Write};
use tiff_core::ByteOrder;
use crate::builder::ImageBuilder;
use crate::compress;
use crate::encoder;
use crate::error::{Error, Result};
use crate::sample::TiffWriteSample;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TiffVariant {
Classic,
BigTiff,
Auto,
}
#[derive(Debug, Clone)]
pub struct WriteOptions {
pub byte_order: ByteOrder,
pub variant: TiffVariant,
}
impl Default for WriteOptions {
fn default() -> Self {
Self {
byte_order: ByteOrder::LittleEndian,
variant: TiffVariant::Auto,
}
}
}
impl WriteOptions {
pub fn auto(estimated_bytes: u64) -> Self {
let variant = if estimated_bytes >= 4_000_000_000 {
TiffVariant::BigTiff
} else {
TiffVariant::Classic
};
Self {
byte_order: ByteOrder::LittleEndian,
variant,
}
}
}
#[derive(Debug, Clone)]
pub struct ImageHandle {
pub(crate) index: usize,
}
struct IfdState {
builder: ImageBuilder,
tags: Vec<tiff_core::Tag>,
ifd_result: encoder::IfdWriteResult,
block_records: Vec<Option<(u64, u64)>>,
}
pub struct TiffWriter<W: Write + Seek> {
sink: W,
byte_order: ByteOrder,
is_bigtiff: bool,
header_offset: u64,
images: Vec<IfdState>,
finalized: bool,
}
impl<W: Write + Seek> TiffWriter<W> {
pub fn new(mut sink: W, options: WriteOptions) -> Result<Self> {
let is_bigtiff = matches!(options.variant, TiffVariant::BigTiff);
let header_offset = encoder::write_header(&mut sink, options.byte_order, is_bigtiff)?;
Ok(Self {
sink,
byte_order: options.byte_order,
is_bigtiff,
header_offset,
images: Vec::new(),
finalized: false,
})
}
pub fn add_image(&mut self, builder: ImageBuilder) -> Result<ImageHandle> {
if self.finalized {
return Err(Error::AlreadyFinalized);
}
builder.validate()?;
let num_blocks = builder.block_count();
let (offsets_tag, byte_counts_tag) = builder.offset_tag_codes();
let layout_tags = builder.layout_tags();
let tags = encoder::build_image_tags(&encoder::ImageTagParams {
width: builder.width,
height: builder.height,
samples_per_pixel: builder.samples_per_pixel,
bits_per_sample: builder.bits_per_sample,
sample_format: builder.sample_format.to_code(),
compression: builder.compression.to_code(),
photometric: builder.photometric.to_code(),
predictor: builder.predictor.to_code(),
planar_configuration: builder.planar_configuration.to_code(),
subfile_type: builder.subfile_type,
extra_tags: &builder.extra_tags,
offsets_tag_code: offsets_tag,
byte_counts_tag_code: byte_counts_tag,
num_blocks,
layout_tags: &layout_tags,
is_bigtiff: self.is_bigtiff,
});
let ifd_result = encoder::write_ifd(
&mut self.sink,
self.byte_order,
self.is_bigtiff,
&tags,
offsets_tag,
byte_counts_tag,
num_blocks,
)?;
let index = self.images.len();
self.images.push(IfdState {
builder,
tags,
ifd_result,
block_records: vec![None; num_blocks],
});
Ok(ImageHandle { index })
}
pub fn write_block<T: TiffWriteSample>(
&mut self,
handle: &ImageHandle,
block_index: usize,
samples: &[T],
) -> Result<()> {
if self.finalized {
return Err(Error::AlreadyFinalized);
}
let state = self
.images
.get(handle.index)
.ok_or(Error::Other("invalid image handle".into()))?;
let total_blocks = state.builder.block_count();
if block_index >= total_blocks {
return Err(Error::BlockIndexOutOfRange {
index: block_index,
total: total_blocks,
});
}
let expected = state.builder.block_sample_count(block_index);
if samples.len() != expected {
return Err(Error::BlockSizeMismatch {
index: block_index,
expected,
actual: samples.len(),
});
}
let compressed = compress::compress_block(
samples,
self.byte_order,
state.builder.compression,
state.builder.predictor,
state.builder.samples_per_pixel,
state.builder.block_row_width(),
block_index,
)?;
self.write_block_raw(handle, block_index, &compressed)
}
pub fn write_block_raw(
&mut self,
handle: &ImageHandle,
block_index: usize,
compressed_bytes: &[u8],
) -> Result<()> {
if self.finalized {
return Err(Error::AlreadyFinalized);
}
let offset = self.sink.seek(SeekFrom::End(0))?;
self.sink.write_all(compressed_bytes)?;
let byte_count = compressed_bytes.len() as u64;
let state = self
.images
.get_mut(handle.index)
.ok_or(Error::Other("invalid image handle".into()))?;
let total = state.builder.block_count();
if block_index >= total {
return Err(Error::BlockIndexOutOfRange {
index: block_index,
total,
});
}
state.block_records[block_index] = Some((offset, byte_count));
Ok(())
}
pub fn finish(mut self) -> Result<W> {
if self.finalized {
return Err(Error::AlreadyFinalized);
}
self.finalized = true;
for (img_idx, state) in self.images.iter().enumerate() {
let total = state.builder.block_count();
let written = state.block_records.iter().filter(|r| r.is_some()).count();
if written != total {
return Err(Error::IncompleteImage { written, total });
}
let offsets: Vec<u64> = state.block_records.iter().map(|r| r.unwrap().0).collect();
let byte_counts: Vec<u64> = state.block_records.iter().map(|r| r.unwrap().1).collect();
let (offsets_tag_code, byte_counts_tag_code) = state.builder.offset_tag_codes();
let is_bigtiff = state.ifd_result.is_bigtiff;
if total == 1 {
if let Some(off) = encoder::find_inline_tag_value_offset(
state.ifd_result.ifd_offset,
is_bigtiff,
&state.tags,
offsets_tag_code,
) {
self.sink.seek(SeekFrom::Start(off))?;
if is_bigtiff {
self.sink
.write_all(&self.byte_order.write_u64(offsets[0]))?;
} else {
self.sink
.write_all(&self.byte_order.write_u32(offsets[0] as u32))?;
}
}
if let Some(off) = encoder::find_inline_tag_value_offset(
state.ifd_result.ifd_offset,
is_bigtiff,
&state.tags,
byte_counts_tag_code,
) {
self.sink.seek(SeekFrom::Start(off))?;
if is_bigtiff {
self.sink
.write_all(&self.byte_order.write_u64(byte_counts[0]))?;
} else {
self.sink
.write_all(&self.byte_order.write_u32(byte_counts[0] as u32))?;
}
}
} else {
if let Some(off) = state.ifd_result.offsets_tag_data_offset {
encoder::patch_block_offsets(
&mut self.sink,
self.byte_order,
is_bigtiff,
off,
&offsets,
)?;
}
if let Some(off) = state.ifd_result.byte_counts_tag_data_offset {
encoder::patch_block_byte_counts(
&mut self.sink,
self.byte_order,
is_bigtiff,
off,
&byte_counts,
)?;
}
}
if img_idx == 0 {
encoder::patch_first_ifd(
&mut self.sink,
self.header_offset,
self.byte_order,
is_bigtiff,
state.ifd_result.ifd_offset,
)?;
} else {
let prev = &self.images[img_idx - 1];
encoder::patch_next_ifd(
&mut self.sink,
self.byte_order,
is_bigtiff,
prev.ifd_result.next_ifd_pointer_offset,
state.ifd_result.ifd_offset,
)?;
}
}
self.sink.seek(SeekFrom::End(0))?;
Ok(self.sink)
}
}