Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
740c5cdeea | ||
|
|
86ac5b601e | ||
|
|
97d357d446 | ||
|
|
61867b46f9 | ||
|
|
0f1f075abb | ||
|
|
5576417abe | ||
|
|
52b9cc7a19 | ||
|
|
006f0b0fa4 | ||
|
|
5b8a2832c3 | ||
|
|
f93a6380e6 | ||
|
|
8a1b65fe68 | ||
|
|
a2996ab34e | ||
|
|
f890d22657 | ||
|
|
6b04bff6b2 | ||
|
|
bd0dccef3a |
No files matched your search
@@ -11,7 +11,7 @@ use std::collections::HashMap;
|
|||||||
///
|
///
|
||||||
/// Since we only consider one child, we run union find to assign each pointer to a known chain.
|
/// Since we only consider one child, we run union find to assign each pointer to a known chain.
|
||||||
/// Then, when we insert a new edge we check if they belong to the same component.
|
/// Then, when we insert a new edge we check if they belong to the same component.
|
||||||
#[derive(Default, Debug)]
|
#[derive(Clone, Default, Debug)]
|
||||||
pub struct IfdCycles {
|
pub struct IfdCycles {
|
||||||
/// The root of each component union.
|
/// The root of each component union.
|
||||||
component_union: HashMap<ComponentId, ComponentId>,
|
component_union: HashMap<ComponentId, ComponentId>,
|
||||||
|
|||||||
@@ -0,0 +1,362 @@
|
|||||||
|
use std::io::{self, Cursor, Read, Seek};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
decoder::{ifd::Entry, stream::EndianReader, ValueReader},
|
||||||
|
tags::{ByteOrder, IfdPointer, Type},
|
||||||
|
TiffResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct EntryBytesReader<'lt, R> {
|
||||||
|
inner: &'lt mut EndianReader<R>,
|
||||||
|
variant: Variant,
|
||||||
|
}
|
||||||
|
|
||||||
|
const OFFSET_BUFFER_LEN: usize = 8;
|
||||||
|
|
||||||
|
enum Variant {
|
||||||
|
Immediate {
|
||||||
|
/// An in-value reader if the offset field contains the data. Note that we rotate it to the
|
||||||
|
/// back of this buffer, not the front, so that `Cursor` already contains the right end.
|
||||||
|
/// We do not let you seek back beyond our start.
|
||||||
|
data: Cursor<[u8; OFFSET_BUFFER_LEN]>,
|
||||||
|
value_bytes: u8,
|
||||||
|
},
|
||||||
|
InFile {
|
||||||
|
// the length relative to the base so we can seek relative without querying the position.
|
||||||
|
value_bytes: u64,
|
||||||
|
remainder: u64,
|
||||||
|
base: u64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'lt, R> EntryBytesReader<'lt, R> {
|
||||||
|
pub(super) fn from_entry(inner: &'lt mut ValueReader<R>, entry: Entry) -> TiffResult<Self>
|
||||||
|
where
|
||||||
|
R: Read + Seek,
|
||||||
|
{
|
||||||
|
let offset: [_; OFFSET_BUFFER_LEN] = *entry.offset_raw();
|
||||||
|
let limit = if inner.bigtiff { 8 } else { 4 };
|
||||||
|
assert!(limit as usize <= OFFSET_BUFFER_LEN);
|
||||||
|
|
||||||
|
let value_bytes = entry.field_type().value_bytes(entry.count())?;
|
||||||
|
|
||||||
|
if value_bytes <= limit {
|
||||||
|
// We move the data to the back of the array whereas it is in the front right now.
|
||||||
|
let mut value = offset;
|
||||||
|
value.rotate_left(value_bytes as usize);
|
||||||
|
let mut cursor = Cursor::new(value);
|
||||||
|
cursor.set_position(OFFSET_BUFFER_LEN as u64 - value_bytes);
|
||||||
|
|
||||||
|
Ok(EntryBytesReader {
|
||||||
|
inner: &mut inner.reader,
|
||||||
|
variant: Variant::Immediate {
|
||||||
|
data: cursor,
|
||||||
|
value_bytes: value_bytes as u8,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// remainder > limit >= 0 so this is an unreachable panic.
|
||||||
|
let pointer = if inner.bigtiff {
|
||||||
|
let mut offset = offset;
|
||||||
|
|
||||||
|
inner
|
||||||
|
.reader
|
||||||
|
.byte_order
|
||||||
|
.convert(Type::IFD8, &mut offset[..], ByteOrder::native());
|
||||||
|
|
||||||
|
IfdPointer(u64::from_ne_bytes(offset))
|
||||||
|
} else {
|
||||||
|
let mut offset = *offset.first_chunk::<4>().unwrap();
|
||||||
|
|
||||||
|
inner
|
||||||
|
.reader
|
||||||
|
.byte_order
|
||||||
|
.convert(Type::IFD, &mut offset[..], ByteOrder::native());
|
||||||
|
|
||||||
|
let offset = u32::from_ne_bytes(offset);
|
||||||
|
IfdPointer(u64::from(offset))
|
||||||
|
};
|
||||||
|
|
||||||
|
inner.reader.goto_offset(pointer.0)?;
|
||||||
|
|
||||||
|
Ok(EntryBytesReader {
|
||||||
|
inner: &mut inner.reader,
|
||||||
|
variant: Variant::InFile {
|
||||||
|
value_bytes,
|
||||||
|
remainder: value_bytes,
|
||||||
|
base: pointer.0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read> Read for EntryBytesReader<'_, R> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
match &mut self.variant {
|
||||||
|
Variant::Immediate { data, .. } => data.read(buf),
|
||||||
|
Variant::InFile { remainder, .. } => {
|
||||||
|
let bound = usize::try_from(*remainder).unwrap_or(usize::MAX);
|
||||||
|
let avail = buf.len().min(bound);
|
||||||
|
let read = self.inner.inner().read(&mut buf[..avail])?;
|
||||||
|
|
||||||
|
// On a well-behaved `Read`: read <= avail <= bound <=(mathematical) remainder
|
||||||
|
let consume = read as u64;
|
||||||
|
*remainder -= consume;
|
||||||
|
Ok(read)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
|
||||||
|
match &mut self.variant {
|
||||||
|
Variant::Immediate { data, .. } => data.read_exact(buf),
|
||||||
|
Variant::InFile { remainder, .. } => {
|
||||||
|
let bound = usize::try_from(*remainder).unwrap_or(usize::MAX);
|
||||||
|
|
||||||
|
// Fail early if we would be sure to over-read.
|
||||||
|
if buf.len() > bound {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::UnexpectedEof,
|
||||||
|
"Tiff value shorter than the requested buffer size",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// since buf.len() <= bound <=(mathematical) remainder
|
||||||
|
let consume = buf.len() as u64;
|
||||||
|
self.inner.inner().read_exact(buf)?;
|
||||||
|
*remainder -= consume;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read + Seek> Seek for EntryBytesReader<'_, R> {
|
||||||
|
fn seek(&mut self, offset: io::SeekFrom) -> io::Result<u64> {
|
||||||
|
let (value_bytes, remainder) = match &self.variant {
|
||||||
|
Variant::Immediate { data, value_bytes } => {
|
||||||
|
let remainder = data.get_ref().len() as u64 - data.position();
|
||||||
|
(*value_bytes as u64, remainder)
|
||||||
|
}
|
||||||
|
&Variant::InFile {
|
||||||
|
value_bytes,
|
||||||
|
remainder,
|
||||||
|
base: _,
|
||||||
|
} => (value_bytes, remainder),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Normalize to an offset from the start
|
||||||
|
let offset_from_start = match offset {
|
||||||
|
io::SeekFrom::Start(n) => Some(n),
|
||||||
|
io::SeekFrom::Current(n) => {
|
||||||
|
let front = value_bytes - remainder;
|
||||||
|
front.checked_add_signed(n)
|
||||||
|
}
|
||||||
|
io::SeekFrom::End(n) => value_bytes.checked_add_signed(n),
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some((offset, remainder)) = offset_from_start.and_then(|n| {
|
||||||
|
let remainder = value_bytes.checked_sub(n)?;
|
||||||
|
Some((n, remainder))
|
||||||
|
}) else {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"seek out of bounds of entry value",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
match &mut self.variant {
|
||||||
|
Variant::Immediate { data, .. } => {
|
||||||
|
let start = OFFSET_BUFFER_LEN as u64 - value_bytes;
|
||||||
|
data.set_position(start + offset);
|
||||||
|
Ok(offset)
|
||||||
|
}
|
||||||
|
&mut Variant::InFile { base, .. } => {
|
||||||
|
// We checked this against overflow on construction. Ignore the offset we get.
|
||||||
|
self.inner
|
||||||
|
.inner()
|
||||||
|
.seek(io::SeekFrom::Start(base + offset))?;
|
||||||
|
|
||||||
|
self.variant = Variant::InFile {
|
||||||
|
value_bytes,
|
||||||
|
remainder,
|
||||||
|
base,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stream_position(&mut self) -> io::Result<u64> {
|
||||||
|
Ok(match &self.variant {
|
||||||
|
Variant::Immediate { data, value_bytes } => {
|
||||||
|
// Recall the value is at the end of the buffer.
|
||||||
|
let from_end = OFFSET_BUFFER_LEN as u64 - data.position();
|
||||||
|
u64::from(*value_bytes) - from_end
|
||||||
|
}
|
||||||
|
// Note: we might think about using the inner `stream_position` here. However that
|
||||||
|
// exposes something interesting: If the internal stream is modified concurrently from
|
||||||
|
// another instance (e.g. a shared `File`) then we may lose track of it anyways.
|
||||||
|
// Especially the `Read` implementation is not bounded on `Seek` so it could not
|
||||||
|
// acquire the position. And it should not since that is just a different form of race
|
||||||
|
// where we risk the position being modified by a different thread just as well.
|
||||||
|
//
|
||||||
|
// So in the interest of least surprise we just assume that we can track the position
|
||||||
|
// accurately here and that the inner reader uses for instance `read_at` consistently.
|
||||||
|
// We own the value (or mutable reference) it after all.
|
||||||
|
//
|
||||||
|
// Also this one MUST be compatible with `seek(SeekFrom::Current(0))` as per trait
|
||||||
|
// defintion—so at least keep those consistent if changing anything.
|
||||||
|
&Variant::InFile {
|
||||||
|
remainder,
|
||||||
|
value_bytes,
|
||||||
|
..
|
||||||
|
} => value_bytes - remainder,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{decoder::Decoder, encoder, tags};
|
||||||
|
use std::io::{Read as _, Seek as _, SeekFrom};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_seek_tag_bytes() -> Result<(), crate::error::TiffError> {
|
||||||
|
let file = std::fs::File::open(concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/images/int8_rgb.tif"
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut decoder = Decoder::open(file).unwrap();
|
||||||
|
decoder.next_directory().unwrap();
|
||||||
|
|
||||||
|
let mut buffer = crate::tags::ValueBuffer::default();
|
||||||
|
|
||||||
|
decoder
|
||||||
|
.current_ifd()
|
||||||
|
.find_tag_buf(tags::Tag::StripByteCounts, &mut buffer)
|
||||||
|
.expect("some strip byte count is present");
|
||||||
|
|
||||||
|
let bytes_of_value = buffer.as_bytes().len() as u64;
|
||||||
|
|
||||||
|
let mut reader = decoder
|
||||||
|
.read_tag(tags::Tag::StripByteCounts)?
|
||||||
|
.expect("some strip byte count is present");
|
||||||
|
|
||||||
|
let mut data = vec![];
|
||||||
|
reader.read_to_end(&mut data)?;
|
||||||
|
|
||||||
|
// `ValueBuffer` also stores its data in the file endianess until converted.
|
||||||
|
assert_eq!(reader.stream_position()?, bytes_of_value);
|
||||||
|
assert_eq!(buffer.as_bytes(), data);
|
||||||
|
|
||||||
|
reader.seek(SeekFrom::Start(0))?;
|
||||||
|
assert_eq!(reader.stream_position()?, 0);
|
||||||
|
reader.read_to_end(&mut data)?;
|
||||||
|
assert_eq!(reader.stream_position()?, bytes_of_value);
|
||||||
|
|
||||||
|
let (first, second) = data.split_at(bytes_of_value as usize);
|
||||||
|
assert_eq!(first, second);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[allow(clippy::seek_from_current)] // We want to test that functionality.
|
||||||
|
fn seek_from_end() -> Result<(), crate::error::TiffError> {
|
||||||
|
const ARTIST: &str = "99 little bugs in the code";
|
||||||
|
|
||||||
|
let data = {
|
||||||
|
let mut data = std::io::Cursor::new(vec![]);
|
||||||
|
let mut encoder = encoder::TiffEncoder::new(&mut data)?;
|
||||||
|
|
||||||
|
let mut dir = encoder.image_directory()?;
|
||||||
|
dir.write_tag(tags::Tag::Artist, ARTIST)?;
|
||||||
|
dir.finish()?;
|
||||||
|
|
||||||
|
data.into_inner()
|
||||||
|
};
|
||||||
|
|
||||||
|
let file = std::io::Cursor::new(data);
|
||||||
|
let mut decoder = Decoder::open(file).unwrap();
|
||||||
|
decoder.next_directory().unwrap();
|
||||||
|
|
||||||
|
let mut reader = decoder
|
||||||
|
.read_tag(tags::Tag::Artist)?
|
||||||
|
.expect("the artist is present?");
|
||||||
|
|
||||||
|
let mut value = vec![];
|
||||||
|
reader.read_to_end(&mut value)?;
|
||||||
|
|
||||||
|
let (&nul, value) = value.split_last().unwrap();
|
||||||
|
assert_eq!(value, ARTIST.as_bytes());
|
||||||
|
assert_eq!(nul, b'\0');
|
||||||
|
|
||||||
|
assert_eq!(reader.seek(SeekFrom::End(0))?, ARTIST.len() as u64 + 1);
|
||||||
|
assert!(reader.seek(SeekFrom::End(1)).is_err());
|
||||||
|
assert_eq!(reader.stream_position()?, ARTIST.len() as u64 + 1);
|
||||||
|
|
||||||
|
// Check relative seeks by comparing against the "code"
|
||||||
|
let code_offset = reader.seek(SeekFrom::End(-5))?;
|
||||||
|
let mut is_code = [0u8; 4];
|
||||||
|
reader.read_exact(&mut is_code)?;
|
||||||
|
assert_eq!(is_code, *value.last_chunk::<4>().unwrap());
|
||||||
|
|
||||||
|
assert_eq!(reader.seek(SeekFrom::Start(0))?, 0);
|
||||||
|
assert_eq!(reader.stream_position()?, 0);
|
||||||
|
assert_eq!(reader.seek(SeekFrom::Current(0))?, 0);
|
||||||
|
assert_eq!(reader.stream_position()?, 0);
|
||||||
|
assert!(reader.seek(SeekFrom::Current(-1)).is_err());
|
||||||
|
assert_eq!(reader.stream_position()?, 0);
|
||||||
|
|
||||||
|
reader.seek(SeekFrom::Start(code_offset))?;
|
||||||
|
reader.read_exact(&mut is_code)?;
|
||||||
|
assert_eq!(reader.stream_position()?, ARTIST.len() as u64);
|
||||||
|
assert_eq!(is_code, *value.last_chunk::<4>().unwrap());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check that the reader from an `offset` field also works.
|
||||||
|
#[test]
|
||||||
|
fn read_from_offset() -> Result<(), crate::error::TiffError> {
|
||||||
|
let data = {
|
||||||
|
let mut data = std::io::Cursor::new(vec![]);
|
||||||
|
let mut encoder = encoder::TiffEncoder::new(&mut data)?;
|
||||||
|
|
||||||
|
let mut dir = encoder.image_directory()?;
|
||||||
|
dir.write_tag(
|
||||||
|
tags::Tag::PlanarConfiguration,
|
||||||
|
tags::PlanarConfiguration::Planar,
|
||||||
|
)?;
|
||||||
|
dir.finish()?;
|
||||||
|
|
||||||
|
data.into_inner()
|
||||||
|
};
|
||||||
|
|
||||||
|
let file = std::io::Cursor::new(data);
|
||||||
|
let mut decoder = Decoder::open(file).unwrap();
|
||||||
|
decoder.next_directory().unwrap();
|
||||||
|
let bo = decoder.byte_order();
|
||||||
|
|
||||||
|
let mut reader = decoder
|
||||||
|
.read_tag(tags::Tag::PlanarConfiguration)?
|
||||||
|
.expect("some artist is present");
|
||||||
|
|
||||||
|
let mut buf = [0; 2];
|
||||||
|
reader.read_exact(&mut buf)?;
|
||||||
|
|
||||||
|
bo.convert(tags::Type::SHORT, &mut buf, tags::ByteOrder::BigEndian);
|
||||||
|
assert_eq!(buf, [0, 2]);
|
||||||
|
|
||||||
|
// Check relative seeks, this item is only two bytes long.
|
||||||
|
assert!(reader.seek(SeekFrom::End(-3)).is_err());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -393,7 +393,7 @@ impl Entry {
|
|||||||
self.count
|
self.count
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn offset(&self) -> &[u8] {
|
pub(crate) fn offset_raw(&self) -> &[u8; 8] {
|
||||||
&self.offset
|
&self.offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-26
@@ -155,32 +155,6 @@ 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),
|
|
||||||
fill_order: 1,
|
|
||||||
color_map: None,
|
|
||||||
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,
|
||||||
@@ -528,6 +502,17 @@ impl Image {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn check_chunk_type(&self, expected: ChunkType) -> TiffResult<()> {
|
||||||
|
if expected != self.chunk_type {
|
||||||
|
return Err(TiffError::UsageError(UsageError::InvalidChunkType(
|
||||||
|
expected,
|
||||||
|
self.chunk_type,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
|
pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
|
||||||
let is_alpha_extra_samples = matches!(
|
let is_alpha_extra_samples = matches!(
|
||||||
self.extra_samples.as_slice(),
|
self.extra_samples.as_slice(),
|
||||||
|
|||||||
+417
-139
@@ -15,6 +15,7 @@ use self::stream::EndianReader;
|
|||||||
|
|
||||||
mod buffer;
|
mod buffer;
|
||||||
mod cycles;
|
mod cycles;
|
||||||
|
mod entry_reader;
|
||||||
pub mod ifd;
|
pub mod ifd;
|
||||||
mod image;
|
mod image;
|
||||||
mod logluv;
|
mod logluv;
|
||||||
@@ -22,6 +23,7 @@ mod stream;
|
|||||||
mod tag_reader;
|
mod tag_reader;
|
||||||
|
|
||||||
pub use self::buffer::{DecodingSampleBuffer, DecodingSampleSlice, DecodingSampleType};
|
pub use self::buffer::{DecodingSampleBuffer, DecodingSampleSlice, DecodingSampleType};
|
||||||
|
pub use self::entry_reader::EntryBytesReader;
|
||||||
|
|
||||||
#[deprecated = "Renamed to `DecodingSampleBuffer`"]
|
#[deprecated = "Renamed to `DecodingSampleBuffer`"]
|
||||||
pub type DecodingResult = DecodingSampleBuffer;
|
pub type DecodingResult = DecodingSampleBuffer;
|
||||||
@@ -229,12 +231,19 @@ pub struct TiffHeader {
|
|||||||
|
|
||||||
/// The representation of a TIFF decoder
|
/// The representation of a TIFF decoder
|
||||||
///
|
///
|
||||||
/// Currently does not support decoding of interlaced images
|
/// There are three groups of operations:
|
||||||
|
///
|
||||||
|
/// - [Manipulate the directory state](#header-manipulate-directory) to which the decoder currently
|
||||||
|
/// refers in the file.
|
||||||
|
/// - [Interact with the underlying stream](#header-stream-access) instead of entries in an IFD.
|
||||||
|
/// - [Access the encoded image](#header-image).
|
||||||
|
///
|
||||||
|
/// ## Limitations
|
||||||
|
///
|
||||||
|
/// Currently does not support decoding of interlaced images. It also does not support images with
|
||||||
|
/// downsampling in any direction; except if they are also JPEG or WebP compressed.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Decoder<R>
|
pub struct Decoder<R> {
|
||||||
where
|
|
||||||
R: Read + Seek,
|
|
||||||
{
|
|
||||||
/// There are grouped for borrow checker reasons. This allows us to implement methods that
|
/// There are grouped for borrow checker reasons. This allows us to implement methods that
|
||||||
/// borrow the stream access and the other fields mutably at the same time.
|
/// borrow the stream access and the other fields mutably at the same time.
|
||||||
value_reader: ValueReader<R>,
|
value_reader: ValueReader<R>,
|
||||||
@@ -244,7 +253,7 @@ where
|
|||||||
ifd_offsets: Vec<IfdPointer>,
|
ifd_offsets: Vec<IfdPointer>,
|
||||||
/// Map from the ifd into the `ifd_offsets` ordered list.
|
/// Map from the ifd into the `ifd_offsets` ordered list.
|
||||||
seen_ifds: cycles::IfdCycles,
|
seen_ifds: cycles::IfdCycles,
|
||||||
image: Image,
|
image: Option<Image>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All the information needed to read and interpret byte slices from the underlying file, i.e. to
|
/// All the information needed to read and interpret byte slices from the underlying file, i.e. to
|
||||||
@@ -617,11 +626,12 @@ impl TiffHeader {
|
|||||||
dir
|
dir
|
||||||
},
|
},
|
||||||
seen_ifds: cycles::IfdCycles::new(),
|
seen_ifds: cycles::IfdCycles::new(),
|
||||||
image: Image::no_image(),
|
image: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <span id=header-manipulate-directory>Methods to manipulate the directory state</span>
|
||||||
impl<R: Read + Seek> Decoder<R> {
|
impl<R: Read + Seek> Decoder<R> {
|
||||||
/// Parse the image header from the underlying file and create a decoder positioned before its
|
/// Parse the image header from the underlying file and create a decoder positioned before its
|
||||||
/// first directory.
|
/// first directory.
|
||||||
@@ -699,22 +709,146 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dimensions(&mut self) -> TiffResult<(u32, u32)> {
|
// FIXME: this tuple would be a good candidate for a proxy, like IfdDecoder. That has been
|
||||||
Ok((self.image.width, self.image.height))
|
// requested in some form or another in a number of issues.
|
||||||
|
#[expect(clippy::unnecessary_unwrap)] // Lifetimes would overlap due to return.
|
||||||
|
fn ensure_image_and_reader(&mut self) -> TiffResult<(&mut Image, &mut ValueReader<R>)> {
|
||||||
|
if self.image.is_some() {
|
||||||
|
return Ok((self.image.as_mut().unwrap(), &mut self.value_reader));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn colortype(&mut self) -> TiffResult<ColorType> {
|
let image = Image::from_reader(&mut self.value_reader, &self.directory)?;
|
||||||
self.image.colortype()
|
Ok((self.image.insert(image), &mut self.value_reader))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the color map (palette) for the current image, if it uses
|
fn ensure_image(&mut self) -> TiffResult<&mut Image> {
|
||||||
/// `PhotometricInterpretation::RGBPalette`.
|
Ok(self.ensure_image_and_reader()?.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change to another reader in a new decoder, taking with it parsed data and limits.
|
||||||
///
|
///
|
||||||
/// The returned slice contains `3 * 2^BitsPerSample` entries as u16 values,
|
/// The reader will keep the IFD state but resources and in particular the parsed image are
|
||||||
/// laid out as: all red values, then all green values, then all blue values.
|
/// transferred to the new decoder. This has two motivations. Firstly, it can not silently
|
||||||
/// Each value is in the range `0..=65535` regardless of the actual bit depth.
|
/// duplicate any limits and exhaust more memory than expected. Secondly, this is compatible
|
||||||
pub fn color_map(&self) -> Option<&[u16]> {
|
/// with additional resources that can not be cloned at all.
|
||||||
self.image.color_map.as_deref()
|
///
|
||||||
|
/// This method assumes that the alternate reader contains the same data as the current reader
|
||||||
|
/// (but not necessarily the same stream).
|
||||||
|
///
|
||||||
|
/// The limits used by `self` get replaced with default limits. To keep using the reader that
|
||||||
|
/// is the receiver of this method you likely want to reinitialize it with
|
||||||
|
/// [`Self::with_limits`].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// This method can also be used to 'fork' the reader into multiple independent iterators over
|
||||||
|
/// the file, provided you use access to [`Self::inner`] to clone the underlying stream. Since
|
||||||
|
/// the first call takes the resources, keep that object separate if you want each worker to
|
||||||
|
/// get the same decoder (each with default configured limits).
|
||||||
|
///
|
||||||
|
/// Here we show the functionality by opening cursors over data in-memory.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::io::Cursor;
|
||||||
|
/// use tiff::{decoder::Decoder, TiffError};
|
||||||
|
///
|
||||||
|
/// let data: &[u8] = /* */
|
||||||
|
/// # &include_bytes!(concat!(
|
||||||
|
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
|
||||||
|
/// # ))[..];
|
||||||
|
/// # const N: usize = 1;
|
||||||
|
/// let decoder = Decoder::open(Cursor::new(data))?;
|
||||||
|
///
|
||||||
|
/// let mut workers = vec![decoder];
|
||||||
|
///
|
||||||
|
/// for _ in 0..N {
|
||||||
|
/// let decoder = workers.last_mut().unwrap();
|
||||||
|
/// // Takes the resources with it, they are always in the last.
|
||||||
|
/// let dup = decoder.take_with_reader(Cursor::new(data));
|
||||||
|
/// workers.push(dup);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let with_resources = workers.pop().unwrap();
|
||||||
|
///
|
||||||
|
/// // Now all `workers` are at the same IFD but in a bare state.
|
||||||
|
///
|
||||||
|
/// Ok::<_, TiffError>(())
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Please note that you do **not** want to use [`File::try_clone`][`std::fs::File::open`], or
|
||||||
|
/// any other method where multiple readers share a single position variable, in combination
|
||||||
|
/// with concurrent use of decoders. When the independent decoders each seek the file
|
||||||
|
/// descriptor in parallel there is a race as to what position they then read from
|
||||||
|
/// (time-to-check-to-time-to-use). This of course applies to the original file passed to
|
||||||
|
/// [`Decoder::open`] already but may be more tempting here. Instead, use a file reader that
|
||||||
|
/// owns a position and uses [`read_at`] or equivalents.
|
||||||
|
///
|
||||||
|
/// [`read_at`]: `std::os::unix::fs::FileExt::read_at`
|
||||||
|
///
|
||||||
|
/// An *incorrect* use would be:
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use std::fs::File;
|
||||||
|
/// use tiff::{decoder::Decoder, TiffError};
|
||||||
|
///
|
||||||
|
/// let file: File = /* */
|
||||||
|
/// # File::open(concat!(
|
||||||
|
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
|
||||||
|
/// # ))?;
|
||||||
|
/// let mut decoder = Decoder::open(&file)?;
|
||||||
|
///
|
||||||
|
/// let concurrent = decoder.inner().try_clone()?;
|
||||||
|
/// // ⚠️Do NOT attempt to use both decoder and racing_decoder in parallel!
|
||||||
|
/// // This will be defined behavior but likely cause both to fail.
|
||||||
|
/// let racing_decoder = decoder.take_with_reader(concurrent);
|
||||||
|
///
|
||||||
|
/// # Ok::<_, TiffError>(())
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// In another use case you have a partially cached file backed by fast storage that you only
|
||||||
|
/// access from slow disk when any operation misses the cache. In that case you may use this
|
||||||
|
/// method to replace the cached reader with a file reader after a miss is detected, without
|
||||||
|
/// losing any of the state collected by the decoder.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tiff::{decoder::Decoder, TiffError};
|
||||||
|
///
|
||||||
|
/// let partial_file = /* */
|
||||||
|
/// # std::io::Cursor::new(include_bytes!(concat!(
|
||||||
|
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
|
||||||
|
/// # )));
|
||||||
|
/// let mut decoder = Decoder::open(partial_file)?;
|
||||||
|
///
|
||||||
|
/// # let backing_file = concat!(
|
||||||
|
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
|
||||||
|
/// # );
|
||||||
|
///
|
||||||
|
/// // Catch cache errors: we are out of pages we have for this cache..
|
||||||
|
/// if let Err(TiffError::IoError(e)) = decoder.next_image() {
|
||||||
|
/// if e.kind() == std::io::ErrorKind::OutOfMemory {
|
||||||
|
/// let file = std::fs::File::open(backing_file)?;
|
||||||
|
/// let mut slow_decoder = decoder.take_with_reader(file);
|
||||||
|
/// // Repeat the op, `next_image` is atomic on error.
|
||||||
|
/// slow_decoder.next_image()?;
|
||||||
|
/// // …
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # Ok::<_, TiffError>(())
|
||||||
|
/// ```
|
||||||
|
pub fn take_with_reader<S: Read + Seek>(&mut self, reader: S) -> Decoder<S> {
|
||||||
|
Decoder {
|
||||||
|
value_reader: ValueReader {
|
||||||
|
reader: EndianReader::new(reader, self.value_reader.reader.byte_order),
|
||||||
|
bigtiff: self.value_reader.bigtiff,
|
||||||
|
limits: core::mem::take(&mut self.value_reader.limits),
|
||||||
|
},
|
||||||
|
ifd_offsets: self.ifd_offsets.clone(),
|
||||||
|
seen_ifds: self.seen_ifds.clone(),
|
||||||
|
directory: self.directory.clone(),
|
||||||
|
current_ifd_pointer: self.current_ifd_pointer,
|
||||||
|
image: core::mem::take(&mut self.image),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The offset of the directory representing the current image.
|
/// The offset of the directory representing the current image.
|
||||||
@@ -722,13 +856,33 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
self.current_ifd_pointer
|
self.current_ifd_pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the IFD decoder for our current IFD.
|
||||||
|
///
|
||||||
|
/// This may be used with any directory, valid image or otherwise. When the decoder has not
|
||||||
|
/// read a directory yet (a state after [`TiffHeader::open`]) the decoder will represent an
|
||||||
|
/// empty directory with only the next pointer set.
|
||||||
|
pub fn current_ifd(&mut self) -> IfdDecoder<'_> {
|
||||||
|
IfdDecoder {
|
||||||
|
inner: tag_reader::TagReader {
|
||||||
|
decoder: &mut self.value_reader,
|
||||||
|
ifd: &self.directory,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Loads the IFD at the specified index in the list, if one exists
|
/// Loads the IFD at the specified index in the list, if one exists
|
||||||
///
|
///
|
||||||
/// Error guarantee: the current directory and [`Self::ifd_pointer`] is not modified when an
|
/// Error guarantee: the current directory and [`Self::ifd_pointer`] is not modified when an
|
||||||
/// error is returned.
|
/// error is returned.
|
||||||
|
///
|
||||||
|
/// The next directory image attributes are *not* parsed on a successful seek, i.e. it need not be
|
||||||
|
/// semantically valid as an image (incoherent sample depths, unsupported planar configuration,
|
||||||
|
/// missing required tags, etc.) but [`Self::read_image`] may not work. Calling methods
|
||||||
|
/// interacting with it as an image after this will first attempt to parse the directory
|
||||||
|
/// instead.
|
||||||
pub fn seek_to_directory(&mut self, ifd_index: usize) -> TiffResult<()> {
|
pub fn seek_to_directory(&mut self, ifd_index: usize) -> TiffResult<()> {
|
||||||
let (offset, directory) = self.find_nth_ifd(ifd_index)?;
|
let (offset, directory) = self.find_nth_ifd(ifd_index)?;
|
||||||
self.image = Image::no_image();
|
self.image = None;
|
||||||
self.current_ifd_pointer = Some(offset);
|
self.current_ifd_pointer = Some(offset);
|
||||||
self.directory = directory;
|
self.directory = directory;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -742,7 +896,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
/// error is returned.
|
/// error is returned.
|
||||||
pub fn seek_to_image(&mut self, ifd_index: usize) -> TiffResult<()> {
|
pub fn seek_to_image(&mut self, ifd_index: usize) -> TiffResult<()> {
|
||||||
let (offset, directory) = self.find_nth_ifd(ifd_index)?;
|
let (offset, directory) = self.find_nth_ifd(ifd_index)?;
|
||||||
self.image = Image::from_reader(&mut self.value_reader, &directory)?;
|
self.image = Some(Image::from_reader(&mut self.value_reader, &directory)?);
|
||||||
self.current_ifd_pointer = Some(offset);
|
self.current_ifd_pointer = Some(offset);
|
||||||
self.directory = directory;
|
self.directory = directory;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -805,15 +959,21 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
/// Reads the next IFD.
|
/// Reads the next IFD.
|
||||||
///
|
///
|
||||||
/// If there is no further image in the TIFF file a format error is returned.
|
/// If there is no further IFD 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 [`Self::more_images`] instead.
|
||||||
|
///
|
||||||
|
/// The next directory image attributes are *not* parsed on a successful seek, i.e. it need not be
|
||||||
|
/// semantically valid as an image (incoherent sample depths, unsupported planar configuration,
|
||||||
|
/// missing required tags, etc.) but [`Self::read_image`] may not work. Calling methods
|
||||||
|
/// interacting with it as an image after this will first attempt to parse the directory
|
||||||
|
/// instead.
|
||||||
///
|
///
|
||||||
/// Error guarantee: the current directory and [`Self::ifd_pointer`] is not modified when an error is
|
/// Error guarantee: the current directory and [`Self::ifd_pointer`] is not modified when an error is
|
||||||
/// returned.
|
/// returned.
|
||||||
pub fn next_directory(&mut self) -> TiffResult<()> {
|
pub fn next_directory(&mut self) -> TiffResult<()> {
|
||||||
let next_ifd = self.directory.next();
|
let next_ifd = self.directory.next();
|
||||||
let (offset, directory) = self.read_chained_ifd(self.current_ifd_pointer, next_ifd)?;
|
let (offset, directory) = self.read_chained_ifd(self.current_ifd_pointer, next_ifd)?;
|
||||||
self.image = Image::no_image();
|
self.image = None;
|
||||||
self.current_ifd_pointer = Some(offset);
|
self.current_ifd_pointer = Some(offset);
|
||||||
self.directory = directory;
|
self.directory = directory;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -822,19 +982,65 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
/// Reads in the next image.
|
/// Reads in the next image.
|
||||||
///
|
///
|
||||||
/// 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 [`Self::more_images`] instead.
|
||||||
///
|
///
|
||||||
/// Error guarantee: the current directory and [`Self::ifd_pointer`] is not modified when an error is
|
/// Error guarantee: the current directory and [`Self::ifd_pointer`] is not modified when an error is
|
||||||
/// returned.
|
/// returned.
|
||||||
pub fn next_image(&mut self) -> TiffResult<()> {
|
pub fn next_image(&mut self) -> TiffResult<()> {
|
||||||
let next_ifd = self.directory.next();
|
let next_ifd = self.directory.next();
|
||||||
let (offset, directory) = self.read_chained_ifd(self.current_ifd_pointer, next_ifd)?;
|
let (offset, directory) = self.read_chained_ifd(self.current_ifd_pointer, next_ifd)?;
|
||||||
self.image = Image::from_reader(&mut self.value_reader, &directory)?;
|
self.image = Some(Image::from_reader(&mut self.value_reader, &directory)?);
|
||||||
self.current_ifd_pointer = Some(offset);
|
self.current_ifd_pointer = Some(offset);
|
||||||
self.directory = directory;
|
self.directory = directory;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prepare reading values for tags of a given directory.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// This method may be used to read the values of tags in directories that have been previously
|
||||||
|
/// read with [`Decoder::read_directory`].
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use tiff::decoder::Decoder;
|
||||||
|
/// use tiff::tags::Tag;
|
||||||
|
///
|
||||||
|
/// # use std::io::Cursor;
|
||||||
|
/// # let mut data = Cursor::new(vec![0]);
|
||||||
|
/// let mut decoder = Decoder::open(&mut data)?;
|
||||||
|
///
|
||||||
|
/// decoder.next_directory()?;
|
||||||
|
/// let sub_ifds = decoder.get_tag(Tag::SubIfd)?.into_ifd_vec()?;
|
||||||
|
///
|
||||||
|
/// for ifd in sub_ifds {
|
||||||
|
/// let subdir = decoder.read_directory(ifd)?;
|
||||||
|
/// let subfile = decoder.read_directory_tags(&subdir).find_tag(Tag::SubfileType)?;
|
||||||
|
/// // omitted: handle the subfiles, e.g. thumbnails
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # Ok::<_, tiff::TiffError>(())
|
||||||
|
/// ```
|
||||||
|
pub fn read_directory_tags<'ifd>(&'ifd mut self, ifd: &'ifd Directory) -> IfdDecoder<'ifd> {
|
||||||
|
IfdDecoder {
|
||||||
|
inner: tag_reader::TagReader {
|
||||||
|
decoder: &mut self.value_reader,
|
||||||
|
ifd,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Utility to create a reader like [`Self::read_entry`], from the current IFD.
|
||||||
|
///
|
||||||
|
/// Returns an `Ok(None)` if the tag is not found in the current directory. See
|
||||||
|
/// [`Self::read_entry´] of which this is a simple wrapper.
|
||||||
|
pub fn read_tag(&mut self, tag: Tag) -> TiffResult<Option<EntryBytesReader<'_, R>>> {
|
||||||
|
match self.current_ifd().find_entry(tag) {
|
||||||
|
None => Ok(None),
|
||||||
|
Some(entry) => self.read_entry(entry).map(Some),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `true` if there is at least one more image available.
|
/// Returns `true` if there is at least one more image available.
|
||||||
pub fn more_images(&self) -> bool {
|
pub fn more_images(&self) -> bool {
|
||||||
self.directory.next().is_some()
|
self.directory.next().is_some()
|
||||||
@@ -849,6 +1055,23 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
pub fn byte_order(&self) -> ByteOrder {
|
pub fn byte_order(&self) -> ByteOrder {
|
||||||
self.value_reader.reader.byte_order
|
self.value_reader.reader.byte_order
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <span id=header-stream-access>Methods that interact with the underlying reader.</span>
|
||||||
|
impl<R: Read> Decoder<R> {
|
||||||
|
/// Returns a mutable reference to the stream being decoded.
|
||||||
|
pub fn inner(&mut self) -> &mut R {
|
||||||
|
self.value_reader.reader.inner()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if this file is a BigTIFF or a standard TIFF.
|
||||||
|
pub fn variant(&self) -> TiffVariant {
|
||||||
|
if self.value_reader.bigtiff {
|
||||||
|
TiffVariant::BigTiff
|
||||||
|
} else {
|
||||||
|
TiffVariant::Standard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn read_ifd_offset(&mut self) -> Result<u64, io::Error> {
|
pub fn read_ifd_offset(&mut self) -> Result<u64, io::Error> {
|
||||||
@@ -859,11 +1082,6 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable reference to the stream being decoded.
|
|
||||||
pub fn inner(&mut self) -> &mut R {
|
|
||||||
self.value_reader.reader.inner()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads a TIFF byte value
|
/// Reads a TIFF byte value
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn read_byte(&mut self) -> Result<u8, io::Error> {
|
pub fn read_byte(&mut self) -> Result<u8, io::Error> {
|
||||||
@@ -943,6 +1161,33 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
Ok(val)
|
Ok(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a byte reader for the value of an arbitrary IFD entry.
|
||||||
|
///
|
||||||
|
/// Small values are stored inline inside the offset field of the entry. The threshold for this
|
||||||
|
/// depends on the [`Self::variant`] of the file. Interpret entries that have not been
|
||||||
|
/// extracted from an IFD of the current decoder with care.
|
||||||
|
///
|
||||||
|
/// The reader returned form this method reads raw bytes. Values retrieved in this way are not
|
||||||
|
/// yet converted to the host endianess.
|
||||||
|
///
|
||||||
|
/// Returns an error if the entry count exceeds the possible offset values or if the offset
|
||||||
|
/// of the entry would overflow the offset in the underlying stream. Otherwise returns a reader
|
||||||
|
/// that yields the bytes of the value.
|
||||||
|
///
|
||||||
|
/// ## Design Notes
|
||||||
|
///
|
||||||
|
/// This method does not appear on [`IfdDecoder`] as we assume the reader type may be important
|
||||||
|
/// to you. It also allows forward compatibility to additional trait implementations without
|
||||||
|
/// demanding them from all types as `dyn` would. There is also no `M×N` instantiation problem
|
||||||
|
/// with this method: you get one type per reader type and monomorphic methods from the traits
|
||||||
|
/// it implements.
|
||||||
|
pub fn read_entry(&mut self, entry: ifd::Entry) -> TiffResult<EntryBytesReader<'_, R>>
|
||||||
|
where
|
||||||
|
R: Seek,
|
||||||
|
{
|
||||||
|
EntryBytesReader::from_entry(&mut self.value_reader, entry)
|
||||||
|
}
|
||||||
|
|
||||||
/// Reads a TIFF IFA offset/value field
|
/// Reads a TIFF IFA offset/value field
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn read_offset_u64(&mut self) -> Result<[u8; 8], io::Error> {
|
pub fn read_offset_u64(&mut self) -> Result<[u8; 8], io::Error> {
|
||||||
@@ -953,14 +1198,58 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
/// Moves the cursor to the specified offset
|
/// Moves the cursor to the specified offset
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn goto_offset(&mut self, offset: u32) -> io::Result<()> {
|
pub fn goto_offset(&mut self, offset: u32) -> io::Result<()>
|
||||||
|
where
|
||||||
|
R: Seek,
|
||||||
|
{
|
||||||
self.goto_offset_u64(offset.into())
|
self.goto_offset_u64(offset.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn goto_offset_u64(&mut self, offset: u64) -> io::Result<()> {
|
pub fn goto_offset_u64(&mut self, offset: u64) -> io::Result<()>
|
||||||
|
where
|
||||||
|
R: Seek,
|
||||||
|
{
|
||||||
self.value_reader.reader.goto_offset(offset)
|
self.value_reader.reader.goto_offset(offset)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <span id=header-image>Methods that interact with the current directory as an image.</span>
|
||||||
|
impl<R: Read + Seek> Decoder<R> {
|
||||||
|
/// Return the current IFD's image size.
|
||||||
|
///
|
||||||
|
/// Returns an error if the current directory could not be parsed as an image. This can occur
|
||||||
|
/// if [`Self::next_directory`] or [`Self::seek_to_directory`] was used to advance the decoder
|
||||||
|
/// to the current directory which implies the IFD will be validated as an image on-demand by
|
||||||
|
/// this method.
|
||||||
|
///
|
||||||
|
/// [`Self::image_buffer_layout`] provides further information on reading the image data and
|
||||||
|
/// can be used alternatively to verify further that the image can be read.
|
||||||
|
pub fn dimensions(&mut self) -> TiffResult<(u32, u32)> {
|
||||||
|
let image = self.ensure_image()?;
|
||||||
|
Ok((image.width, image.height))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the color type resulting from reading the image (in the current configuration).
|
||||||
|
///
|
||||||
|
/// Returns an error if the current directory could not be parsed as an image. This can occur
|
||||||
|
/// if [`Self::next_directory`] or [`Self::seek_to_directory`] was used to advance the decoder
|
||||||
|
/// to the current directory which implies the IFD will be validated as an image on-demand by
|
||||||
|
/// this method.
|
||||||
|
pub fn colortype(&mut self) -> TiffResult<ColorType> {
|
||||||
|
let image = self.ensure_image()?;
|
||||||
|
image.colortype()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the color map (palette) for the current image, if it uses
|
||||||
|
/// `PhotometricInterpretation::RGBPalette`.
|
||||||
|
///
|
||||||
|
/// The returned slice contains `3 * 2^BitsPerSample` entries as u16 values,
|
||||||
|
/// laid out as: all red values, then all green values, then all blue values.
|
||||||
|
/// Each value is in the range `0..=65535` regardless of the actual bit depth.
|
||||||
|
pub fn color_map(&self) -> Option<&[u16]> {
|
||||||
|
self.image.as_ref().and_then(|img| img.color_map.as_deref())
|
||||||
|
}
|
||||||
|
|
||||||
/// Read a tag-entry map from a known offset.
|
/// Read a tag-entry map from a known offset.
|
||||||
///
|
///
|
||||||
@@ -985,40 +1274,37 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
self.value_reader.read_directory(ptr)
|
self.value_reader.read_directory(ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_chunk_type(&self, expected: ChunkType) -> TiffResult<()> {
|
|
||||||
if expected != self.image.chunk_type {
|
|
||||||
return Err(TiffError::UsageError(UsageError::InvalidChunkType(
|
|
||||||
expected,
|
|
||||||
self.image.chunk_type,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The chunk type (Strips / Tiles) of the image
|
/// The chunk type (Strips / Tiles) of the image
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This method panics if the directory was read with [`Self::next_directory`] or
|
||||||
|
/// [`Self::seek_to_directory`] and [`Self::dimensions`] has not yet succeeded. (Other
|
||||||
|
/// image-related methods with signature (`fn(&mut self) -> TiffResult<_>`) can be substituted
|
||||||
|
/// but use that one by default).
|
||||||
pub fn get_chunk_type(&self) -> ChunkType {
|
pub fn get_chunk_type(&self) -> ChunkType {
|
||||||
self.image.chunk_type
|
self.image.as_ref().unwrap().chunk_type
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Number of strips in image
|
/// Number of strips in image
|
||||||
pub fn strip_count(&mut self) -> TiffResult<u32> {
|
pub fn strip_count(&mut self) -> TiffResult<u32> {
|
||||||
self.check_chunk_type(ChunkType::Strip)?;
|
let image = self.ensure_image()?;
|
||||||
let rows_per_strip = self.image.strip_decoder.as_ref().unwrap().rows_per_strip;
|
image.check_chunk_type(ChunkType::Strip)?;
|
||||||
|
let rows_per_strip = image.strip_decoder.as_ref().unwrap().rows_per_strip;
|
||||||
|
|
||||||
if rows_per_strip == 0 {
|
if rows_per_strip == 0 {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// rows_per_strip - 1 can never fail since we know it's at least 1
|
// rows_per_strip - 1 can never fail since we know it's at least 1
|
||||||
let height = match self.image.height.checked_add(rows_per_strip - 1) {
|
let height = match image.height.checked_add(rows_per_strip - 1) {
|
||||||
Some(h) => h,
|
Some(h) => h,
|
||||||
None => return Err(TiffError::IntSizeError),
|
None => return Err(TiffError::IntSizeError),
|
||||||
};
|
};
|
||||||
|
|
||||||
let strips = match self.image.planar_config {
|
let strips = match image.planar_config {
|
||||||
PlanarConfiguration::Chunky => height / rows_per_strip,
|
PlanarConfiguration::Chunky => height / rows_per_strip,
|
||||||
PlanarConfiguration::Planar => height / rows_per_strip * self.image.samples as u32,
|
PlanarConfiguration::Planar => height / rows_per_strip * image.samples as u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(strips)
|
Ok(strips)
|
||||||
@@ -1026,8 +1312,9 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
/// Number of tiles in image
|
/// Number of tiles in image
|
||||||
pub fn tile_count(&mut self) -> TiffResult<u32> {
|
pub fn tile_count(&mut self) -> TiffResult<u32> {
|
||||||
self.check_chunk_type(ChunkType::Tile)?;
|
let image = self.ensure_image()?;
|
||||||
Ok(u32::try_from(self.image.chunk_offsets.len())?)
|
image.check_chunk_type(ChunkType::Tile)?;
|
||||||
|
Ok(u32::try_from(image.chunk_offsets.len())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_chunk_to_bytes(
|
fn read_chunk_to_bytes(
|
||||||
@@ -1036,12 +1323,10 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
chunk_index: u32,
|
chunk_index: u32,
|
||||||
layout: &image::ReadoutLayout,
|
layout: &image::ReadoutLayout,
|
||||||
) -> TiffResult<()> {
|
) -> TiffResult<()> {
|
||||||
let offset = self.image.chunk_file_range(chunk_index)?.0;
|
let (image, value_reader) = self.ensure_image_and_reader()?;
|
||||||
self.goto_offset_u64(offset)?;
|
let offset = image.chunk_file_range(chunk_index)?.0;
|
||||||
|
value_reader.reader.goto_offset(offset)?;
|
||||||
self.image
|
image.expand_chunk(value_reader, buffer, layout, chunk_index)?;
|
||||||
.expand_chunk(&mut self.value_reader, buffer, layout, chunk_index)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1056,8 +1341,9 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
chunk_index: u32,
|
chunk_index: u32,
|
||||||
) -> TiffResult<BufferLayoutPreference> {
|
) -> TiffResult<BufferLayoutPreference> {
|
||||||
let data_dims = self.image.chunk_data_dimensions(chunk_index)?;
|
let image = self.ensure_image()?;
|
||||||
let readout = self.image.readout_for_size(data_dims.0, data_dims.1)?;
|
let data_dims = image.chunk_data_dimensions(chunk_index)?;
|
||||||
|
let readout = image.readout_for_size(data_dims.0, data_dims.1)?;
|
||||||
|
|
||||||
let extent = readout.result_extent_for_planes(0..1)?;
|
let extent = readout.result_extent_for_planes(0..1)?;
|
||||||
let sample_type = extent.sample_type();
|
let sample_type = extent.sample_type();
|
||||||
@@ -1072,7 +1358,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
planes: 1,
|
planes: 1,
|
||||||
plane_stride,
|
plane_stride,
|
||||||
complete_len: layout.size(),
|
complete_len: layout.size(),
|
||||||
sample_format: self.image.sample_format,
|
sample_format: image.sample_format,
|
||||||
sample_type: Some(sample_type),
|
sample_type: Some(sample_type),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1089,17 +1375,15 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
code_unit: TiffCodingUnit,
|
code_unit: TiffCodingUnit,
|
||||||
) -> TiffResult<BufferLayoutPreference> {
|
) -> TiffResult<BufferLayoutPreference> {
|
||||||
match self.image.planar_config {
|
let image = self.ensure_image()?;
|
||||||
|
|
||||||
|
match image.planar_config {
|
||||||
PlanarConfiguration::Chunky => return self.image_chunk_buffer_layout(code_unit.0),
|
PlanarConfiguration::Chunky => return self.image_chunk_buffer_layout(code_unit.0),
|
||||||
PlanarConfiguration::Planar => {}
|
PlanarConfiguration::Planar => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (width, height) = self.image.chunk_data_dimensions(code_unit.0)?;
|
let (width, height) = image.chunk_data_dimensions(code_unit.0)?;
|
||||||
|
let layout = image.readout_for_size(width, height)?.to_plane_layout()?;
|
||||||
let layout = self
|
|
||||||
.image
|
|
||||||
.readout_for_size(width, height)?
|
|
||||||
.to_plane_layout()?;
|
|
||||||
|
|
||||||
if code_unit.0 >= layout.readout.chunks_per_plane {
|
if code_unit.0 >= layout.readout.chunks_per_plane {
|
||||||
return Err(TiffError::UsageError(UsageError::InvalidCodingUnit(
|
return Err(TiffError::UsageError(UsageError::InvalidCodingUnit(
|
||||||
@@ -1115,9 +1399,10 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
///
|
///
|
||||||
/// Note that for planar images each chunk contains only one sample of the underlying data.
|
/// Note that for planar images each chunk contains only one sample of the underlying data.
|
||||||
pub fn read_chunk(&mut self, chunk_index: u32) -> TiffResult<DecodingSampleBuffer> {
|
pub fn read_chunk(&mut self, chunk_index: u32) -> TiffResult<DecodingSampleBuffer> {
|
||||||
let (width, height) = self.image.chunk_data_dimensions(chunk_index)?;
|
let image = self.ensure_image()?;
|
||||||
|
|
||||||
let readout = self.image.readout_for_size(width, height)?;
|
let (width, height) = image.chunk_data_dimensions(chunk_index)?;
|
||||||
|
let readout = image.readout_for_size(width, height)?;
|
||||||
|
|
||||||
let mut result = readout
|
let mut result = readout
|
||||||
.result_extent_for_planes(0..1)?
|
.result_extent_for_planes(0..1)?
|
||||||
@@ -1136,9 +1421,10 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
///
|
///
|
||||||
/// Note that for planar images each chunk contains only one sample of the underlying data.
|
/// Note that for planar images each chunk contains only one sample of the underlying data.
|
||||||
pub fn read_chunk_bytes(&mut self, chunk_index: u32, buffer: &mut [u8]) -> TiffResult<()> {
|
pub fn read_chunk_bytes(&mut self, chunk_index: u32, buffer: &mut [u8]) -> TiffResult<()> {
|
||||||
let (width, height) = self.image.chunk_data_dimensions(chunk_index)?;
|
let image = self.ensure_image()?;
|
||||||
|
|
||||||
let layout = self.image.readout_for_size(width, height)?;
|
let (width, height) = image.chunk_data_dimensions(chunk_index)?;
|
||||||
|
let layout = image.readout_for_size(width, height)?;
|
||||||
layout.assert_min_layout(buffer)?;
|
layout.assert_min_layout(buffer)?;
|
||||||
|
|
||||||
self.read_chunk_to_bytes(buffer, chunk_index, &layout)?;
|
self.read_chunk_to_bytes(buffer, chunk_index, &layout)?;
|
||||||
@@ -1159,9 +1445,10 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
chunk_index: u32,
|
chunk_index: u32,
|
||||||
output_width: usize,
|
output_width: usize,
|
||||||
) -> TiffResult<()> {
|
) -> TiffResult<()> {
|
||||||
let (width, height) = self.image.chunk_data_dimensions(chunk_index)?;
|
let image = self.ensure_image()?;
|
||||||
|
|
||||||
let mut layout = self.image.readout_for_size(width, height)?;
|
let (width, height) = image.chunk_data_dimensions(chunk_index)?;
|
||||||
|
let mut layout = image.readout_for_size(width, height)?;
|
||||||
layout.set_row_stride(output_width)?;
|
layout.set_row_stride(output_width)?;
|
||||||
|
|
||||||
let extent = layout.result_extent_for_planes(0..1)?;
|
let extent = layout.result_extent_for_planes(0..1)?;
|
||||||
@@ -1191,8 +1478,10 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
slice: TiffCodingUnit,
|
slice: TiffCodingUnit,
|
||||||
buffer: &mut [u8],
|
buffer: &mut [u8],
|
||||||
) -> TiffResult<()> {
|
) -> TiffResult<()> {
|
||||||
let (width, height) = self.image.chunk_data_dimensions(slice.0)?;
|
let (image, value_reader) = self.ensure_image_and_reader()?;
|
||||||
let readout = self.image.readout_for_size(width, height)?;
|
|
||||||
|
let (width, height) = image.chunk_data_dimensions(slice.0)?;
|
||||||
|
let readout = image.readout_for_size(width, height)?;
|
||||||
|
|
||||||
let ref layout @ image::PlaneLayout {
|
let ref layout @ image::PlaneLayout {
|
||||||
ref plane_offsets,
|
ref plane_offsets,
|
||||||
@@ -1214,14 +1503,9 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
|
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
|
||||||
let chunk = slice.0 + idx as u32 * readout.chunks_per_plane;
|
let chunk = slice.0 + idx as u32 * readout.chunks_per_plane;
|
||||||
self.goto_offset_u64(self.image.chunk_offsets[chunk as usize])?;
|
let offset = image.chunk_offsets[chunk as usize];
|
||||||
|
value_reader.reader.goto_offset(offset)?;
|
||||||
self.image.expand_chunk(
|
image.expand_chunk(value_reader, &mut buffer[plane_offset..], readout, chunk)?;
|
||||||
&mut self.value_reader,
|
|
||||||
&mut buffer[plane_offset..],
|
|
||||||
readout,
|
|
||||||
chunk,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1229,14 +1513,32 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
/// Returns the default chunk size for the current image. Any given chunk in the image is at most as large as
|
/// Returns the default chunk size for the current image. Any given chunk in the image is at most as large as
|
||||||
/// the value returned here. For the size of the data (chunk minus padding), use `chunk_data_dimensions`.
|
/// the value returned here. For the size of the data (chunk minus padding), use `chunk_data_dimensions`.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This method panics if the directory was read with [`Self::next_directory`] or
|
||||||
|
/// [`Self::seek_to_directory`] and [`Self::dimensions`] has not yet succeeded. (Other
|
||||||
|
/// image-related methods with signature (`fn(&mut self) -> TiffResult<_>`) can be substituted
|
||||||
|
/// but use that one by default).
|
||||||
pub fn chunk_dimensions(&self) -> (u32, u32) {
|
pub fn chunk_dimensions(&self) -> (u32, u32) {
|
||||||
self.image.chunk_dimensions().unwrap()
|
self.image.as_ref().unwrap().chunk_dimensions().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the size of the data in the chunk with the specified index. This is the default size of the chunk,
|
/// Returns the size of the data in the chunk with the specified index. This is the default size of the chunk,
|
||||||
/// minus any padding.
|
/// minus any padding.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This method panics if the directory was read with [`Self::next_directory`] or
|
||||||
|
/// [`Self::seek_to_directory`] and [`Self::dimensions`] has not yet succeeded. (Other
|
||||||
|
/// image-related methods with signature (`fn(&mut self) -> TiffResult<_>`) can be substituted
|
||||||
|
/// but use that one to be sure).
|
||||||
|
///
|
||||||
|
/// This method panics if the index is out-of-range.
|
||||||
pub fn chunk_data_dimensions(&self, chunk_index: u32) -> (u32, u32) {
|
pub fn chunk_data_dimensions(&self, chunk_index: u32) -> (u32, u32) {
|
||||||
self.image
|
self.image
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
.chunk_data_dimensions(chunk_index)
|
.chunk_data_dimensions(chunk_index)
|
||||||
.expect("invalid chunk_index")
|
.expect("invalid chunk_index")
|
||||||
}
|
}
|
||||||
@@ -1255,7 +1557,8 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
/// indicate the layout needed to read the first data plane. This will be fixed in a future
|
/// indicate the layout needed to read the first data plane. This will be fixed in a future
|
||||||
/// major version of `tiff`.
|
/// major version of `tiff`.
|
||||||
pub fn image_buffer_layout(&mut self) -> TiffResult<BufferLayoutPreference> {
|
pub fn image_buffer_layout(&mut self) -> TiffResult<BufferLayoutPreference> {
|
||||||
let layout = self.image.readout_for_image()?.to_plane_layout()?;
|
let image = self.ensure_image()?;
|
||||||
|
let layout = image.readout_for_image()?.to_plane_layout()?;
|
||||||
Ok(BufferLayoutPreference::from_planes(&layout))
|
Ok(BufferLayoutPreference::from_planes(&layout))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1279,7 +1582,8 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
/// replaced, to ensure that existing code will not run into unexpectedly large allocations
|
/// replaced, to ensure that existing code will not run into unexpectedly large allocations
|
||||||
/// that will error on limits instead.
|
/// that will error on limits instead.
|
||||||
pub fn read_image(&mut self) -> TiffResult<DecodingSampleBuffer> {
|
pub fn read_image(&mut self) -> TiffResult<DecodingSampleBuffer> {
|
||||||
let readout = self.image.readout_for_image()?;
|
let image = self.ensure_image()?;
|
||||||
|
let readout = image.readout_for_image()?;
|
||||||
|
|
||||||
let mut result = readout
|
let mut result = readout
|
||||||
.result_extent_for_planes(0..1)?
|
.result_extent_for_planes(0..1)?
|
||||||
@@ -1341,7 +1645,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
result: &mut DecodingSampleBuffer,
|
result: &mut DecodingSampleBuffer,
|
||||||
) -> TiffResult<BufferLayoutPreference> {
|
) -> TiffResult<BufferLayoutPreference> {
|
||||||
let readout = self.image.readout_for_image()?;
|
let readout = self.ensure_image()?.readout_for_image()?;
|
||||||
let planes = readout.to_plane_layout()?;
|
let planes = readout.to_plane_layout()?;
|
||||||
|
|
||||||
let num_planes = if planes.total_bytes <= self.value_reader.limits.decoding_buffer_size {
|
let num_planes = if planes.total_bytes <= self.value_reader.limits.decoding_buffer_size {
|
||||||
@@ -1372,7 +1676,8 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
/// Returns an error if the buffer fits less than one plane. In particular, for non-planar
|
/// Returns an error if the buffer fits less than one plane. In particular, for non-planar
|
||||||
/// images returns an error if the buffer does not fit the required size.
|
/// images returns an error if the buffer does not fit the required size.
|
||||||
pub fn read_image_bytes(&mut self, buffer: &mut [u8]) -> TiffResult<()> {
|
pub fn read_image_bytes(&mut self, buffer: &mut [u8]) -> TiffResult<()> {
|
||||||
let readout = self.image.readout_for_image()?;
|
let (image, value_reader) = self.ensure_image_and_reader()?;
|
||||||
|
let readout = image.readout_for_image()?;
|
||||||
|
|
||||||
let ref layout @ image::PlaneLayout {
|
let ref layout @ image::PlaneLayout {
|
||||||
ref plane_offsets,
|
ref plane_offsets,
|
||||||
@@ -1396,10 +1701,11 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
|
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
|
||||||
let chunk = chunk + idx as u32 * readout.chunks_per_plane;
|
let chunk = chunk + idx as u32 * readout.chunks_per_plane;
|
||||||
self.goto_offset_u64(self.image.chunk_offsets[chunk as usize])?;
|
let offset = image.chunk_offsets[chunk as usize];
|
||||||
|
value_reader.reader.goto_offset(offset)?;
|
||||||
|
|
||||||
self.image.expand_chunk(
|
image.expand_chunk(
|
||||||
&mut self.value_reader,
|
value_reader,
|
||||||
&mut buffer[plane_offset..][buffer_offset..],
|
&mut buffer[plane_offset..][buffer_offset..],
|
||||||
readout,
|
readout,
|
||||||
chunk,
|
chunk,
|
||||||
@@ -1409,70 +1715,28 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the IFD decoder for our current IFD.
|
|
||||||
///
|
|
||||||
/// This may be used with any directory, valid image or otherwise. When the decoder has not
|
|
||||||
/// read a directory yet (a state after [`TiffHeader::open`]) the decoder will represent an
|
|
||||||
/// empty directory with only the next pointer set.
|
|
||||||
pub fn current_ifd(&mut self) -> IfdDecoder<'_> {
|
|
||||||
IfdDecoder {
|
|
||||||
inner: tag_reader::TagReader {
|
|
||||||
decoder: &mut self.value_reader,
|
|
||||||
ifd: &self.directory,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prepare reading values for tags of a given directory.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// This method may be used to read the values of tags in directories that have been previously
|
|
||||||
/// read with [`Decoder::read_directory`].
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// use tiff::decoder::Decoder;
|
|
||||||
/// use tiff::tags::Tag;
|
|
||||||
///
|
|
||||||
/// # use std::io::Cursor;
|
|
||||||
/// # let mut data = Cursor::new(vec![0]);
|
|
||||||
/// let mut decoder = Decoder::open(&mut data)?;
|
|
||||||
///
|
|
||||||
/// decoder.next_directory()?;
|
|
||||||
/// let sub_ifds = decoder.get_tag(Tag::SubIfd)?.into_ifd_vec()?;
|
|
||||||
///
|
|
||||||
/// for ifd in sub_ifds {
|
|
||||||
/// let subdir = decoder.read_directory(ifd)?;
|
|
||||||
/// let subfile = decoder.read_directory_tags(&subdir).find_tag(Tag::SubfileType)?;
|
|
||||||
/// // omitted: handle the subfiles, e.g. thumbnails
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// # Ok::<_, tiff::TiffError>(())
|
|
||||||
/// ```
|
|
||||||
pub fn read_directory_tags<'ifd>(&'ifd mut self, ifd: &'ifd Directory) -> IfdDecoder<'ifd> {
|
|
||||||
IfdDecoder {
|
|
||||||
inner: tag_reader::TagReader {
|
|
||||||
decoder: &mut self.value_reader,
|
|
||||||
ifd,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[allow(deprecated)]
|
||||||
|
impl<R: Read + Seek> Decoder<R> {
|
||||||
/// Tries to retrieve a tag from the current image directory.
|
/// Tries to retrieve a tag from the current image directory.
|
||||||
/// Return `Ok(None)` if the tag is not present.
|
/// Return `Ok(None)` if the tag is not present.
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().find_tag`"]
|
||||||
pub fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<ifd::Value>> {
|
pub fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<ifd::Value>> {
|
||||||
self.current_ifd().find_tag(tag)
|
self.current_ifd().find_tag(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to retrieve a tag in the current image directory and convert it to the desired
|
/// Tries to retrieve a tag in the current image directory and convert it to the desired
|
||||||
/// unsigned type.
|
/// unsigned type.
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().find_tag_unsigned`"]
|
||||||
pub fn find_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<T>> {
|
pub fn find_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<T>> {
|
||||||
self.current_ifd().find_tag_unsigned(tag)
|
self.current_ifd().find_tag_unsigned(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to retrieve a vector of all a tag's values and convert them to the desired unsigned
|
/// Tries to retrieve a vector of all a tag's values and convert them to the desired unsigned
|
||||||
/// type.
|
/// type.
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().find_tag_unsigned_vec`"]
|
||||||
pub fn find_tag_unsigned_vec<T: TryFrom<u64>>(
|
pub fn find_tag_unsigned_vec<T: TryFrom<u64>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
tag: Tag,
|
tag: Tag,
|
||||||
@@ -1482,67 +1746,81 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
/// Tries to retrieve a tag from the current image directory and convert it to the desired
|
/// Tries to retrieve a tag from the current image directory and convert it to the desired
|
||||||
/// unsigned type. Returns an error if the tag is not present.
|
/// unsigned type. Returns an error if the tag is not present.
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().get_tag_unsigned`"]
|
||||||
pub fn get_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<T> {
|
pub fn get_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<T> {
|
||||||
self.current_ifd().get_tag_unsigned(tag)
|
self.current_ifd().get_tag_unsigned(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to retrieve a tag from the current image directory.
|
/// Tries to retrieve a tag from the current image directory.
|
||||||
/// Returns an error if the tag is not present
|
/// Returns an error if the tag is not present
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().get_tag`"]
|
||||||
pub fn get_tag(&mut self, tag: Tag) -> TiffResult<ifd::Value> {
|
pub fn get_tag(&mut self, tag: Tag) -> TiffResult<ifd::Value> {
|
||||||
self.current_ifd().get_tag(tag)
|
self.current_ifd().get_tag(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().get_tag_u32`"]
|
||||||
pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult<u32> {
|
pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult<u32> {
|
||||||
self.get_tag(tag)?.into_u32()
|
self.get_tag(tag)?.into_u32()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().get_tag_u64`"]
|
||||||
pub fn get_tag_u64(&mut self, tag: Tag) -> TiffResult<u64> {
|
pub fn get_tag_u64(&mut self, tag: Tag) -> TiffResult<u64> {
|
||||||
self.get_tag(tag)?.into_u64()
|
self.get_tag(tag)?.into_u64()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to retrieve a tag and convert it to the desired type.
|
/// Tries to retrieve a tag and convert it to the desired type.
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().get_tag_f32`"]
|
||||||
pub fn get_tag_f32(&mut self, tag: Tag) -> TiffResult<f32> {
|
pub fn get_tag_f32(&mut self, tag: Tag) -> TiffResult<f32> {
|
||||||
self.get_tag(tag)?.into_f32()
|
self.get_tag(tag)?.into_f32()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to retrieve a tag and convert it to the desired type.
|
/// Tries to retrieve a tag and convert it to the desired type.
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().get_tag_f64`"]
|
||||||
pub fn get_tag_f64(&mut self, tag: Tag) -> TiffResult<f64> {
|
pub fn get_tag_f64(&mut self, tag: Tag) -> TiffResult<f64> {
|
||||||
self.get_tag(tag)?.into_f64()
|
self.get_tag(tag)?.into_f64()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to retrieve a tag and convert it to the desired type.
|
/// Tries to retrieve a tag and convert it to the desired type.
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().get_tag_u32_vec`"]
|
||||||
pub fn get_tag_u32_vec(&mut self, tag: Tag) -> TiffResult<Vec<u32>> {
|
pub fn get_tag_u32_vec(&mut self, tag: Tag) -> TiffResult<Vec<u32>> {
|
||||||
self.get_tag(tag)?.into_u32_vec()
|
self.get_tag(tag)?.into_u32_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().get_tag_u16_vec`"]
|
||||||
pub fn get_tag_u16_vec(&mut self, tag: Tag) -> TiffResult<Vec<u16>> {
|
pub fn get_tag_u16_vec(&mut self, tag: Tag) -> TiffResult<Vec<u16>> {
|
||||||
self.get_tag(tag)?.into_u16_vec()
|
self.get_tag(tag)?.into_u16_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().get_tag_u64_vec`"]
|
||||||
pub fn get_tag_u64_vec(&mut self, tag: Tag) -> TiffResult<Vec<u64>> {
|
pub fn get_tag_u64_vec(&mut self, tag: Tag) -> TiffResult<Vec<u64>> {
|
||||||
self.get_tag(tag)?.into_u64_vec()
|
self.get_tag(tag)?.into_u64_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to retrieve a tag and convert it to the desired type.
|
/// Tries to retrieve a tag and convert it to the desired type.
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().get_tag_f32_vec`"]
|
||||||
pub fn get_tag_f32_vec(&mut self, tag: Tag) -> TiffResult<Vec<f32>> {
|
pub fn get_tag_f32_vec(&mut self, tag: Tag) -> TiffResult<Vec<f32>> {
|
||||||
self.get_tag(tag)?.into_f32_vec()
|
self.get_tag(tag)?.into_f32_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to retrieve a tag and convert it to the desired type.
|
/// Tries to retrieve a tag and convert it to the desired type.
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().get_tag_f64_vec`"]
|
||||||
pub fn get_tag_f64_vec(&mut self, tag: Tag) -> TiffResult<Vec<f64>> {
|
pub fn get_tag_f64_vec(&mut self, tag: Tag) -> TiffResult<Vec<f64>> {
|
||||||
self.get_tag(tag)?.into_f64_vec()
|
self.get_tag(tag)?.into_f64_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to retrieve a tag and convert it to a 8bit vector.
|
/// Tries to retrieve a tag and convert it to a 8bit vector.
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().get_tag_u8_vec`"]
|
||||||
pub fn get_tag_u8_vec(&mut self, tag: Tag) -> TiffResult<Vec<u8>> {
|
pub fn get_tag_u8_vec(&mut self, tag: Tag) -> TiffResult<Vec<u8>> {
|
||||||
self.get_tag(tag)?.into_u8_vec()
|
self.get_tag(tag)?.into_u8_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to retrieve a tag and convert it to a ascii vector.
|
/// Tries to retrieve a tag and convert it to a ascii vector.
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().get_tag_ascii_string`"]
|
||||||
pub fn get_tag_ascii_string(&mut self, tag: Tag) -> TiffResult<String> {
|
pub fn get_tag_ascii_string(&mut self, tag: Tag) -> TiffResult<String> {
|
||||||
self.get_tag(tag)?.into_string()
|
self.get_tag(tag)?.into_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Replace with `self.current_ifd().tag_iter`"]
|
||||||
pub fn tag_iter(&mut self) -> impl Iterator<Item = TiffResult<(Tag, ifd::Value)>> + '_ {
|
pub fn tag_iter(&mut self) -> impl Iterator<Item = TiffResult<(Tag, ifd::Value)>> + '_ {
|
||||||
self.current_ifd().tag_iter()
|
self.current_ifd().tag_iter()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use crate::{
|
|||||||
/// [`Decoder::read_directory_tags`](crate::decoder::Decoder::read_directory_tags) to read the
|
/// [`Decoder::read_directory_tags`](crate::decoder::Decoder::read_directory_tags) to read the
|
||||||
/// values associated with tags from an underlying file.
|
/// values associated with tags from an underlying file.
|
||||||
#[doc(alias = "IFD")]
|
#[doc(alias = "IFD")]
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Directory {
|
pub struct Directory {
|
||||||
/// There are at most `u16::MAX` entries in any single directory, the count is stored as a
|
/// There are at most `u16::MAX` entries in any single directory, the count is stored as a
|
||||||
/// 2-byte value. The order in the file is implied to be ascending by tag value (the decoder
|
/// 2-byte value. The order in the file is implied to be ascending by tag value (the decoder
|
||||||
|
|||||||
+2
-11
@@ -28,8 +28,6 @@ use self::compression::Compression as Comp;
|
|||||||
use self::compression::*;
|
use self::compression::*;
|
||||||
use self::writer::*;
|
use self::writer::*;
|
||||||
|
|
||||||
pub use self::writer::TiffWriter;
|
|
||||||
|
|
||||||
/// Type of prediction to prepare the image with.
|
/// Type of prediction to prepare the image with.
|
||||||
///
|
///
|
||||||
/// Image data can be very unpredictable, and thus very hard to compress. Predictors are simple
|
/// Image data can be very unpredictable, and thus very hard to compress. Predictors are simple
|
||||||
@@ -187,14 +185,6 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to a writer for raw values.
|
|
||||||
///
|
|
||||||
/// This can be used to write custom data sections to the underlying writer that may or may not
|
|
||||||
/// be referenced in the IFDs of the tiff structure.
|
|
||||||
pub fn get_mut(&mut self) -> &mut TiffWriter<W> {
|
|
||||||
&mut self.writer
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a [`DirectoryEncoder`] to encode an ifd directory.
|
/// Create a [`DirectoryEncoder`] to encode an ifd directory.
|
||||||
#[deprecated = "`image_directory` replaced the old behavior and clarifies the intent"]
|
#[deprecated = "`image_directory` replaced the old behavior and clarifies the intent"]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
@@ -498,7 +488,8 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
|||||||
self.writer.write_u16(entry.field_type().to_u16())?;
|
self.writer.write_u16(entry.field_type().to_u16())?;
|
||||||
let count = K::convert_offset(entry.count())?;
|
let count = K::convert_offset(entry.count())?;
|
||||||
count.write(self.writer)?;
|
count.write(self.writer)?;
|
||||||
self.writer.write_bytes(&entry.offset()[..offset_bytes])?;
|
self.writer
|
||||||
|
.write_bytes(&entry.offset_raw()[..offset_bytes])?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(offset)
|
Ok(offset)
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ pub fn write_bigtiff_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A writer that tracks offset and can compress.
|
|
||||||
pub struct TiffWriter<W> {
|
pub struct TiffWriter<W> {
|
||||||
writer: W,
|
writer: W,
|
||||||
offset: u64,
|
offset: u64,
|
||||||
@@ -62,7 +61,6 @@ impl<W: Write> TiffWriter<W> {
|
|||||||
self.compressor = compressor;
|
self.compressor = compressor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unset the compression.
|
|
||||||
pub fn reset_compression(&mut self) {
|
pub fn reset_compression(&mut self) {
|
||||||
self.compressor = Compressor::default();
|
self.compressor = Compressor::default();
|
||||||
}
|
}
|
||||||
@@ -75,7 +73,6 @@ impl<W: Write> TiffWriter<W> {
|
|||||||
self.offset() - core::mem::size_of::<K::OffsetType>() as u64
|
self.offset() - core::mem::size_of::<K::OffsetType>() as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the number of bytes written by the last write operation.
|
|
||||||
pub fn last_written(&self) -> u64 {
|
pub fn last_written(&self) -> u64 {
|
||||||
self.byte_count
|
self.byte_count
|
||||||
}
|
}
|
||||||
@@ -186,19 +183,6 @@ impl<W: Write> TiffWriter<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write uncompressed bytes to the raw underlying file.
|
|
||||||
impl<W: Write> std::io::Write for TiffWriter<W> {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
let n = self.writer.write(buf)?;
|
|
||||||
self.offset += n as u64;
|
|
||||||
Ok(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
self.writer.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Seek> TiffWriter<W> {
|
impl<W: Seek> TiffWriter<W> {
|
||||||
pub fn goto_offset(&mut self, offset: u64) -> Result<(), io::Error> {
|
pub fn goto_offset(&mut self, offset: u64) -> Result<(), io::Error> {
|
||||||
self.offset = offset;
|
self.offset = offset;
|
||||||
|
|||||||
@@ -567,6 +567,13 @@ impl ValueBuffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create an empty value buffer with `Byte` type. This type choice is arbitrary.
|
||||||
|
impl Default for ValueBuffer {
|
||||||
|
fn default() -> Self {
|
||||||
|
ValueBuffer::empty(Type::BYTE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Byte order of the TIFF file.
|
/// Byte order of the TIFF file.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum ByteOrder {
|
pub enum ByteOrder {
|
||||||
|
|||||||
@@ -26,21 +26,20 @@ fn test_big_tiff() {
|
|||||||
decoder.colortype().expect("Cannot get colortype"),
|
decoder.colortype().expect("Cannot get colortype"),
|
||||||
ColorType::RGB(8)
|
ColorType::RGB(8)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut ifd = decoder.current_ifd();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decoder
|
ifd.get_tag_u64(Tag::StripOffsets)
|
||||||
.get_tag_u64(Tag::StripOffsets)
|
|
||||||
.expect("Cannot get StripOffsets"),
|
.expect("Cannot get StripOffsets"),
|
||||||
16
|
16
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decoder
|
ifd.get_tag_u64(Tag::RowsPerStrip)
|
||||||
.get_tag_u64(Tag::RowsPerStrip)
|
|
||||||
.expect("Cannot get RowsPerStrip"),
|
.expect("Cannot get RowsPerStrip"),
|
||||||
64
|
64
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decoder
|
ifd.get_tag_u64(Tag::StripByteCounts)
|
||||||
.get_tag_u64(Tag::StripByteCounts)
|
|
||||||
.expect("Cannot get StripByteCounts"),
|
.expect("Cannot get StripByteCounts"),
|
||||||
12288
|
12288
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -30,27 +30,25 @@ fn test_geo_tiff() {
|
|||||||
num_samples: 5
|
num_samples: 5
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut ifd = decoder.current_ifd();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decoder
|
ifd.get_tag_u64(Tag::StripOffsets)
|
||||||
.get_tag_u64(Tag::StripOffsets)
|
|
||||||
.expect("Cannot get StripOffsets"),
|
.expect("Cannot get StripOffsets"),
|
||||||
418
|
418
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decoder
|
ifd.get_tag_u64(Tag::RowsPerStrip)
|
||||||
.get_tag_u64(Tag::RowsPerStrip)
|
|
||||||
.expect("Cannot get RowsPerStrip"),
|
.expect("Cannot get RowsPerStrip"),
|
||||||
10
|
10
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decoder
|
ifd.get_tag_u64(Tag::StripByteCounts)
|
||||||
.get_tag_u64(Tag::StripByteCounts)
|
|
||||||
.expect("Cannot get StripByteCounts"),
|
.expect("Cannot get StripByteCounts"),
|
||||||
1000
|
1000
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decoder
|
ifd.get_tag(Tag::ModelPixelScaleTag)
|
||||||
.get_tag(Tag::ModelPixelScaleTag)
|
|
||||||
.expect("Cannot get pixel scale")
|
.expect("Cannot get pixel scale")
|
||||||
.into_f64_vec()
|
.into_f64_vec()
|
||||||
.expect("Cannot get pixel scale"),
|
.expect("Cannot get pixel scale"),
|
||||||
|
|||||||
@@ -404,7 +404,10 @@ fn test_string_tags() {
|
|||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
let img_file = File::open(path).expect("Cannot find test image!");
|
||||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||||
decoder.next_image().expect("Cannot read image IFD");
|
decoder.next_image().expect("Cannot read image IFD");
|
||||||
let software = decoder.get_tag(tiff::tags::Tag::Software).unwrap();
|
let software = decoder
|
||||||
|
.current_ifd()
|
||||||
|
.get_tag(tiff::tags::Tag::Software)
|
||||||
|
.unwrap();
|
||||||
match software {
|
match software {
|
||||||
ifd::Value::Ascii(s) => assert_eq!(
|
ifd::Value::Ascii(s) => assert_eq!(
|
||||||
&s,
|
&s,
|
||||||
@@ -478,7 +481,8 @@ fn test_inner_access() {
|
|||||||
decoder.next_image().expect("Cannot read image IFD");
|
decoder.next_image().expect("Cannot read image IFD");
|
||||||
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||||
|
|
||||||
let c = decoder
|
let mut ifd = decoder.current_ifd();
|
||||||
|
let c = ifd
|
||||||
.get_tag(tiff::tags::Tag::Compression)
|
.get_tag(tiff::tags::Tag::Compression)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_u16()
|
.into_u16()
|
||||||
@@ -487,13 +491,13 @@ fn test_inner_access() {
|
|||||||
|
|
||||||
// Because the image is uncompressed, reading the first tile directly with the inner reader
|
// Because the image is uncompressed, reading the first tile directly with the inner reader
|
||||||
// should yield the same result as reading it with the decoder's read_chunk method.
|
// should yield the same result as reading it with the decoder's read_chunk method.
|
||||||
let first_offset = decoder
|
let first_offset = ifd
|
||||||
.get_tag_u32_vec(tiff::tags::Tag::TileOffsets)
|
.get_tag_u32_vec(tiff::tags::Tag::TileOffsets)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let first_byte_count = decoder
|
let first_byte_count = ifd
|
||||||
.get_tag_u32_vec(tiff::tags::Tag::TileByteCounts)
|
.get_tag_u32_vec(tiff::tags::Tag::TileByteCounts)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
+40
-11
@@ -40,6 +40,7 @@ fn encode_decode() {
|
|||||||
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
||||||
|
|
||||||
let mut all_tags = decoder
|
let mut all_tags = decoder
|
||||||
|
.current_ifd()
|
||||||
.tag_iter()
|
.tag_iter()
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -80,7 +81,7 @@ fn encode_decode() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decoder.get_tag(Tag::Artist).unwrap(),
|
decoder.current_ifd().get_tag(Tag::Artist).unwrap(),
|
||||||
ifd::Value::Ascii("Image-tiff".into())
|
ifd::Value::Ascii("Image-tiff".into())
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -125,6 +126,7 @@ fn encode_decode_big() {
|
|||||||
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
||||||
|
|
||||||
let mut all_tags = decoder
|
let mut all_tags = decoder
|
||||||
|
.current_ifd()
|
||||||
.tag_iter()
|
.tag_iter()
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -165,7 +167,7 @@ fn encode_decode_big() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decoder.get_tag(Tag::Artist).unwrap(),
|
decoder.current_ifd().get_tag(Tag::Artist).unwrap(),
|
||||||
ifd::Value::Ascii("Image-tiff".into())
|
ifd::Value::Ascii("Image-tiff".into())
|
||||||
);
|
);
|
||||||
let mut buffer = tiff::decoder::DecodingSampleBuffer::U8(vec![]);
|
let mut buffer = tiff::decoder::DecodingSampleBuffer::U8(vec![]);
|
||||||
@@ -539,37 +541,57 @@ trait AssertDecode {
|
|||||||
|
|
||||||
impl<R: std::io::Read + std::io::Seek> AssertDecode for Decoder<R> {
|
impl<R: std::io::Read + std::io::Seek> AssertDecode for Decoder<R> {
|
||||||
fn assert_tag_u32(&mut self, tag: u16) -> u32 {
|
fn assert_tag_u32(&mut self, tag: u16) -> u32 {
|
||||||
self.get_tag(Tag::Unknown(tag)).unwrap().into_u32().unwrap()
|
self.current_ifd()
|
||||||
|
.get_tag(Tag::Unknown(tag))
|
||||||
|
.unwrap()
|
||||||
|
.into_u32()
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32> {
|
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32> {
|
||||||
self.get_tag(Tag::Unknown(tag))
|
self.current_ifd()
|
||||||
|
.get_tag(Tag::Unknown(tag))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_u32_vec()
|
.into_u32_vec()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
fn assert_tag_i32(&mut self, tag: u16) -> i32 {
|
fn assert_tag_i32(&mut self, tag: u16) -> i32 {
|
||||||
self.get_tag(Tag::Unknown(tag)).unwrap().into_i32().unwrap()
|
self.current_ifd()
|
||||||
|
.get_tag(Tag::Unknown(tag))
|
||||||
|
.unwrap()
|
||||||
|
.into_i32()
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
fn assert_tag_i32_vec(&mut self, tag: u16) -> Vec<i32> {
|
fn assert_tag_i32_vec(&mut self, tag: u16) -> Vec<i32> {
|
||||||
self.get_tag(Tag::Unknown(tag))
|
self.current_ifd()
|
||||||
|
.get_tag(Tag::Unknown(tag))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_i32_vec()
|
.into_i32_vec()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
fn assert_tag_u64(&mut self, tag: u16) -> u64 {
|
fn assert_tag_u64(&mut self, tag: u16) -> u64 {
|
||||||
self.get_tag(Tag::Unknown(tag)).unwrap().into_u64().unwrap()
|
self.current_ifd()
|
||||||
|
.get_tag(Tag::Unknown(tag))
|
||||||
|
.unwrap()
|
||||||
|
.into_u64()
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
fn assert_tag_u64_vec(&mut self, tag: u16) -> Vec<u64> {
|
fn assert_tag_u64_vec(&mut self, tag: u16) -> Vec<u64> {
|
||||||
self.get_tag(Tag::Unknown(tag))
|
self.current_ifd()
|
||||||
|
.get_tag(Tag::Unknown(tag))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_u64_vec()
|
.into_u64_vec()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
fn assert_tag_i64(&mut self, tag: u16) -> i64 {
|
fn assert_tag_i64(&mut self, tag: u16) -> i64 {
|
||||||
self.get_tag(Tag::Unknown(tag)).unwrap().into_i64().unwrap()
|
self.current_ifd()
|
||||||
|
.get_tag(Tag::Unknown(tag))
|
||||||
|
.unwrap()
|
||||||
|
.into_i64()
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
fn assert_tag_i64_vec(&mut self, tag: u16) -> Vec<i64> {
|
fn assert_tag_i64_vec(&mut self, tag: u16) -> Vec<i64> {
|
||||||
self.get_tag(Tag::Unknown(tag))
|
self.current_ifd()
|
||||||
|
.get_tag(Tag::Unknown(tag))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_i64_vec()
|
.into_i64_vec()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -776,7 +798,13 @@ fn test_rows_per_strip() {
|
|||||||
{
|
{
|
||||||
let mut decoder = Decoder::open(&mut file).unwrap();
|
let mut decoder = Decoder::open(&mut file).unwrap();
|
||||||
decoder.next_image().unwrap();
|
decoder.next_image().unwrap();
|
||||||
assert_eq!(decoder.get_tag_u64(Tag::RowsPerStrip).unwrap(), 2);
|
assert_eq!(
|
||||||
|
decoder
|
||||||
|
.current_ifd()
|
||||||
|
.get_tag_u64(Tag::RowsPerStrip)
|
||||||
|
.unwrap(),
|
||||||
|
2
|
||||||
|
);
|
||||||
assert_eq!(decoder.strip_count().unwrap(), 50);
|
assert_eq!(decoder.strip_count().unwrap(), 50);
|
||||||
|
|
||||||
for i in 0..50 {
|
for i in 0..50 {
|
||||||
@@ -829,6 +857,7 @@ fn test_auxiliary_directory() {
|
|||||||
let _ = decoder.read_image().unwrap();
|
let _ = decoder.read_image().unwrap();
|
||||||
|
|
||||||
let exif_location = decoder
|
let exif_location = decoder
|
||||||
|
.current_ifd()
|
||||||
.get_tag(Tag::ExifDirectory)
|
.get_tag(Tag::ExifDirectory)
|
||||||
.expect("second directory missing Exif")
|
.expect("second directory missing Exif")
|
||||||
.into_ifd_pointer()
|
.into_ifd_pointer()
|
||||||
|
|||||||
Reference in new issue
Block a user