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 { - 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::__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) -> TiffResult, { - if value_count as usize > limits.decoding_buffer_size / mem::size_of::() { + let value_count = usize::try_from(value_count)?; + if value_count > limits.decoding_buffer_size / mem::size_of::() { 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::__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::__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::__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::__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(image: &mut [T], size: (u32, u32), samples: usize) +fn rev_hpredict_nsamp(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 Decoder { )), } }, - 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 Decoder { /// 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 Decoder { // Type 2 bytes // Count 4 bytes // Value 4 bytes either a pointer the value itself - fn read_entry(&mut self) -> TiffResult> { - 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> { + 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 Decoder { /// Tries to retrieve a tag. /// Return `Ok(None)` if the tag is not present. - pub fn find_tag(&mut self, tag: ifd::Tag) -> TiffResult> { + pub fn find_tag(&mut self, tag: Tag) -> TiffResult> { let entry = match self.ifd.as_ref().unwrap().get(&tag) { None => return Ok(None), Some(entry) => entry.clone(), @@ -606,25 +513,36 @@ impl Decoder { 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> { - 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>(&mut self, tag: Tag) -> TiffResult> { + 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>> { - 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>(&mut self, tag: Tag) -> TiffResult>> { + 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>(&mut self, tag: Tag) -> TiffResult { + 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 { + pub fn get_tag(&mut self, tag: Tag) -> TiffResult { match self.find_tag(tag)? { Some(val) => Ok(val), None => Err(TiffError::FormatError( @@ -634,12 +552,12 @@ impl Decoder { } /// Tries to retrieve a tag and convert it to the desired type. - pub fn get_tag_u32(&mut self, tag: ifd::Tag) -> TiffResult { + pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult { 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> { + pub fn get_tag_u32_vec(&mut self, tag: Tag) -> TiffResult> { self.get_tag(tag)?.into_u32_vec() } @@ -658,14 +576,14 @@ impl Decoder { 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 Decoder { 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 Decoder { 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 Decoder { /// Number of strips in image pub fn strip_count(&mut self) -> TiffResult { 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 Decoder { 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 Decoder { 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 Decoder { 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 Decoder { 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 Decoder { Ok(()) } + fn result_buffer(&self, height: usize) -> TiffResult { + 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 { 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 Decoder { /// Decodes the entire image and return it as a Vector pub fn read_image(&mut self) -> TiffResult { 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(); + usize::try_from(self.width)? * rows_per_strip * self.bits_per_sample.len(); - let buffer_size = - self.width as usize * self.height as usize * self.bits_per_sample.iter().count(); + let mut result = self.result_buffer(usize::try_from(self.height)?)?; - 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), - )) - } - }; - - 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 TiffEncoder { 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 TiffEncoder { 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(&mut self, tag: Tag, value: T) { + pub fn write_tag(&mut self, tag: Tag, value: T) -> TiffResult<()> { let len = ::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(), (::FIELD_TYPE.to_u16(), value.count(), bytes)); + + Ok(()) } fn write_directory(&mut self) -> TiffResult { @@ -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> { - let row_samples = u64::from(width) * ::BITS_PER_SAMPLE.len() as u64; + let row_samples = u64::from(width) * u64::try_from(::BITS_PER_SAMPLE.len())?; let row_bytes = row_samples * u64::from(::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, ::BITS_PER_SAMPLE); - encoder.write_tag(Tag::PhotometricInterpretation, ::TIFF_VALUE.to_u16()); + encoder.write_tag(Tag::BitsPerSample, ::BITS_PER_SAMPLE)?; + encoder.write_tag(Tag::PhotometricInterpretation, ::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, ::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(::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 for TiffError { } } +impl From for TiffError { + fn from(err: TiffFormatError) -> TiffError { + TiffError::FormatError(err) + } +} + +impl From for TiffError { + fn from(err: TiffUnsupportedError) -> TiffError { + TiffError::UnsupportedError(err) + } +} + +impl From for TiffError { + fn from(_err: std::num::TryFromIntError) -> TiffError { + TiffError::IntSizeError + } +} + /// Result of an image decoding/encoding process pub type TiffResult = Result; 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 { + 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::__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 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::("rgb-3c-8b.tiff", ColorType::RGB(8)); } +#[test] +fn test_cmyk_u8_roundtrip() { + test_u8_roundtrip::("cmyk-3c-8b.tiff", ColorType::CMYK(8)); +} + #[test] fn test_gray_u16_roundtrip() { test_u16_roundtrip::("minisblack-1c-16b.tiff", ColorType::Gray(16)); @@ -115,6 +120,11 @@ fn test_rgb_u16_roundtrip() { test_u16_roundtrip::("rgb-3c-16b.tiff", ColorType::RGB(16)); } +#[test] +fn test_cmyk_u16_roundtrip() { + test_u16_roundtrip::("cmyk-3c-16b.tiff", ColorType::CMYK(16)); +} + #[test] fn test_gray_u32_roundtrip() { test_u32_roundtrip::("gradient-1c-32b.tiff", ColorType::Gray(32)); @@ -144,11 +154,11 @@ fn test_multiple_byte() { let mut image_encoder = tiff.new_image::(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(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); - 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(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); - encoder.write_tag(Tag::Unknown(65021), &[-1_i32][..]); - encoder.write_tag(Tag::Unknown(65022), &[-1_i32, 2][..]); + 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 }); - encoder.write_tag(Tag::Unknown(65031), &[SRational { n: -1, d: 100 }, SRational { n: 2, d: 100 }][..]); + 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 diff --git a/tests/images/cmyk-3c-16b.tiff b/tests/images/cmyk-3c-16b.tiff new file mode 100644 index 0000000..3b79eae Binary files /dev/null and b/tests/images/cmyk-3c-16b.tiff differ diff --git a/tests/images/cmyk-3c-8b.tiff b/tests/images/cmyk-3c-8b.tiff new file mode 100644 index 0000000..d906bbc Binary files /dev/null and b/tests/images/cmyk-3c-8b.tiff differ