Move image loading to Image::from_reader
This commit is contained in:
1 parent
b385a6e752
commit
47080293db
3 files changed
+251
-139
No files matched your search
+193
-6
@@ -1,8 +1,11 @@
|
|||||||
use super::ChunkType;
|
use super::ifd::{Directory, Value};
|
||||||
use super::ifd::Directory;
|
use super::tag_reader::TagReader;
|
||||||
use crate::tags::{
|
use super::Limits;
|
||||||
CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat,
|
use super::{stream::SmartReader, ChunkType};
|
||||||
};
|
use crate::tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag};
|
||||||
|
use crate::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
|
||||||
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct StripDecodeState {
|
pub(crate) struct StripDecodeState {
|
||||||
@@ -92,5 +95,189 @@ pub(crate) struct Image {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Image {
|
impl Image {
|
||||||
|
pub fn from_reader<R: Read + Seek>(
|
||||||
|
reader: &mut SmartReader<R>,
|
||||||
|
ifd: Directory,
|
||||||
|
limits: &Limits,
|
||||||
|
bigtiff: bool,
|
||||||
|
) -> TiffResult<Image> {
|
||||||
|
let mut tag_reader = TagReader {
|
||||||
|
reader,
|
||||||
|
limits,
|
||||||
|
ifd: &ifd,
|
||||||
|
bigtiff,
|
||||||
|
};
|
||||||
|
|
||||||
}
|
let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
|
||||||
|
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
|
||||||
|
|
||||||
|
let photometric_interpretation = tag_reader
|
||||||
|
.find_tag(Tag::PhotometricInterpretation)?
|
||||||
|
.map(Value::into_u16)
|
||||||
|
.transpose()?
|
||||||
|
.and_then(PhotometricInterpretation::from_u16)
|
||||||
|
.ok_or(TiffUnsupportedError::UnknownInterpretation)?;
|
||||||
|
|
||||||
|
// Try to parse both the compression method and the number, format, and bits of the included samples.
|
||||||
|
// If they are not explicitly specified, those tags are reset to their default values and not carried from previous images.
|
||||||
|
let compression_method = match tag_reader.find_tag(Tag::Compression)? {
|
||||||
|
Some(val) => CompressionMethod::from_u16(val.into_u16()?)
|
||||||
|
.ok_or(TiffUnsupportedError::UnknownCompressionMethod)?,
|
||||||
|
None => CompressionMethod::None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG
|
||||||
|
&& ifd.contains_key(&Tag::JPEGTables)
|
||||||
|
{
|
||||||
|
let vec = tag_reader
|
||||||
|
.find_tag(Tag::JPEGTables)?
|
||||||
|
.unwrap()
|
||||||
|
.into_u8_vec()?;
|
||||||
|
if vec.len() < 2 {
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Some(vec)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let samples = tag_reader
|
||||||
|
.find_tag(Tag::SamplesPerPixel)?
|
||||||
|
.map(Value::into_u16)
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or(1)
|
||||||
|
.try_into()?;
|
||||||
|
|
||||||
|
let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? {
|
||||||
|
Some(vals) => {
|
||||||
|
let sample_format: Vec<_> = vals
|
||||||
|
.into_iter()
|
||||||
|
.map(SampleFormat::from_u16_exhaustive)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// TODO: for now, only homogenous formats across samples are supported.
|
||||||
|
if !sample_format.windows(2).all(|s| s[0] == s[1]) {
|
||||||
|
return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
sample_format
|
||||||
|
}
|
||||||
|
None => vec![SampleFormat::Uint],
|
||||||
|
};
|
||||||
|
|
||||||
|
let bits_per_sample = match samples {
|
||||||
|
1 | 3 | 4 => tag_reader
|
||||||
|
.find_tag_uint_vec(Tag::BitsPerSample)?
|
||||||
|
.unwrap_or_else(|| vec![1]),
|
||||||
|
_ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(samples).into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let predictor = tag_reader
|
||||||
|
.find_tag(Tag::Predictor)?
|
||||||
|
.map(Value::into_u16)
|
||||||
|
.transpose()?
|
||||||
|
.map(|p| {
|
||||||
|
Predictor::from_u16(p)
|
||||||
|
.ok_or(TiffError::FormatError(TiffFormatError::UnknownPredictor(p)))
|
||||||
|
})
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or(Predictor::None);
|
||||||
|
|
||||||
|
let chunk_type;
|
||||||
|
let chunk_offsets;
|
||||||
|
let chunk_bytes;
|
||||||
|
let strip_decoder;
|
||||||
|
let tile_attributes;
|
||||||
|
match (
|
||||||
|
ifd.contains_key(&Tag::StripByteCounts),
|
||||||
|
ifd.contains_key(&Tag::StripOffsets),
|
||||||
|
ifd.contains_key(&Tag::TileByteCounts),
|
||||||
|
ifd.contains_key(&Tag::TileOffsets),
|
||||||
|
) {
|
||||||
|
(true, true, false, false) => {
|
||||||
|
chunk_type = ChunkType::Strip;
|
||||||
|
|
||||||
|
chunk_offsets = tag_reader
|
||||||
|
.find_tag(Tag::StripOffsets)?
|
||||||
|
.unwrap()
|
||||||
|
.into_u64_vec()?;
|
||||||
|
chunk_bytes = tag_reader
|
||||||
|
.find_tag(Tag::StripByteCounts)?
|
||||||
|
.unwrap()
|
||||||
|
.into_u64_vec()?;
|
||||||
|
let rows_per_strip = tag_reader
|
||||||
|
.find_tag(Tag::RowsPerStrip)?
|
||||||
|
.map(Value::into_u32)
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or(height);
|
||||||
|
strip_decoder = Some(StripDecodeState { rows_per_strip });
|
||||||
|
tile_attributes = None;
|
||||||
|
}
|
||||||
|
(false, false, true, true) => {
|
||||||
|
chunk_type = ChunkType::Tile;
|
||||||
|
|
||||||
|
let tile_width =
|
||||||
|
usize::try_from(tag_reader.require_tag(Tag::TileWidth)?.into_u32()?)?;
|
||||||
|
let tile_length =
|
||||||
|
usize::try_from(tag_reader.require_tag(Tag::TileLength)?.into_u32()?)?;
|
||||||
|
|
||||||
|
if tile_width == 0 {
|
||||||
|
return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into());
|
||||||
|
} else if tile_length == 0 {
|
||||||
|
return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
strip_decoder = None;
|
||||||
|
tile_attributes = Some(TileAttributes {
|
||||||
|
image_width: usize::try_from(width)?,
|
||||||
|
image_height: usize::try_from(height)?,
|
||||||
|
samples_per_pixel: bits_per_sample.len(),
|
||||||
|
tile_width,
|
||||||
|
tile_length,
|
||||||
|
});
|
||||||
|
chunk_offsets = tag_reader
|
||||||
|
.find_tag(Tag::TileOffsets)?
|
||||||
|
.unwrap()
|
||||||
|
.into_u64_vec()?;
|
||||||
|
chunk_bytes = tag_reader
|
||||||
|
.find_tag(Tag::TileByteCounts)?
|
||||||
|
.unwrap()
|
||||||
|
.into_u64_vec()?;
|
||||||
|
|
||||||
|
let tile = tile_attributes.as_ref().unwrap();
|
||||||
|
if chunk_offsets.len() != chunk_bytes.len()
|
||||||
|
|| chunk_offsets.len() != tile.tiles_down() * tile.tiles_across()
|
||||||
|
{
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(_, _, _, _) => {
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::StripTileTagConflict,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Image {
|
||||||
|
ifd: Some(ifd),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
bits_per_sample,
|
||||||
|
samples,
|
||||||
|
sample_format,
|
||||||
|
photometric_interpretation,
|
||||||
|
compression_method,
|
||||||
|
jpeg_tables,
|
||||||
|
predictor,
|
||||||
|
chunk_type,
|
||||||
|
strip_decoder,
|
||||||
|
tile_attributes,
|
||||||
|
chunk_offsets,
|
||||||
|
chunk_bytes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-133
@@ -8,7 +8,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use self::ifd::Directory;
|
use self::ifd::Directory;
|
||||||
use self::image::{Image, StripDecodeState, TileAttributes};
|
use self::image::Image;
|
||||||
use crate::tags::{
|
use crate::tags::{
|
||||||
CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type,
|
CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type,
|
||||||
};
|
};
|
||||||
@@ -20,6 +20,7 @@ use self::stream::{
|
|||||||
pub mod ifd;
|
pub mod ifd;
|
||||||
mod image;
|
mod image;
|
||||||
mod stream;
|
mod stream;
|
||||||
|
mod tag_reader;
|
||||||
|
|
||||||
/// Result of a decoding process
|
/// Result of a decoding process
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -594,139 +595,15 @@ 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<()> {
|
||||||
|
let ifd = self.read_ifd()?;
|
||||||
|
|
||||||
self.current_chunk = 0;
|
self.current_chunk = 0;
|
||||||
|
self.image = Image::from_reader(
|
||||||
self.image.ifd = Some(self.read_ifd()?);
|
&mut self.reader,
|
||||||
self.image.width = self.get_tag_u32(Tag::ImageWidth)?;
|
ifd,
|
||||||
self.image.height = self.get_tag_u32(Tag::ImageLength)?;
|
&self.limits,
|
||||||
self.image.strip_decoder = None;
|
self.bigtiff
|
||||||
self.image.tile_attributes = None;
|
)?;
|
||||||
self.image.chunk_offsets.clear();
|
|
||||||
self.image.chunk_bytes.clear();
|
|
||||||
self.image.jpeg_tables = None;
|
|
||||||
|
|
||||||
self.image.photometric_interpretation = self
|
|
||||||
.find_tag_unsigned(Tag::PhotometricInterpretation)?
|
|
||||||
.and_then(PhotometricInterpretation::from_u16)
|
|
||||||
.ok_or(TiffUnsupportedError::UnknownInterpretation)?;
|
|
||||||
|
|
||||||
// Try to parse both the compression method and the number, format, and bits of the included samples.
|
|
||||||
// If they are not explicitly specified, those tags are reset to their default values and not carried from previous images.
|
|
||||||
self.image.compression_method = match self.find_tag_unsigned(Tag::Compression)? {
|
|
||||||
Some(val) => CompressionMethod::from_u16(val)
|
|
||||||
.ok_or(TiffUnsupportedError::UnknownCompressionMethod)?,
|
|
||||||
None => CompressionMethod::None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.image.compression_method == CompressionMethod::ModernJPEG {
|
|
||||||
self.image.jpeg_tables = self
|
|
||||||
.find_tag(Tag::JPEGTables)?
|
|
||||||
.map(|_| {
|
|
||||||
let vec = self.get_tag_u8_vec(Tag::JPEGTables)?;
|
|
||||||
if vec.len() < 2 {
|
|
||||||
return Err(TiffError::FormatError(
|
|
||||||
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(vec)
|
|
||||||
})
|
|
||||||
.transpose()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.image.samples = self.find_tag_unsigned(Tag::SamplesPerPixel)?.unwrap_or(1);
|
|
||||||
|
|
||||||
self.image.sample_format = match self.find_tag_unsigned_vec(Tag::SampleFormat)? {
|
|
||||||
Some(vals) => {
|
|
||||||
let sample_format: Vec<_> = vals
|
|
||||||
.into_iter()
|
|
||||||
.map(SampleFormat::from_u16_exhaustive)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// TODO: for now, only homogenous formats across samples are supported.
|
|
||||||
if !sample_format.windows(2).all(|s| s[0] == s[1]) {
|
|
||||||
return Err(TiffUnsupportedError::UnsupportedSampleFormat(
|
|
||||||
self.image.sample_format.clone(),
|
|
||||||
)
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
sample_format
|
|
||||||
}
|
|
||||||
None => vec![SampleFormat::Uint],
|
|
||||||
};
|
|
||||||
|
|
||||||
self.image.bits_per_sample = match self.image.samples {
|
|
||||||
1 | 3 | 4 => self
|
|
||||||
.find_tag_unsigned_vec(Tag::BitsPerSample)?
|
|
||||||
.unwrap_or_else(|| vec![1]),
|
|
||||||
_ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(self.image.samples).into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.image.predictor = self
|
|
||||||
.find_tag_unsigned(Tag::Predictor)?
|
|
||||||
.map(|p| {
|
|
||||||
Predictor::from_u16(p)
|
|
||||||
.ok_or(TiffError::FormatError(TiffFormatError::UnknownPredictor(p)))
|
|
||||||
})
|
|
||||||
.transpose()?
|
|
||||||
.unwrap_or(Predictor::None);
|
|
||||||
|
|
||||||
let ifd = self.image.ifd.as_ref().unwrap();
|
|
||||||
match (
|
|
||||||
ifd.contains_key(&Tag::StripByteCounts),
|
|
||||||
ifd.contains_key(&Tag::StripOffsets),
|
|
||||||
ifd.contains_key(&Tag::TileByteCounts),
|
|
||||||
ifd.contains_key(&Tag::TileOffsets),
|
|
||||||
) {
|
|
||||||
(true, true, false, false) => {
|
|
||||||
self.image.chunk_type = ChunkType::Strip;
|
|
||||||
|
|
||||||
self.image.chunk_offsets = self.get_tag_u64_vec(Tag::StripOffsets)?;
|
|
||||||
self.image.chunk_bytes = self.get_tag_u64_vec(Tag::StripByteCounts)?;
|
|
||||||
let rows_per_strip = self
|
|
||||||
.find_tag(Tag::RowsPerStrip)?
|
|
||||||
.unwrap_or(ifd::Value::Unsigned(self.image.height))
|
|
||||||
.into_u32()?;
|
|
||||||
self.image.strip_decoder = Some(StripDecodeState { rows_per_strip });
|
|
||||||
}
|
|
||||||
(false, false, true, true) => {
|
|
||||||
self.image.chunk_type = ChunkType::Tile;
|
|
||||||
|
|
||||||
let tile_width = usize::try_from(self.get_tag_u32(Tag::TileWidth)?)?;
|
|
||||||
let tile_length = usize::try_from(self.get_tag_u32(Tag::TileLength)?)?;
|
|
||||||
|
|
||||||
if tile_width == 0 {
|
|
||||||
return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into());
|
|
||||||
} else if tile_length == 0 {
|
|
||||||
return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.image.tile_attributes = Some(TileAttributes {
|
|
||||||
image_width: usize::try_from(self.image.width)?,
|
|
||||||
image_height: usize::try_from(self.image.height)?,
|
|
||||||
samples_per_pixel: self.image.bits_per_sample.len(),
|
|
||||||
tile_width,
|
|
||||||
tile_length,
|
|
||||||
});
|
|
||||||
self.image.chunk_offsets = self.get_tag_u64_vec(Tag::TileOffsets)?;
|
|
||||||
self.image.chunk_bytes = self.get_tag_u64_vec(Tag::TileByteCounts)?;
|
|
||||||
|
|
||||||
let tile = self.image.tile_attributes.as_ref().unwrap();
|
|
||||||
if self.image.chunk_offsets.len() != self.image.chunk_bytes.len()
|
|
||||||
|| self.image.chunk_offsets.len() != tile.tiles_down() * tile.tiles_across()
|
|
||||||
{
|
|
||||||
return Err(TiffError::FormatError(
|
|
||||||
TiffFormatError::InconsistentSizesEncountered,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(_, _, _, _) => {
|
|
||||||
return Err(TiffError::FormatError(
|
|
||||||
TiffFormatError::StripTileTagConflict,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
|
use crate::tags::Tag;
|
||||||
|
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||||
|
|
||||||
|
use super::ifd::{Directory, Value};
|
||||||
|
use super::stream::SmartReader;
|
||||||
|
use super::Limits;
|
||||||
|
|
||||||
|
pub(crate) struct TagReader<'a, R: Read + Seek> {
|
||||||
|
pub reader: &'a mut SmartReader<R>,
|
||||||
|
pub ifd: &'a Directory,
|
||||||
|
pub limits: &'a Limits,
|
||||||
|
pub bigtiff: bool,
|
||||||
|
}
|
||||||
|
impl<'a, R: Read + Seek> TagReader<'a, R> {
|
||||||
|
pub(crate) fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<Value>> {
|
||||||
|
Ok(match self.ifd.get(&tag) {
|
||||||
|
Some(entry) => Some(entry.clone().val(self.limits, self.bigtiff, self.reader)?),
|
||||||
|
None => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub(crate) fn require_tag(&mut self, tag: Tag) -> TiffResult<Value> {
|
||||||
|
match self.find_tag(tag)? {
|
||||||
|
Some(val) => Ok(val),
|
||||||
|
None => Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::RequiredTagNotFound(tag),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn find_tag_uint_vec<T: TryFrom<u64>>(
|
||||||
|
&mut self,
|
||||||
|
tag: Tag,
|
||||||
|
) -> TiffResult<Option<Vec<T>>> {
|
||||||
|
self.find_tag(tag)?
|
||||||
|
.map(|v| v.into_u64_vec())
|
||||||
|
.transpose()?
|
||||||
|
.map(|v| {
|
||||||
|
v.into_iter()
|
||||||
|
.map(|u| {
|
||||||
|
T::try_from(u).map_err(|_| TiffFormatError::InvalidTagValueType(tag).into())
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user