diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..143b1ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ + +/target/ +**/*.rs.bk +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..67b6b1c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "tiff" +version = "0.1.0" +authors = [ + "ccgn", + "bvssvni ", + "nwin", + "TyOverby ", + "HeroicKatora", + "Calum", + "CensoredUsername ", + "Robzz"] + +[dependencies] +byteorder = "1.2.*" +lzw = "0.10.*" +num-derive = "0.2.*" +num-traits = "0.2.*" diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs new file mode 100644 index 0000000..8ac9cab --- /dev/null +++ b/src/decoder/ifd.rs @@ -0,0 +1,187 @@ +//! Function for reading TIFF tags + +use std::io::{self, Read, Seek}; +use std::collections::{HashMap}; + +use super::stream::{ByteOrder, SmartReader, EndianReader}; +use ::{TiffError, TiffResult}; + +use self::Value::{Unsigned, List}; + +macro_rules! tags { + {$( + $tag:ident + $val:expr; + )*} => { + + /// TIFF tag + #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] + pub enum Tag { + $($tag,)* + Unknown(u16) + } + impl Tag { + pub fn from_u16(n: u16) -> Tag { + $(if n == $val { Tag::$tag } else)* { + Tag::Unknown(n) + } + } + } + } +} + +// Note: These tags appear in the order they are mentioned in the TIFF reference +tags!{ + // Baseline tags: + Artist 315; // TODO add support + // 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; // TODO add support + DateTime 306; // TODO add support + 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; // TODO add support + ImageDescription 270; // TODO add support + ImageLength 257; + ImageWidth 256; + Make 271; // TODO add support + MaxSampleValue 281; // TODO add support + MinSampleValue 280; // TODO add support + Model 272; // TODO add support + 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; +} + +#[derive(Clone, Copy, Debug, FromPrimitive)] +pub enum Type { + BYTE = 1, + ASCII = 2, + SHORT = 3, + LONG = 4, + RATIONAL = 5, +} + + +#[allow(unused_qualifications)] +#[derive(Debug)] +pub enum Value { + //Signed(i32), + Unsigned(u32), + List(Vec) +} + +impl Value { + pub fn into_u32(self) -> TiffResult { + match self { + Unsigned(val) => Ok(val), + val => Err(TiffError::FormatError(format!( + "Expected unsigned integer, {:?} found.", val + ))) + } + } + pub fn into_u32_vec(self) -> TiffResult> { + match self { + List(vec) => { + let mut new_vec = Vec::with_capacity(vec.len()); + for v in vec { + new_vec.push(v.into_u32()?) + } + Ok(new_vec) + }, + Unsigned(val) => Ok(vec![val]), + //_ => Err(::image::FormatError("Tag data malformed.".to_string())) + } + } +} + +pub struct Entry { + type_: Type, + count: u32, + offset: [u8; 4], +} + +impl ::std::fmt::Debug for Entry { + fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { + fmt.write_str(&format!("Entry {{ type_: {:?}, count: {:?}, offset: {:?} }}", + self.type_, + self.count, + &self.offset + )) + } +} + +impl Entry { + pub fn new(type_: Type, count: u32, offset: [u8; 4]) -> Entry { + Entry { type_, count, offset } + } + + /// Returns a mem_reader for the offset/value field + fn r(&self, byte_order: ByteOrder) -> SmartReader>> { + SmartReader::wrap( + io::Cursor::new(self.offset.to_vec()), + byte_order + ) + } + + pub fn val(&self, decoder: &mut super::Decoder) + -> TiffResult { + let bo = decoder.byte_order(); + match (self.type_, self.count) { + // TODO check if this could give wrong results + // at a different endianess of file/computer. + (Type::BYTE, 1) => Ok(Unsigned(u32::from(self.offset[0]))), + (Type::SHORT, 1) => Ok(Unsigned(u32::from(self.r(bo).read_u16()?))), + (Type::SHORT, 2) => { + let mut r = self.r(bo); + Ok(List(vec![ + Unsigned(u32::from(r.read_u16()?)), + Unsigned(u32::from(r.read_u16()?)) + ])) + }, + (Type::SHORT, n) => { + let mut v = Vec::with_capacity(n as usize); + try!(decoder.goto_offset(try!(self.r(bo).read_u32()))); + for _ in 0 .. n { + v.push(Unsigned(u32::from(decoder.read_short()?))) + } + Ok(List(v)) + }, + (Type::LONG, 1) => Ok(Unsigned(try!(self.r(bo).read_u32()))), + (Type::LONG, n) => { + let mut v = Vec::with_capacity(n as usize); + try!(decoder.goto_offset(try!(self.r(bo).read_u32()))); + for _ in 0 .. n { + v.push(Unsigned(try!(decoder.read_long()))) + } + Ok(List(v)) + } + _ => Err(TiffError::UnsupportedError("Unsupported data type.".to_string())) + } + } +} + +/// Type representing an Image File Directory +pub type Directory = HashMap; diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs new file mode 100644 index 0000000..3f512d5 --- /dev/null +++ b/src/decoder/mod.rs @@ -0,0 +1,541 @@ +use std::io::{self, Read, Seek}; +use std::mem; +use num_traits::{FromPrimitive, Num}; +use std::collections::HashMap; + +use ::{ColorType, TiffError, TiffResult}; + +use self::ifd::Directory; + +use self::stream::{ + ByteOrder, + EndianReader, + SmartReader, + LZWReader, + PackBitsReader +}; + +mod ifd; +mod stream; + +/// Result of a decoding process +#[derive(Debug)] +pub enum DecodingResult { + /// A vector of unsigned bytes + U8(Vec), + /// A vector of unsigned words + U16(Vec) +} + +// A buffer for image decoding +enum DecodingBuffer<'a> { + /// A slice of unsigned bytes + U8(&'a mut [u8]), + /// A slice of unsigned words + U16(&'a mut [u16]) +} + +#[derive(Clone, Copy, Debug, PartialEq, FromPrimitive)] +enum PhotometricInterpretation { + WhiteIsZero = 0, + BlackIsZero = 1, + RGB = 2, + RGBPalette = 3, + TransparencyMask = 4, + CMYK = 5, + YCbCr = 6, + CIELab = 8, +} + +#[derive(Clone, Copy, Debug, FromPrimitive)] +enum CompressionMethod { + None = 1, + Huffman = 2, + Fax3 = 3, + Fax4 = 4, + LZW = 5, + JPEG = 6, + PackBits = 0x8005 +} + +#[derive(Clone, Copy, Debug, FromPrimitive)] +enum PlanarConfiguration { + Chunky = 1, + Planar = 2 +} + +#[derive(Clone, Copy, Debug, FromPrimitive)] +enum Predictor { + None = 1, + Horizontal = 2 +} + +/// The representation of a TIFF decoder +/// +/// Currently does not support decoding of interlaced images +#[derive(Debug)] +pub struct Decoder where R: Read + Seek { + reader: SmartReader, + byte_order: ByteOrder, + next_ifd: Option, + ifd: Option, + width: u32, + height: u32, + bits_per_sample: Vec, + samples: u8, + photometric_interpretation: PhotometricInterpretation, + compression_method: CompressionMethod +} + +trait Wrapping { + fn wrapping_add(&self, other: Self) -> Self; +} + +impl Wrapping for u8 { + fn wrapping_add(&self, other: Self) -> Self { + u8::wrapping_add(*self, other) + } +} + +impl Wrapping for u16 { + fn wrapping_add(&self, other: Self) -> Self { + u16::wrapping_add(*self, other) + } +} + +fn rev_hpredict_nsamp(mut image: Vec, + size: (u32, u32), + samples: usize) + -> Vec + where T: Num + Copy + Wrapping { + let width = size.0 as usize; + let height = size.1 as usize; + for row in 0..height { + for col in samples..width * samples { + let prev_pixel = image[(row * width * samples + col - samples)]; + let pixel = &mut image[(row * width * samples + col)]; + *pixel = pixel.wrapping_add(prev_pixel); + } + } + image +} + +fn rev_hpredict(image: DecodingResult, size: (u32, u32), color_type: ColorType) -> TiffResult { + let samples = match color_type { + ColorType::Gray(8) | ColorType::Gray(16) => 1, + ColorType::RGB(8) | ColorType::RGB(16) => 3, + ColorType::RGBA(8) | ColorType::RGBA(16) => 4, + _ => return Err(TiffError::UnsupportedError(format!( + "Horizontal predictor for {:?} is unsupported.", color_type + ))) + }; + Ok(match image { + DecodingResult::U8(buf) => { + DecodingResult::U8(rev_hpredict_nsamp(buf, size, samples)) + }, + DecodingResult::U16(buf) => { + DecodingResult::U16(rev_hpredict_nsamp(buf, size, samples)) + } + }) +} + +impl Decoder { + /// Create a new decoder that decodes from the stream ```r``` + pub fn new(r: R) -> TiffResult> { + Decoder { + reader: SmartReader::wrap(r, ByteOrder::LittleEndian), + byte_order: ByteOrder::LittleEndian, + next_ifd: None, + ifd: None, + width: 0, + height: 0, + bits_per_sample: vec![1], + samples: 1, + photometric_interpretation: PhotometricInterpretation::BlackIsZero, + compression_method: CompressionMethod::None + }.init() + } + + pub fn dimensions(&mut self) -> TiffResult<(u32, u32)> { + Ok((self.width, self.height)) + } + + pub fn colortype(&mut self) -> TiffResult { + match self.photometric_interpretation { + // TODO: catch also [ 8, 8, 8, _] this does not work due to a bug in rust atm + PhotometricInterpretation::RGB if self.bits_per_sample == [8, 8, 8, 8] => Ok(ColorType::RGBA(8)), + PhotometricInterpretation::RGB if self.bits_per_sample == [8, 8, 8] => Ok(ColorType::RGB(8)), + PhotometricInterpretation::RGB if self.bits_per_sample == [16, 16, 16, 16] => Ok(ColorType::RGBA(16)), + PhotometricInterpretation::RGB if self.bits_per_sample == [16, 16, 16] => Ok(ColorType::RGB(16)), + PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero + if self.bits_per_sample.len() == 1 => Ok(ColorType::Gray(self.bits_per_sample[0])), + + _ => Err(TiffError::UnsupportedError(format!( + "{:?} with {:?} bits per sample is unsupported", self.photometric_interpretation, self.bits_per_sample + ))) // TODO: this is bad we should not fail at this point} + } + } + + fn read_header(&mut self) -> TiffResult<()> { + let mut endianess = Vec::with_capacity(2); + try!(self.reader.by_ref().take(2).read_to_end(&mut endianess)); + match &*endianess { + b"II" => { + self.byte_order = ByteOrder::LittleEndian; + self.reader.byte_order = ByteOrder::LittleEndian; }, + b"MM" => { + self.byte_order = ByteOrder::BigEndian; + self.reader.byte_order = ByteOrder::BigEndian; }, + _ => return Err(TiffError::FormatError( + "TIFF signature not found.".to_string() + )) + } + if try!(self.read_short()) != 42 { + return Err(TiffError::FormatError("TIFF signature invalid.".to_string())) + } + self.next_ifd = match try!(self.read_long()) { + 0 => None, + n => Some(n) + }; + Ok(()) + } + + /// Initializes the decoder. + pub fn init(mut self) -> TiffResult> { + try!(self.read_header()); + self.next_image() + } + + /// Reads in the next image. + /// If there is no further image in the TIFF file a format error is returned. + /// To determine whether there are more images call `TIFFDecoder::more_images` instead. + pub fn next_image(mut self) -> TiffResult> { + self.ifd = Some(try!(self.read_ifd())); + self.width = try!(self.get_tag_u32(ifd::Tag::ImageWidth)); + self.height = try!(self.get_tag_u32(ifd::Tag::ImageLength)); + self.photometric_interpretation = match FromPrimitive::from_u32( + try!(self.get_tag_u32(ifd::Tag::PhotometricInterpretation)) + ) { + Some(val) => val, + None => return Err(TiffError::UnsupportedError( + "The image is using an unknown photometric interpretation.".to_string() + )) + }; + if let Some(val) = try!(self.find_tag_u32(ifd::Tag::Compression)) { + match FromPrimitive::from_u32(val) { + Some(method) => { + self.compression_method = method + }, + None => return Err(TiffError::UnsupportedError( + "Unknown compression method.".to_string() + )) + } + } + if let Some(val) = try!(self.find_tag_u32(ifd::Tag::SamplesPerPixel)) { + self.samples = val as u8 + } + match self.samples { + 1 => { + if let Some(val) = try!(self.find_tag_u32(ifd::Tag::BitsPerSample)) { + self.bits_per_sample = vec![val as u8] + } + } + 3 | 4 => { + if let Some(val) = try!(self.find_tag_u32_vec(ifd::Tag::BitsPerSample)) { + self.bits_per_sample = val.iter().map(|&v| v as u8).collect() + } + + } + _ => return Err(TiffError::UnsupportedError( + format!("{} samples per pixel is supported.", self.samples) + )) + } + Ok(self) + } + + /// Returns `true` if there is at least one more image available. + pub fn more_images(&self) -> bool { + match self.next_ifd { + Some(_) => true, + None => false + } + } + + /// Returns the byte_order + pub fn byte_order(&self) -> ByteOrder { + self.byte_order + } + + /// Reads a TIFF short value + #[inline] + pub fn read_short(&mut self) -> Result { + self.reader.read_u16() + } + + /// Reads a TIFF long value + #[inline] + pub fn read_long(&mut self) -> Result { + self.reader.read_u32() + } + + /// Reads a TIFF IFA offset/value field + #[inline] + pub fn read_offset(&mut self) -> Result<[u8; 4], io::Error> { + let mut val = [0; 4]; + try!(self.reader.read_exact(&mut val)); + Ok(val) + } + + /// Moves the cursor to the specified offset + #[inline] + pub fn goto_offset(&mut self, offset: u32) -> io::Result<()> { + self.reader.seek(io::SeekFrom::Start(u64::from(offset))).map(|_| ()) + } + + /// Reads a IFD entry. + // An IFD entry has four fields: + // + // Tag 2 bytes + // 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(try!(self.read_short())); + let type_: ifd::Type = match FromPrimitive::from_u16(try!(self.read_short())) { + Some(t) => t, + None => { + // Unknown type. Skip this entry according to spec. + try!(self.read_long()); + try!(self.read_long()); + return Ok(None) + + } + }; + Ok(Some((tag, ifd::Entry::new( + type_, + try!(self.read_long()), // count + try!(self.read_offset()) // offset + )))) + } + + /// Reads the next IFD + fn read_ifd(&mut self) -> TiffResult { + let mut dir: Directory = HashMap::new(); + match self.next_ifd { + None => return Err(TiffError::FormatError( + "Image file directory not found.".to_string()) + ), + Some(offset) => try!(self.goto_offset(offset)) + } + for _ in 0..try!(self.read_short()) { + let (tag, entry) = match try!(self.read_entry()) { + Some(val) => val, + None => continue // Unknown data type in tag, skip + }; + dir.insert(tag, entry); + } + self.next_ifd = match try!(self.read_long()) { + 0 => None, + n => Some(n) + }; + Ok(dir) + } + + /// Tries to retrieve a tag. + /// Return `Ok(None)` if the tag is not present. + fn find_tag(&mut self, tag: ifd::Tag) -> TiffResult> { + let ifd: &Directory = unsafe { + let ifd = self.ifd.as_ref().unwrap(); // Ok to fail + // Get a immutable borrow of self + // This is ok because entry val only changes the stream + // but not the directory. + mem::transmute_copy(&ifd) + }; + match ifd.get(&tag) { + None => Ok(None), + Some(entry) => Ok(Some(try!(entry.val(self)))) + } + } + + /// Tries to retrieve a tag and convert it to the desired type. + 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 type. + 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 tag. + /// Returns an error if the tag is not present + fn get_tag(&mut self, tag: ifd::Tag) -> TiffResult { + match try!(self.find_tag(tag)) { + Some(val) => Ok(val), + None => Err(TiffError::FormatError(format!( + "Required tag `{:?}` not found.", tag + ))) + } + } + + /// Tries to retrieve a tag and convert it to the desired type. + fn get_tag_u32(&mut self, tag: ifd::Tag) -> TiffResult { + self.get_tag(tag)?.into_u32() + } + + /// Tries to retrieve a tag and convert it to the desired type. + fn get_tag_u32_vec(&mut self, tag: ifd::Tag) -> TiffResult> { + self.get_tag(tag)?.into_u32_vec() + } + + /// Decompresses the strip into the supplied buffer. + /// Returns the number of bytes read. + fn expand_strip<'a>(&mut self, buffer: DecodingBuffer<'a>, offset: u32, length: u32, max_uncompressed_length: usize) -> TiffResult { + let color_type = try!(self.colortype()); + try!(self.goto_offset(offset)); + let (bytes, mut reader): (usize, Box) = match self.compression_method { + CompressionMethod::None => { + let order = self.reader.byte_order; + (length as usize, Box::new(SmartReader::wrap(&mut self.reader, order))) + }, + CompressionMethod::LZW => { + let (bytes, reader) = try!(LZWReader::new(&mut self.reader, length as usize, max_uncompressed_length)); + (bytes, Box::new(reader)) + }, + CompressionMethod::PackBits => { + let order = self.reader.byte_order; + let (bytes, reader) = try!(PackBitsReader::new(&mut self.reader, order, length as usize)); + (bytes, Box::new(reader)) + }, + method => return Err(TiffError::UnsupportedError(format!( + "Compression method {:?} is unsupported", method + ))) + }; + Ok(match (color_type, buffer) { + (ColorType:: RGB(8), DecodingBuffer::U8(ref mut buffer)) | + (ColorType::RGBA(8), DecodingBuffer::U8(ref mut buffer)) => { + try!(reader.read(&mut buffer[..bytes])) + } + (ColorType::RGBA(16), DecodingBuffer::U16(ref mut buffer)) | + (ColorType:: RGB(16), DecodingBuffer::U16(ref mut buffer)) => { + for datum in buffer[..bytes/2].iter_mut() { + *datum = try!(reader.read_u16()) + } + bytes/2 + } + (ColorType::Gray(16), DecodingBuffer::U16(ref mut buffer)) => { + for datum in buffer[..bytes/2].iter_mut() { + *datum = try!(reader.read_u16()); + if self.photometric_interpretation == PhotometricInterpretation::WhiteIsZero { + *datum = 0xffff - *datum + } + } + bytes/2 + } + (ColorType::Gray(n), DecodingBuffer::U8(ref mut buffer)) if n <= 8 => { + try!(reader.read_exact(&mut buffer[..bytes])); + if self.photometric_interpretation == PhotometricInterpretation::WhiteIsZero { + for byte in buffer[..bytes].iter_mut() { + *byte = 0xff - *byte + } + } + bytes + } + (type_, _) => return Err(TiffError::UnsupportedError(format!( + "Color type {:?} is unsupported", type_ + ))) + }) + } + + /// Decodes the entire image and return it as a Vector + pub fn read_image(&mut self) -> TiffResult { + let bits_per_pixel: u8 = self.bits_per_sample.iter().cloned().sum(); + let scanline_size_bits = self.width as usize * bits_per_pixel as usize; + let scanline_size = (scanline_size_bits + 7) / 8; + let rows_per_strip = self.get_tag_u32(ifd::Tag::RowsPerStrip) + .unwrap_or(self.height) as usize; + let buffer_size = + self.width as usize + * self.height as usize + * 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::U8(Vec::with_capacity(buffer_size)), + n if n <= 16 => DecodingResult::U16(Vec::with_capacity(buffer_size)), + n => return Err( + TiffError::UnsupportedError( + format!("{} bits per channel not supported", n) + ) + ) + }; + if let Ok(config) = self.get_tag_u32(ifd::Tag::PlanarConfiguration) { + match FromPrimitive::from_u32(config) { + Some(PlanarConfiguration::Chunky) => {}, + config => return Err(TiffError::UnsupportedError( + format!("Unsupported planar configuration “{:?}”.", config) + )) + } + } + // Safe since the uninitialized values are never read. + match result { + DecodingResult::U8(ref mut buffer) => + unsafe { buffer.set_len(buffer_size) }, + DecodingResult::U16(ref mut buffer) => + unsafe { buffer.set_len(buffer_size) }, + } + let mut units_read = 0; + for (i, (&offset, &byte_count)) in try!(self.get_tag_u32_vec(ifd::Tag::StripOffsets)) + .iter().zip(try!(self.get_tag_u32_vec(ifd::Tag::StripByteCounts)).iter()).enumerate() { + let uncompressed_strip_size = scanline_size + * (self.height as usize - i * rows_per_strip); + + units_read += match result { + DecodingResult::U8(ref mut buffer) => { + try!(self.expand_strip( + DecodingBuffer::U8(&mut buffer[units_read..]), + offset, byte_count, uncompressed_strip_size + )) + }, + DecodingResult::U16(ref mut buffer) => { + try!(self.expand_strip( + DecodingBuffer::U16(&mut buffer[units_read..]), + offset, byte_count, uncompressed_strip_size + )) + }, + }; + if units_read == buffer_size { + break + } + } + // Shrink length such that the uninitialized memory is not exposed. + if units_read < buffer_size { + match result { + DecodingResult::U8(ref mut buffer) => + unsafe { buffer.set_len(units_read) }, + DecodingResult::U16(ref mut buffer) => + unsafe { buffer.set_len(units_read) }, + } + } + if let Ok(predictor) = self.get_tag_u32(ifd::Tag::Predictor) { + result = match FromPrimitive::from_u32(predictor) { + Some(Predictor::None) => result, + Some(Predictor::Horizontal) => { + try!(rev_hpredict( + result, + try!(self.dimensions()), + try!(self.colortype()) + )) + }, + None => return Err(TiffError::FormatError( + format!("Unknown predictor “{}” encountered", predictor) + )) + } + } + Ok(result) + } +} diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs new file mode 100644 index 0000000..090a833 --- /dev/null +++ b/src/decoder/stream.rs @@ -0,0 +1,201 @@ +//! All IO functionality needed for TIFF decoding + +use std::io::{self, Read, Seek}; +use byteorder::{ReadBytesExt, BigEndian, LittleEndian}; +use lzw; + +/// Byte order of the TIFF file. +#[derive(Clone, Copy, Debug)] +pub enum ByteOrder { + /// little endian byte order + LittleEndian, + /// big endian byte order + BigEndian +} + + +/// Reader that is aware of the byte order. +pub trait EndianReader: Read { + /// Byte order that should be adhered to + fn byte_order(&self) -> ByteOrder; + + /// Reads an u16 + #[inline(always)] + fn read_u16(&mut self) -> Result { + match self.byte_order() { + ByteOrder::LittleEndian => ::read_u16::(self), + ByteOrder::BigEndian => ::read_u16::(self) + } + } + + /// Reads an u32 + #[inline(always)] + fn read_u32(&mut self) -> Result { + match self.byte_order() { + ByteOrder::LittleEndian => ::read_u32::(self), + ByteOrder::BigEndian => ::read_u32::(self) + } + } +} + +/// Reader that decompresses LZW streams +pub struct LZWReader { + buffer: io::Cursor>, + byte_order: ByteOrder +} + +impl LZWReader { + /// Wraps a reader + pub fn new(reader: &mut SmartReader, compressed_length: usize, max_uncompressed_length: usize) -> io::Result<(usize, LZWReader)> where R: Read + Seek { + let order = reader.byte_order; + let mut compressed = vec![0; compressed_length as usize]; + try!(reader.read_exact(&mut compressed[..])); + let mut uncompressed = Vec::with_capacity(max_uncompressed_length); + let mut decoder = lzw::DecoderEarlyChange::new(lzw::MsbReader::new(), 8); + let mut bytes_read = 0; + while bytes_read < compressed_length && uncompressed.len() < max_uncompressed_length { + let (len, bytes) = try!(decoder.decode_bytes(&compressed[bytes_read..])); + bytes_read += len; + uncompressed.extend_from_slice(bytes); + } + + let bytes = uncompressed.len(); + Ok((bytes, LZWReader { + buffer: io::Cursor::new(uncompressed), + byte_order: order + })) + } +} + +impl Read for LZWReader { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.buffer.read(buf) + } +} + +impl EndianReader for LZWReader { + #[inline(always)] + fn byte_order(&self) -> ByteOrder { + self.byte_order + } +} + +/// Reader that unpacks Apple's `PackBits` format +pub struct PackBitsReader { + buffer: io::Cursor>, + byte_order: ByteOrder +} + +impl PackBitsReader { + /// Wraps a reader + pub fn new(mut reader: R, byte_order: ByteOrder, length: usize) -> io::Result<(usize, PackBitsReader)> { + let mut buffer = Vec::new(); + let mut read: usize = 0; + while read < length { + read += try!(read_packbits_run(&mut reader, &mut buffer)); + } + Ok((buffer.len(), PackBitsReader { + buffer: io::Cursor::new(buffer), + byte_order + })) + } +} + +fn read_packbits_run(reader: &mut R, buffer: &mut Vec) -> io::Result { + let mut header: [u8; 1] = [0]; + + let bytes = try!(reader.read(&mut header)); + + match bytes { + 0 => Ok(0), + _ => match header[0] as i8 { + -128 => Ok(1), + h if h >= -127 && h <= -1 => { + let new_len = buffer.len() + (1 - h as isize) as usize; + try!(reader.read_exact(&mut header)); + buffer.resize(new_len, header[0]); + Ok(2) + }, + h => { + let num_vals = h as usize + 1; + let start = buffer.len(); + buffer.resize(start + num_vals, 0); + try!(reader.read_exact(&mut buffer[start..])); + Ok(num_vals + 1) + } + } + } +} + +impl Read for PackBitsReader { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.buffer.read(buf) + } +} + +impl EndianReader for PackBitsReader { + #[inline(always)] + fn byte_order(&self) -> ByteOrder { + self.byte_order + } +} + +/// Reader that is aware of the byte order. +#[derive(Debug)] +pub struct SmartReader where R: Read + Seek { + reader: R, + pub byte_order: ByteOrder +} + +impl SmartReader where R: Read + Seek { + /// Wraps a reader + pub fn wrap(reader: R, byte_order: ByteOrder) -> SmartReader { + SmartReader { reader, byte_order } + } +} + +impl EndianReader for SmartReader where R: Read + Seek { + #[inline(always)] + fn byte_order(&self) -> ByteOrder { + self.byte_order + } +} + +impl Read for SmartReader { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.reader.read(buf) + } +} + +impl Seek for SmartReader { + #[inline] + fn seek(&mut self, pos: io::SeekFrom) -> io::Result { + self.reader.seek(pos) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_packbits() { + let encoded = vec![0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, + 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA]; + let encoded_len = encoded.len(); + + let buff = io::Cursor::new(encoded); + let (_, mut decoder) = PackBitsReader::new(buff, ByteOrder::LittleEndian, encoded_len).unwrap(); + + let mut decoded = Vec::new(); + decoder.read_to_end(&mut decoded).unwrap(); + + let expected = vec![0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, + 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA]; + assert_eq!(decoded, expected); + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..bc6bf70 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,53 @@ +use std::error::Error; +use std::fmt; +use std::io; + +/// Tiff error kinds. +#[derive(Debug)] +pub enum TiffError { + /// The Image is not formatted properly + FormatError(String), + + /// The Decoder does not support this image format + UnsupportedError(String), + + /// An I/O Error occurred while decoding the image + IoError(io::Error), +} + +impl fmt::Display for TiffError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + TiffError::FormatError(ref e) => write!(fmt, "Format error: {}", e), + TiffError::UnsupportedError(ref f) => write!(fmt, "The Decoder does not support the \ + image format `{}`", f), + TiffError::IoError(ref e) => e.fmt(fmt), + } + } +} + +impl Error for TiffError { + fn description (&self) -> &str { + match *self { + TiffError::FormatError(..) => "Format error", + TiffError::UnsupportedError(..) => "Unsupported error", + TiffError::IoError(..) => "IO error", + } + } + + fn cause (&self) -> Option<&Error> { + match *self { + TiffError::IoError(ref e) => Some(e), + _ => None + } + } +} + +impl From for TiffError { + fn from(err: io::Error) -> TiffError { + TiffError::IoError(err) + } +} + +/// Result of an image decoding/encoding process +pub type TiffResult = Result; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2f74a6a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,36 @@ +//! Decoding and Encoding of TIFF Images +//! +//! TIFF (Tagged Image File Format) is a versatile image format that supports +//! lossless and lossy compression. +//! +//! # Related Links +//! * - The TIFF specification + +extern crate byteorder; +extern crate lzw; +#[macro_use] extern crate num_derive; +extern crate num_traits; + +pub mod decoder; +mod error; + +pub use self::error::{TiffError, TiffResult}; + +/// An enumeration over supported color types and their bit depths +#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] +pub enum ColorType { + /// Pixel is grayscale + Gray(u8), + + /// Pixel contains R, G and B channels + RGB(u8), + + /// Pixel is an index into a color palette + Palette(u8), + + /// Pixel is grayscale with an alpha channel + GrayA(u8), + + /// Pixel is RGB with an alpha channel + RGBA(u8), +} diff --git a/tests/decode_images.rs b/tests/decode_images.rs new file mode 100644 index 0000000..f8e3a25 --- /dev/null +++ b/tests/decode_images.rs @@ -0,0 +1,57 @@ +extern crate tiff; + +use tiff::ColorType; +use tiff::decoder::{Decoder, DecodingResult}; + +use std::fs::File; + +#[test] +fn test_gray_u8() +{ + let img_file = File::open("./tests/images/minisblack-1c-8b.tiff").expect("Cannot find test image!"); + let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + assert_eq!(decoder.colortype().unwrap(), ColorType::Gray(8)); + let img_res = decoder.read_image(); + assert!(img_res.is_ok()); +} + +#[test] +fn test_rgb_u8() +{ + let img_file = File::open("./tests/images/rgb-3c-8b.tiff").expect("Cannot find test image!"); + let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); + let img_res = decoder.read_image(); + assert!(img_res.is_ok()); +} + +#[test] +fn test_gray_u16() +{ + let img_file = File::open("./tests/images/minisblack-1c-16b.tiff").expect("Cannot find test image!"); + let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + assert_eq!(decoder.colortype().unwrap(), ColorType::Gray(16)); + let img_res = decoder.read_image(); + assert!(img_res.is_ok()); +} + +#[test] +fn test_rgb_u16() +{ + let img_file = File::open("./tests/images/rgb-3c-16b.tiff").expect("Cannot find test image!"); + let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(16)); + let img_res = decoder.read_image(); + assert!(img_res.is_ok()); +} + +// TODO: GrayA support +//#[test] +//fn test_gray_alpha_u8() +//{ + //let img_file = File::open("./tests/images/minisblack-2c-8b-alpha.tiff").expect("Cannot find test image!"); + //let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + //assert_eq!(decoder.colortype().unwrap(), ColorType::GrayA(8)); + //let img_res = decoder.read_image(); + //assert!(img_res.is_ok()); +//} diff --git a/tests/images/COPYRIGHT b/tests/images/COPYRIGHT new file mode 100644 index 0000000..8282186 --- /dev/null +++ b/tests/images/COPYRIGHT @@ -0,0 +1,21 @@ +Copyright (c) 1988-1997 Sam Leffler +Copyright (c) 1991-1997 Silicon Graphics, Inc. + +Permission to use, copy, modify, distribute, and sell this software and +its documentation for any purpose is hereby granted without fee, provided +that (i) the above copyright notices and this permission notice appear in +all copies of the software and related documentation, and (ii) the names of +Sam Leffler and Silicon Graphics may not be used in any advertising or +publicity relating to the software without the specific, prior written +permission of Sam Leffler and Silicon Graphics. + +THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, +EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY +WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR +ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF +LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +OF THIS SOFTWARE. diff --git a/tests/images/README.txt b/tests/images/README.txt new file mode 100644 index 0000000..17f6292 --- /dev/null +++ b/tests/images/README.txt @@ -0,0 +1,29 @@ +The test files in this directory mostly have dimension 157x151. +The naming convention is + + photometric-channels-bits.tiff + +minisblack-1c-16b.tiff +minisblack-1c-8b.tiff +miniswhite-1c-1b.tiff +palette-1c-1b.tiff +palette-1c-4b.tiff +palette-1c-8b.tiff +rgb-3c-16b.tiff +rgb-3c-8b.tiff + +logluv-3c-16b.tiff: logluv compression/photometric interp +minisblack-2c-8b-alpha.tiff: grey+alpha + +BMP files (anchient BMPv2 since v3 does not work): + + palette-1c-8b.bmp + rgb-3c-8b.bmp + +GIF files (anchient GIF '87 since modern GIF does not work): + palette-1c-8b.gif + +PNM files: + minisblack-1c-8b.pgm + miniswhite-1c-1b.pbm + rgb-3c-8b.ppm diff --git a/tests/images/logluv-3c-16b.tiff b/tests/images/logluv-3c-16b.tiff new file mode 100644 index 0000000..d43680f Binary files /dev/null and b/tests/images/logluv-3c-16b.tiff differ diff --git a/tests/images/minisblack-1c-16b.tiff b/tests/images/minisblack-1c-16b.tiff new file mode 100644 index 0000000..a8dbae7 Binary files /dev/null and b/tests/images/minisblack-1c-16b.tiff differ diff --git a/tests/images/minisblack-1c-8b.pgm b/tests/images/minisblack-1c-8b.pgm new file mode 100644 index 0000000..0faca40 Binary files /dev/null and b/tests/images/minisblack-1c-8b.pgm differ diff --git a/tests/images/minisblack-1c-8b.tiff b/tests/images/minisblack-1c-8b.tiff new file mode 100644 index 0000000..ea0f9ac Binary files /dev/null and b/tests/images/minisblack-1c-8b.tiff differ diff --git a/tests/images/minisblack-2c-8b-alpha.tiff b/tests/images/minisblack-2c-8b-alpha.tiff new file mode 100644 index 0000000..5fe8f58 Binary files /dev/null and b/tests/images/minisblack-2c-8b-alpha.tiff differ diff --git a/tests/images/miniswhite-1c-1b.pbm b/tests/images/miniswhite-1c-1b.pbm new file mode 100644 index 0000000..6cff929 Binary files /dev/null and b/tests/images/miniswhite-1c-1b.pbm differ diff --git a/tests/images/miniswhite-1c-1b.tiff b/tests/images/miniswhite-1c-1b.tiff new file mode 100644 index 0000000..bfc0838 Binary files /dev/null and b/tests/images/miniswhite-1c-1b.tiff differ diff --git a/tests/images/palette-1c-1b.tiff b/tests/images/palette-1c-1b.tiff new file mode 100644 index 0000000..25a1a32 Binary files /dev/null and b/tests/images/palette-1c-1b.tiff differ diff --git a/tests/images/palette-1c-4b.tiff b/tests/images/palette-1c-4b.tiff new file mode 100644 index 0000000..38ba325 Binary files /dev/null and b/tests/images/palette-1c-4b.tiff differ diff --git a/tests/images/palette-1c-8b.tiff b/tests/images/palette-1c-8b.tiff new file mode 100644 index 0000000..b87685a Binary files /dev/null and b/tests/images/palette-1c-8b.tiff differ diff --git a/tests/images/quad-lzw-compat.tiff b/tests/images/quad-lzw-compat.tiff new file mode 100644 index 0000000..ec614bf Binary files /dev/null and b/tests/images/quad-lzw-compat.tiff differ diff --git a/tests/images/quad-tile.jpg.tiff b/tests/images/quad-tile.jpg.tiff new file mode 100644 index 0000000..99b0bc2 Binary files /dev/null and b/tests/images/quad-tile.jpg.tiff differ diff --git a/tests/images/rgb-3c-16b.tiff b/tests/images/rgb-3c-16b.tiff new file mode 100644 index 0000000..f1a0279 Binary files /dev/null and b/tests/images/rgb-3c-16b.tiff differ diff --git a/tests/images/rgb-3c-8b.ppm b/tests/images/rgb-3c-8b.ppm new file mode 100644 index 0000000..6eca035 Binary files /dev/null and b/tests/images/rgb-3c-8b.ppm differ diff --git a/tests/images/rgb-3c-8b.tiff b/tests/images/rgb-3c-8b.tiff new file mode 100644 index 0000000..f64d404 Binary files /dev/null and b/tests/images/rgb-3c-8b.tiff differ