Repository: image-rs/image-tiff
Pull request #151 (2021-12-21 15:59:01)

Add TIFF compression during encoding and corresponding tests
In a joint effort, @Pretorer and I add support for compression with LZW and Deflate during encoding. The changes in the public interface are quite small and do not change any existing behavior users may rely on. The compression must be enabled manually by calling the new function `new_image_with_compression`.

Due to the generic interface with the default to no compression at all, this should add only those runtime costs explicitly requested by the user. No additional dependencies are required. The changes are supported by corresponding unit and integration tests.


Patch:
diff --git a/README.md b/README.md
index a5fc0a5..f494a50 100644
--- a/README.md
+++ b/README.md
@@ -16,25 +16,25 @@ TIFF decoding and encoding library in pure Rust
 ### Formats
 This table lists photometric interpretations and sample formats which are supported for encoding and decoding. The entries are `ColorType` variants for which sample bit depths are supported. Only samples where all bit depths are equal are currently supported. For example, `RGB(8)` means that the bit depth [8, 8, 8] is supported and will be interpreted as an 8 bit per channel RGB color type.
 
-| `PhotometricInterpretation` | UINT Format | IEEEFP Format |
-|-|-|-|
-| `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
-| `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
-| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32\|64), RGBA(32\|64) |
-| `RGBPalette` | | |
-| `Mask` | | |
-| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) |
-| `YCbCr` | | |
-| `CIELab` | | |
+| `PhotometricInterpretation` | UINT Format                             | IEEEFP Format             |
+| --------------------------- | --------------------------------------- | ------------------------- |
+| `WhiteIsZero`               | Gray(8\|16\|32\|64)                     | Gray(32\|64)              |
+| `BlackIsZero`               | Gray(8\|16\|32\|64)                     | Gray(32\|64)              |
+| `RGB`                       | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32\|64), RGBA(32\|64) |
+| `RGBPalette`                |                                         |                           |
+| `Mask`                      |                                         |                           |
+| `CMYK`                      | CMYK(8\|16\|32\|64)                     | CMYK(32\|64)              |
+| `YCbCr`                     |                                         |                           |
+| `CIELab`                    |                                         |                           |
 
 ### Compressions
 
-| | Decoding | Encoding |
-|-|-|-|
-| None | ✓ | ✓ |
-| LZW | ✓ | |
-| Deflate | ✓ | |
-| PackBits | ✓ | |
+|          | Decoding | Encoding |
+| -------- | -------- | -------- |
+| None     | ✓        | ✓        |
+| LZW      | ✓        | ✓        |
+| Deflate  | ✓        | ✓        |
+| PackBits | ✓        | ✓        |
 
 
 ## Not yet supported
diff --git a/benches/lzw.rs b/benches/lzw.rs
index 64782d9..47f513b 100644
--- a/benches/lzw.rs
+++ b/benches/lzw.rs
@@ -33,7 +33,7 @@ fn main() {
                 def.data,
                 |b, input| b.iter(|| read_image(input)),
             );
-    };
+    }
 
     let mut c = Criterion::default().configure_from_args();
     let mut group = c.benchmark_group("tiff-lzw");
diff --git a/src/encoder/compression/deflate.rs b/src/encoder/compression/deflate.rs
new file mode 100644
index 0000000..c198dbc
--- /dev/null
+++ b/src/encoder/compression/deflate.rs
@@ -0,0 +1,83 @@
+use crate::{encoder::compression::*, tags::CompressionMethod};
+use flate2::{write::ZlibEncoder, Compression as FlateCompression};
+use std::io::Write;
+
+/// The Deflate algorithm used to compress image data in TIFF files.
+#[derive(Debug, Clone, Copy)]
+pub struct Deflate {
+    level: FlateCompression,
+}
+
+/// The level of compression used by the Deflate algorithm.
+/// It allows trading compression ratio for compression speed.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+#[non_exhaustive]
+pub enum DeflateLevel {
+    /// The fastest possible compression mode.
+    Fast = 1,
+    /// The conserative choice between speed and ratio.
+    Balanced = 6,
+    /// The best compression available with Deflate.
+    Best = 9,
+}
+
+impl Default for DeflateLevel {
+    fn default() -> Self {
+        DeflateLevel::Balanced
+    }
+}
+
+impl Deflate {
+    /// Create a new deflate compressor with a specific level of compression.
+    pub fn with_level(level: DeflateLevel) -> Self {
+        Self {
+            level: FlateCompression::new(level as u32),
+        }
+    }
+}
+
+impl Default for Deflate {
+    fn default() -> Self {
+        Self::with_level(DeflateLevel::default())
+    }
+}
+
+impl Compression for Deflate {
+    const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::Deflate;
+
+    fn get_algorithm(&self) -> Compressor {
+        Compressor::Deflate(self.clone())
+    }
+}
+
+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(&bytes)?;
+        encoder.try_finish()?;
+        Ok(encoder.total_out())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::encoder::compression::tests::TEST_DATA;
+    use std::io::Cursor;
+
+    #[test]
+    fn test_deflate() {
+        const EXPECTED_COMPRESSED_DATA: [u8; 64] = [
+            0x78, 0x9C, 0x15, 0xC7, 0xD1, 0x0D, 0x80, 0x20, 0x0C, 0x04, 0xD0, 0x55, 0x6E, 0x02,
+            0xA7, 0x71, 0x81, 0xA6, 0x41, 0xDA, 0x28, 0xD4, 0xF4, 0xD0, 0xF9, 0x81, 0xE4, 0xFD,
+            0xBC, 0xD3, 0x9C, 0x58, 0x04, 0x1C, 0xE9, 0xBD, 0xE2, 0x8A, 0x84, 0x5A, 0xD1, 0x7B,
+            0xE7, 0x97, 0xF4, 0xF8, 0x08, 0x8D, 0xF6, 0x66, 0x21, 0x3D, 0x3A, 0xE4, 0xA9, 0x91,
+            0x3E, 0xAC, 0xF1, 0x98, 0xB9, 0x70, 0x17, 0x13,
+        ];
+
+        let mut compressed_data = Vec::<u8>::new();
+        let mut writer = Cursor::new(&mut compressed_data);
+        Deflate::default().write_to(&mut writer, TEST_DATA).unwrap();
+        assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice());
+    }
+}
diff --git a/src/encoder/compression/lzw.rs b/src/encoder/compression/lzw.rs
new file mode 100644
index 0000000..0e0f2aa
--- /dev/null
+++ b/src/encoder/compression/lzw.rs
@@ -0,0 +1,47 @@
+use crate::{encoder::compression::*, tags::CompressionMethod};
+use std::io::Write;
+use weezl::encode::Encoder as LZWEncoder;
+
+/// The LZW algorithm used to compress image data in TIFF files.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub struct Lzw;
+
+impl Compression for Lzw {
+    const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::LZW;
+
+    fn get_algorithm(&self) -> Compressor {
+        Compressor::Lzw(*self)
+    }
+}
+
+impl CompressionAlgorithm for Lzw {
+    fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
+        let mut encoder = LZWEncoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
+        let result = encoder.into_stream(writer).encode_all(bytes);
+        let byte_count = result.bytes_written as u64;
+        result.status.map(|_| byte_count)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::encoder::compression::tests::TEST_DATA;
+    use std::io::Cursor;
+
+    #[test]
+    fn test_lzw() {
+        const EXPECTED_COMPRESSED_DATA: [u8; 63] = [
+            0x80, 0x15, 0x0D, 0x06, 0x93, 0x98, 0x82, 0x08, 0x20, 0x30, 0x88, 0x0E, 0x67, 0x43,
+            0x91, 0xA4, 0xDC, 0x67, 0x10, 0x19, 0x8D, 0xE7, 0x21, 0x01, 0x8C, 0xD0, 0x65, 0x31,
+            0x9A, 0xE1, 0xD1, 0x03, 0xB1, 0x86, 0x1A, 0x6F, 0x3A, 0xC1, 0x4C, 0x66, 0xF3, 0x69,
+            0xC0, 0xE4, 0x65, 0x39, 0x9C, 0xCD, 0x26, 0xF3, 0x74, 0x20, 0xD8, 0x67, 0x89, 0x9A,
+            0x4E, 0x86, 0x83, 0x69, 0xCC, 0x5D, 0x01,
+        ];
+
+        let mut compressed_data = Vec::<u8>::new();
+        let mut writer = Cursor::new(&mut compressed_data);
+        Lzw::default().write_to(&mut writer, TEST_DATA).unwrap();
+        assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice());
+    }
+}
diff --git a/src/encoder/compression/mod.rs b/src/encoder/compression/mod.rs
new file mode 100644
index 0000000..04baef3
--- /dev/null
+++ b/src/encoder/compression/mod.rs
@@ -0,0 +1,60 @@
+use crate::tags::CompressionMethod;
+use std::io::{self, Write};
+
+mod deflate;
+mod lzw;
+mod packbits;
+mod uncompressed;
+
+pub use self::deflate::{Deflate, DeflateLevel};
+pub use self::lzw::Lzw;
+pub use self::packbits::Packbits;
+pub use self::uncompressed::Uncompressed;
+
+/// An algorithm used for compression
+pub trait CompressionAlgorithm {
+    /// The algorithm writes data directly into the writer.
+    /// It returns the total number of bytes written.
+    fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error>;
+}
+
+/// An algorithm used for compression with associated enums and optional configurations.
+pub trait Compression: CompressionAlgorithm {
+    /// The corresponding tag to the algorithm.
+    const COMPRESSION_METHOD: CompressionMethod;
+
+    /// Method to optain a type that can store each variant of comression algorithm.
+    fn get_algorithm(&self) -> Compressor;
+}
+
+/// An enum to store each compression algorithm.
+pub enum Compressor {
+    Uncompressed(Uncompressed),
+    Lzw(Lzw),
+    Deflate(Deflate),
+    Packbits(Packbits),
+}
+
+impl Default for Compressor {
+    /// The default compression strategy does not apply any compression.
+    fn default() -> Self {
+        Compressor::Uncompressed(Uncompressed::default())
+    }
+}
+
+impl CompressionAlgorithm for Compressor {
+    fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
+        match self {
+            Compressor::Uncompressed(algorithm) => algorithm.write_to(writer, bytes),
+            Compressor::Lzw(algorithm) => algorithm.write_to(writer, bytes),
+            Compressor::Deflate(algorithm) => algorithm.write_to(writer, bytes),
+            Compressor::Packbits(algorithm) => algorithm.write_to(writer, bytes),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    pub const TEST_DATA: &'static [u8] =
+        b"This is a string for checking various compression algorithms.";
+}
diff --git a/src/encoder/compression/packbits.rs b/src/encoder/compression/packbits.rs
new file mode 100644
index 0000000..fc032d0
--- /dev/null
+++ b/src/encoder/compression/packbits.rs
@@ -0,0 +1,213 @@
+use crate::{encoder::compression::*, tags::CompressionMethod};
+use std::io::{BufWriter, Error, ErrorKind, Write};
+
+/// Compressor that uses the Packbits[^note] algorithm to compress bytes.
+///
+/// [^note]: PackBits is often ineffective on continuous tone images,
+///          including many grayscale images. In such cases, it is better
+///          to leave the image uncompressed.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub struct Packbits;
+
+impl Compression for Packbits {
+    const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::PackBits;
+
+    fn get_algorithm(&self) -> Compressor {
+        Compressor::Packbits(*self)
+    }
+}
+
+impl CompressionAlgorithm for Packbits {
+    fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
+        // Inspired by https://github.com/skirridsystems/packbits
+
+        const MIN_REPT: u8 = 3; // Minimum run to compress between differ blocks
+        const MAX_BYTES: u8 = 128; // Maximum number of bytes that can be encoded in a header byte
+
+        // Encoding for header byte based on number of bytes represented.
+        fn encode_diff(n: u8) -> u8 {
+            n - 1
+        }
+        fn encode_rept(n: u8) -> u8 {
+            let var = 256 - (n - 1) as u16;
+            var as u8
+        }
+
+        fn write_u8<W: Write>(writer: &mut W, byte: u8) -> Result<u64, Error> {
+            writer.write(&[byte]).map(|byte_count| byte_count as u64)
+        }
+
+        let mut bufwriter = BufWriter::new(writer);
+        let mut bytes_written = 0u64; // The number of bytes written into the writer
+        let mut offset: Option<u64> = None; // The index of the first byte written into the writer
+
+        let mut src_index: usize = 0; // Index of the current byte
+        let mut src_count = bytes.len(); //The number of bytes remaining to be compressed
+
+        let mut in_run = false; // Indication whether counting of similar bytes is performed
+        let mut run_index = 0u8; // Distance into pending bytes that a run starts
+
+        let mut bytes_pending = 0u8; // Bytes looked at but not yet output
+        let mut pending_index = 0usize; // Index of the first pending byte
+
+        let mut curr_byte: u8; // Byte currently being considered
+        let mut last_byte: u8; // Previous byte
+
+        // Need at least one byte to compress
+        if src_count == 0 {
+            return Err(Error::new(ErrorKind::WriteZero, "write zero"));
+        }
+
+        // Prime compressor with first character.
+        last_byte = bytes[src_index];
+        src_index += 1;
+        bytes_pending += 1;
+
+        while src_count - 1 != 0 {
+            src_count -= 1;
+            curr_byte = bytes[src_index];
+            src_index += 1;
+            bytes_pending += 1;
+
+            if in_run {
+                if (curr_byte != last_byte) || (bytes_pending > MAX_BYTES) {
+                    offset.get_or_insert(write_u8(&mut bufwriter, encode_rept(bytes_pending - 1))?);
+                    write_u8(&mut bufwriter, last_byte)?;
+                    bytes_written += 2;
+
+                    bytes_pending = 1;
+                    pending_index = src_index - 1;
+                    run_index = 0;
+                    in_run = false;
+                }
+            } else {
+                if bytes_pending > MAX_BYTES {
+                    // We have as much differing data as we can output in one chunk.
+                    // Output MAX_BYTES leaving one byte.
+                    offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(MAX_BYTES))?);
+                    bufwriter.write(&bytes[pending_index..pending_index + MAX_BYTES as usize])?;
+                    bytes_written += 1 + MAX_BYTES as u64;
+
+                    pending_index += MAX_BYTES as usize;
+                    bytes_pending -= MAX_BYTES;
+                    run_index = bytes_pending - 1; // A run could start here
+                } else if curr_byte == last_byte {
+                    if (bytes_pending - run_index >= MIN_REPT) || (run_index == 0) {
+                        // This is a worthwhile run
+                        if run_index != 0 {
+                            // Flush differing data out of input buffer
+                            offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(run_index))?);
+                            bufwriter
+                                .write(&bytes[pending_index..pending_index + run_index as usize])?;
+                            bytes_written += 1 + run_index as u64;
+                        }
+                        bytes_pending -= run_index; // Length of run
+                        in_run = true;
+                    }
+                } else {
+                    run_index = bytes_pending - 1; // A run could start here
+                }
+            }
+            last_byte = curr_byte;
+        }
+
+        // Output the remainder
+        if in_run {
+            bytes_written += 2;
+            offset.get_or_insert(write_u8(&mut bufwriter, encode_rept(bytes_pending))?);
+            write_u8(&mut bufwriter, last_byte)?;
+        } else {
+            bytes_written += 1 + bytes_pending as u64;
+            offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(bytes_pending))?);
+            bufwriter.write(&bytes[pending_index..pending_index + bytes_pending as usize])?;
+        }
+
+        Ok(bytes_written)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::encoder::compression::tests::TEST_DATA;
+    use std::io::Cursor;
+
+    #[test]
+    fn test_packbits_single_byte() {
+        // compress single byte
+        const UNCOMPRESSED_DATA: [u8; 1] = [0x3F];
+        const EXPECTED_COMPRESSED_DATA: [u8; 2] = [0x00, 0x3F];
+
+        let mut compressed_data = Vec::<u8>::new();
+        let mut writer = Cursor::new(&mut compressed_data);
+        Packbits::default()
+            .write_to(&mut writer, &UNCOMPRESSED_DATA)
+            .unwrap();
+        assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
+    }
+
+    #[test]
+    fn test_packbits_rept() {
+        // compress buffer with repetitive sequence
+        const UNCOMPRESSED_DATA: &'static [u8] =
+            b"This strrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrring hangs.";
+        const EXPECTED_COMPRESSED_DATA: &'static [u8] = b"\x06This st\xD1r\x09ing hangs.";
+
+        let mut compressed_data = Vec::<u8>::new();
+        let mut writer = Cursor::new(&mut compressed_data);
+        Packbits::default()
+            .write_to(&mut writer, &UNCOMPRESSED_DATA)
+            .unwrap();
+        assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
+    }
+
+    #[test]
+    fn test_packbits_large_rept_nonrept() {
+        // compress buffer with large repetitive and non-repetitive sequence
+        let mut data = b"This st".to_vec();
+        for _i in 0..158 {
+            data.push(b'r');
+        }
+        data.extend_from_slice(b"ing hangs.");
+        for i in 0..158 {
+            data.push(i);
+        }
+
+        const EXPECTED_COMPRESSED_DATA: [u8; 182] = [
+            0x06, 0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x74, 0x81, 0x72, 0xE3, 0x72, 0x7F, 0x69,
+            0x6E, 0x67, 0x20, 0x68, 0x61, 0x6E, 0x67, 0x73, 0x2E, 0x00, 0x01, 0x02, 0x03, 0x04,
+            0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
+            0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
+            0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E,
+            0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C,
+            0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
+            0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
+            0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
+            0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
+            0x75, 0x27, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81,
+            0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+            0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D,
+        ];
+
+        let mut compressed_data = Vec::<u8>::new();
+        let mut writer = Cursor::new(&mut compressed_data);
+        Packbits::default()
+            .write_to(&mut writer, data.as_slice())
+            .unwrap();
+        assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
+    }
+
+    #[test]
+    fn test_packbits() {
+        // compress teststring
+        const EXPECTED_COMPRESSED_DATA: &'static [u8] =
+            b"\x3CThis is a string for checking various compression algorithms.";
+
+        let mut compressed_data = Vec::<u8>::new();
+        let mut writer = Cursor::new(&mut compressed_data);
+        Packbits::default()
+            .write_to(&mut writer, &TEST_DATA)
+            .unwrap();
+        assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
+    }
+}
diff --git a/src/encoder/compression/uncompressed.rs b/src/encoder/compression/uncompressed.rs
new file mode 100644
index 0000000..900426f
--- /dev/null
+++ b/src/encoder/compression/uncompressed.rs
@@ -0,0 +1,37 @@
+use crate::{encoder::compression::*, tags::CompressionMethod};
+use std::io::Write;
+
+/// The default algorithm which does not compress at all.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub struct Uncompressed;
+
+impl Compression for Uncompressed {
+    const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::None;
+
+    fn get_algorithm(&self) -> Compressor {
+        Compressor::Uncompressed(*self)
+    }
+}
+
+impl CompressionAlgorithm for Uncompressed {
+    fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
+        writer.write(bytes).map(|byte_count| byte_count as u64)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::encoder::compression::tests::TEST_DATA;
+    use std::io::Cursor;
+
+    #[test]
+    fn test_no_compression() {
+        let mut compressed_data = Vec::<u8>::new();
+        let mut writer = Cursor::new(&mut compressed_data);
+        Uncompressed::default()
+            .write_to(&mut writer, TEST_DATA)
+            .unwrap();
+        assert_eq!(TEST_DATA, compressed_data);
+    }
+}
diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs
index 2881d46..7ca6f60 100644
--- a/src/encoder/mod.rs
+++ b/src/encoder/mod.rs
@@ -12,14 +12,16 @@ use std::{
 
 use crate::{
     error::TiffResult,
-    tags::{self, ResolutionUnit, Tag},
+    tags::{ResolutionUnit, Tag},
 };
 
 pub mod colortype;
+pub mod compression;
 mod tiff_value;
 mod writer;
 
 use self::colortype::*;
+use self::compression::*;
 use self::writer::*;
 
 /// Encoder for Tiff and BigTiff files.
@@ -98,11 +100,22 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
         &mut self,
         width: u32,
         height: u32,
-    ) -> TiffResult<ImageEncoder<W, C, K>> {
+    ) -> TiffResult<ImageEncoder<W, C, K, Uncompressed>> {
         let encoder = DirectoryEncoder::new(&mut self.writer)?;
         ImageEncoder::new(encoder, width, height)
     }
 
+    /// Create an [`ImageEncoder`] to encode an image one slice at a time.
+    pub fn new_image_with_compression<C: ColorType, D: Compression>(
+        &mut self,
+        width: u32,
+        height: u32,
+        compression: D,
+    ) -> TiffResult<ImageEncoder<W, C, K, D>> {
+        let encoder = DirectoryEncoder::new(&mut self.writer)?;
+        ImageEncoder::with_compression(encoder, width, height, compression)
+    }
+
     /// Convenience function to write an entire image from memory.
     pub fn write_image<C: ColorType>(
         &mut self,
@@ -117,6 +130,23 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
         let image: ImageEncoder<W, C, K> = ImageEncoder::new(encoder, width, height)?;
         image.write_data(data)
     }
+
+    /// Convenience function to write an entire image from memory with a given compression.
+    pub fn write_image_with_compression<C: ColorType, D: Compression>(
+        &mut self,
+        width: u32,
+        height: u32,
+        compression: D,
+        data: &[C::Inner],
+    ) -> TiffResult<()>
+    where
+        [C::Inner]: TiffValue,
+    {
+        let encoder = DirectoryEncoder::new(&mut self.writer)?;
+        let image: ImageEncoder<W, C, K, D> =
+            ImageEncoder::with_compression(encoder, width, height, compression)?;
+        image.write_data(data)
+    }
 }
 
 /// Low level interface to encode ifd directories.
@@ -216,6 +246,11 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
         Ok(offset)
     }
 
+    /// Provides the number of bytes written by the underlying TiffWriter during the last call.
+    fn last_written(&self) -> u64 {
+        self.writer.last_written()
+    }
+
     fn finish_internal(&mut self) -> TiffResult<()> {
         let ifd_pointer = self.write_directory()?;
         let curr_pos = self.writer.offset();
@@ -277,7 +312,13 @@ impl<'a, W: Write + Seek, K: TiffKind> Drop for DirectoryEncoder<'a, W, K> {
 /// # }
 /// ```
 /// You can also call write_data function wich will encode by strip and finish
-pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> {
+pub struct ImageEncoder<
+    'a,
+    W: 'a + Write + Seek,
+    C: ColorType,
+    K: TiffKind,
+    D: Compression = Uncompressed,
+> {
     encoder: DirectoryEncoder<'a, W, K>,
     strip_idx: u64,
     strip_count: u64,
@@ -288,11 +329,26 @@ pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> {
     strip_offsets: Vec<K::OffsetType>,
     strip_byte_count: Vec<K::OffsetType>,
     dropped: bool,
+    compression: D,
     _phantom: ::std::marker::PhantomData<C>,
 }
 
-impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, K> {
-    fn new(mut encoder: DirectoryEncoder<'a, W, K>, width: u32, height: u32) -> TiffResult<Self> {
+impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
+    ImageEncoder<'a, W, T, K, D>
+{
+    fn new(encoder: DirectoryEncoder<'a, W, K>, width: u32, height: u32) -> TiffResult<Self>
+    where
+        D: Default,
+    {
+        Self::with_compression(encoder, width, height, D::default())
+    }
+
+    fn with_compression(
+        mut encoder: DirectoryEncoder<'a, W, K>,
+        width: u32,
+        height: u32,
+        compression: D,
+    ) -> TiffResult<Self> {
         let row_samples = u64::from(width) * u64::try_from(<T>::BITS_PER_SAMPLE.len())?;
         let row_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN);
 
@@ -304,7 +360,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
 
         encoder.write_tag(Tag::ImageWidth, width)?;
         encoder.write_tag(Tag::ImageLength, height)?;
-        encoder.write_tag(Tag::Compression, tags::CompressionMethod::None.to_u16())?;
+        encoder.write_tag(Tag::Compression, D::COMPRESSION_METHOD.to_u16())?;
 
         encoder.write_tag(Tag::BitsPerSample, <T>::BITS_PER_SAMPLE)?;
         let sample_format: Vec<_> = <T>::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect();
@@ -332,6 +388,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
             strip_offsets: Vec::new(),
             strip_byte_count: Vec::new(),
             dropped: false,
+            compression: compression,
             _phantom: ::std::marker::PhantomData,
         })
     }
@@ -354,7 +411,6 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
     where
         [T::Inner]: TiffValue,
     {
-        // TODO: Compression
         let samples = self.next_strip_sample_count();
         if u64::try_from(value.len())? != samples {
             return Err(io::Error::new(
@@ -364,9 +420,12 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
             .into());
         }
 
+        // Write the (possible compressed) data to the encoder.
         let offset = self.encoder.write_data(value)?;
+        let byte_count = self.encoder.last_written() as usize;
+
         self.strip_offsets.push(K::convert_offset(offset)?);
-        self.strip_byte_count.push(value.bytes().try_into()?);
+        self.strip_byte_count.push(byte_count.try_into()?);
 
         self.strip_idx += 1;
         Ok(())
@@ -392,12 +451,19 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
             )
             .into());
         }
+
+        self.encoder
+            .writer
+            .set_compression(self.compression.get_algorithm());
+
         let mut idx = 0;
         while self.next_strip_sample_count() > 0 {
             let sample_count = usize::try_from(self.next_strip_sample_count())?;
             self.write_strip(&data[idx..idx + sample_count])?;
             idx += sample_count;
         }
+
+        self.encoder.writer.reset_compression();
         self.finish()?;
         Ok(())
     }
@@ -475,7 +541,9 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
     }
 }
 
-impl<'a, W: Write + Seek, C: ColorType, K: TiffKind> Drop for ImageEncoder<'a, W, C, K> {
+impl<'a, W: Write + Seek, C: ColorType, K: TiffKind, D: Compression> Drop
+    for ImageEncoder<'a, W, C, K, D>
+{
     fn drop(&mut self) {
         if !self.dropped {
             let _ = self.finish_internal();
diff --git a/src/encoder/tiff_value.rs b/src/encoder/tiff_value.rs
index 1009acb..43653f4 100644
--- a/src/encoder/tiff_value.rs
+++ b/src/encoder/tiff_value.rs
@@ -1,4 +1,4 @@
-use std::io::Write;
+use std::{borrow::Cow, io::Write, slice::from_ref};
 
 use crate::{bytecast, tags::Type, TiffError, TiffFormatError, TiffResult};
 
@@ -12,7 +12,18 @@ pub trait TiffValue {
     fn bytes(&self) -> usize {
         self.count() * usize::from(Self::BYTE_LEN)
     }
-    fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()>;
+
+    /// Access this value as an contiguous sequence of bytes.
+    /// If their is no trivial representation, allocate it on the heap.
+    fn data(&self) -> Cow<[u8]>;
+
+    /// Write this value to a TiffWriter.
+    /// While the default implementation will work in all cases, it may require unnecessary allocations.
+    /// The written bytes of any custom implementation MUST be the same as yielded by `self.data()`.
+    fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
+        writer.write_bytes(&self.data())?;
+        Ok(())
+    }
 }
 
 impl TiffValue for [u8] {
@@ -23,9 +34,8 @@ impl TiffValue for [u8] {
         self.len()
     }
 
-    fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
-        writer.write_bytes(self)?;
-        Ok(())
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(self)
     }
 }
 
@@ -37,10 +47,8 @@ impl TiffValue for [i8] {
         self.len()
     }
 
-    fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
-        let slice = bytecast::i8_as_ne_bytes(self);
-        writer.write_bytes(slice)?;
-        Ok(())
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
     }
 }
 
@@ -52,10 +60,8 @@ impl TiffValue for [u16] {
         self.len()
     }
 
-    fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
-        let slice = bytecast::u16_as_ne_bytes(self);
-        writer.write_bytes(slice)?;
-        Ok(())
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
     }
 }
 
@@ -67,10 +73,8 @@ impl TiffValue for [i16] {
         self.len()
     }
 
-    fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
-        let slice = bytecast::i16_as_ne_bytes(self);
-        writer.write_bytes(slice)?;
-        Ok(())
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
     }
 }
 
@@ -82,10 +86,8 @@ impl TiffValue for [u32] {
         self.len()
     }
 
-    fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
-        let slice = bytecast::u32_as_ne_bytes(self);
-        writer.write_bytes(slice)?;
-        Ok(())
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
     }
 }
 
@@ -97,10 +99,8 @@ impl TiffValue for [i32] {
         self.len()
     }
 
-    fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
-        let slice = bytecast::i32_as_ne_bytes(self);
-        writer.write_bytes(slice)?;
-        Ok(())
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
     }
 }
 
@@ -112,10 +112,8 @@ impl TiffValue for [u64] {
         self.len()
     }
 
-    fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
-        let slice = bytecast::u64_as_ne_bytes(self);
-        writer.write_bytes(slice)?;
-        Ok(())
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
     }
 }
 
@@ -127,10 +125,8 @@ impl TiffValue for [i64] {
         self.len()
     }
 
-    fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
-        let slice = bytecast::i64_as_ne_bytes(self);
-        writer.write_bytes(slice)?;
-        Ok(())
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
     }
 }
 
@@ -142,11 +138,9 @@ impl TiffValue for [f32] {
         self.len()
     }
 
-    fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
-        // We write using nativeedian so this sould be safe
-        let slice = bytecast::f32_as_ne_bytes(self);
-        writer.write_bytes(slice)?;
-        Ok(())
+    fn data(&self) -> Cow<[u8]> {
+        // We write using native endian so this should be safe
+        Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
     }
 }
 
@@ -158,75 +152,9 @@ impl TiffValue for [f64] {
         self.len()
     }
 
-    fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
-        // We write using nativeedian so this sould be safe
-        let slice = bytecast::f64_as_ne_bytes(self);
-        writer.write_bytes(slice)?;
-        Ok(())
-    }
-}
-
-impl TiffValue for [Ifd] {
-    const BYTE_LEN: u8 = 4;
-    const FIELD_TYPE: Type = Type::IFD;
-
-    fn count(&self) -> usize {
-        self.len()
-    }
-
-    fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
-        for x in self {
-            x.write(writer)?;
-        }
-        Ok(())
-    }
-}
-
-impl TiffValue for [Ifd8] {
-    const BYTE_LEN: u8 = 8;
-    const FIELD_TYPE: Type = Type::IFD8;
-
-    fn count(&self) -> usize {
-        self.len()
-    }
-
-    fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
-        for x in self {
-            x.write(writer)?;
-        }
-        Ok(())
-    }
-}
-
-impl TiffValue for [Rational] {
-    const BYTE_LEN: u8 = 8;
-    const FIELD_TYPE: Type = Type::RATIONAL;
-
-    fn count(&self) -> usize {
-        self.len()
-    }
-
-    fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
-        for x in self {
-            x.write(writer)?;
-        }
-        Ok(())
-    }
-}
-
-impl TiffValue for [SRational] {
-    const BYTE_LEN: u8 = 8;
-    const FIELD_TYPE: Type = Type::SRATIONAL;
-
-    fn count(&self) -> usize {
-        self.len()
-    }
-
-    fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
-        for x in self {
-            x.write(writer)?;
-        }
-        Ok(())
+    fn data(&self) -> Cow<[u8]> {
+        // We write using native endian so this should be safe
+        Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
     }
 }
 
@@ -242,6 +170,10 @@ impl TiffValue for u8 {
         writer.write_u8(*self)?;
         Ok(())
     }
+
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(from_ref(self))
+    }
 }
 
 impl TiffValue for i8 {
@@ -256,6 +188,10 @@ impl TiffValue for i8 {
         writer.write_i8(*self)?;
         Ok(())
     }
+
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
+    }
 }
 
 impl TiffValue for u16 {
@@ -270,6 +206,10 @@ impl TiffValue for u16 {
         writer.write_u16(*self)?;
         Ok(())
     }
+
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
+    }
 }
 
 impl TiffValue for i16 {
@@ -284,6 +224,10 @@ impl TiffValue for i16 {
         writer.write_i16(*self)?;
         Ok(())
     }
+
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
+    }
 }
 
 impl TiffValue for u32 {
@@ -298,6 +242,10 @@ impl TiffValue for u32 {
         writer.write_u32(*self)?;
         Ok(())
     }
+
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
+    }
 }
 
 impl TiffValue for i32 {
@@ -312,6 +260,10 @@ impl TiffValue for i32 {
         writer.write_i32(*self)?;
         Ok(())
     }
+
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
+    }
 }
 
 impl TiffValue for u64 {
@@ -326,6 +278,10 @@ impl TiffValue for u64 {
         writer.write_u64(*self)?;
         Ok(())
     }
+
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
+    }
 }
 
 impl TiffValue for i64 {
@@ -340,6 +296,10 @@ impl TiffValue for i64 {
         writer.write_i64(*self)?;
         Ok(())
     }
+
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
+    }
 }
 
 impl TiffValue for f32 {
@@ -354,6 +314,10 @@ impl TiffValue for f32 {
         writer.write_f32(*self)?;
         Ok(())
     }
+
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
+    }
 }
 
 impl TiffValue for f64 {
@@ -368,6 +332,10 @@ impl TiffValue for f64 {
         writer.write_f64(*self)?;
         Ok(())
     }
+
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
+    }
 }
 
 impl TiffValue for Ifd {
@@ -382,6 +350,10 @@ impl TiffValue for Ifd {
         writer.write_u32(self.0)?;
         Ok(())
     }
+
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
+    }
 }
 
 impl TiffValue for Ifd8 {
@@ -396,6 +368,10 @@ impl TiffValue for Ifd8 {
         writer.write_u64(self.0)?;
         Ok(())
     }
+
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
+    }
 }
 
 impl TiffValue for Rational {
@@ -411,6 +387,14 @@ impl TiffValue for Rational {
         writer.write_u32(self.d)?;
         Ok(())
     }
+
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Owned({
+            let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
+            let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
+            [first_dword, second_dword].concat()
+        })
+    }
 }
 
 impl TiffValue for SRational {
@@ -426,6 +410,14 @@ impl TiffValue for SRational {
         writer.write_i32(self.d)?;
         Ok(())
     }
+
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Owned({
+            let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
+            let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d));
+            [first_dword, second_dword].concat()
+        })
+    }
 }
 
 impl TiffValue for str {
@@ -445,6 +437,17 @@ impl TiffValue for str {
             Err(TiffError::FormatError(TiffFormatError::InvalidTag))
         }
     }
+
+    fn data(&self) -> Cow<[u8]> {
+        Cow::Owned({
+            if self.is_ascii() && !self.bytes().any(|b| b == 0) {
+                let bytes: &[u8] = self.as_bytes();
+                [bytes, &[0]].concat()
+            } else {
+                vec![]
+            }
+        })
+    }
 }
 
 impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
@@ -458,8 +461,45 @@ impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
     fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
         (*self).write(writer)
     }
+
+    fn data(&self) -> Cow<[u8]> {
+        T::data(self)
+    }
 }
 
+macro_rules! impl_tiff_value_for_contiguous_sequence {
+    ($inner_type:ty; $bytes:expr; $field_type:expr) => {
+        impl $crate::encoder::TiffValue for [$inner_type] {
+            const BYTE_LEN: u8 = $bytes;
+            const FIELD_TYPE: Type = $field_type;
+
+            fn count(&self) -> usize {
+                self.len()
+            }
+
+            fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
+                for x in self {
+                    x.write(writer)?;
+                }
+                Ok(())
+            }
+
+            fn data(&self) -> Cow<[u8]> {
+                let mut buf: Vec<u8> = Vec::with_capacity(Self::BYTE_LEN as usize * self.len());
+                for x in self {
+                    buf.extend_from_slice(&x.data());
+                }
+                Cow::Owned(buf)
+            }
+        }
+    };
+}
+
+impl_tiff_value_for_contiguous_sequence!(Ifd; 4; Type::IFD);
+impl_tiff_value_for_contiguous_sequence!(Ifd8; 8; Type::IFD8);
+impl_tiff_value_for_contiguous_sequence!(Rational; 8; Type::RATIONAL);
+impl_tiff_value_for_contiguous_sequence!(SRational; 8; Type::SRATIONAL);
+
 /// Type to represent tiff values of type `IFD`
 #[derive(Clone)]
 pub struct Ifd(pub u32);
diff --git a/src/encoder/writer.rs b/src/encoder/writer.rs
index 5ba47d1..c5139e9 100644
--- a/src/encoder/writer.rs
+++ b/src/encoder/writer.rs
@@ -1,3 +1,4 @@
+use crate::encoder::compression::*;
 use crate::error::TiffResult;
 use std::io::{self, Seek, SeekFrom, Write};
 
@@ -42,87 +43,126 @@ pub fn write_bigtiff_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<
 pub struct TiffWriter<W> {
     writer: W,
     offset: u64,
+    byte_count: u64,
+    compressor: Compressor,
 }
 
 impl<W: Write> TiffWriter<W> {
     pub fn new(writer: W) -> Self {
-        Self { writer, offset: 0 }
+        Self {
+            writer,
+            offset: 0,
+            byte_count: 0,
+            compressor: Compressor::default(),
+        }
+    }
+
+    pub fn set_compression(&mut self, compressor: Compressor) {
+        self.compressor = compressor;
+    }
+
+    pub fn reset_compression(&mut self) {
+        self.compressor = Compressor::default();
     }
 
     pub fn offset(&self) -> u64 {
         self.offset
     }
 
+    pub fn last_written(&self) -> u64 {
+        self.byte_count
+    }
+
     pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), io::Error> {
-        self.writer.write_all(bytes)?;
-        self.offset += bytes.len() as u64;
+        self.byte_count = self.compressor.write_to(&mut self.writer, bytes)?;
+        self.offset += self.byte_count;
         Ok(())
     }
 
     pub fn write_u8(&mut self, n: u8) -> Result<(), io::Error> {
-        self.writer.write_all(&n.to_ne_bytes())?;
-        self.offset += 1;
+        self.byte_count = self
+            .compressor
+            .write_to(&mut self.writer, &n.to_ne_bytes())?;
+        self.offset += self.byte_count;
         Ok(())
     }
 
     pub fn write_i8(&mut self, n: i8) -> Result<(), io::Error> {
-        self.writer.write_all(&n.to_ne_bytes())?;
-        self.offset += 1;
+        self.byte_count = self
+            .compressor
+            .write_to(&mut self.writer, &n.to_ne_bytes())?;
+        self.offset += self.byte_count;
         Ok(())
     }
 
     pub fn write_u16(&mut self, n: u16) -> Result<(), io::Error> {
-        self.writer.write_all(&n.to_ne_bytes())?;
-        self.offset += 2;
+        self.byte_count = self
+            .compressor
+            .write_to(&mut self.writer, &n.to_ne_bytes())?;
+        self.offset += self.byte_count;
 
         Ok(())
     }
 
     pub fn write_i16(&mut self, n: i16) -> Result<(), io::Error> {
-        self.writer.write_all(&n.to_ne_bytes())?;
-        self.offset += 2;
+        self.byte_count = self
+            .compressor
+            .write_to(&mut self.writer, &n.to_ne_bytes())?;
+        self.offset += self.byte_count;
 
         Ok(())
     }
 
     pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> {
-        self.writer.write_all(&n.to_ne_bytes())?;
-        self.offset += 4;
+        self.byte_count = self
+            .compressor
+            .write_to(&mut self.writer, &n.to_ne_bytes())?;
+        self.offset += self.byte_count;
 
         Ok(())
     }
 
     pub fn write_i32(&mut self, n: i32) -> Result<(), io::Error> {
-        self.writer.write_all(&n.to_ne_bytes())?;
-        self.offset += 4;
+        self.byte_count = self
+            .compressor
+            .write_to(&mut self.writer, &n.to_ne_bytes())?;
+        self.offset += self.byte_count;
 
         Ok(())
     }
 
     pub fn write_u64(&mut self, n: u64) -> Result<(), io::Error> {
-        self.writer.write_all(&n.to_ne_bytes())?;
-        self.offset += 8;
+        self.byte_count = self
+            .compressor
+            .write_to(&mut self.writer, &n.to_ne_bytes())?;
+        self.offset += self.byte_count;
 
         Ok(())
     }
 
     pub fn write_i64(&mut self, n: i64) -> Result<(), io::Error> {
-        self.writer.write_all(&n.to_ne_bytes())?;
-        self.offset += 8;
+        self.byte_count = self
+            .compressor
+            .write_to(&mut self.writer, &n.to_ne_bytes())?;
+        self.offset += self.byte_count;
 
         Ok(())
     }
 
     pub fn write_f32(&mut self, n: f32) -> Result<(), io::Error> {
-        self.writer.write_all(&u32::to_ne_bytes(n.to_bits()))?;
-        self.offset += 4;
+        self.byte_count = self
+            .compressor
+            .write_to(&mut self.writer, &u32::to_ne_bytes(n.to_bits()))?;
+        self.offset += self.byte_count;
 
         Ok(())
     }
 
     pub fn write_f64(&mut self, n: f64) -> Result<(), io::Error> {
-        self.writer.write_all(&u64::to_ne_bytes(n.to_bits()))?;
-        self.offset += 8;
+        self.byte_count = self
+            .compressor
+            .write_to(&mut self.writer, &u64::to_ne_bytes(n.to_bits()))?;
+        self.offset += self.byte_count;
 
         Ok(())
     }
diff --git a/src/error.rs b/src/error.rs
index ea6819d..45ea5b0 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -10,6 +10,8 @@ use crate::tags::{
 };
 use crate::ColorType;
 
+use crate::weezl::LzwError;
+
 /// Tiff error kinds.
 #[derive(Debug)]
 pub enum TiffError {
@@ -288,5 +290,15 @@ impl From<std::num::TryFromIntError> for TiffError {
     }
 }
 
+impl From<LzwError> for TiffError {
+    fn from(err: LzwError) -> TiffError {
+        match err {
+            LzwError::InvalidCode => TiffError::FormatError(TiffFormatError::Format(String::from(
+                "LZW compressed data corrupted",
+            ))),
+        }
+    }
+}
+
 /// Result of an image decoding/encoding process
 pub type TiffResult<T> = Result<T, TiffError>;


Test patch:
diff --git a/tests/encode_images_with_compression.rs b/tests/encode_images_with_compression.rs
new file mode 100644
index 0000000..1cf178f
--- /dev/null
+++ b/tests/encode_images_with_compression.rs
@@ -0,0 +1,157 @@
+extern crate tiff;
+
+use std::io::{Cursor, Seek, Write};
+use tiff::{
+    decoder::{Decoder, DecodingResult},
+    encoder::{
+        colortype::{self, ColorType},
+        compression::*,
+        TiffEncoder, TiffValue,
+    },
+};
+
+trait TestImage<const NUM_CHANNELS: usize>: From<Vec<<Self::Color as ColorType>::Inner>> {
+    const WIDTH: u32;
+    const HEIGHT: u32;
+    type Color: ColorType;
+
+    fn reference_data(&self) -> &[<Self::Color as ColorType>::Inner];
+    fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; NUM_CHANNELS];
+
+    fn compress<C: Compression, W: Write + Seek>(
+        &self,
+        encoder: &mut TiffEncoder<W>,
+        compression: C,
+    ) where
+        [<Self::Color as ColorType>::Inner]: TiffValue,
+    {
+        let image = encoder
+            .new_image_with_compression::<Self::Color, C>(Self::WIDTH, Self::HEIGHT, compression)
+            .unwrap();
+        image.write_data(self.reference_data()).unwrap();
+    }
+
+    fn generate() -> Self {
+        assert_eq!(
+            Self::Color::BITS_PER_SAMPLE.len(),
+            NUM_CHANNELS,
+            "Incompatible color type"
+        );
+
+        let mut data = Vec::with_capacity((Self::WIDTH * Self::HEIGHT) as usize * NUM_CHANNELS);
+        for x in 0..Self::WIDTH {
+            for y in 0..Self::HEIGHT {
+                data.extend(IntoIterator::into_iter(Self::generate_pixel(x, y)));
+            }
+        }
+        Self::from(data)
+    }
+}
+
+struct TestImageColor(Vec<u16>);
+
+impl From<Vec<u16>> for TestImageColor {
+    fn from(value: Vec<u16>) -> Self {
+        Self(value)
+    }
+}
+
+impl TestImage<3> for TestImageColor {
+    const WIDTH: u32 = 1;
+    const HEIGHT: u32 = 7;
+    type Color = colortype::RGB16;
+
+    fn reference_data(&self) -> &[u16] {
+        &self.0
+    }
+
+    fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; 3] {
+        let val = (x + y) % <Self::Color as ColorType>::Inner::MAX as u32;
+        [val as <Self::Color as ColorType>::Inner; 3]
+    }
+}
+
+struct TestImageGrayscale(Vec<u8>);
+
+impl From<Vec<u8>> for TestImageGrayscale {
+    fn from(value: Vec<u8>) -> Self {
+        Self(value)
+    }
+}
+
+impl TestImage<1> for TestImageGrayscale {
+    const WIDTH: u32 = 21;
+    const HEIGHT: u32 = 10;
+    type Color = colortype::Gray8;
+
+    fn reference_data(&self) -> &[u8] {
+        &self.0
+    }
+
+    fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; 1] {
+        let val = (x + y) % <Self::Color as ColorType>::Inner::MAX as u32;
+        [val as <Self::Color as ColorType>::Inner]
+    }
+}
+
+fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
+    let mut data = Cursor::new(Vec::new());
+
+    let image_rgb = TestImageColor::generate();
+    let image_grayscale = TestImageGrayscale::generate();
+
+    // Encode tiff with compression
+    {
+        // Create a multipage image with 2 images
+        let mut encoder = TiffEncoder::new(&mut data).unwrap();
+        image_rgb.compress(&mut encoder, compression.clone());
+        image_grayscale.compress(&mut encoder, compression);
+    }
+
+    // Decode tiff
+    data.set_position(0);
+    {
+        let mut decoder = Decoder::new(data).unwrap();
+
+        // Check the RGB image
+        assert_eq!(
+            match decoder.read_image() {
+                Ok(DecodingResult::U16(image_data)) => image_data,
+                unexpected => panic!("Descoding RGB failed: {:?}", unexpected),
+            },
+            image_rgb.reference_data()
+        );
+
+        // Check the grayscale image
+        decoder.next_image().unwrap();
+        assert_eq!(
+            match decoder.read_image() {
+                Ok(DecodingResult::U8(image_data)) => image_data,
+                unexpected => panic!("Decoding grayscale failed: {:?}", unexpected),
+            },
+            image_grayscale.reference_data()
+        );
+    }
+}
+
+#[test]
+fn encode_decode_without_compression() {
+    encode_decode_with_compression(Uncompressed::default());
+}
+
+#[test]
+fn encode_decode_with_lzw() {
+    encode_decode_with_compression(Lzw::default());
+}
+
+#[test]
+fn encode_decode_with_deflate() {
+    encode_decode_with_compression(Deflate::with_level(DeflateLevel::Fast));
+    encode_decode_with_compression(Deflate::with_level(DeflateLevel::Balanced));
+    encode_decode_with_compression(Deflate::with_level(DeflateLevel::Best));
+}
+
+#[test]
+fn encode_decode_with_packbits() {
+    encode_decode_with_compression(Packbits::default());
+}