Repository: image-rs/image-tiff (rust)

Issue:
Provide access to read raw bytes in decoder
**Would you be amenable to adding a `read_bytes(length: usize)` method to the [`tiff::decoder::Decoder`](https://docs.rs/tiff/latest/tiff/decoder/struct.Decoder.html) struct?** This would be similar to the [`read_byte()`](https://docs.rs/tiff/latest/tiff/decoder/struct.Decoder.html#method.read_byte) method but would allow for more optimized reads by reading in a number of bytes from the underlying reader.

Use Case:
I have a use case where I want to access the raw bytes of the underlying TIFF file. In particular I want to be able to access the raw tiles/strips without decoding them (effectively [`read_chunk`](https://docs.rs/tiff/latest/tiff/decoder/struct.Decoder.html#method.read_chunk) but without decoding). This is a part of a file conversion process where I do not want to incur the data loss from decoding and re-encoding. Currently I am doing this by maintaining two copies of the Read + Seek object (e.g. two calls to `std::fs::File::open` or reading the file into memory and using two `std::io::Cursor`s on the data). I am able to get the tile offsets and byte counts (or strip offsets and byte counts) using the `tiff::decoder::Decoder` and then use the other reader to read the actual bytes. I can't reuse the reader I pass to `tiff::decoder::Decoder` due to ownership issues.

I did try repeated calls to [`goto_offset_u64`](https://docs.rs/tiff/latest/tiff/decoder/struct.Decoder.html#method.goto_offset_u64) and [`read_byte`](https://docs.rs/tiff/latest/tiff/decoder/struct.Decoder.html#method.read_byte), but it is prohibitively slow.

I'm happy to submit a PR if this `read_bytes(length: usize)` method sounds like something you would support!

Thank you for your work on this great project!

Pull request:
Add inner() method to decoder to provide access to raw reader
This provides an `inner()` method to the `Decoder` struct to make it easy to access the raw stream (`Read + Seek`) object provided to `Decoder`.

Closes #288

Patch:
diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs
index ae0ec7a..28a20b5 100644
--- a/src/decoder/mod.rs
+++ b/src/decoder/mod.rs
@@ -660,6 +660,11 @@ 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
     #[inline]
     pub fn read_byte(&mut self) -> Result<u8, io::Error> {


Test patch:
diff --git a/tests/decode_images.rs b/tests/decode_images.rs
index b53fa82..967a28a 100644
--- a/tests/decode_images.rs
+++ b/tests/decode_images.rs
@@ -4,6 +4,7 @@ use tiff::decoder::{ifd, Decoder, DecodingResult};
 use tiff::ColorType;
 
 use std::fs::File;
+use std::io::{Read, Seek};
 use std::path::PathBuf;
 
 const TEST_IMAGE_DIR: &str = "./tests/images/";
@@ -231,6 +232,54 @@ fn test_tiled_rect_rgb_u8() {
     test_image_sum_u8("tiled-rect-rgb-u8.tif", ColorType::RGB(8), 62081032);
 }
 
+#[test]
+fn test_inner_access() {
+    let path = PathBuf::from(TEST_IMAGE_DIR).join("tiled-rect-rgb-u8.tif");
+    let img_file = File::open(path).expect("Cannot find test image!");
+    let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
+    assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
+
+    let c = decoder
+        .get_tag(tiff::tags::Tag::Compression)
+        .unwrap()
+        .into_u16()
+        .unwrap();
+    assert_eq!(c, tiff::tags::CompressionMethod::None.to_u16());
+
+    // 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.
+    let first_offset = decoder
+        .get_tag_u32_vec(tiff::tags::Tag::TileOffsets)
+        .unwrap()
+        .into_iter()
+        .next()
+        .unwrap();
+    let first_byte_count = decoder
+        .get_tag_u32_vec(tiff::tags::Tag::TileByteCounts)
+        .unwrap()
+        .into_iter()
+        .next()
+        .unwrap();
+    decoder
+        .inner()
+        .seek(std::io::SeekFrom::Start(first_offset as u64))
+        .expect("Cannot seek to tile offset");
+    let mut buf = vec![0; first_byte_count as usize];
+    decoder
+        .inner()
+        .read_exact(&mut buf)
+        .expect("Cannot read tile data");
+    let raw_sum: u64 = buf.into_iter().map(<u64>::from).sum();
+
+    match decoder.read_chunk(0).unwrap() {
+        DecodingResult::U8(chunk) => {
+            let sum: u64 = chunk.into_iter().map(<u64>::from).sum();
+            assert_eq!(sum, raw_sum);
+        }
+        _ => panic!("Wrong bit depth"),
+    }
+}
+
 /* #[test]
 fn test_tiled_jpeg_rgb_u8() {
     test_image_sum_u8("tiled-jpeg-rgb-u8.tif", ColorType::RGB(8), 93031606);
@@ -292,6 +341,11 @@ fn test_tiled_incremental() {
 
 #[test]
 fn test_planar_rgb_u8() {
+    test_image_sum_u8("planar-rgb-u8.tif", ColorType::RGB(8), 15417630);
+}
+
+#[test]
+fn test_read_planar_bands() {
     // gdal_translate tiled-rgb-u8.tif planar-rgb-u8.tif -co INTERLEAVE=BAND -co COMPRESS=LZW -co PROFILE=BASELINE
     let file = "planar-rgb-u8.tif";
     let expected_type = ColorType::RGB(8);
@@ -328,8 +382,6 @@ fn test_planar_rgb_u8() {
         }
         _ => panic!("Wrong bit depth"),
     }
-
-    test_image_sum_u8(file, ColorType::RGB(8), 15417630);
 }
 
 #[test]