diff --git a/Cargo.toml b/Cargo.toml index f9b673f3..4a5ec466 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,6 @@ byteorder = "1.2" lzw = "0.10" num-derive = "0.2" num-traits = "0.2" + +[dev-dependencies] +tempfile = "3.0" diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs index 3679e51c..515006e7 100644 --- a/src/decoder/ifd.rs +++ b/src/decoder/ifd.rs @@ -26,6 +26,12 @@ macro_rules! tags { Tag::Unknown(n) } } + pub fn to_u16(&self) -> u16 { + match self { + $( Tag::$tag => $val, )* + Tag::Unknown(n) => *n, + } + } } } } diff --git a/src/encoder/colortype.rs b/src/encoder/colortype.rs new file mode 100644 index 00000000..bc52a3d6 --- /dev/null +++ b/src/encoder/colortype.rs @@ -0,0 +1,60 @@ +use crate::decoder::PhotometricInterpretation; + +/// Trait for different colortypes that can be encoded. +pub trait ColorType { + /// The type of each sample of this colortype + type Inner: super::TiffValue; + /// The value of the tiff tag `PhotometricInterpretation` + const TIFF_VALUE: PhotometricInterpretation; + /// The value of the tiff tag `BitsPerSample` + const BITS_PER_SAMPLE: &'static [u16]; +} + +pub struct Gray8; +impl ColorType for Gray8 { + type Inner = u8; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[8]; +} + +pub struct Gray16; +impl ColorType for Gray16 { + type Inner = u16; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[16]; +} + +pub struct RGB8; +impl ColorType for RGB8 { + type Inner = u8; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8]; +} + +pub struct RGB16; +impl ColorType for RGB16 { + type Inner = u16; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16]; +} + +pub struct RGBA8; +impl ColorType for RGBA8 { + type Inner = u8; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8]; +} + +pub struct RGBA16; +impl ColorType for RGBA16 { + type Inner = u16; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16]; +} + +pub struct CMYK8; +impl ColorType for CMYK8 { + type Inner = u8; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; + const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8]; +} diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs new file mode 100644 index 00000000..eeb9e1ed --- /dev/null +++ b/src/encoder/mod.rs @@ -0,0 +1,507 @@ +use std::io::{Write, Seek}; +use std::collections::BTreeMap; +use byteorder::{NativeEndian}; + +use crate::decoder::ifd::{self, Tag}; +use crate::error::{TiffResult, TiffFormatError, TiffError}; + +mod writer; +pub mod colortype; + +use self::writer::*; +use self::colortype::*; + +/// Type to represent tiff values of type `RATIONAL` +#[derive(Clone)] +pub struct Rational { + pub n: u32, + pub d: u32, +} + +/// 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; + fn count(&self) -> u32; + fn bytes(&self) -> u32 { + self.count() * Self::BYTE_LEN + } + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()>; +} + +impl TiffValue for [u8] { + const BYTE_LEN: u32 = 1; + const FIELD_TYPE: ifd::Type = ifd::Type::BYTE; + + fn count(&self) -> u32 { + self.len() as u32 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + writer.write_bytes(self)?; + Ok(()) + } +} + +impl TiffValue for [u16] { + const BYTE_LEN: u32 = 2; + const FIELD_TYPE: ifd::Type = ifd::Type::SHORT; + + fn count(&self) -> u32 { + self.len() as u32 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + // We write using nativeedian so this sould be safe + let slice = unsafe { + std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len()*2) + }; + writer.write_bytes(slice)?; + Ok(()) + } +} + +impl TiffValue for [u32] { + const BYTE_LEN: u32 = 4; + const FIELD_TYPE: ifd::Type = ifd::Type::LONG; + + fn count(&self) -> u32 { + self.len() as u32 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + // We write using nativeedian so this sould be safe + let slice = unsafe { + std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len()*4) + }; + writer.write_bytes(slice)?; + Ok(()) + } +} + +impl TiffValue for [Rational] { + const BYTE_LEN: u32 = 8; + const FIELD_TYPE: ifd::Type = ifd::Type::RATIONAL; + + fn count(&self) -> u32 { + self.len() as u32 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + for x in self { + x.write(writer)?; + } + Ok(()) + } +} + +impl TiffValue for u8 { + const BYTE_LEN: u32 = 1; + const FIELD_TYPE: ifd::Type = ifd::Type::BYTE; + + fn count(&self) -> u32 { + 1 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + writer.write_u8(*self)?; + Ok(()) + } +} + +impl TiffValue for u16 { + const BYTE_LEN: u32 = 2; + const FIELD_TYPE: ifd::Type = ifd::Type::SHORT; + + fn count(&self) -> u32 { + 1 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + writer.write_u16(*self)?; + Ok(()) + } +} + +impl TiffValue for u32 { + const BYTE_LEN: u32 = 4; + const FIELD_TYPE: ifd::Type = ifd::Type::LONG; + + fn count(&self) -> u32 { + 1 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + writer.write_u32(*self)?; + Ok(()) + } +} + +impl TiffValue for Rational { + const BYTE_LEN: u32 = 8; + const FIELD_TYPE: ifd::Type = ifd::Type::RATIONAL; + + fn count(&self) -> u32 { + 1 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + writer.write_u32(self.n)?; + writer.write_u32(self.d)?; + Ok(()) + } +} + +impl TiffValue for str { + const BYTE_LEN: u32 = 1; + const FIELD_TYPE: ifd::Type = ifd::Type::ASCII; + + fn count(&self) -> u32 { + self.len() as u32 + 1 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + if self.is_ascii() && !self.bytes().any(|b| b == 0) { + writer.write_bytes(self.as_bytes())?; + writer.write_u8(0)?; + Ok(()) + } + else { + Err(TiffError::FormatError(TiffFormatError::InvalidTag)) + } + } +} + +impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T { + const BYTE_LEN: u32 = T::BYTE_LEN; + const FIELD_TYPE: ifd::Type = T::FIELD_TYPE; + + fn count(&self) -> u32 { + (*self).count() + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + (*self).write(writer) + } +} + +/// Tiff encoder. +/// +/// With this type you can get a `DirectoryEncoder` or a `ImageEncoder` +/// to encode tiff ifd directories with images. +/// +/// See `DirectoryEncoder` and `ImageEncoder`. +/// +/// # Examples +/// ``` +/// # extern crate tempfile; +/// # let mut file = tempfile::tempfile().unwrap(); +/// # let image_data = vec![0; 100*100*3]; +/// use tiff::encoder::*; +/// +/// let mut tiff = TiffEncoder::new(&mut file).unwrap(); +/// +/// tiff.write_image::(100, 100, &image_data).unwrap(); +/// ``` +pub struct TiffEncoder { + writer: TiffWriter, +} + +impl TiffEncoder { + pub fn new(writer: W) -> TiffResult> { + let mut encoder = TiffEncoder { + writer: TiffWriter::new(writer), + }; + + NativeEndian::write_header(&mut encoder.writer)?; + + Ok(encoder) + } + + /// Create a `DirectoryEncoder` to encode an ifd directory. + pub fn new_directory(&mut self) -> TiffResult> { + DirectoryEncoder::new(&mut self.writer) + } + + /// Create an 'ImageEncoder' to encode an image one slice at a time. + pub fn new_image(&mut self, width: u32, height: u32) -> TiffResult> { + let encoder = DirectoryEncoder::new(&mut self.writer)?; + ImageEncoder::new(encoder, width, height) + } + + /// Convenience function to write an entire image from memory. + pub fn write_image(&mut self, width: u32, height: u32, data: &[C::Inner]) -> TiffResult<()> where [C::Inner]: TiffValue { + let encoder = DirectoryEncoder::new(&mut self.writer)?; + let mut image: ImageEncoder = ImageEncoder::new(encoder, width, height).unwrap(); + + let mut idx = 0; + while image.next_strip_sample_count() > 0 { + let sample_count = image.next_strip_sample_count() as usize; + image.write_strip(&data[idx..idx+sample_count]).unwrap(); + idx += sample_count; + } + image.finish() + } +} + +/// Low level interface to encode ifd directories. +/// +/// You should call `finish` on this when you are finished with it. +/// Encoding can silently fail while this is dropping. +pub struct DirectoryEncoder<'a, W: Write + Seek> { + writer: &'a mut TiffWriter, + dropped: bool, + // We use BTreeMap to make sure tags are written in correct order + ifd_pointer_pos: u64, + ifd: BTreeMap)>, +} + +impl<'a, W: Write + Seek> DirectoryEncoder<'a, W> { + fn new(writer: &'a mut TiffWriter) -> TiffResult> { + writer.pad_word_boundary()?; + let ifd_pointer_pos = writer.offset(); + writer.write_u32(0)?; + Ok(DirectoryEncoder { + writer, + dropped: false, + ifd_pointer_pos, + ifd: BTreeMap::new(), + }) + } + + /// Write a single ifd tag. + pub fn write_tag(&mut self, tag: Tag, value: T) { + let len = ::BYTE_LEN * value.count(); + let mut bytes = Vec::with_capacity(len as usize); + { + let mut writer = TiffWriter::new(&mut bytes); + value.write(&mut writer).unwrap(); + } + + self.ifd.insert(tag.to_u16(), (::FIELD_TYPE as u16, value.count(), bytes)); + } + + fn write_directory(&mut self) -> TiffResult { + // Start by writing out all values + for (_, _, ref mut bytes) in self.ifd.values_mut() { + if bytes.len() > 4 { + let offset = self.writer.offset(); + 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)?; + } + else { + while bytes.len() < 4 { + bytes.push(0); + } + } + } + + let offset = self.writer.offset(); + + self.writer.write_u16(self.ifd.len() as u16)?; + for (tag, (field_type, count, offset)) in self.ifd.iter() { + self.writer.write_u16(*tag)?; + self.writer.write_u16(*field_type)?; + self.writer.write_u32(*count)?; + self.writer.write_bytes(offset)?; + } + + Ok(offset) + } + + /// Write some data to the tiff file, the offset of the data is returned. + /// + /// This could be used to write tiff strips. + pub fn write_data(&mut self, value: T) -> TiffResult { + let offset = self.writer.offset(); + value.write(&mut self.writer)?; + Ok(offset) + } + + fn finish_internal(&mut self) -> TiffResult<()> { + let ifd_pointer = self.write_directory()?; + let curr_pos = self.writer.offset(); + + self.writer.goto_offset(self.ifd_pointer_pos)?; + self.writer.write_u32(ifd_pointer as u32)?; + self.writer.goto_offset(curr_pos)?; + self.writer.write_u32(0)?; + + self.dropped = true; + + Ok(()) + } + + /// Write out the ifd directory. + pub fn finish(mut self) -> TiffResult<()> { + self.finish_internal() + } +} + +impl<'a, W: Write + Seek> Drop for DirectoryEncoder<'a, W> { + fn drop(&mut self) { + if !self.dropped { + let _ = self.finish_internal(); + } + } +} + + +/// Type to encode images strip by strip. +/// +/// You should call `finish` on this when you are finished with it. +/// Encoding can silently fail while this is dropping. +/// +/// # Examples +/// ``` +/// # extern crate tempfile; +/// # let mut file = tempfile::tempfile().unwrap(); +/// # let image_data = vec![0; 100*100*3]; +/// use tiff::encoder::*; +/// +/// let mut tiff = TiffEncoder::new(&mut file).unwrap(); +/// let mut image = tiff.new_image::(100, 100).unwrap(); +/// +/// let mut idx = 0; +/// while image.next_strip_sample_count() > 0 { +/// let sample_count = image.next_strip_sample_count() as usize; +/// image.write_strip(&image_data[idx..idx+sample_count]).unwrap(); +/// idx += sample_count; +/// } +/// image.finish().unwrap(); +/// ``` +pub struct ImageEncoder<'a, W: Write + Seek, C: ColorType> { + encoder: DirectoryEncoder<'a, W>, + strip_idx: u64, + strip_count: u64, + row_samples: u64, + height: u32, + rows_per_strip: u64, + strip_offsets: Vec, + strip_byte_count: Vec, + dropped: bool, + _phantom: std::marker::PhantomData, +} + +impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> { + fn new(mut encoder: DirectoryEncoder<'a, W>, width: u32, height: u32) -> TiffResult> { + let row_samples = u64::from(width) * ::BITS_PER_SAMPLE.len() as u64; + let row_bytes = row_samples * u64::from(::BYTE_LEN); + + // As per tiff spec each strip should be about 8k long + let rows_per_strip = (8000 + row_bytes - 1) / row_bytes; + + 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::BitsPerSample, ::BITS_PER_SAMPLE); + encoder.write_tag(Tag::PhotometricInterpretation, ::TIFF_VALUE as u16); + + encoder.write_tag(Tag::RowsPerStrip, rows_per_strip as u32); + + 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); + + Ok(ImageEncoder { + encoder, + strip_count, + strip_idx: 0, + row_samples, + rows_per_strip, + height, + strip_offsets: Vec::new(), + strip_byte_count: Vec::new(), + dropped: false, + _phantom: std::marker::PhantomData, + }) + } + + /// Number of samples the next strip should have. + pub fn next_strip_sample_count(&self) -> u64 { + if self.strip_idx >= self.strip_count { + return 0 + } + + let start_row = std::cmp::min(u64::from(self.height), self.strip_idx * self.rows_per_strip); + let end_row = std::cmp::min(u64::from(self.height), (self.strip_idx+1)*self.rows_per_strip); + + (end_row - start_row) * self.row_samples + } + + /// Write a single strip. + pub fn write_strip(&mut self, value: &[T::Inner]) -> TiffResult<()> where [T::Inner]: TiffValue { + // TODO: Compression + let samples = self.next_strip_sample_count(); + assert_eq!(value.len() as u64, samples); + + 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_idx += 1; + Ok(()) + } + + /// 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); + } + + /// Set image resolution unit + pub fn resolution_unit(&mut self, unit: ResolutionUnit) { + self.encoder.write_tag(Tag::ResolutionUnit, unit as u16); + } + + /// Set image x-resolution + pub fn x_resolution(&mut self, value: Rational) { + self.encoder.write_tag(Tag::XResolution, value); + } + + /// Set image y-resolution + pub fn y_resolution(&mut self, value: Rational) { + self.encoder.write_tag(Tag::YResolution, value); + } + + fn finish_internal(&mut self) -> TiffResult<()> { + self.encoder.write_tag(Tag::StripOffsets, &*self.strip_offsets); + self.encoder.write_tag(Tag::StripByteCounts, &*self.strip_byte_count); + self.dropped = true; + + self.encoder.finish_internal() + } + + /// Get a reference of the underlying `DirectoryEncoder` + pub fn encoder(&mut self) -> &mut DirectoryEncoder<'a, W> { + &mut self.encoder + } + + /// Write out image and ifd directory. + pub fn finish(mut self) -> TiffResult<()> { + self.finish_internal() + } +} + +impl<'a, W: Write + Seek, C: ColorType> Drop for ImageEncoder<'a, W, C> { + fn drop(&mut self) { + if !self.dropped { + let _ = self.finish_internal(); + } + } +} + diff --git a/src/encoder/writer.rs b/src/encoder/writer.rs new file mode 100644 index 00000000..1f0eef17 --- /dev/null +++ b/src/encoder/writer.rs @@ -0,0 +1,96 @@ +use std::io::{self, Write, Seek, SeekFrom}; +use byteorder::{ByteOrder, WriteBytesExt, BigEndian, LittleEndian, NativeEndian}; +use crate::error::TiffResult; + +pub trait TiffByteOrder: ByteOrder { + fn write_header(writer: &mut TiffWriter) -> TiffResult<()>; +} + +impl TiffByteOrder for LittleEndian { + fn write_header(writer: &mut TiffWriter) -> TiffResult<()> { + writer.writer.write_u16::(0x4949)?; + writer.writer.write_u16::(42)?; + writer.offset += 4; + + Ok(()) + } +} + +impl TiffByteOrder for BigEndian { + fn write_header(writer: &mut TiffWriter) -> TiffResult<()> { + writer.writer.write_u16::(0x4d4d)?; + writer.writer.write_u16::(42)?; + writer.offset += 4; + + Ok(()) + } +} + +pub struct TiffWriter { + writer: W, + offset: u64, +} + +impl TiffWriter { + pub fn new(writer: W) -> Self { + Self { + writer, + offset: 0, + } + } + + pub fn offset(&self) -> u64 { + self.offset + } + + pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), io::Error> { + self.writer.write_all(bytes)?; + self.offset += bytes.len() as u64; + Ok(()) + } + + pub fn write_u8(&mut self, n: u8) -> Result<(), io::Error> { + self.writer.write_u8(n)?; + self.offset += 1; + Ok(()) + } + + pub fn write_u16(&mut self, n: u16) -> Result<(), io::Error> { + self.writer.write_u16::(n)?; + self.offset += 2; + + Ok(()) + } + + pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> { + self.writer.write_u32::(n)?; + self.offset += 4; + + Ok(()) + } + + pub fn write_u64(&mut self, n: u64) -> Result<(), io::Error> { + self.writer.write_u64::(n)?; + self.offset += 8; + + Ok(()) + } + + pub fn pad_word_boundary(&mut self) -> Result<(), io::Error> { + if self.offset % 4 != 0 { + let padding = [0, 0, 0]; + let padd_len = 4 - (self.offset % 4); + self.writer.write_all(&padding[..padd_len as usize])?; + } + + Ok(()) + } +} + +impl TiffWriter { + pub fn goto_offset(&mut self, offset: u64) -> Result<(), io::Error> { + self.offset = offset; + self.writer.seek(SeekFrom::Start(offset as u64))?; + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 2722a90e..bf4a1bb4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ extern crate lzw; extern crate num_traits; pub mod decoder; +pub mod encoder; mod error; pub use self::error::{TiffError, TiffFormatError, TiffUnsupportedError, TiffResult}; diff --git a/tests/encode_images.rs b/tests/encode_images.rs new file mode 100644 index 00000000..f726f571 --- /dev/null +++ b/tests/encode_images.rs @@ -0,0 +1,167 @@ +extern crate tiff; +extern crate tempfile; + +use tiff::ColorType; +use tiff::decoder::{Decoder, DecodingResult}; +use tiff::encoder::{colortype, TiffEncoder}; + +use std::io::{Seek, SeekFrom}; +use std::fs::File; + +#[test] +fn encode_decode() { + let mut image_data = Vec::new(); + for x in 0..100 { + for y in 0..100u8 { + let val = x + y; + image_data.push(val); + image_data.push(val); + image_data.push(val); + } + } + let mut file = tempfile::tempfile().unwrap(); + { + let mut tiff = TiffEncoder::new(&mut file).unwrap(); + + tiff.write_image::(100, 100, &image_data).unwrap(); + } + { + file.seek(SeekFrom::Start(0)).unwrap(); + let mut decoder = Decoder::new(&mut file).unwrap(); + assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); + assert_eq!(decoder.dimensions().unwrap(), (100, 100)); + if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() { + assert_eq!(image_data, img_res); + } + else { + panic!("Wrong data type"); + } + } +} + + +#[test] +fn test_gray_u8_roundtrip() +{ + 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 image_data = match decoder.read_image().unwrap() { + DecodingResult::U8(res) => res, + _ => panic!("Wrong data type"), + }; + + let mut file = tempfile::tempfile().unwrap(); + { + let mut tiff = TiffEncoder::new(&mut file).unwrap(); + + let (width, height) = decoder.dimensions().unwrap(); + tiff.write_image::(width, height, &image_data).unwrap(); + } + file.seek(SeekFrom::Start(0)).unwrap(); + { + let mut decoder = Decoder::new(&mut file).unwrap(); + if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() { + assert_eq!(image_data, img_res); + } + else { + panic!("Wrong data type"); + } + } +} + +#[test] +fn test_rgb_u8_roundtrip() +{ + 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 image_data = match decoder.read_image().unwrap() { + DecodingResult::U8(res) => res, + _ => panic!("Wrong data type"), + }; + + let mut file = tempfile::tempfile().unwrap(); + { + let mut tiff = TiffEncoder::new(&mut file).unwrap(); + + let (width, height) = decoder.dimensions().unwrap(); + tiff.write_image::(width, height, &image_data).unwrap(); + } + file.seek(SeekFrom::Start(0)).unwrap(); + { + let mut decoder = Decoder::new(&mut file).unwrap(); + if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() { + assert_eq!(image_data, img_res); + } + else { + panic!("Wrong data type"); + } + } +} + +#[test] +fn test_gray_u16_roundtrip() +{ + 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 image_data = match decoder.read_image().unwrap() { + DecodingResult::U16(res) => res, + _ => panic!("Wrong data type"), + }; + + let mut file = tempfile::tempfile().unwrap(); + { + let mut tiff = TiffEncoder::new(&mut file).unwrap(); + + let (width, height) = decoder.dimensions().unwrap(); + tiff.write_image::(width, height, &image_data).unwrap(); + } + file.seek(SeekFrom::Start(0)).unwrap(); + { + let mut decoder = Decoder::new(&mut file).unwrap(); + if let DecodingResult::U16(img_res) = decoder.read_image().unwrap() { + assert_eq!(image_data, img_res); + } + else { + panic!("Wrong data type"); + } + } +} + +#[test] +fn test_rgb_u16_roundtrip() +{ + 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()); + + let image_data = match decoder.read_image().unwrap() { + DecodingResult::U16(res) => res, + _ => panic!("Wrong data type"), + }; + + let mut file = tempfile::tempfile().unwrap(); + { + let mut tiff = TiffEncoder::new(&mut file).unwrap(); + + let (width, height) = decoder.dimensions().unwrap(); + tiff.write_image::(width, height, &image_data).unwrap(); + } + file.seek(SeekFrom::Start(0)).unwrap(); + { + let mut decoder = Decoder::new(&mut file).unwrap(); + if let DecodingResult::U16(img_res) = decoder.read_image().unwrap() { + assert_eq!(image_data, img_res); + } + else { + panic!("Wrong data type"); + } + } +}