Multi-page TIFF
I would like to be able to read multi-page TIFF files.
TIFF files may contain multiple images in them (called pages or subfiles). See this Wikipedia section for some details.
The TiffDecoder should implement AnimationDecoder the same way GifDecoder does to read multiple images from the file. Each image may be a different resolution in a TIFF.
If you want to do this today you can use the tiff crate directly, I think it may take some time before this functionality is implemented into this crate.
That is good to know that the tiff crate does support this, at least. Thanks for letting me know.
I implemented this locally with a vendored copy of src/codecs/tiff.rs (I wanted to get it working locally fairly quickly, and it has other things like decoding bilevel to L8), and I'd like to help upstream it.
My general approach needed three basic changes, and I'm not sure if this is the right approach, but it seems to work OK for me;
-
Copy the implementations of
fn into_reader(mut self) -> ImageResult<Self::Reader>,fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()>(for TiffDecoder<R>) into inherent methods. I renamed them to beself_into_readerandself_read_image_to_bufferto avoid naming issues. I also changed them to take&mut selfinstead ofselformut self. Then update the TiffDecoder impl of into_reader and read_image to use those methods -
Add the following struct, and implement the Decoder trait for it. Most of the time the method calls are just
self.inner.method(...), but forinto_readerandread_imageI had to callself.inner.self_into_reader()andself.inner.self_read_image_to_buffer. That's why doing step one was necessary.
struct TiffDecoderHandle<'b: 'a, 'a, R: 'a+Read+Seek> {
inner: &'b mut TiffDecoder<R>,
phantom: PhantomData<&'a R>,
}
- Then we add a few inherent methods to the TiffDecoder allowing us to pluck pages as necessary, or setting up which page we want to start with. I moved the
newmethod logic into its own method that also takes an instantiatedtiff::Decoder. That allows for adding a new_with_page_number. And the other bit is adding methods to convert a TiffDecoder directly into a DynamicImage:
/// Create a new TiffDecoder.
pub fn new(r: R) -> Result<Self, ImageError> {
let inner = tiff::decoder::Decoder::new(r).map_err(image_error_from_tiff_decode)?;
Self::from_tiff_decoder(inner)
}
pub fn new_at_page(r: R, page_idx: usize) -> Result<Self, ImageError> {
let mut inner = tiff::decoder::Decoder::new(r).map_err(image_error_from_tiff_decode)?;
inner.seek_to_image(page_idx).map_err(|e| ImageError::Unsupported(UnsupportedError::from_format_and_kind(
ImageFormat::Tiff.into(),
UnsupportedErrorKind::GenericFeature(format!("{}", e)),
)))?;
Self::from_tiff_decoder(inner)
}
pub fn seek_page(&mut self, page_idx: usize) -> Result<(), ImageError> {
let mut inner = self.inner.take().unwrap();
inner.seek_to_image(page_idx).map_err(|e| ImageError::Unsupported(UnsupportedError::from_format_and_kind(
ImageFormat::Tiff.into(),
UnsupportedErrorKind::GenericFeature(format!("{}", e)),
)))?;
let mut new = Self::from_tiff_decoder(inner)?;
self.dimensions = new.dimensions;
self.color_type = new.color_type;
self.inner = new.inner.take();
Ok(())
}
pub fn has_next_page(&mut self) -> ImageResult<bool>{
Ok(self.inner.as_mut().unwrap().more_images())
}
pub fn to_next_page(&mut self) -> ImageResult<()>{
Ok(self.inner.as_mut().unwrap().next_image().unwrap())
}
pub fn read_page(&mut self) -> ImageResult<DynamicImage> {
DynamicImage::from_decoder(TiffDecoderHandle {
inner: self,
phantom: Default::default(),
})
}