Repository: image-rs/image-tiff
Pull request #67 (2019-12-21 14:00:56)

Refactoring, cleanup, safety, docs & CMYK(16|32|64) support
This is a collection of many mostly small changes that would be tedious to separate. Motivations:
- Preparing for floating point support, which is working and will be PR'd following this, but is much simpler with these changes than without.
- Preparing for BigTIFF support, which is still in progress but depends on these changes.
- Removing most unchecked casts, both for the purposes of robustness/correctness and as necessary preparation for generic bounds for BigTIFF.
- Improving some code structure and encoder/decoder code reuse.

Summary of major and notable breaking changes:
- Addition of a `TiffError::IntSizeError` top level variant to indicate size casting errors that don't necessarily fall into an existing error (i.e., may not be format errors or limit errors).
- Moving tags into a top-level module so they can be reused in the encoder/decoder and by users of the library. Some traits that may make it easier for consumers to use their own tag enums are also exposed.
- The README now summarizes supported and unsupported features, so users don't have to experiment to figure out what works.
- Add support for decoding and encoding CMYK(16|32|64). This is trivial due to the other refactoring and is mostly bringing features up to a logical symmetry rather than a new feature for a separate PR.

**A note about breaking changes:** only two published crates depend on `image-tiff`: `image`, which we control, and `n5`, which I control. A github code search found only one unpublished crate that did, [`ndimage`](https://github.com/Robzz/ndimage). Additionally `image-tiff` is pre-1.0 and still has a ways to go in feature support, so I'm in favor of being aggressive with breaking changes if they bring more structure necessary for full support of the format's capabilities.

PR is draft for the moment as I might amend a few more changes from my BigTIFF prototype into this.

HeroicKatora: >  Additionally image-tiff is pre-1.0 and still has a ways to go in feature support, so I'm in favor of being aggressive with breaking changes if they bring more structure necessary for full support of the format's capabilities.

Supporting a whole new spectrum of color type, and refactoring encoding and decoding paths is an effort easily worth another major release. :+1:

> This is a collection of many mostly small changes that would be tedious to separate.

If it's possible, I could likely very quickly review the removal of `tempfile` and the related refactoring  and deduplication of tests. That's quite a bit of work that I'd wanted to do at some point but never quite found the time.

Patch:
diff --git a/README.md b/README.md
index 977eb1f..df41496 100644
--- a/README.md
+++ b/README.md
@@ -4,3 +4,43 @@
 [![Further crate info](https://img.shields.io/crates/v/tiff.svg)](https://crates.io/crates/tiff)
 
 TIFF decoding and encoding library in pure Rust
+
+## Supported
+
+### Features
+- Baseline spec (other than formats and tags listed below as not supported)
+- Multipage
+- Incremental decoding
+
+### 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) | |
+| `BlackIsZero` | Gray(8\|16\|32\|64) | |
+| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | |
+| `RGBPalette` | | |
+| `Mask` | | |
+| `CMYK` | CMYK(8\|16\|32\|64) | |
+| `YCbCr` | | |
+| `CIELab` | | |
+
+### Compressions
+
+| | Decoding | Encoding |
+|-|-|-|
+| None | ✓ | ✓ |
+| LZW | ✓ | |
+| Deflate | ✓ | |
+| PackBits | ✓ | |
+
+
+## Not yet supported
+
+Formats and interpretations not listed above or with empty entries are unsupported.
+
+- Baseline tags
+  - `ExtraSamples`
+- Extension tags
+- BigTIFF
diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs
index 51553d2..58965d1 100644
--- a/src/decoder/ifd.rs
+++ b/src/decoder/ifd.rs
@@ -1,142 +1,16 @@
 //! Function for reading TIFF tags
 
 use std::collections::HashMap;
+use std::convert::TryFrom;
 use std::io::{self, Read, Seek};
 use std::mem;
 
 use super::stream::{ByteOrder, EndianReader, SmartReader};
+use tags::{Tag, Type};
 use {TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
 
 use self::Value::{Ascii, List, Rational, Unsigned, Signed, SRational};
 
-macro_rules! tags {
-    {
-        // Permit arbitrary meta items, which include documentation.
-        $( #[$enum_attr:meta] )*
-        $vis:vis enum $name:ident($ty:ty) $(unknown($unknown_doc:literal))* {
-            // Each of the `Name = Val,` permitting documentation.
-            $($(#[$ident_attr:meta])* $tag:ident = $val:expr,)*
-        }
-    } => {
-        $( #[$enum_attr] )*
-        #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
-        pub enum $name {
-            $($(#[$ident_attr])* $tag,)*
-            // FIXME: switch to non_exhaustive once stabilized and compiler requirement new enough
-            #[doc(hidden)]
-            __NonExhaustive,
-            $( 
-                #[doc = $unknown_doc]
-                Unknown($ty),
-            )*
-        }
-
-        impl $name {
-            #[inline(always)]
-            fn __from_inner_type(n: $ty) -> Result<Self, $ty> {
-                match n {
-                    $( $val => Ok($name::$tag), )*
-                    n => Err(n),
-                }
-            }
-
-            #[inline(always)]
-            fn __to_inner_type(&self) -> $ty {
-                match *self {
-                    $( $name::$tag => $val, )*
-                    $( $name::Unknown(n) => { $unknown_doc; n }, )*
-                    $name::__NonExhaustive => unreachable!(),
-                }
-            }
-        }
-    }
-}
-
-// Note: These tags appear in the order they are mentioned in the TIFF reference
-tags! {
-/// TIFF tags
-pub enum Tag(u16) unknown("A private or extension tag") {
-    // Baseline tags:
-    Artist = 315,
-    // grayscale images PhotometricInterpretation 1 or 3
-    BitsPerSample = 258,
-    CellLength = 265, // TODO add support
-    CellWidth = 264, // TODO add support
-    // palette-color images (PhotometricInterpretation 3)
-    ColorMap = 320, // TODO add support
-    Compression = 259, // TODO add support for 2 and 32773
-    Copyright = 33_432,
-    DateTime = 306,
-    ExtraSamples = 338, // TODO add support
-    FillOrder = 266, // TODO add support
-    FreeByteCounts = 289, // TODO add support
-    FreeOffsets = 288, // TODO add support
-    GrayResponseCurve = 291, // TODO add support
-    GrayResponseUnit = 290, // TODO add support
-    HostComputer = 316,
-    ImageDescription = 270,
-    ImageLength = 257,
-    ImageWidth = 256,
-    Make = 271,
-    MaxSampleValue = 281, // TODO add support
-    MinSampleValue = 280, // TODO add support
-    Model = 272,
-    NewSubfileType = 254, // TODO add support
-    Orientation = 274, // TODO add support
-    PhotometricInterpretation = 262,
-    PlanarConfiguration = 284,
-    ResolutionUnit = 296, // TODO add support
-    RowsPerStrip = 278,
-    SamplesPerPixel = 277,
-    Software = 305,
-    StripByteCounts = 279,
-    StripOffsets = 273,
-    SubfileType = 255, // TODO add support
-    Threshholding = 263, // TODO add support
-    XResolution = 282,
-    YResolution = 283,
-    // Advanced tags
-    Predictor = 317,
-}
-}
-
-impl Tag {
-    pub fn from_u16(val: u16) -> Self {
-        Self::__from_inner_type(val).unwrap_or_else(Tag::Unknown)
-    }
-
-    pub fn to_u16(&self) -> u16 {
-        Self::__to_inner_type(self)
-    }
-}
-
-tags! {
-/// The type of an IFD entry (a 2 byte field).
-pub enum Type(u16) {
-    BYTE = 1,
-    ASCII = 2,
-    SHORT = 3,
-    LONG = 4,
-    RATIONAL = 5,
-    SBYTE = 6,
-    SSHORT = 8,
-    SLONG = 9,
-    SRATIONAL = 10,
-    /// BigTIFF 64-bit unsigned integer
-    LONG8 = 16,
-}
-}
-
-impl Type {
-    pub fn from_u16(val: u16) -> Option<Self> {
-        Self::__from_inner_type(val).ok()
-    }
-
-    pub fn to_u16(&self) -> u16 {
-        Self::__to_inner_type(self)
-    }
-}
-
 
 #[allow(unused_qualifications)]
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -181,7 +55,7 @@ impl Value {
             }
             Unsigned(val) => Ok(vec![val]),
             Rational(numerator, denominator) => Ok(vec![numerator, denominator]),
-            Ascii(val) => Ok(val.chars().map(|x| x as u32).collect()),
+            Ascii(val) => Ok(val.chars().map(u32::from).collect()),
             val => Err(TiffError::FormatError(
                 TiffFormatError::UnsignedIntegerExpected(val),
             )),
@@ -320,11 +194,12 @@ impl Entry {
                 ))
             }),
             (Type::ASCII, n) => {
-                if n as usize > limits.decoding_buffer_size {
+                let n = usize::try_from(n)?;
+                if n > limits.decoding_buffer_size {
                     return Err(TiffError::LimitsExceeded);
                 }
                 decoder.goto_offset(self.r(bo).read_u32()?)?;
-                let string = decoder.read_string(n as usize)?;
+                let string = decoder.read_string(n)?;
                 Ok(Ascii(string))
             }
             _ => Err(TiffError::UnsupportedError(
@@ -339,11 +214,12 @@ impl Entry {
             R: Read + Seek,
             F: Fn(&mut super::Decoder<R>) -> TiffResult<Value>,
     {
-        if value_count as usize > limits.decoding_buffer_size / mem::size_of::<Value>() {
+        let value_count = usize::try_from(value_count)?;
+        if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
             return Err(TiffError::LimitsExceeded);
         }
 
-        let mut v = Vec::with_capacity(value_count as usize);
+        let mut v = Vec::with_capacity(value_count);
         decoder.goto_offset(self.r(bo).read_u32()?)?;
         for _ in 0..value_count {
             v.push(decode_fn(decoder)?)
diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs
index f879df6..4836c32 100644
--- a/src/decoder/mod.rs
+++ b/src/decoder/mod.rs
@@ -1,3 +1,4 @@
+use std::convert::TryFrom;
 use std::collections::HashMap;
 use std::io::{self, Read, Seek};
 use std::cmp;
@@ -5,10 +6,10 @@ use std::cmp;
 use {ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
 
 use self::ifd::Directory;
+use tags::{CompressionMethod, PhotometricInterpretation, Predictor, Tag, Type};
 
 use self::stream::{ByteOrder, EndianReader, LZWReader, DeflateReader, PackBitsReader, SmartReader};
 
-#[macro_use]
 pub mod ifd;
 mod stream;
 
@@ -112,87 +113,6 @@ impl<'a> DecodingBuffer<'a> {
     }
 }
 
-tags! {
-pub enum PhotometricInterpretation(u16) {
-    WhiteIsZero = 0,
-    BlackIsZero = 1,
-    RGB = 2,
-    RGBPalette = 3,
-    TransparencyMask = 4,
-    CMYK = 5,
-    YCbCr = 6,
-    CIELab = 8,
-}
-}
-
-tags! {
-pub enum CompressionMethod(u16) {
-    None = 1,
-    Huffman = 2,
-    Fax3 = 3,
-    Fax4 = 4,
-    LZW = 5,
-    JPEG = 6,
-    Deflate = 8,
-    OldDeflate = 0x80B2,
-    PackBits = 0x8005,
-}
-}
-
-tags! {
-pub enum PlanarConfiguration(u16) {
-    Chunky = 1,
-    Planar = 2,
-}
-}
-
-tags! {
-enum Predictor(u16) {
-    None = 1,
-    Horizontal = 2,
-}
-}
-
-impl PhotometricInterpretation {
-    pub fn from_u16(val: u16) -> Option<Self> {
-        Self::__from_inner_type(val).ok()
-    }
-
-    pub fn to_u16(&self) -> u16 {
-        Self::__to_inner_type(self)
-    }
-}
-
-impl CompressionMethod {
-    pub fn from_u16(val: u16) -> Option<Self> {
-        Self::__from_inner_type(val).ok()
-    }
-
-    pub fn to_u16(&self) -> u16 {
-        Self::__to_inner_type(self)
-    }
-}
-
-impl PlanarConfiguration {
-    pub fn from_u16(val: u16) -> Option<Self> {
-        Self::__from_inner_type(val).ok()
-    }
-
-    pub fn to_u16(&self) -> u16 {
-        Self::__to_inner_type(self)
-    }
-}
-
-impl Predictor {
-    pub fn from_u16(val: u16) -> Option<Self> {
-        Self::__from_inner_type(val).ok()
-    }
-
-    pub fn to_u16(&self) -> u16 {
-        Self::__to_inner_type(self)
-    }
-}
-
 #[derive(Debug)]
 struct StripDecodeState {
     strip_index: usize,
@@ -277,12 +197,12 @@ impl Wrapping for u64 {
     }
 }
 
-fn rev_hpredict_nsamp<T>(image: &mut [T], size: (u32, u32), samples: usize)
+fn rev_hpredict_nsamp<T>(image: &mut [T], size: (u32, u32), samples: usize) -> TiffResult<()>
 where
     T: Copy + Wrapping,
 {
-    let width = size.0 as usize;
-    let height = size.1 as usize;
+    let width = usize::try_from(size.0)?;
+    let height = usize::try_from(size.1)?;
     for row in 0..height {
         for col in samples..width * samples {
             let prev_pixel = image[(row * width * samples + col - samples)];
@@ -290,13 +210,15 @@ where
             *pixel = pixel.wrapping_add(prev_pixel);
         }
     }
+    Ok(())
 }
 
 fn rev_hpredict(image: DecodingBuffer, size: (u32, u32), color_type: ColorType) -> TiffResult<()> {
     let samples = match color_type {
         ColorType::Gray(8) | ColorType::Gray(16) | ColorType::Gray(32) | ColorType::Gray(64) => 1,
         ColorType::RGB(8) | ColorType::RGB(16) | ColorType::RGB(32) | ColorType::RGB(64) => 3,
-        ColorType::RGBA(8) | ColorType::RGBA(16) | ColorType::RGBA(32) | ColorType::RGBA(64) | ColorType::CMYK(8) => 4,
+        ColorType::RGBA(8) | ColorType::RGBA(16) | ColorType::RGBA(32) | ColorType::RGBA(64)
+        | ColorType::CMYK(8) | ColorType::CMYK(16) | ColorType::CMYK(32) | ColorType::CMYK(64) => 4,
         _ => {
             return Err(TiffError::UnsupportedError(
                 TiffUnsupportedError::HorizontalPredictor(color_type),
@@ -305,16 +227,16 @@ fn rev_hpredict(image: DecodingBuffer, size: (u32, u32), color_type: ColorType)
     };
     match image {
         DecodingBuffer::U8(buf) => {
-            rev_hpredict_nsamp(buf, size, samples);
+            rev_hpredict_nsamp(buf, size, samples)?;
         }
         DecodingBuffer::U16(buf) => {
-            rev_hpredict_nsamp(buf, size, samples);
+            rev_hpredict_nsamp(buf, size, samples)?;
         }
         DecodingBuffer::U32(buf) => {
-            rev_hpredict_nsamp(buf, size, samples);
+            rev_hpredict_nsamp(buf, size, samples)?;
         }
         DecodingBuffer::U64(buf) => {
-            rev_hpredict_nsamp(buf, size, samples);
+            rev_hpredict_nsamp(buf, size, samples)?;
         }
     }
     Ok(())
@@ -363,9 +285,17 @@ impl<R: Read + Seek> Decoder<R> {
                     )),
                 }
             },
-            PhotometricInterpretation::CMYK if self.bits_per_sample == [8, 8, 8, 8] => {
-                Ok(ColorType::CMYK(8))
-            }
+            PhotometricInterpretation::CMYK => {
+                match self.bits_per_sample[..] {
+                    [c, m, y, k] if [c, c, c] == [m, y, k] => Ok(ColorType::CMYK(c)),
+                    _ => Err(TiffError::UnsupportedError(
+                        TiffUnsupportedError::InterpretationWithBits(
+                            self.photometric_interpretation,
+                            self.bits_per_sample.clone(),
+                        ),
+                    )),
+                }
+            },
             PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero
                 if self.bits_per_sample.len() == 1 =>
             {
@@ -424,50 +354,27 @@ impl<R: Read + Seek> Decoder<R> {
     /// To determine whether there are more images call `TIFFDecoder::more_images` instead.
     pub fn next_image(&mut self) -> TiffResult<()> {
         self.ifd = Some(self.read_ifd()?);
-        self.width = self.get_tag_u32(ifd::Tag::ImageWidth)?;
-        self.height = self.get_tag_u32(ifd::Tag::ImageLength)?;
+        self.width = self.get_tag_u32(Tag::ImageWidth)?;
+        self.height = self.get_tag_u32(Tag::ImageLength)?;
         self.strip_decoder = None;
-        // TODO: error on non-SHORT value.
-        self.photometric_interpretation = match PhotometricInterpretation::from_u16(
-            self.get_tag_u32(ifd::Tag::PhotometricInterpretation)? as u16
-        ) {
-            Some(val) => val,
-            None => {
-                return Err(TiffError::UnsupportedError(
-                    TiffUnsupportedError::UnknownInterpretation,
-                ))
-            }
-        };
-        // TODO: error on non-SHORT value.
-        if let Some(val) = self.find_tag_u32(ifd::Tag::Compression)? {
-            match CompressionMethod::from_u16(val as u16) {
-                Some(method) => self.compression_method = method,
-                None => {
-                    return Err(TiffError::UnsupportedError(
-                        TiffUnsupportedError::UnknownCompressionMethod,
-                    ))
-                }
-            }
+
+        self.photometric_interpretation = self
+            .find_tag_unsigned(Tag::PhotometricInterpretation)?
+            .and_then(PhotometricInterpretation::from_u16)
+            .ok_or(TiffUnsupportedError::UnknownInterpretation)?;
+
+        if let Some(val) = self.find_tag_unsigned(Tag::Compression)? {
+            self.compression_method = CompressionMethod::from_u16(val)
+                .ok_or(TiffUnsupportedError::UnknownCompressionMethod)?;
         }
-        if let Some(val) = self.find_tag_u32(ifd::Tag::SamplesPerPixel)? {
-            self.samples = val as u8
+        if let Some(val) = self.find_tag_unsigned(Tag::SamplesPerPixel)? {
+            self.samples = val;
         }
         match self.samples {
-            1 => {
-                if let Some(val) = self.find_tag_u32(ifd::Tag::BitsPerSample)? {
-                    self.bits_per_sample = vec![val as u8]
-                }
-            }
-            3 | 4 => {
-                if let Some(val) = self.find_tag_u32_vec(ifd::Tag::BitsPerSample)? {
-                    self.bits_per_sample = val.iter().map(|&v| v as u8).collect()
-                }
-            }
-            _ => {
-                return Err(TiffError::UnsupportedError(
-                    TiffUnsupportedError::UnsupportedSampleDepth(self.samples),
-                ))
-            }
+            1 | 3 | 4 => if let Some(val) = self.find_tag_unsigned_vec(Tag::BitsPerSample)? {
+                self.bits_per_sample = val;
+            },
+            _ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(self.samples).into()),
         }
         Ok(())
     }
@@ -547,9 +454,9 @@ impl<R: Read + Seek> Decoder<R> {
     // Type  2 bytes
     // Count 4 bytes
     // Value 4 bytes either a pointer the value itself
-    fn read_entry(&mut self) -> TiffResult<Option<(ifd::Tag, ifd::Entry)>> {
-        let tag = ifd::Tag::from_u16(self.read_short()?);
-        let type_ = match ifd::Type::from_u16(self.read_short()?) {
+    fn read_entry(&mut self) -> TiffResult<Option<(Tag, ifd::Entry)>> {
+        let tag = Tag::from_u16_exhaustive(self.read_short()?);
+        let type_ = match Type::from_u16(self.read_short()?) {
             Some(t) => t,
             None => {
                 // Unknown type. Skip this entry according to spec.
@@ -595,7 +502,7 @@ impl<R: Read + Seek> Decoder<R> {
 
     /// Tries to retrieve a tag.
     /// Return `Ok(None)` if the tag is not present.
-    pub fn find_tag(&mut self, tag: ifd::Tag) -> TiffResult<Option<ifd::Value>> {
+    pub fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<ifd::Value>> {
         let entry = match self.ifd.as_ref().unwrap().get(&tag) {
             None => return Ok(None),
             Some(entry) => entry.clone(),
@@ -606,25 +513,36 @@ impl<R: Read + Seek> Decoder<R> {
         Ok(Some(entry.val(&limits, self)?))
     }
 
-    /// Tries to retrieve a tag and convert it to the desired type.
-    pub fn find_tag_u32(&mut self, tag: ifd::Tag) -> TiffResult<Option<u32>> {
-        match self.find_tag(tag)? {
-            Some(val) => val.into_u32().map(Some),
-            None => Ok(None),
-        }
+    /// Tries to retrieve a tag and convert it to the desired unsigned type.
+    pub fn find_tag_unsigned<T: TryFrom<u32>>(&mut self, tag: Tag) -> TiffResult<Option<T>> {
+        self.find_tag(tag)?
+            .map(|v| v.into_u32()).transpose()?
+            .map(|value| T::try_from(value)
+                .map_err(|_| TiffFormatError::InvalidTagValueType(tag).into()))
+            .transpose()
     }
 
-    /// Tries to retrieve a tag and convert it to the desired type.
-    pub fn find_tag_u32_vec(&mut self, tag: ifd::Tag) -> TiffResult<Option<Vec<u32>>> {
-        match self.find_tag(tag)? {
-            Some(val) => val.into_u32_vec().map(Some),
-            None => Ok(None),
-        }
+    /// Tries to retrieve a vector of all a tag's values and convert them to
+    /// the desired unsigned type.
+    pub fn find_tag_unsigned_vec<T: TryFrom<u32>>(&mut self, tag: Tag) -> TiffResult<Option<Vec<T>>> {
+        self.find_tag(tag)?
+            .map(|v| v.into_u32_vec()).transpose()?
+            .map(|v| v.into_iter()
+                .map(|u| T::try_from(u).map_err(|_| TiffFormatError::InvalidTagValueType(tag).into()))
+                .collect())
+            .transpose()
+    }
+
+    /// Tries to retrieve a tag and convert it to the desired unsigned type.
+    /// Returns an error if the tag is not present.
+    pub fn get_tag_unsigned<T: TryFrom<u32>>(&mut self, tag: Tag) -> TiffResult<T> {
+        self.find_tag_unsigned(tag)?
+            .ok_or_else(|| TiffFormatError::RequiredTagNotFound(tag).into())
     }
 
     /// Tries to retrieve a tag.
     /// Returns an error if the tag is not present
-    pub fn get_tag(&mut self, tag: ifd::Tag) -> TiffResult<ifd::Value> {
+    pub fn get_tag(&mut self, tag: Tag) -> TiffResult<ifd::Value> {
         match self.find_tag(tag)? {
             Some(val) => Ok(val),
             None => Err(TiffError::FormatError(
@@ -634,12 +552,12 @@ impl<R: Read + Seek> Decoder<R> {
     }
 
     /// Tries to retrieve a tag and convert it to the desired type.
-    pub fn get_tag_u32(&mut self, tag: ifd::Tag) -> TiffResult<u32> {
+    pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult<u32> {
         self.get_tag(tag)?.into_u32()
     }
 
     /// Tries to retrieve a tag and convert it to the desired type.
-    pub fn get_tag_u32_vec(&mut self, tag: ifd::Tag) -> TiffResult<Vec<u32>> {
+    pub fn get_tag_u32_vec(&mut self, tag: Tag) -> TiffResult<Vec<u32>> {
         self.get_tag(tag)?.into_u32_vec()
     }
 
@@ -658,14 +576,14 @@ impl<R: Read + Seek> Decoder<R> {
             CompressionMethod::None => {
                 let order = self.reader.byte_order;
                 (
-                    length as usize,
+                    usize::try_from(length)?,
                     Box::new(SmartReader::wrap(&mut self.reader, order)),
                 )
             }
             CompressionMethod::LZW => {
                 let (bytes, reader) = LZWReader::new(
                     &mut self.reader,
-                    length as usize,
+                    usize::try_from(length)?,
                     max_uncompressed_length
                 )?;
                 (bytes, Box::new(reader))
@@ -675,7 +593,7 @@ impl<R: Read + Seek> Decoder<R> {
                 let (bytes, reader) = PackBitsReader::new(
                     &mut self.reader,
                     order,
-                    length as usize
+                    usize::try_from(length)?,
                 )?;
                 (bytes, Box::new(reader))
             }
@@ -704,17 +622,20 @@ impl<R: Read + Seek> Decoder<R> {
                 bytes
             }
             (ColorType::RGBA(16), DecodingBuffer::U16(ref mut buffer))
-            | (ColorType::RGB(16), DecodingBuffer::U16(ref mut buffer)) => {
+            | (ColorType::RGB(16), DecodingBuffer::U16(ref mut buffer))
+            | (ColorType::CMYK(16), DecodingBuffer::U16(ref mut buffer)) => {
                 reader.read_u16_into(&mut buffer[..bytes / 2])?;
                 bytes / 2
             }
             (ColorType::RGBA(32), DecodingBuffer::U32(ref mut buffer))
-            | (ColorType::RGB(32), DecodingBuffer::U32(ref mut buffer)) => {
+            | (ColorType::RGB(32), DecodingBuffer::U32(ref mut buffer))
+            | (ColorType::CMYK(32), DecodingBuffer::U32(ref mut buffer)) => {
                 reader.read_u32_into(&mut buffer[..bytes / 4])?;
                 bytes / 4
             }
             (ColorType::RGBA(64), DecodingBuffer::U64(ref mut buffer))
-            | (ColorType::RGB(64), DecodingBuffer::U64(ref mut buffer)) => {
+            | (ColorType::RGB(64), DecodingBuffer::U64(ref mut buffer))
+            | (ColorType::CMYK(64), DecodingBuffer::U64(ref mut buffer)) => {
                 reader.read_u64_into(&mut buffer[..bytes / 8])?;
                 bytes / 8
             }
@@ -765,7 +686,7 @@ impl<R: Read + Seek> Decoder<R> {
     /// Number of strips in image
     pub fn strip_count(&mut self) -> TiffResult<u32> {
         let rows_per_strip = self
-            .get_tag_u32(ifd::Tag::RowsPerStrip)
+            .get_tag_u32(Tag::RowsPerStrip)
             .unwrap_or(self.height);
 
         if rows_per_strip == 0 {
@@ -777,8 +698,8 @@ impl<R: Read + Seek> Decoder<R> {
 
     fn initialize_strip_decoder(&mut self) -> TiffResult<()> {
         if self.strip_decoder.is_none() {
-            let strip_offsets = self.get_tag_u32_vec(ifd::Tag::StripOffsets)?;
-            let strip_bytes = self.get_tag_u32_vec(ifd::Tag::StripByteCounts)?;
+            let strip_offsets = self.get_tag_u32_vec(Tag::StripOffsets)?;
+            let strip_bytes = self.get_tag_u32_vec(Tag::StripByteCounts)?;
 
             self.strip_decoder = Some(StripDecodeState {
                 strip_index: 0,
@@ -812,18 +733,18 @@ impl<R: Read + Seek> Decoder<R> {
                 TiffFormatError::InconsistentSizesEncountered,
             ))?;
 
-        let rows_per_strip = self
-            .get_tag_u32(ifd::Tag::RowsPerStrip)
-            .unwrap_or(self.height) as usize;
+        let rows_per_strip = usize::try_from(self
+            .get_tag_u32(Tag::RowsPerStrip)
+            .unwrap_or(self.height))?;
 
         let strip_height = cmp::min(
             rows_per_strip,
-            self.height as usize - index * rows_per_strip,
+            usize::try_from(self.height)? - index * rows_per_strip,
         );
 
-        let buffer_size = self.width as usize * strip_height * self.bits_per_sample.len();
+        let buffer_size = usize::try_from(self.width)? * strip_height * self.bits_per_sample.len();
 
-        if buffer.len() < buffer_size || byte_count as usize / buffer.byte_len() > buffer_size {
+        if buffer.len() < buffer_size || usize::try_from(byte_count)? / buffer.byte_len() > buffer_size {
             return Err(TiffError::FormatError(
                 TiffFormatError::InconsistentSizesEncountered,
             ));
@@ -833,7 +754,7 @@ impl<R: Read + Seek> Decoder<R> {
 
         self.strip_decoder.as_mut().unwrap().strip_index += 1;
 
-        if index as u32 == self.strip_count()? {
+        if u32::try_from(index)? == self.strip_count()? {
             self.strip_decoder = None;
         }
 
@@ -842,14 +763,13 @@ impl<R: Read + Seek> Decoder<R> {
                 TiffFormatError::InconsistentSizesEncountered,
             ));
         }
-        if let Ok(predictor) = self.get_tag_u32(ifd::Tag::Predictor) {
-            // TODO: error on non-SHORT value.
-            match Predictor::from_u16(predictor as u16) {
+        if let Ok(predictor) = self.get_tag_unsigned(Tag::Predictor) {
+            match Predictor::from_u16(predictor) {
                 Some(Predictor::None) => (),
                 Some(Predictor::Horizontal) => {
                     rev_hpredict(
                         buffer.copy(),
-                        (self.width, strip_height as u32),
+                        (self.width, u32::try_from(strip_height)?),
                         self.colortype()?,
                     )?;
                 }
@@ -864,33 +784,37 @@ impl<R: Read + Seek> Decoder<R> {
         Ok(())
     }
 
+    fn result_buffer(&self, height: usize) -> TiffResult<DecodingResult> {
+        let buffer_size = usize::try_from(self.width)? * height * self.bits_per_sample.len();
+
+        match self.bits_per_sample.iter().cloned().max().unwrap_or(8) {
+            n if n <= 8 => DecodingResult::new_u8(buffer_size, &self.limits),
+            n if n <= 16 => DecodingResult::new_u16(buffer_size, &self.limits),
+            n if n <= 32 => DecodingResult::new_u32(buffer_size, &self.limits),
+            n if n <= 64 => DecodingResult::new_u64(buffer_size, &self.limits),
+            n => {
+                Err(TiffError::UnsupportedError(
+                    TiffUnsupportedError::UnsupportedBitsPerChannel(n),
+                ))
+            }
+        }
+    }
+
     /// Read a single strip from the image and return it as a Vector
     pub fn read_strip(&mut self) -> TiffResult<DecodingResult> {
         self.initialize_strip_decoder()?;
         let index = self.strip_decoder.as_ref().unwrap().strip_index;
 
-        let rows_per_strip = self
-            .get_tag_u32(ifd::Tag::RowsPerStrip)
-            .unwrap_or(self.height) as usize;
+        let rows_per_strip = usize::try_from(self
+            .get_tag_u32(Tag::RowsPerStrip)
+            .unwrap_or(self.height))?;
 
         let strip_height = cmp::min(
             rows_per_strip,
-            self.height as usize - index * rows_per_strip,
+            usize::try_from(self.height)? - index * rows_per_strip,
         );
 
-        let buffer_size = self.width as usize * strip_height * self.bits_per_sample.iter().count();
-
-        let mut result = match self.bits_per_sample.iter().cloned().max().unwrap_or(8) {
-            n if n <= 8 => DecodingResult::new_u8(buffer_size, &self.limits)?,
-            n if n <= 16 => DecodingResult::new_u16(buffer_size, &self.limits)?,
-            n if n <= 32 => DecodingResult::new_u32(buffer_size, &self.limits)?,
-            n if n <= 64 => DecodingResult::new_u64(buffer_size, &self.limits)?,
-            n => {
-                return Err(TiffError::UnsupportedError(
-                    TiffUnsupportedError::UnsupportedBitsPerChannel(n),
-                ))
-            }
-        };
+        let mut result = self.result_buffer(strip_height)?;
 
         self.read_strip_to_buffer(result.as_buffer(0))?;
 
@@ -900,29 +824,16 @@ impl<R: Read + Seek> Decoder<R> {
     /// Decodes the entire image and return it as a Vector
     pub fn read_image(&mut self) -> TiffResult<DecodingResult> {
         self.initialize_strip_decoder()?;
-        let rows_per_strip = self
-            .get_tag_u32(ifd::Tag::RowsPerStrip)
-            .unwrap_or(self.height) as usize;
+        let rows_per_strip = usize::try_from(self
+            .get_tag_u32(Tag::RowsPerStrip)
+            .unwrap_or(self.height))?;
 
         let samples_per_strip =
-            self.width as usize * rows_per_strip * self.bits_per_sample.iter().count();
-
-        let buffer_size =
-            self.width as usize * self.height as usize * self.bits_per_sample.iter().count();
+            usize::try_from(self.width)? * rows_per_strip * self.bits_per_sample.len();
 
-        let mut result = match self.bits_per_sample.iter().cloned().max().unwrap_or(8) {
-            n if n <= 8 => DecodingResult::new_u8(buffer_size, &self.limits)?,
-            n if n <= 16 => DecodingResult::new_u16(buffer_size, &self.limits)?,
-            n if n <= 32 => DecodingResult::new_u32(buffer_size, &self.limits)?,
-            n if n <= 64 => DecodingResult::new_u64(buffer_size, &self.limits)?,
-            n => {
-                return Err(TiffError::UnsupportedError(
-                    TiffUnsupportedError::UnsupportedBitsPerChannel(n),
-                ))
-            }
-        };
+        let mut result = self.result_buffer(usize::try_from(self.height)?)?;
 
-        for i in 0..self.strip_count()? as usize {
+        for i in 0..usize::try_from(self.strip_count()?)? {
             self.read_strip_to_buffer(result.as_buffer(samples_per_strip * i))?;
         }
 
diff --git a/src/encoder/colortype.rs b/src/encoder/colortype.rs
index 2f49fdb..2f97066 100644
--- a/src/encoder/colortype.rs
+++ b/src/encoder/colortype.rs
@@ -1,4 +1,4 @@
-use decoder::PhotometricInterpretation;
+use tags::PhotometricInterpretation;
 
 /// Trait for different colortypes that can be encoded.
 pub trait ColorType {
@@ -100,3 +100,24 @@ impl ColorType for CMYK8 {
     const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
     const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
 }
+
+pub struct CMYK16;
+impl ColorType for CMYK16 {
+    type Inner = u16;
+    const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
+    const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
+}
+
+pub struct CMYK32;
+impl ColorType for CMYK32 {
+    type Inner = u32;
+    const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
+    const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
+}
+
+pub struct CMYK64;
+impl ColorType for CMYK64 {
+    type Inner = u64;
+    const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
+    const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
+}
diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs
index 96e8bfb..22e39d3 100644
--- a/src/encoder/mod.rs
+++ b/src/encoder/mod.rs
@@ -1,9 +1,10 @@
 use byteorder::NativeEndian;
 use std::collections::BTreeMap;
+use std::convert::TryFrom;
 use std::io::{Seek, Write};
 use std::mem;
 
-use decoder::ifd::{self, Tag};
+use tags::{self, ResolutionUnit, Tag, Type};
 use error::{TiffError, TiffFormatError, TiffResult};
 
 pub mod colortype;
@@ -25,17 +26,10 @@ pub struct SRational {
     pub d: i32,
 }
 
-/// Type to represent resolution units
-pub enum ResolutionUnit {
-    None = 1,
-    Inch = 2,
-    Centimeter = 3,
-}
-
 /// Trait for types that can be encoded in a tiff file
 pub trait TiffValue {
     const BYTE_LEN: u32;
-    const FIELD_TYPE: ifd::Type;
+    const FIELD_TYPE: Type;
     fn count(&self) -> u32;
     fn bytes(&self) -> u32 {
         self.count() * Self::BYTE_LEN
@@ -45,7 +39,7 @@ pub trait TiffValue {
 
 impl TiffValue for [u8] {
     const BYTE_LEN: u32 = 1;
-    const FIELD_TYPE: ifd::Type = ifd::Type::BYTE;
+    const FIELD_TYPE: Type = Type::BYTE;
 
     fn count(&self) -> u32 {
         self.len() as u32
@@ -59,7 +53,7 @@ impl TiffValue for [u8] {
 
 impl TiffValue for [i8] {
     const BYTE_LEN: u32 = 1;
-    const FIELD_TYPE: ifd::Type = ifd::Type::SBYTE;
+    const FIELD_TYPE: Type = Type::SBYTE;
 
     fn count(&self) -> u32 {
         self.len() as u32
@@ -76,7 +70,7 @@ impl TiffValue for [i8] {
 
 impl TiffValue for [u16] {
     const BYTE_LEN: u32 = 2;
-    const FIELD_TYPE: ifd::Type = ifd::Type::SHORT;
+    const FIELD_TYPE: Type = Type::SHORT;
 
     fn count(&self) -> u32 {
         self.len() as u32
@@ -93,7 +87,7 @@ impl TiffValue for [u16] {
 
 impl TiffValue for [i16] {
     const BYTE_LEN: u32 = 2;
-    const FIELD_TYPE: ifd::Type = ifd::Type::SSHORT;
+    const FIELD_TYPE: Type = Type::SSHORT;
 
     fn count(&self) -> u32 {
         self.len() as u32
@@ -110,7 +104,7 @@ impl TiffValue for [i16] {
 
 impl TiffValue for [u32] {
     const BYTE_LEN: u32 = 4;
-    const FIELD_TYPE: ifd::Type = ifd::Type::LONG;
+    const FIELD_TYPE: Type = Type::LONG;
 
     fn count(&self) -> u32 {
         self.len() as u32
@@ -127,7 +121,7 @@ impl TiffValue for [u32] {
 
 impl TiffValue for [i32] {
     const BYTE_LEN: u32 = 4;
-    const FIELD_TYPE: ifd::Type = ifd::Type::SLONG;
+    const FIELD_TYPE: Type = Type::SLONG;
 
     fn count(&self) -> u32 {
         self.len() as u32
@@ -144,7 +138,7 @@ impl TiffValue for [i32] {
 
 impl TiffValue for [u64] {
     const BYTE_LEN: u32 = 8;
-    const FIELD_TYPE: ifd::Type = ifd::Type::LONG8;
+    const FIELD_TYPE: Type = Type::LONG8;
 
     fn count(&self) -> u32 {
         self.len() as u32
@@ -161,7 +155,7 @@ impl TiffValue for [u64] {
 
 impl TiffValue for [Rational] {
     const BYTE_LEN: u32 = 8;
-    const FIELD_TYPE: ifd::Type = ifd::Type::RATIONAL;
+    const FIELD_TYPE: Type = Type::RATIONAL;
 
     fn count(&self) -> u32 {
         self.len() as u32
@@ -177,7 +171,7 @@ impl TiffValue for [Rational] {
 
 impl TiffValue for [SRational] {
     const BYTE_LEN: u32 = 8;
-    const FIELD_TYPE: ifd::Type = ifd::Type::SRATIONAL;
+    const FIELD_TYPE: Type = Type::SRATIONAL;
 
     fn count(&self) -> u32 {
         self.len() as u32
@@ -193,7 +187,7 @@ impl TiffValue for [SRational] {
 
 impl TiffValue for u8 {
     const BYTE_LEN: u32 = 1;
-    const FIELD_TYPE: ifd::Type = ifd::Type::BYTE;
+    const FIELD_TYPE: Type = Type::BYTE;
 
     fn count(&self) -> u32 {
         1
@@ -207,7 +201,7 @@ impl TiffValue for u8 {
 
 impl TiffValue for i8 {
     const BYTE_LEN: u32 = 1;
-    const FIELD_TYPE: ifd::Type = ifd::Type::SBYTE;
+    const FIELD_TYPE: Type = Type::SBYTE;
 
     fn count(&self) -> u32 {
         1
@@ -221,7 +215,7 @@ impl TiffValue for i8 {
 
 impl TiffValue for u16 {
     const BYTE_LEN: u32 = 2;
-    const FIELD_TYPE: ifd::Type = ifd::Type::SHORT;
+    const FIELD_TYPE: Type = Type::SHORT;
 
     fn count(&self) -> u32 {
         1
@@ -235,7 +229,7 @@ impl TiffValue for u16 {
 
 impl TiffValue for i16 {
     const BYTE_LEN: u32 = 2;
-    const FIELD_TYPE: ifd::Type = ifd::Type::SSHORT;
+    const FIELD_TYPE: Type = Type::SSHORT;
 
     fn count(&self) -> u32 {
         1
@@ -249,7 +243,7 @@ impl TiffValue for i16 {
 
 impl TiffValue for u32 {
     const BYTE_LEN: u32 = 4;
-    const FIELD_TYPE: ifd::Type = ifd::Type::LONG;
+    const FIELD_TYPE: Type = Type::LONG;
 
     fn count(&self) -> u32 {
         1
@@ -263,7 +257,7 @@ impl TiffValue for u32 {
 
 impl TiffValue for i32 {
     const BYTE_LEN: u32 = 4;
-    const FIELD_TYPE: ifd::Type = ifd::Type::SLONG;
+    const FIELD_TYPE: Type = Type::SLONG;
 
     fn count(&self) -> u32 {
         1
@@ -277,7 +271,7 @@ impl TiffValue for i32 {
 
 impl TiffValue for u64 {
     const BYTE_LEN: u32 = 8;
-    const FIELD_TYPE: ifd::Type = ifd::Type::LONG8;
+    const FIELD_TYPE: Type = Type::LONG8;
 
     fn count(&self) -> u32 {
         1
@@ -291,7 +285,7 @@ impl TiffValue for u64 {
 
 impl TiffValue for Rational {
     const BYTE_LEN: u32 = 8;
-    const FIELD_TYPE: ifd::Type = ifd::Type::RATIONAL;
+    const FIELD_TYPE: Type = Type::RATIONAL;
 
     fn count(&self) -> u32 {
         1
@@ -306,7 +300,7 @@ impl TiffValue for Rational {
 
 impl TiffValue for SRational {
     const BYTE_LEN: u32 = 8;
-    const FIELD_TYPE: ifd::Type = ifd::Type::SRATIONAL;
+    const FIELD_TYPE: Type = Type::SRATIONAL;
 
     fn count(&self) -> u32 {
         1
@@ -321,7 +315,7 @@ impl TiffValue for SRational {
 
 impl TiffValue for str {
     const BYTE_LEN: u32 = 1;
-    const FIELD_TYPE: ifd::Type = ifd::Type::ASCII;
+    const FIELD_TYPE: Type = Type::ASCII;
 
     fn count(&self) -> u32 {
         self.len() as u32 + 1
@@ -340,7 +334,7 @@ impl TiffValue for str {
 
 impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
     const BYTE_LEN: u32 = T::BYTE_LEN;
-    const FIELD_TYPE: ifd::Type = T::FIELD_TYPE;
+    const FIELD_TYPE: Type = T::FIELD_TYPE;
 
     fn count(&self) -> u32 {
         (*self).count()
@@ -413,7 +407,7 @@ impl<W: Write + Seek> TiffEncoder<W> {
     where
         [C::Inner]: TiffValue,
     {
-        let num_pix = (width as usize).checked_mul(height as usize)
+        let num_pix = usize::try_from(width)?.checked_mul(usize::try_from(height)?)
             .ok_or_else(|| ::std::io::Error::new(
                 ::std::io::ErrorKind::InvalidInput,
                 "Image width * height exceeds usize"))?;
@@ -428,7 +422,7 @@ impl<W: Write + Seek> TiffEncoder<W> {
 
         let mut idx = 0;
         while image.next_strip_sample_count() > 0 {
-            let sample_count = image.next_strip_sample_count() as usize;
+            let sample_count = usize::try_from(image.next_strip_sample_count())?;
             image.write_strip(&data[idx..idx + sample_count])?;
             idx += sample_count;
         }
@@ -462,16 +456,18 @@ impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
     }
 
     /// Write a single ifd tag.
-    pub fn write_tag<T: TiffValue>(&mut self, tag: Tag, value: T) {
+    pub fn write_tag<T: TiffValue>(&mut self, tag: Tag, value: T) -> TiffResult<()> {
         let len = <T>::BYTE_LEN * value.count();
-        let mut bytes = Vec::with_capacity(len as usize);
+        let mut bytes = Vec::with_capacity(usize::try_from(len)?);
         {
             let mut writer = TiffWriter::new(&mut bytes);
-            value.write(&mut writer).unwrap();
+            value.write(&mut writer)?;
         }
 
         self.ifd
             .insert(tag.to_u16(), (<T>::FIELD_TYPE.to_u16(), value.count(), bytes));
+
+        Ok(())
     }
 
     fn write_directory(&mut self) -> TiffResult<u64> {
@@ -482,7 +478,7 @@ impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
                 self.writer.write_bytes(bytes)?;
                 *bytes = vec![0, 0, 0, 0];
                 let mut writer = TiffWriter::new(bytes as &mut [u8]);
-                writer.write_u32(offset as u32)?;
+                writer.write_u32(u32::try_from(offset)?)?;
             } else {
                 while bytes.len() < 4 {
                     bytes.push(0);
@@ -492,7 +488,7 @@ impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
 
         let offset = self.writer.offset();
 
-        self.writer.write_u16(self.ifd.len() as u16)?;
+        self.writer.write_u16(u16::try_from(self.ifd.len())?)?;
         for (tag, &(ref field_type, ref count, ref offset)) in self.ifd.iter() {
             self.writer.write_u16(*tag)?;
             self.writer.write_u16(*field_type)?;
@@ -517,7 +513,7 @@ impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
         let curr_pos = self.writer.offset();
 
         self.writer.goto_offset(self.ifd_pointer_pos)?;
-        self.writer.write_u32(ifd_pointer as u32)?;
+        self.writer.write_u32(u32::try_from(ifd_pointer)?)?;
         self.writer.goto_offset(curr_pos)?;
         self.writer.write_u32(0)?;
 
@@ -584,7 +580,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
         width: u32,
         height: u32,
     ) -> TiffResult<ImageEncoder<'a, W, T>> {
-        let row_samples = u64::from(width) * <T>::BITS_PER_SAMPLE.len() as u64;
+        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);
 
         // As per tiff spec each strip should be about 8k long
@@ -592,19 +588,19 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
 
         let strip_count = (u64::from(height) + rows_per_strip - 1) / rows_per_strip;
 
-        encoder.write_tag(Tag::ImageWidth, width);
-        encoder.write_tag(Tag::ImageLength, height);
-        encoder.write_tag(Tag::Compression, 1u16);
+        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::BitsPerSample, <T>::BITS_PER_SAMPLE);
-        encoder.write_tag(Tag::PhotometricInterpretation, <T>::TIFF_VALUE.to_u16());
+        encoder.write_tag(Tag::BitsPerSample, <T>::BITS_PER_SAMPLE)?;
+        encoder.write_tag(Tag::PhotometricInterpretation, <T>::TIFF_VALUE.to_u16())?;
 
-        encoder.write_tag(Tag::RowsPerStrip, rows_per_strip as u32);
+        encoder.write_tag(Tag::RowsPerStrip, u32::try_from(rows_per_strip)?)?;
 
-        encoder.write_tag(Tag::SamplesPerPixel, <T>::BITS_PER_SAMPLE.len() as u16);
-        encoder.write_tag(Tag::XResolution, Rational { n: 1, d: 1 });
-        encoder.write_tag(Tag::YResolution, Rational { n: 1, d: 1 });
-        encoder.write_tag(Tag::ResolutionUnit, 1u16);
+        encoder.write_tag(Tag::SamplesPerPixel, u16::try_from(<T>::BITS_PER_SAMPLE.len())?)?;
+        encoder.write_tag(Tag::XResolution, Rational { n: 1, d: 1 })?;
+        encoder.write_tag(Tag::YResolution, Rational { n: 1, d: 1 })?;
+        encoder.write_tag(Tag::ResolutionUnit, ResolutionUnit::None.to_u16())?;
 
         Ok(ImageEncoder {
             encoder,
@@ -642,15 +638,15 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
     {
         // TODO: Compression
         let samples = self.next_strip_sample_count();
-        if value.len() as u64 != samples {
+        if u64::try_from(value.len())? != samples {
             return Err(::std::io::Error::new(
                 ::std::io::ErrorKind::InvalidData,
                 "Slice is wrong size for strip").into());
         }
 
         let offset = self.encoder.write_data(value)?;
-        self.strip_offsets.push(offset as u32);
-        self.strip_byte_count.push(value.bytes() as u32);
+        self.strip_offsets.push(u32::try_from(offset)?);
+        self.strip_byte_count.push(value.bytes());
 
         self.strip_idx += 1;
         Ok(())
@@ -658,31 +654,31 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
 
     /// Set image resolution
     pub fn resolution(&mut self, unit: ResolutionUnit, value: Rational) {
-        self.encoder.write_tag(Tag::ResolutionUnit, unit as u16);
-        self.encoder.write_tag(Tag::XResolution, value.clone());
-        self.encoder.write_tag(Tag::YResolution, value);
+        self.encoder.write_tag(Tag::ResolutionUnit, unit.to_u16()).unwrap();
+        self.encoder.write_tag(Tag::XResolution, value.clone()).unwrap();
+        self.encoder.write_tag(Tag::YResolution, value).unwrap();
     }
 
     /// Set image resolution unit
     pub fn resolution_unit(&mut self, unit: ResolutionUnit) {
-        self.encoder.write_tag(Tag::ResolutionUnit, unit as u16);
+        self.encoder.write_tag(Tag::ResolutionUnit, unit.to_u16()).unwrap();
     }
 
     /// Set image x-resolution
     pub fn x_resolution(&mut self, value: Rational) {
-        self.encoder.write_tag(Tag::XResolution, value);
+        self.encoder.write_tag(Tag::XResolution, value).unwrap();
     }
 
     /// Set image y-resolution
     pub fn y_resolution(&mut self, value: Rational) {
-        self.encoder.write_tag(Tag::YResolution, value);
+        self.encoder.write_tag(Tag::YResolution, value).unwrap();
     }
 
     fn finish_internal(&mut self) -> TiffResult<()> {
         self.encoder
-            .write_tag(Tag::StripOffsets, &*self.strip_offsets);
+            .write_tag(Tag::StripOffsets, &*self.strip_offsets)?;
         self.encoder
-            .write_tag(Tag::StripByteCounts, &*self.strip_byte_count);
+            .write_tag(Tag::StripByteCounts, &*self.strip_byte_count)?;
         self.dropped = true;
 
         self.encoder.finish_internal()
diff --git a/src/error.rs b/src/error.rs
index 34902fe..02acbe0 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -3,8 +3,8 @@ use std::fmt;
 use std::io;
 use std::string;
 
-use decoder::ifd::{Tag, Value};
-use decoder::{CompressionMethod, PhotometricInterpretation, PlanarConfiguration};
+use decoder::ifd::{Value};
+use tags::{CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Tag};
 use miniz_oxide::inflate::TINFLStatus;
 use ColorType;
 
@@ -22,6 +22,10 @@ pub enum TiffError {
 
     /// The Limits of the Decoder is exceeded.
     LimitsExceeded,
+
+    /// An integer conversion to or from a platform size failed, either due to
+    /// limits of the platform size or limits of the format.
+    IntSizeError,
 }
 
 /// The image is not formatted properly.
@@ -38,8 +42,9 @@ pub enum TiffFormatError {
     ImageFileDirectoryNotFound,
     InconsistentSizesEncountered,
     InvalidTag,
+    InvalidTagValueType(Tag),
     RequiredTagNotFound(Tag),
-    UnknownPredictor(u32),
+    UnknownPredictor(u16),
     UnsignedIntegerExpected(Value),
     SignedIntegerExpected(Value),
     InflateError(InflateError),
@@ -57,6 +62,8 @@ impl fmt::Display for TiffFormatError {
             ImageFileDirectoryNotFound => write!(fmt, "Image file directory not found."),
             InconsistentSizesEncountered => write!(fmt, "Inconsistent sizes encountered."),
             InvalidTag => write!(fmt, "Image contains invalid tag."),
+            InvalidTagValueType(ref tag) =>
+                write!(fmt, "Tag `{:?}` did not have the expected value type.", tag),
             RequiredTagNotFound(ref tag) => write!(fmt, "Required tag `{:?}` not found.", tag),
             UnknownPredictor(ref predictor) => {
                 write!(fmt, "Unknown predictor “{}” encountered", predictor)
@@ -139,7 +146,7 @@ impl fmt::Display for TiffUnsupportedError {
                 write!(fmt, "Compression method {:?} is unsupported", method)
             }
             UnsupportedSampleDepth(samples) => {
-                write!(fmt, "{} samples per pixel is supported.", samples)
+                write!(fmt, "{} samples per pixel is unsupported.", samples)
             }
             UnsupportedColorType(color_type) => {
                 write!(fmt, "Color type {:?} is unsupported", color_type)
@@ -168,6 +175,7 @@ impl fmt::Display for TiffError {
             ),
             TiffError::IoError(ref e) => e.fmt(fmt),
             TiffError::LimitsExceeded => write!(fmt, "The Decoder limits are exceeded"),
+            TiffError::IntSizeError => write!(fmt, "Platform or format size limits exceeded"),
         }
     }
 }
@@ -179,6 +187,7 @@ impl Error for TiffError {
             TiffError::UnsupportedError(..) => "Unsupported error",
             TiffError::IoError(..) => "IO error",
             TiffError::LimitsExceeded => "Decoder limits exceeded",
+            TiffError::IntSizeError => "Platform or format size limits exceeded",
         }
     }
 
@@ -202,5 +211,23 @@ impl From<string::FromUtf8Error> for TiffError {
     }
 }
 
+impl From<TiffFormatError> for TiffError {
+    fn from(err: TiffFormatError) -> TiffError {
+        TiffError::FormatError(err)
+    }
+}
+
+impl From<TiffUnsupportedError> for TiffError {
+    fn from(err: TiffUnsupportedError) -> TiffError {
+        TiffError::UnsupportedError(err)
+    }
+}
+
+impl From<std::num::TryFromIntError> for TiffError {
+    fn from(_err: std::num::TryFromIntError) -> TiffError {
+        TiffError::IntSizeError
+    }
+}
+
 /// Result of an image decoding/encoding process
 pub type TiffResult<T> = Result<T, TiffError>;
diff --git a/src/lib.rs b/src/lib.rs
index 03ab3e2..7dec5ba 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -13,6 +13,7 @@ extern crate miniz_oxide;
 pub mod decoder;
 pub mod encoder;
 mod error;
+pub mod tags;
 
 pub use self::error::{InflateError, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
 
diff --git a/src/tags.rs b/src/tags.rs
new file mode 100644
index 0000000..fabb74d
--- /dev/null
+++ b/src/tags.rs
@@ -0,0 +1,183 @@
+macro_rules! tags {
+    {
+        // Permit arbitrary meta items, which include documentation.
+        $( #[$enum_attr:meta] )*
+        $vis:vis enum $name:ident($ty:tt) $(unknown($unknown_doc:literal))* {
+            // Each of the `Name = Val,` permitting documentation.
+            $($(#[$ident_attr:meta])* $tag:ident = $val:expr,)*
+        }
+    } => {
+        $( #[$enum_attr] )*
+        #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
+        pub enum $name {
+            $($(#[$ident_attr])* $tag,)*
+            // FIXME: switch to non_exhaustive once stabilized and compiler requirement new enough
+            #[doc(hidden)]
+            __NonExhaustive,
+            $(
+                #[doc = $unknown_doc]
+                Unknown($ty),
+            )*
+        }
+
+        impl $name {
+            #[inline(always)]
+            fn __from_inner_type(n: $ty) -> Result<Self, $ty> {
+                match n {
+                    $( $val => Ok($name::$tag), )*
+                    n => Err(n),
+                }
+            }
+
+            #[inline(always)]
+            fn __to_inner_type(&self) -> $ty {
+                match *self {
+                    $( $name::$tag => $val, )*
+                    $( $name::Unknown(n) => { $unknown_doc; n }, )*
+                    $name::__NonExhaustive => unreachable!(),
+                }
+            }
+        }
+
+        tags!($name, $ty);
+    };
+    // For u16 tags, provide direct inherent primitive conversion methods.
+    ($name:tt, u16) => {
+        impl $name {
+            #[inline(always)]
+            pub fn from_u16(val: u16) -> Option<Self> {
+                Self::__from_inner_type(val).ok()
+            }
+
+            #[inline(always)]
+            pub fn to_u16(&self) -> u16 {
+                Self::__to_inner_type(self)
+            }
+        }
+    };
+    // For other tag types, do nothing for now. With concat_idents one could
+    // provide inherent conversion methods for all types.
+    ($name:tt, $ty:tt) => {};
+}
+
+// Note: These tags appear in the order they are mentioned in the TIFF reference
+tags! {
+/// TIFF tags
+pub enum Tag(u16) unknown("A private or extension tag") {
+    // Baseline tags:
+    Artist = 315,
+    // grayscale images PhotometricInterpretation 1 or 3
+    BitsPerSample = 258,
+    CellLength = 265, // TODO add support
+    CellWidth = 264, // TODO add support
+    // palette-color images (PhotometricInterpretation 3)
+    ColorMap = 320, // TODO add support
+    Compression = 259, // TODO add support for 2 and 32773
+    Copyright = 33_432,
+    DateTime = 306,
+    ExtraSamples = 338, // TODO add support
+    FillOrder = 266, // TODO add support
+    FreeByteCounts = 289, // TODO add support
+    FreeOffsets = 288, // TODO add support
+    GrayResponseCurve = 291, // TODO add support
+    GrayResponseUnit = 290, // TODO add support
+    HostComputer = 316,
+    ImageDescription = 270,
+    ImageLength = 257,
+    ImageWidth = 256,
+    Make = 271,
+    MaxSampleValue = 281, // TODO add support
+    MinSampleValue = 280, // TODO add support
+    Model = 272,
+    NewSubfileType = 254, // TODO add support
+    Orientation = 274, // TODO add support
+    PhotometricInterpretation = 262,
+    PlanarConfiguration = 284,
+    ResolutionUnit = 296, // TODO add support
+    RowsPerStrip = 278,
+    SamplesPerPixel = 277,
+    Software = 305,
+    StripByteCounts = 279,
+    StripOffsets = 273,
+    SubfileType = 255, // TODO add support
+    Threshholding = 263, // TODO add support
+    XResolution = 282,
+    YResolution = 283,
+    // Advanced tags
+    Predictor = 317,
+}
+}
+
+impl Tag {
+    pub fn from_u16_exhaustive(val: u16) -> Self {
+        Self::from_u16(val).unwrap_or_else(|| Tag::Unknown(val))
+    }
+}
+
+tags! {
+/// The type of an IFD entry (a 2 byte field).
+pub enum Type(u16) {
+    BYTE = 1,
+    ASCII = 2,
+    SHORT = 3,
+    LONG = 4,
+    RATIONAL = 5,
+    SBYTE = 6,
+    SSHORT = 8,
+    SLONG = 9,
+    SRATIONAL = 10,
+    /// BigTIFF 64-bit unsigned integer
+    LONG8 = 16,
+}
+}
+
+
+tags! {
+pub enum CompressionMethod(u16) {
+    None = 1,
+    Huffman = 2,
+    Fax3 = 3,
+    Fax4 = 4,
+    LZW = 5,
+    JPEG = 6,
+    Deflate = 8,
+    OldDeflate = 0x80B2,
+    PackBits = 0x8005,
+}
+}
+
+tags! {
+pub enum PhotometricInterpretation(u16) {
+    WhiteIsZero = 0,
+    BlackIsZero = 1,
+    RGB = 2,
+    RGBPalette = 3,
+    TransparencyMask = 4,
+    CMYK = 5,
+    YCbCr = 6,
+    CIELab = 8,
+}
+}
+
+tags! {
+pub enum PlanarConfiguration(u16) {
+    Chunky = 1,
+    Planar = 2,
+}
+}
+
+tags! {
+pub enum Predictor(u16) {
+    None = 1,
+    Horizontal = 2,
+}
+}
+
+tags!{
+/// Type to represent resolution units
+pub enum ResolutionUnit(u16) {
+    None = 1,
+    Inch = 2,
+    Centimeter = 3,
+}
+}
\ No newline at end of file


Test patch:
diff --git a/tests/decode_images.rs b/tests/decode_images.rs
index 7437cb1..9fc8676 100644
--- a/tests/decode_images.rs
+++ b/tests/decode_images.rs
@@ -47,6 +47,16 @@ fn test_image_color_type_unsupported(file: &str, expected_type: ColorType) {
     });
 }
 
+#[test]
+fn test_cmyk_u8() {
+    test_image_sum_u8("cmyk-3c-8b.tiff", ColorType::CMYK(8), 8522658);
+}
+
+#[test]
+fn test_cmyk_u16() {
+    test_image_sum_u16("cmyk-3c-16b.tiff", ColorType::CMYK(16), 2181426827);
+}
+
 #[test]
 fn test_gray_u8() {
     test_image_sum_u8("minisblack-1c-8b.tiff", ColorType::Gray(8), 2840893);
@@ -106,7 +116,7 @@ fn test_string_tags() {
         let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
         let img_file = File::open(path).expect("Cannot find test image!");
         let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
-        let software = decoder.get_tag(ifd::Tag::Software).unwrap();
+        let software = decoder.get_tag(tiff::tags::Tag::Software).unwrap();
         match software {
             ifd::Value::Ascii(s) => assert_eq!(
                 &s,
diff --git a/tests/encode_images.rs b/tests/encode_images.rs
index 988d386..9d1ecfd 100644
--- a/tests/encode_images.rs
+++ b/tests/encode_images.rs
@@ -1,9 +1,9 @@
 extern crate tiff;
 
 use tiff::decoder::{Decoder, DecodingResult};
-use tiff::decoder::ifd::Tag;
 use tiff::encoder::{colortype, TiffEncoder, SRational};
 use tiff::ColorType;
+use tiff::tags::Tag;
 
 use std::fs::File;
 use std::io::{Cursor, Seek, SeekFrom};
@@ -105,6 +105,11 @@ fn test_rgb_u8_roundtrip() {
     test_u8_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
 }
 
+#[test]
+fn test_cmyk_u8_roundtrip() {
+    test_u8_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
+}
+
 #[test]
 fn test_gray_u16_roundtrip() {
     test_u16_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
@@ -115,6 +120,11 @@ fn test_rgb_u16_roundtrip() {
     test_u16_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
 }
 
+#[test]
+fn test_cmyk_u16_roundtrip() {
+    test_u16_roundtrip::<colortype::CMYK16>("cmyk-3c-16b.tiff", ColorType::CMYK(16));
+}
+
 #[test]
 fn test_gray_u32_roundtrip() {
     test_u32_roundtrip::<colortype::Gray32>("gradient-1c-32b.tiff", ColorType::Gray(32));
@@ -144,11 +154,11 @@ fn test_multiple_byte() {
         let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
         let encoder = image_encoder.encoder();
 
-        encoder.write_tag(Tag::Unknown(65000), &[1_u8][..]);
-        encoder.write_tag(Tag::Unknown(65001), &[1_u8, 2][..]);
-        encoder.write_tag(Tag::Unknown(65002), &[1_u8, 2, 3][..]);
-        encoder.write_tag(Tag::Unknown(65003), &[1_u8, 2, 3, 4][..]);
-        encoder.write_tag(Tag::Unknown(65004), &[1_u8, 2, 3, 4, 5][..]);
+        encoder.write_tag(Tag::Unknown(65000), &[1_u8][..]).unwrap();
+        encoder.write_tag(Tag::Unknown(65001), &[1_u8, 2][..]).unwrap();
+        encoder.write_tag(Tag::Unknown(65002), &[1_u8, 2, 3][..]).unwrap();
+        encoder.write_tag(Tag::Unknown(65003), &[1_u8, 2, 3, 4][..]).unwrap();
+        encoder.write_tag(Tag::Unknown(65004), &[1_u8, 2, 3, 4, 5][..]).unwrap();
     }
 
     data.set_position(0);
@@ -174,24 +184,24 @@ fn test_signed() {
         let encoder = image_encoder.encoder();
 
         //Use the "reusable" tags section as per the TIFF6 spec
-        encoder.write_tag(Tag::Unknown(65000), -1_i8);
-        encoder.write_tag(Tag::Unknown(65001), &[-1_i8][..]);
-        encoder.write_tag(Tag::Unknown(65002), &[-1_i8, 2][..]);
-        encoder.write_tag(Tag::Unknown(65003), &[-1_i8, 2, -3][..]);
-        encoder.write_tag(Tag::Unknown(65004), &[-1_i8, 2, -3, 4][..]);
-        encoder.write_tag(Tag::Unknown(65005), &[-1_i8, 2, -3, 4, -5][..]);
-
-        encoder.write_tag(Tag::Unknown(65010), -1_i16);
-        encoder.write_tag(Tag::Unknown(65011), -1_i16);
-        encoder.write_tag(Tag::Unknown(65012), &[-1_i16, 2][..]);
-        encoder.write_tag(Tag::Unknown(65013), &[-1_i16, 2, -3][..]);
-
-        encoder.write_tag(Tag::Unknown(65020), -1_i32);
-        encoder.write_tag(Tag::Unknown(65021), &[-1_i32][..]);
-        encoder.write_tag(Tag::Unknown(65022), &[-1_i32, 2][..]);
-
-        encoder.write_tag(Tag::Unknown(65030), SRational { n: -1, d: 100 });
-        encoder.write_tag(Tag::Unknown(65031), &[SRational { n: -1, d: 100 }, SRational { n: 2, d: 100 }][..]);
+        encoder.write_tag(Tag::Unknown(65000), -1_i8).unwrap();
+        encoder.write_tag(Tag::Unknown(65001), &[-1_i8][..]).unwrap();
+        encoder.write_tag(Tag::Unknown(65002), &[-1_i8, 2][..]).unwrap();
+        encoder.write_tag(Tag::Unknown(65003), &[-1_i8, 2, -3][..]).unwrap();
+        encoder.write_tag(Tag::Unknown(65004), &[-1_i8, 2, -3, 4][..]).unwrap();
+        encoder.write_tag(Tag::Unknown(65005), &[-1_i8, 2, -3, 4, -5][..]).unwrap();
+
+        encoder.write_tag(Tag::Unknown(65010), -1_i16).unwrap();
+        encoder.write_tag(Tag::Unknown(65011), -1_i16).unwrap();
+        encoder.write_tag(Tag::Unknown(65012), &[-1_i16, 2][..]).unwrap();
+        encoder.write_tag(Tag::Unknown(65013), &[-1_i16, 2, -3][..]).unwrap();
+
+        encoder.write_tag(Tag::Unknown(65020), -1_i32).unwrap();
+        encoder.write_tag(Tag::Unknown(65021), &[-1_i32][..]).unwrap();
+        encoder.write_tag(Tag::Unknown(65022), &[-1_i32, 2][..]).unwrap();
+
+        encoder.write_tag(Tag::Unknown(65030), SRational { n: -1, d: 100 }).unwrap();
+        encoder.write_tag(Tag::Unknown(65031), &[SRational { n: -1, d: 100 }, SRational { n: 2, d: 100 }][..]).unwrap();
     }
 
     //Rewind the cursor for reading