image icon indicating copy to clipboard operation
image copied to clipboard

Multi-page TIFF

Open vadixidav opened this issue 6 years ago • 3 comments

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.

vadixidav avatar Dec 12 '19 20:12 vadixidav

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.

birktj avatar Jan 04 '20 13:01 birktj

That is good to know that the tiff crate does support this, at least. Thanks for letting me know.

vadixidav avatar Jan 06 '20 00:01 vadixidav

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;

  1. 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 be self_into_reader and self_read_image_to_buffer to avoid naming issues. I also changed them to take &mut self instead of self or mut self. Then update the TiffDecoder impl of into_reader and read_image to use those methods

  2. Add the following struct, and implement the Decoder trait for it. Most of the time the method calls are just self.inner.method(...), but for into_reader and read_image I had to call self.inner.self_into_reader() and self.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>,
}
  1. 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 new method logic into its own method that also takes an instantiated tiff::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(),
        })
    }  

samsieber avatar Nov 01 '23 17:11 samsieber