Repository: image-rs/image-tiff
Pull request #163 (2022-05-02 06:57:43)

Add ability to select image directory and tile using index
Currently it is only possible to cycle through the image directories and the tiles in order. For handling WSI files (e.g. .svs), where the image data is stored as a 'pyramid' (each image directory has a copy of the image at a different resolution), I need to be able to select which tiles (part of an image) and which image directory (which resolution) I want to access. 

This PR adds in this functionality, and can be used as so:

```rust
// Select the image directory at the third position (4th directory) in the linked list
decoder.image_at(3)?;

let tiles = decoder.tile_count()?;
let (width, height) = decoder.dimensions()?;

println!("# tiles {}", tiles);

for tile in 0..tiles {
    // As some tiles (on the boundaries) do not have the same size 
    // as the rest (due to padding) we need to be able to determine its size
    let chunk_info = decoder.chunk_info(tile as usize)?;

    match decoder.read_tile_at(tile as usize)? {
            DecodingResult::U8(res) => {
                // Do some thing with the tile, e.g. save out as a .png 
                image::save_buffer(
                    format!("image_{}.png", tile),
                    &res,
                    chunk_info.data_width as u32,
                    chunk_info.data_height as u32,
                    image::ColorType::Rgb8,
                )?;
            }
            _ => panic!("Wrong bit depth"),
    }
}
```


Patch:
diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs
index bea51a1..d028ebb 100644
--- a/src/decoder/ifd.rs
+++ b/src/decoder/ifd.rs
@@ -451,8 +451,8 @@ impl Entry {
         // Case 3: There is more than one value, but it fits in the offset field.
         if value_bytes <= 4 || bigtiff && value_bytes <= 8 {
             match self.type_ {
-                Type::BYTE => return Ok(offset_to_bytes(self.count as usize, self)?),
-                Type::SBYTE => return Ok(offset_to_sbytes(self.count as usize, self)?),
+                Type::BYTE => return offset_to_bytes(self.count as usize, self),
+                Type::SBYTE => return offset_to_sbytes(self.count as usize, self),
                 Type::ASCII => {
                     let mut buf = vec![0; self.count as usize];
                     self.r(bo).read_exact(&mut buf)?;
diff --git a/src/decoder/image.rs b/src/decoder/image.rs
index c525ed2..00cdd65 100644
--- a/src/decoder/image.rs
+++ b/src/decoder/image.rs
@@ -7,7 +7,7 @@ use super::tag_reader::TagReader;
 use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits};
 use super::{stream::SmartReader, ChunkType};
 use crate::tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag};
-use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
+use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
 use std::convert::{TryFrom, TryInto};
 use std::io::{self, Cursor, Read, Seek};
 use std::sync::Arc;
@@ -357,28 +357,65 @@ impl Image {
         })
     }
 
-    pub(crate) fn chunk_file_range(&self, chunk: usize) -> TiffResult<(u64, u64)> {
-        let file_offset = self.chunk_offsets.get(chunk).ok_or(TiffError::FormatError(
-            TiffFormatError::InconsistentSizesEncountered,
-        ))?;
-
-        let compressed_bytes = self.chunk_bytes.get(chunk).ok_or(TiffError::FormatError(
-            TiffFormatError::InconsistentSizesEncountered,
-        ))?;
+    pub(crate) fn chunk_file_range(&self, chunk: u32) -> TiffResult<(u64, u64)> {
+        let file_offset = self
+            .chunk_offsets
+            .get(chunk as usize)
+            .ok_or(TiffError::FormatError(
+                TiffFormatError::InconsistentSizesEncountered,
+            ))?;
+
+        let compressed_bytes =
+            self.chunk_bytes
+                .get(chunk as usize)
+                .ok_or(TiffError::FormatError(
+                    TiffFormatError::InconsistentSizesEncountered,
+                ))?;
 
         Ok((*file_offset, *compressed_bytes))
     }
 
-    pub(crate) fn chunk_dimensions(&self) -> TiffResult<(usize, usize)> {
+    pub(crate) fn chunk_dimensions(&self) -> TiffResult<(u32, u32)> {
         match self.chunk_type {
             ChunkType::Strip => {
-                let width = usize::try_from(self.width)?;
                 let strip_attrs = self.strip_decoder.as_ref().unwrap();
-                Ok((width, usize::try_from(strip_attrs.rows_per_strip)?))
+                Ok((self.width, strip_attrs.rows_per_strip))
+            }
+            ChunkType::Tile => {
+                let tile_attrs = self.tile_attributes.as_ref().unwrap();
+                Ok((
+                    u32::try_from(tile_attrs.tile_width)?,
+                    u32::try_from(tile_attrs.tile_length)?,
+                ))
+            }
+        }
+    }
+
+    pub(crate) fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> {
+        let dims = self.chunk_dimensions()?;
+
+        match self.chunk_type {
+            ChunkType::Strip => {
+                let strip_height_without_padding = chunk_index
+                    .checked_mul(dims.1)
+                    .and_then(|x| self.height.checked_sub(x))
+                    .ok_or(TiffError::UsageError(UsageError::InvalidChunkIndex(
+                        chunk_index,
+                    )))?;
+
+                // Ignore potential vertical padding on the bottommost strip
+                let strip_height = dims.1.min(strip_height_without_padding);
+
+                Ok((dims.0, strip_height))
             }
             ChunkType::Tile => {
                 let tile_attrs = self.tile_attributes.as_ref().unwrap();
-                Ok((tile_attrs.tile_width, tile_attrs.tile_length))
+                let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index as usize);
+
+                let tile_width = tile_attrs.tile_width - padding_right;
+                let tile_length = tile_attrs.tile_length - padding_down;
+
+                Ok((u32::try_from(tile_width)?, u32::try_from(tile_length)?))
             }
         }
     }
@@ -389,7 +426,7 @@ impl Image {
         mut buffer: DecodingBuffer,
         output_width: usize,
         byte_order: ByteOrder,
-        chunk: usize,
+        chunk_index: u32,
     ) -> TiffResult<()> {
         // Validate that the provided buffer is of the expected type.
         let color_type = self.colortype()?;
@@ -438,9 +475,12 @@ impl Image {
             _ => {}
         }
 
-        let compressed_bytes = self.chunk_bytes.get(chunk).ok_or(TiffError::FormatError(
-            TiffFormatError::InconsistentSizesEncountered,
-        ))?;
+        let compressed_bytes =
+            self.chunk_bytes
+                .get(chunk_index as usize)
+                .ok_or(TiffError::FormatError(
+                    TiffFormatError::InconsistentSizesEncountered,
+                ))?;
 
         let byte_len = buffer.byte_len();
         let compression_method = self.compression_method;
@@ -448,48 +488,37 @@ impl Image {
         let predictor = self.predictor;
         let samples = self.bits_per_sample.len();
 
-        let padding;
-        let chunk_dimensions;
-        match self.chunk_type {
-            ChunkType::Strip => {
-                let width = usize::try_from(self.width)?;
-                let height = usize::try_from(self.height)?;
-                let strip_attrs = self.strip_decoder.as_ref().unwrap();
-                chunk_dimensions = (width, usize::try_from(strip_attrs.rows_per_strip)?);
-                padding = (0, (chunk_dimensions.1 * (chunk + 1)).max(height) - height);
-            }
-            ChunkType::Tile => {
-                let tile_attrs = self.tile_attributes.as_ref().unwrap();
-                padding = tile_attrs.get_padding(chunk);
-                chunk_dimensions = (tile_attrs.tile_width, tile_attrs.tile_length);
-            }
-        }
+        let chunk_dims = self.chunk_dimensions()?;
+        let data_dims = self.chunk_data_dimensions(chunk_index)?;
+
+        let padding_right = chunk_dims.0 - data_dims.0;
 
         let jpeg_tables = self.jpeg_tables.clone();
         let mut reader =
             Self::create_reader(reader, compression_method, *compressed_bytes, jpeg_tables)?;
 
-        if output_width == chunk_dimensions.0 && padding.0 == 0 {
-            let total_samples = chunk_dimensions.0 * (chunk_dimensions.1 - padding.1) * samples;
+        if output_width == data_dims.0 as usize && padding_right == 0 {
+            let total_samples = data_dims.0 as usize * data_dims.1 as usize * samples;
             let tile = &mut buffer.as_bytes_mut()[..total_samples * byte_len];
             reader.read_exact(tile)?;
 
-            for row in 0..(chunk_dimensions.1 - padding.1) {
-                let row_start = row * output_width * samples;
-                let row_end = (row + 1) * output_width * samples;
+            for row in 0..data_dims.1 as usize {
+                let row_start = row as usize * output_width as usize * samples;
+                let row_end = (row + 1) * output_width as usize * samples;
                 let row = buffer.subrange(row_start..row_end);
                 super::fix_endianness_and_predict(row, samples, byte_order, predictor);
             }
             if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
                 super::invert_colors(&mut buffer.subrange(0..total_samples), color_type);
             }
-        } else if padding.0 > 0 && self.predictor == Predictor::FloatingPoint {
+        } else if padding_right > 0 && self.predictor == Predictor::FloatingPoint {
             // The floating point predictor shuffles the padding bytes into the encoded output, so
             // this case is handled specially when needed.
-            let mut encoded = vec![0u8; chunk_dimensions.0 * samples * byte_len];
-            for row in 0..(chunk_dimensions.1 - padding.1) {
-                let row_start = row * output_width * samples;
-                let row_end = row_start + (chunk_dimensions.0 - padding.0) * samples;
+            let mut encoded = vec![0u8; chunk_dims.0 as usize * samples * byte_len];
+
+            for row in 0..data_dims.1 as usize {
+                let row_start = row * output_width as usize * samples;
+                let row_end = row_start + data_dims.0 as usize * samples;
 
                 reader.read_exact(&mut encoded)?;
                 match buffer.subrange(row_start..row_end) {
@@ -502,16 +531,16 @@ impl Image {
                 }
             }
         } else {
-            for row in 0..(chunk_dimensions.1 - padding.1) {
-                let row_start = row * output_width * samples;
-                let row_end = row_start + (chunk_dimensions.0 - padding.0) * samples;
+            for row in 0..data_dims.1 as usize {
+                let row_start = row * output_width as usize * samples;
+                let row_end = row_start + data_dims.0 as usize * samples;
 
                 let row = &mut buffer.as_bytes_mut()[(row_start * byte_len)..(row_end * byte_len)];
                 reader.read_exact(row)?;
 
                 // Skip horizontal padding
-                if padding.0 > 0 {
-                    let len = u64::try_from(padding.0 * samples * byte_len)?;
+                if padding_right > 0 {
+                    let len = u64::try_from(padding_right as usize * samples * byte_len)?;
                     io::copy(&mut reader.by_ref().take(len), &mut io::sink())?;
                 }
 
diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs
index c4299af..5359339 100644
--- a/src/decoder/mod.rs
+++ b/src/decoder/mod.rs
@@ -1,7 +1,7 @@
 use std::collections::{HashMap, HashSet};
 use std::convert::TryFrom;
 use std::io::{self, Read, Seek};
-use std::{cmp, ops::Range};
+use std::ops::Range;
 
 use crate::{
     bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
@@ -301,8 +301,9 @@ where
     reader: SmartReader<R>,
     bigtiff: bool,
     limits: Limits,
+    current_chunk: u32,
     next_ifd: Option<u64>,
-    current_chunk: usize,
+    ifd_offsets: Vec<u64>,
     seen_ifds: HashSet<u64>,
     image: Image,
 }
@@ -559,12 +560,15 @@ impl<R: Read + Seek> Decoder<R> {
 
         let mut seen_ifds = HashSet::new();
         seen_ifds.insert(*next_ifd.as_ref().unwrap());
+        let ifd_offsets = vec![*next_ifd.as_ref().unwrap()];
 
         let mut decoder = Decoder {
             reader,
             bigtiff,
             limits: Default::default(),
             next_ifd,
+            ifd_offsets,
+            seen_ifds,
             image: Image {
                 ifd: None,
                 width: 0,
@@ -583,7 +587,6 @@ impl<R: Read + Seek> Decoder<R> {
                 chunk_bytes: Vec::new(),
             },
             current_chunk: 0,
-            seen_ifds,
         };
         decoder.next_image()?;
         Ok(decoder)
@@ -606,6 +609,46 @@ impl<R: Read + Seek> Decoder<R> {
         &self.image
     }
 
+    /// Loads the IFD at the specified index in the list, if one exists
+    pub fn seek_to_image(&mut self, ifd_index: usize) -> TiffResult<()> {
+        // Check whether we have seen this IFD before, if so then the index will be less than the length of the list of ifd offsets
+        if ifd_index >= self.ifd_offsets.len() {
+            // We possibly need to load in the next IFD
+            if self.next_ifd.is_none() {
+                return Err(TiffError::FormatError(
+                    TiffFormatError::ImageFileDirectoryNotFound,
+                ));
+            }
+
+            loop {
+                // Follow the list until we find the one we want, or we reach the end, whichever happens first
+                let (_ifd, next_ifd) = self.next_ifd()?;
+
+                if next_ifd.is_none() {
+                    break;
+                }
+
+                if ifd_index < self.ifd_offsets.len() {
+                    break;
+                }
+            }
+        }
+
+        // If the index is within the list of ifds then we can load the selected image/IFD
+        if let Some(ifd_offset) = self.ifd_offsets.get(ifd_index) {
+            let (ifd, _next_ifd) = Self::read_ifd(&mut self.reader, self.bigtiff, *ifd_offset)?;
+
+            self.current_chunk = 0;
+            self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?;
+
+            Ok(())
+        } else {
+            Err(TiffError::FormatError(
+                TiffFormatError::ImageFileDirectoryNotFound,
+            ))
+        }
+    }
+
     /// Reset the decoder.
     #[deprecated = "Never should have been public. Only use Decoder::new()"]
     pub fn init(self) -> TiffResult<Decoder<R>> {
@@ -613,10 +656,7 @@ impl<R: Read + Seek> Decoder<R> {
         Self::new(reader.into_inner())
     }
 
-    /// Reads in the next image.
-    /// 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.
-    pub fn next_image(&mut self) -> TiffResult<()> {
+    fn next_ifd(&mut self) -> TiffResult<(Directory, Option<u64>)> {
         if self.next_ifd.is_none() {
             return Err(TiffError::FormatError(
                 TiffFormatError::ImageFileDirectoryNotFound,
@@ -634,8 +674,18 @@ impl<R: Read + Seek> Decoder<R> {
                 return Err(TiffError::FormatError(TiffFormatError::CycleInOffsets));
             }
             self.next_ifd = Some(next);
+            self.ifd_offsets.push(next);
         }
 
+        Ok((ifd, next_ifd))
+    }
+
+    /// Reads in the next image.
+    /// 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.
+    pub fn next_image(&mut self) -> TiffResult<()> {
+        let (ifd, _next_ifd) = self.next_ifd()?;
+
         self.current_chunk = 0;
         self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?;
         Ok(())
@@ -992,6 +1042,7 @@ impl<R: Read + Seek> Decoder<R> {
         self.read_image()
     }
 
+    #[deprecated = "Use read_chunk_to_buffer instead"]
     pub fn read_strip_to_buffer(&mut self, mut buffer: DecodingBuffer) -> TiffResult<()> {
         self.check_chunk_type(ChunkType::Strip)?;
 
@@ -1013,6 +1064,28 @@ impl<R: Read + Seek> Decoder<R> {
         Ok(())
     }
 
+    pub fn read_chunk_to_buffer(
+        &mut self,
+        mut buffer: DecodingBuffer,
+        chunk_index: u32,
+        output_width: usize,
+    ) -> TiffResult<()> {
+        let offset = self.image.chunk_file_range(chunk_index)?.0;
+        self.goto_offset_u64(offset)?;
+
+        let byte_order = self.reader.byte_order;
+
+        self.image.expand_chunk(
+            &mut self.reader,
+            buffer.copy(),
+            output_width,
+            byte_order,
+            chunk_index,
+        )?;
+
+        Ok(())
+    }
+
     fn result_buffer(&self, width: usize, height: usize) -> TiffResult<DecodingResult> {
         let buffer_size = match width
             .checked_mul(height)
@@ -1066,61 +1139,60 @@ impl<R: Read + Seek> Decoder<R> {
         }
     }
 
-    /// Read a single strip from the image and return it as a Vector
+    /// Read a single strip from the image and return it as a Vector. This method does not return
+    /// information on the size of the strip read and so the caller must take care of the final
+    /// strip in the image as the data read may be less than the expected strip size. This interface
+    /// may change at a future date to reflect this.
+    #[deprecated = "Use read_chunk instead"]
     pub fn read_strip(&mut self) -> TiffResult<DecodingResult> {
-        self.check_chunk_type(ChunkType::Strip)?;
-        let index = self.current_chunk;
-
-        let rows_per_strip =
-            usize::try_from(self.image().strip_decoder.as_ref().unwrap().rows_per_strip)?;
-
-        let strip_height = cmp::min(
-            rows_per_strip,
-            usize::try_from(self.image().height)? - index * rows_per_strip,
-        );
-
-        let mut result = self.result_buffer(usize::try_from(self.image().width)?, strip_height)?;
-        self.read_strip_to_buffer(result.as_buffer(0))?;
+        let result = self.read_chunk(self.current_chunk)?;
 
         Ok(result)
     }
 
-    /// Read a single tile from the image and return it as a Vector
+    /// Read a single tile from the image and return it as a Vector. This method does not return
+    /// information on the size of the tile read and so the caller must take care of tiles on the
+    /// rightmost column and the bottommost row in the image as the data read may be less than the
+    /// expected tile size. This interface may change at a future date to reflect this.
+    #[deprecated = "Use read_chunk instead"]
     pub fn read_tile(&mut self) -> TiffResult<DecodingResult> {
-        self.check_chunk_type(ChunkType::Tile)?;
+        let result = self.read_chunk(self.current_chunk)?;
 
-        let tile = self.current_chunk;
+        self.current_chunk += 1;
 
-        let tile_attrs = self.image().tile_attributes.as_ref().unwrap();
-        let (padding_right, padding_down) = tile_attrs.get_padding(tile);
+        Ok(result)
+    }
 
-        let tile_width = tile_attrs.tile_width - padding_right;
-        let tile_length = tile_attrs.tile_length - padding_down;
+    /// Read the specified chunk (at index `chunk_index`) and return the binary data as a Vector.
+    pub fn read_chunk(&mut self, chunk_index: u32) -> TiffResult<DecodingResult> {
+        let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
 
-        let mut result = self.result_buffer(tile_width, tile_length)?;
+        let mut result = self.result_buffer(data_dims.0 as usize, data_dims.1 as usize)?;
 
-        let offset = self.image.chunk_file_range(tile)?.0;
-        self.goto_offset_u64(offset)?;
+        self.read_chunk_to_buffer(result.as_buffer(0), chunk_index, data_dims.0 as usize)?;
 
-        let byte_order = self.reader.byte_order;
-        self.image.expand_chunk(
-            &mut self.reader,
-            result.as_buffer(0),
-            tile_width,
-            byte_order,
-            tile,
-        )?;
+        Ok(result)
+    }
 
-        self.current_chunk += 1;
+    /// 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`.
+    pub fn chunk_dimensions(&self) -> (u32, u32) {
+        self.image().chunk_dimensions().unwrap()
+    }
 
-        Ok(result)
+    /// Returns the size of the data in the chunk with the specified index. This is the default size of the chunk,
+    /// minus any padding.
+    pub fn chunk_data_dimensions(&self, chunk_index: u32) -> (u32, u32) {
+        self.image()
+            .chunk_data_dimensions(chunk_index)
+            .expect("invalid chunk_index")
     }
 
     /// Decodes the entire image and return it as a Vector
     pub fn read_image(&mut self) -> TiffResult<DecodingResult> {
-        let width = usize::try_from(self.image().width)?;
-        let height = usize::try_from(self.image().height)?;
-        let mut result = self.result_buffer(width, height)?;
+        let width = self.image().width;
+        let height = self.image().height;
+        let mut result = self.result_buffer(width as usize, height as usize)?;
         if width == 0 || height == 0 {
             return Ok(result);
         }
@@ -1143,22 +1215,22 @@ impl<R: Read + Seek> Decoder<R> {
             ));
         }
 
-        let chunks_across = (width - 1) / chunk_dimensions.0 + 1;
-        let strip_samples = width * chunk_dimensions.1 * samples;
+        let chunks_across = ((width - 1) / chunk_dimensions.0 + 1) as usize;
+        let strip_samples = width as usize * chunk_dimensions.1 as usize * samples;
 
         for chunk in 0..self.image().chunk_offsets.len() {
             self.goto_offset_u64(self.image().chunk_offsets[chunk])?;
 
             let x = chunk % chunks_across;
             let y = chunk / chunks_across;
-            let buffer_offset = y * strip_samples + x * chunk_dimensions.0 * samples;
+            let buffer_offset = y * strip_samples + x * chunk_dimensions.0 as usize * samples;
             let byte_order = self.reader.byte_order;
             self.image.expand_chunk(
                 &mut self.reader,
                 result.as_buffer(buffer_offset).copy(),
-                width,
+                width as usize,
                 byte_order,
-                chunk,
+                chunk as u32,
             )?;
         }
 
diff --git a/src/encoder/compression/deflate.rs b/src/encoder/compression/deflate.rs
index db64b70..5e7a261 100644
--- a/src/encoder/compression/deflate.rs
+++ b/src/encoder/compression/deflate.rs
@@ -53,7 +53,7 @@ impl Compression for Deflate {
 impl CompressionAlgorithm for Deflate {
     fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
         let mut encoder = ZlibEncoder::new(writer, self.level);
-        encoder.write_all(&bytes)?;
+        encoder.write_all(bytes)?;
         encoder.try_finish()?;
         Ok(encoder.total_out())
     }
diff --git a/src/encoder/compression/packbits.rs b/src/encoder/compression/packbits.rs
index 464d1e7..7ba3833 100644
--- a/src/encoder/compression/packbits.rs
+++ b/src/encoder/compression/packbits.rs
@@ -157,7 +157,7 @@ mod tests {
         let mut compressed_data = Vec::<u8>::new();
         let mut writer = Cursor::new(&mut compressed_data);
         Packbits::default()
-            .write_to(&mut writer, &UNCOMPRESSED_DATA)
+            .write_to(&mut writer, UNCOMPRESSED_DATA)
             .unwrap();
         assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
     }
@@ -207,7 +207,7 @@ mod tests {
         let mut compressed_data = Vec::<u8>::new();
         let mut writer = Cursor::new(&mut compressed_data);
         Packbits::default()
-            .write_to(&mut writer, &TEST_DATA)
+            .write_to(&mut writer, TEST_DATA)
             .unwrap();
         assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
     }
diff --git a/src/error.rs b/src/error.rs
index 0f91ad8..8c8ac8b 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -205,6 +205,7 @@ impl fmt::Display for TiffUnsupportedError {
 #[derive(Debug)]
 pub enum UsageError {
     InvalidChunkType(ChunkType, ChunkType),
+    InvalidChunkIndex(u32),
 }
 
 impl fmt::Display for UsageError {
@@ -218,6 +219,7 @@ impl fmt::Display for UsageError {
                     expected, actual
                 )
             }
+            InvalidChunkIndex(index) => write!(fmt, "Image chunk index ({}) requested.", index),
         }
     }
 }
@@ -290,6 +292,12 @@ impl From<TiffUnsupportedError> for TiffError {
     }
 }
 
+impl From<UsageError> for TiffError {
+    fn from(err: UsageError) -> TiffError {
+        TiffError::UsageError(err)
+    }
+}
+
 impl From<std::num::TryFromIntError> for TiffError {
     fn from(_err: std::num::TryFromIntError) -> TiffError {
         TiffError::IntSizeError


Test patch:
diff --git a/tests/decode_images.rs b/tests/decode_images.rs
index df43524..6a262bf 100644
--- a/tests/decode_images.rs
+++ b/tests/decode_images.rs
@@ -275,7 +275,7 @@ fn test_tiled_incremental() {
     assert_eq!(tiles as usize, sums.len());
 
     for tile in 0..tiles {
-        match decoder.read_tile().unwrap() {
+        match decoder.read_chunk(tile).unwrap() {
             DecodingResult::U8(res) => {
                 let sum: u64 = res.into_iter().map(<u64>::from).sum();
                 assert_eq!(sum, sums[tile as usize]);
diff --git a/tests/encode_images.rs b/tests/encode_images.rs
index a8e1ca3..9fd9474 100644
--- a/tests/encode_images.rs
+++ b/tests/encode_images.rs
@@ -512,7 +512,7 @@ fn test_rows_per_strip() {
 
         for i in 0..50 {
             let img2 = [i; 2 * 100];
-            match decoder.read_strip().unwrap() {
+            match decoder.read_chunk(i as u32).unwrap() {
                 DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]),
                 other => panic!("Incorrect strip type {:?}", other),
             }