Add TiffHeader to open a file without seeking

This commit is contained in:
A. Molzer committed 2026-03-06 02:59:45 +01:00
1 parent cf8f96568e
commit 647e9b4a81
3 files changed
+159 -41

No files matched your search

+24
View File
@@ -146,6 +146,30 @@ pub(crate) struct ReadoutLayout {
} }
impl Image { impl Image {
pub(crate) fn no_image() -> Image {
Image {
width: 0,
height: 0,
bits_per_sample: 1,
samples: 1,
extra_samples: vec![],
photometric_samples: 1,
sample_format: SampleFormat::Uint,
photometric_interpretation: PhotometricInterpretation::BlackIsZero,
compression_method: CompressionMethod::None,
jpeg_tables: None,
predictor: Predictor::None,
chunk_type: ChunkType::Strip,
planar_config: PlanarConfiguration::Chunky,
strip_decoder: None,
tile_attributes: None,
chunk_offsets: Vec::new(),
chunk_bytes: Vec::new(),
chroma_subsampling: (2, 2),
decompression_to_host_endian: false,
}
}
pub fn from_reader<R: Read + Seek>( pub fn from_reader<R: Read + Seek>(
decoder: &mut ValueReader<R>, decoder: &mut ValueReader<R>,
ifd: &Directory, ifd: &Directory,
+126 -41
View File
@@ -4,8 +4,8 @@ use std::io::{self, Read, Seek};
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use crate::tags::{ use crate::tags::{
CompressionMethod, IfdPointer, PhotometricInterpretation, PlanarConfiguration, Predictor, ByteOrder, IfdPointer, PlanarConfiguration, Predictor, SampleFormat, Tag, TiffVariant, Type,
SampleFormat, Tag, Type, ValueBuffer, ValueBuffer,
}; };
use crate::{ use crate::{
bytecast, ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, bytecast, ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError,
@@ -14,7 +14,7 @@ use crate::{
use half::f16; use half::f16;
use self::image::Image; use self::image::Image;
use self::stream::{ByteOrder, EndianReader}; use self::stream::EndianReader;
mod cycles; mod cycles;
pub mod ifd; pub mod ifd;
@@ -505,6 +505,14 @@ impl Default for Limits {
} }
} }
/// Describe the header of TIFF file, including where to find the first image directory.
#[derive(Debug)]
pub struct TiffHeader {
pub byte_order: ByteOrder,
pub variant: TiffVariant,
pub first_ifd: IfdPointer,
}
/// The representation of a TIFF decoder /// The representation of a TIFF decoder
/// ///
/// Currently does not support decoding of interlaced images /// Currently does not support decoding of interlaced images
@@ -767,8 +775,8 @@ fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
host.convert_endian_bytes(class, buf, byte_order); host.convert_endian_bytes(class, buf, byte_order);
} }
impl<R: Read + Seek> Decoder<R> { impl TiffHeader {
pub fn new(mut r: R) -> TiffResult<Decoder<R>> { pub fn parse<R: Read + Seek>(mut r: R) -> TiffResult<Self> {
let mut endianess = Vec::with_capacity(2); let mut endianess = Vec::with_capacity(2);
(&mut r).take(2).read_to_end(&mut endianess)?; (&mut r).take(2).read_to_end(&mut endianess)?;
let byte_order = match &*endianess { let byte_order = match &*endianess {
@@ -780,6 +788,7 @@ impl<R: Read + Seek> Decoder<R> {
)) ))
} }
}; };
let mut reader = EndianReader::new(r, byte_order); let mut reader = EndianReader::new(r, byte_order);
let bigtiff = match reader.read_u16()? { let bigtiff = match reader.read_u16()? {
@@ -806,57 +815,69 @@ impl<R: Read + Seek> Decoder<R> {
} }
}; };
let next_ifd = if bigtiff { let next_ifd = IfdPointer(if bigtiff {
Some(reader.read_u64()?) reader.read_u64()?
} else { } else {
Some(u64::from(reader.read_u32()?)) u64::from(reader.read_u32()?)
} });
.map(IfdPointer);
let current_ifd = *next_ifd.as_ref().unwrap(); Ok(TiffHeader {
let ifd_offsets = vec![current_ifd]; byte_order,
variant: if bigtiff {
TiffVariant::BigTiff
} else {
TiffVariant::Standard
},
first_ifd: next_ifd,
})
}
/// Open a file, without seeking to any IFD yet.
pub fn open<R: Read + Seek>(&self, r: R) -> Decoder<R> {
let reader = EndianReader::new(r, self.byte_order);
Decoder {
value_reader: ValueReader {
reader,
bigtiff: matches!(self.variant, TiffVariant::BigTiff),
limits: Default::default(),
},
next_ifd_pointer: Some(self.first_ifd),
ifd_offsets: vec![],
current_ifd_pointer: None,
directory: Directory::empty(),
seen_ifds: cycles::IfdCycles::new(),
image: Image::no_image(),
}
}
}
impl<R: Read + Seek> Decoder<R> {
pub fn new(mut r: R) -> TiffResult<Decoder<R>> {
let header = TiffHeader::parse(&mut r)?;
let reader = EndianReader::new(r, header.byte_order);
let first_ifd = header.first_ifd;
let ifd_offsets = vec![first_ifd];
let mut decoder = Decoder { let mut decoder = Decoder {
value_reader: ValueReader { value_reader: ValueReader {
reader, reader,
bigtiff, bigtiff: matches!(header.variant, TiffVariant::BigTiff),
limits: Default::default(), limits: Default::default(),
}, },
next_ifd_pointer: next_ifd, next_ifd_pointer: Some(first_ifd),
ifd_offsets, ifd_offsets,
current_ifd_pointer: None, current_ifd_pointer: None,
directory: Directory::empty(), directory: Directory::empty(),
seen_ifds: cycles::IfdCycles::new(), seen_ifds: cycles::IfdCycles::new(),
image: Self::no_image(), image: Image::no_image(),
}; };
decoder.next_image()?; decoder.next_image()?;
Ok(decoder) Ok(decoder)
} }
fn no_image() -> Image {
Image {
width: 0,
height: 0,
bits_per_sample: 1,
samples: 1,
extra_samples: vec![],
photometric_samples: 1,
sample_format: SampleFormat::Uint,
photometric_interpretation: PhotometricInterpretation::BlackIsZero,
compression_method: CompressionMethod::None,
jpeg_tables: None,
predictor: Predictor::None,
chunk_type: ChunkType::Strip,
planar_config: PlanarConfiguration::Chunky,
strip_decoder: None,
tile_attributes: None,
chunk_offsets: Vec::new(),
chunk_bytes: Vec::new(),
chroma_subsampling: (2, 2),
decompression_to_host_endian: false,
}
}
pub fn with_limits(mut self, limits: Limits) -> Decoder<R> { pub fn with_limits(mut self, limits: Limits) -> Decoder<R> {
self.value_reader.limits = limits; self.value_reader.limits = limits;
self self
@@ -951,7 +972,7 @@ impl<R: Read + Seek> Decoder<R> {
/// If there is no further image in the TIFF file a format error is returned. /// If there is no further image in the TIFF file a format error is returned.
/// To determine whether there are more images call `TIFFDecoder::more_images` instead. /// To determine whether there are more images call `TIFFDecoder::more_images` instead.
pub fn next_directory(&mut self) -> TiffResult<()> { pub fn next_directory(&mut self) -> TiffResult<()> {
self.image = Self::no_image(); self.image = Image::no_image();
self.read_next_ifd_pointer() self.read_next_ifd_pointer()
} }
@@ -960,6 +981,7 @@ impl<R: Read + Seek> Decoder<R> {
/// If there is no further image in the TIFF file a format error is returned. /// If there is no further image in the TIFF file a format error is returned.
/// To determine whether there are more images call `TIFFDecoder::more_images` instead. /// To determine whether there are more images call `TIFFDecoder::more_images` instead.
pub fn next_image(&mut self) -> TiffResult<()> { pub fn next_image(&mut self) -> TiffResult<()> {
self.image = Image::no_image();
self.read_next_ifd_pointer()?; self.read_next_ifd_pointer()?;
self.image = Image::from_reader(&mut self.value_reader, &self.directory)?; self.image = Image::from_reader(&mut self.value_reader, &self.directory)?;
Ok(()) Ok(())
@@ -1911,7 +1933,7 @@ impl<'l> IfdDecoder<'l> {
mod tests { mod tests {
use super::Decoder; use super::Decoder;
use crate::{ use crate::{
bytecast, bytecast, encoder,
tags::{ByteOrder, Tag, ValueBuffer}, tags::{ByteOrder, Tag, ValueBuffer},
}; };
@@ -1972,4 +1994,67 @@ mod tests {
assert_eq!(&by_bytes[..3], &[8, 8, 8]); assert_eq!(&by_bytes[..3], &[8, 8, 8]);
} }
} }
#[test]
fn non_image_directory_between() {
let mut cursor = std::io::Cursor::new(vec![]);
{
let mut encoder = encoder::TiffEncoder::new(&mut cursor).unwrap();
encoder
.write_image::<encoder::colortype::Gray8>(1, 1, &[0])
.unwrap();
encoder.image_directory().unwrap().finish().unwrap();
encoder
.write_image::<encoder::colortype::Gray8>(1, 1, &[0])
.unwrap();
}
cursor.set_position(0);
let mut decoder = Decoder::new(cursor).unwrap();
let first_ifd = decoder.image_ifd();
assert!(first_ifd.directory().get(Tag::ImageWidth).is_some());
assert!(decoder.more_images());
assert!(decoder.next_image().is_err());
let second_ifd = decoder.image_ifd();
assert!(second_ifd.directory().get(Tag::ImageWidth).is_none());
assert!(decoder.more_images());
assert!(decoder.next_image().is_ok());
let third_ifd = decoder.image_ifd();
assert!(third_ifd.directory().get(Tag::ImageWidth).is_some());
assert!(!decoder.more_images());
}
#[test]
fn non_image_directory_start() {
let mut cursor = std::io::Cursor::new(vec![]);
{
let mut encoder = encoder::TiffEncoder::new(&mut cursor).unwrap();
encoder.image_directory().unwrap().finish().unwrap();
encoder
.write_image::<encoder::colortype::Gray8>(1, 1, &[0])
.unwrap();
}
cursor.set_position(0);
let header = super::TiffHeader::parse(&mut cursor).unwrap();
let mut decoder = header.open(cursor);
assert!(decoder.more_images());
assert_eq!(decoder.ifd_pointer(), None);
assert!(decoder.next_directory().is_ok());
let first_ifd = decoder.image_ifd();
assert!(first_ifd.directory().get(Tag::ImageWidth).is_none());
assert!(decoder.more_images());
assert!(decoder.next_image().is_ok());
let second_ifd = decoder.image_ifd();
assert!(second_ifd.directory().get(Tag::ImageWidth).is_some());
assert!(!decoder.more_images());
}
} }
+9
View File
@@ -589,3 +589,12 @@ pub(crate) enum EndianBytes {
Four, Four,
Eight, Eight,
} }
/// Defines how directories are laid out in the file.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TiffVariant {
/// TIFF-V6.0 with 32-bit offsets and counts.
Standard,
/// Extended TIFF with 64-bit offsets and counts and long directories.
BigTiff,
}