From fb30648c12a774e9a4603a77ab83b38c4a5f0878 Mon Sep 17 00:00:00 2001 From: Birk Tjelmeland Date: Thu, 31 Jan 2019 21:20:49 +0100 Subject: [PATCH 01/10] Implemented basic tiff encoding --- src/decoder/ifd.rs | 6 + src/encoder/mod.rs | 264 ++++++++++++++++++++++++++++++++++++++++++ src/encoder/writer.rs | 92 +++++++++++++++ src/lib.rs | 1 + 4 files changed, 363 insertions(+) create mode 100644 src/encoder/mod.rs create mode 100644 src/encoder/writer.rs 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/mod.rs b/src/encoder/mod.rs new file mode 100644 index 00000000..f0a197e8 --- /dev/null +++ b/src/encoder/mod.rs @@ -0,0 +1,264 @@ +mod writer; +use self::writer::*; +use std::io::{self, Write, Seek}; +use crate::decoder::ifd; +use std::collections::{BTreeMap, HashMap}; + +pub struct Rational { + pub n: u32, + pub d: u32, +} + +pub trait IfdType { + type Inner; + fn byte_len() -> u32; + fn field_type() -> u16; + fn count(&self) -> u32; + fn write(&self, writer: &mut TiffWriter) -> Result<(), io::Error>; +} + +impl IfdType for [u8] { + type Inner = u8; + fn byte_len() -> u32 { + 1 + } + fn field_type() -> u16 { + 1 + } + fn count(&self) -> u32 { + self.len() as u32 + } + fn write(&self, writer: &mut TiffWriter) -> Result<(), io::Error> { + for x in self { + writer.write_u8(*x)?; + } + Ok(()) + } +} + +impl IfdType for [u16] { + type Inner = u16; + fn byte_len() -> u32 { + 2 + } + fn field_type() -> u16 { + 3 + } + fn count(&self) -> u32 { + self.len() as u32 + } + fn write(&self, writer: &mut TiffWriter) -> Result<(), io::Error> { + for x in self { + writer.write_u16(*x)?; + } + Ok(()) + } +} + +impl IfdType for [u32] { + type Inner = u32; + fn byte_len() -> u32 { + 4 + } + fn field_type() -> u16 { + 4 + } + fn count(&self) -> u32 { + self.len() as u32 + } + fn write(&self, writer: &mut TiffWriter) -> Result<(), io::Error> { + for x in self { + writer.write_u32(*x)?; + } + Ok(()) + } +} + +impl IfdType for [Rational] { + type Inner = Rational; + fn byte_len() -> u32 { + 8 + } + fn field_type() -> u16 { + 5 + } + fn count(&self) -> u32 { + self.len() as u32 + } + fn write(&self, writer: &mut TiffWriter) -> Result<(), io::Error> { + for x in self { + writer.write_u32(x.n)?; + writer.write_u32(x.d)?; + } + Ok(()) + } +} + +impl IfdType for [i8] { + type Inner = i8; + fn byte_len() -> u32 { + 1 + } + fn field_type() -> u16 { + 6 + } + fn count(&self) -> u32 { + self.len() as u32 + } + fn write(&self, writer: &mut TiffWriter) -> Result<(), io::Error> { + for x in self { + writer.write_u8(*x as u8)?; + } + Ok(()) + } +} + +impl IfdType for str { + type Inner = u8; + fn byte_len() -> u32 { + 1 + } + fn field_type() -> u16 { + 2 + } + fn count(&self) -> u32 { + self.len() as u32 + } + fn write(&self, writer: &mut TiffWriter) -> Result<(), io::Error> { + for x in self.chars() { + if x.is_ascii() { + writer.write_u8(x as u8)?; + } + } + writer.write_u8(0)?; + Ok(()) + } +} + +pub struct TiffEncoder { + writer: TiffWriter, + // Sort tags by ascending order + ifd: BTreeMap)>, + ifd_offsets: HashMap, + strip_number: u32, +} + +impl TiffEncoder { + pub fn new_le(writer: W) -> TiffEncoder { + TiffEncoder { + writer: TiffWriter::new_le(writer), + ifd: BTreeMap::new(), + ifd_offsets: HashMap::new(), + strip_number: 0, + } + } + + pub fn new_be(writer: W) -> TiffEncoder { + TiffEncoder { + writer: TiffWriter::new_be(writer), + ifd: BTreeMap::new(), + ifd_offsets: HashMap::new(), + strip_number: 0, + } + } + + pub fn write_header(&mut self) -> Result<(), io::Error> { + match self.writer.byte_order() { + ByteOrder::LittleEndian => self.writer.write_u16(0x4949)?, + ByteOrder::BigEndian => self.writer.write_u16(0x4d4d)?, + }; + self.writer.write_u16(42)?; + + Ok(()) + } + + pub fn new_ifd(&mut self) -> Result<(), io::Error> { + self.writer.pad_word_boundary()?; + let offset = self.writer.offset(); + self.writer.write_u32(offset + 4)?; + + self.ifd = BTreeMap::new(); + + Ok(()) + } + + pub fn new_ifd_entry(&mut self, tag: ifd::Tag, value: &T) { + let mut bytes: Vec = Vec::new(); + { + let mut writer = TiffWriter::new(&mut bytes, self.writer.byte_order()); + value.write(&mut writer).unwrap(); + } + self.ifd.insert(tag.to_u16(), (::field_type(), value.count(), bytes)); + } + + pub fn finish_ifd(&mut self) -> Result<(), io::Error> { + self.ifd_offsets = HashMap::new(); + self.writer.write_u16(self.ifd.len() as u16)?; + + let mut value_offset = self.writer.offset() + self.ifd.len() as u32 * 12 + 4; + + { + let mut ifd_values = Vec::new(); + + for (tag, (type_, count, bytes)) in &self.ifd { + self.writer.write_u16(*tag)?; + self.writer.write_u16(*type_)?; + self.writer.write_u32(*count)?; + if bytes.len() <= 4 { + self.ifd_offsets.insert(*tag, self.writer.offset()); + self.writer.write_bytes(bytes)?; + for _ in 0..4 - bytes.len() { + self.writer.write_u8(0)?; + } + } + else { + self.ifd_offsets.insert(*tag, value_offset); + self.writer.write_u32(value_offset)?; + ifd_values.push(bytes); + value_offset += bytes.len() as u32; + } + } + + self.writer.write_u32(0)?; + + for value in ifd_values { + self.writer.write_bytes(value)?; + } + } + + self.ifd = BTreeMap::new(); + + Ok(()) + } + + pub fn update_ifd_value(&mut self, tag: ifd::Tag, idx: u32, value: &T) -> Result<(), io::Error> { + if let Some(off) = self.ifd_offsets.get(&tag.to_u16()) { + let curr_offset = self.writer.offset(); + self.writer.too_offset(off + idx * ::byte_len())?; + value.write(&mut self.writer)?; + self.writer.too_offset(curr_offset)?; + } + + Ok(()) + } + + pub fn write_strip(&mut self, strip_value: &[u8]) -> Result<(), io::Error> { + let offset = self.writer.offset(); + self.writer.write_bytes(strip_value)?; + + let n = self.strip_number; + + self.update_ifd_value::<[u32]>(ifd::Tag::StripOffsets, n, &[offset])?; + self.update_ifd_value::<[u32]>(ifd::Tag::StripByteCounts, n, &[strip_value.len() as u32])?; + + Ok(()) + } +} + +pub struct IfdEncoder<'a, W> { + tiff_encoder: &'a mut TiffEncoder, +} + +pub struct ImageEncoder<'a, W> { + tiff_encoder: &'a mut TiffEncoder, +} diff --git a/src/encoder/writer.rs b/src/encoder/writer.rs new file mode 100644 index 00000000..1e4ac10b --- /dev/null +++ b/src/encoder/writer.rs @@ -0,0 +1,92 @@ +use std::io::{self, Write, Seek, SeekFrom}; +use byteorder::{WriteBytesExt, BigEndian, LittleEndian}; + +/// Byte order of the TIFF file. +#[derive(Clone, Copy, Debug)] +pub enum ByteOrder { + /// little endian byte order + LittleEndian, + /// big endian byte order + BigEndian +} + +pub struct TiffWriter { + writer: W, + byte_order: ByteOrder, + offset: u32, +} + +impl TiffWriter { + pub fn new(writer: W, byte_order: ByteOrder) -> Self { + Self { + writer, + byte_order, + offset: 0, + } + } + pub fn new_le(writer: W) -> Self { + Self::new(writer, ByteOrder::LittleEndian) + } + + pub fn new_be(writer: W) -> Self { + Self::new(writer, ByteOrder::BigEndian) + } + + pub fn byte_order(&self) -> ByteOrder { + self.byte_order + } + + pub fn offset(&self) -> u32 { + self.offset + } + + pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), io::Error> { + self.writer.write_all(bytes)?; + self.offset += bytes.len() as u32; + 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> { + match self.byte_order { + ByteOrder::LittleEndian => self.writer.write_u16::(n), + ByteOrder::BigEndian => self.writer.write_u16::(n), + }?; + + self.offset += 2; + + Ok(()) + } + + pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> { + match self.byte_order { + ByteOrder::LittleEndian => self.writer.write_u32::(n), + ByteOrder::BigEndian => self.writer.write_u32::(n), + }?; + + self.offset += 4; + + Ok(()) + } + + pub fn pad_word_boundary(&mut self) -> Result<(), io::Error> { + while self.offset % 4 != 0 { + self.writer.write_u8(0)?; + } + + Ok(()) + } +} + +impl TiffWriter { + pub fn too_offset(&mut self, offset: u32) -> 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}; From 6ccca781c3d95bb9be88605a48fec942584245f8 Mon Sep 17 00:00:00 2001 From: Birk Tjelmeland Date: Thu, 31 Jan 2019 21:32:14 +0100 Subject: [PATCH 02/10] Create encode test --- tests/encode_images.rs | 58 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/encode_images.rs diff --git a/tests/encode_images.rs b/tests/encode_images.rs new file mode 100644 index 00000000..92383c7e --- /dev/null +++ b/tests/encode_images.rs @@ -0,0 +1,58 @@ +extern crate tiff; + +use tiff::ColorType; +use tiff::decoder::{Decoder, DecodingResult, ifd::Tag, ifd::Value}; +use tiff::encoder::{TiffEncoder, Rational}; + +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 file = std::fs::File::create("test.tiff").unwrap(); + let mut tiff = TiffEncoder::new_le(file); + + tiff.write_header().unwrap(); + + tiff.new_ifd().unwrap(); + tiff.new_ifd_entry(Tag::ImageWidth, &[100u32][..]); + tiff.new_ifd_entry(Tag::ImageLength, &[100u32][..]); + tiff.new_ifd_entry(Tag::BitsPerSample, &[8u16,8,8][..]); + tiff.new_ifd_entry(Tag::Compression, &[1u16][..]); + tiff.new_ifd_entry(Tag::PhotometricInterpretation, &[2u16][..]); + tiff.new_ifd_entry(Tag::StripOffsets, &[0u32][..]); + tiff.new_ifd_entry(Tag::SamplesPerPixel, &[3u16][..]); + tiff.new_ifd_entry(Tag::RowsPerStrip, &[100u32][..]); + tiff.new_ifd_entry(Tag::StripByteCounts, &[0u32][..]); + tiff.new_ifd_entry(Tag::XResolution, &[Rational {n: 1, d: 1}][..]); + tiff.new_ifd_entry(Tag::YResolution, &[Rational {n: 1, d: 1}][..]); + tiff.new_ifd_entry(Tag::ResolutionUnit, &[1u16][..]); + tiff.finish_ifd().unwrap(); + + tiff.write_strip(&image_data).unwrap(); + } + { + let file = File::open("test.tiff").unwrap(); + let mut decoder = Decoder::new(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"); + } + } + + std::fs::remove_file("test.tiff").unwrap(); +} From f38c3f9c4a99e0bc4828aab722e251a9129f7403 Mon Sep 17 00:00:00 2001 From: Birk Tjelmeland Date: Fri, 8 Feb 2019 21:04:07 +0100 Subject: [PATCH 03/10] Clean up api --- src/encoder/mod.rs | 417 ++++++++++++++++++++++++----------------- src/encoder/writer.rs | 76 ++++---- tests/encode_images.rs | 31 ++- 3 files changed, 298 insertions(+), 226 deletions(-) diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index f0a197e8..fbfa9bae 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -1,130 +1,110 @@ -mod writer; -use self::writer::*; use std::io::{self, Write, Seek}; -use crate::decoder::ifd; use std::collections::{BTreeMap, HashMap}; +use byteorder::{ByteOrder, WriteBytesExt, NativeEndian}; + +use crate::decoder::ifd; +use crate::error::TiffResult; + +mod writer; +use self::writer::*; pub struct Rational { pub n: u32, pub d: u32, } -pub trait IfdType { - type Inner; - fn byte_len() -> u32; - fn field_type() -> u16; +pub trait TiffValue { + const BYTE_LEN: u32; + const FIELD_TYPE: u16; fn count(&self) -> u32; - fn write(&self, writer: &mut TiffWriter) -> Result<(), io::Error>; + fn bytes(&self) -> u32 { + self.count() * Self::BYTE_LEN + } + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()>; } -impl IfdType for [u8] { - type Inner = u8; - fn byte_len() -> u32 { - 1 - } - fn field_type() -> u16 { - 1 - } +impl TiffValue for &[T] { + const BYTE_LEN: u32 = T::BYTE_LEN; + const FIELD_TYPE: u16 = T::FIELD_TYPE; + fn count(&self) -> u32 { - self.len() as u32 + self.iter().map(|x| x.count()).sum() } - fn write(&self, writer: &mut TiffWriter) -> Result<(), io::Error> { - for x in self { - writer.write_u8(*x)?; + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + for x in *self { + x.write(writer)?; } Ok(()) } } -impl IfdType for [u16] { - type Inner = u16; - fn byte_len() -> u32 { - 2 - } - fn field_type() -> u16 { - 3 - } +impl TiffValue for u8 { + const BYTE_LEN: u32 = 1; + const FIELD_TYPE: u16 = 1; + fn count(&self) -> u32 { - self.len() as u32 + 1 } - fn write(&self, writer: &mut TiffWriter) -> Result<(), io::Error> { - for x in self { - writer.write_u16(*x)?; - } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + writer.write_u8(*self)?; Ok(()) } } -impl IfdType for [u32] { - type Inner = u32; - fn byte_len() -> u32 { - 4 - } - fn field_type() -> u16 { - 4 - } +impl TiffValue for u16 { + const BYTE_LEN: u32 = 2; + const FIELD_TYPE: u16 = 3; + fn count(&self) -> u32 { - self.len() as u32 + 1 } - fn write(&self, writer: &mut TiffWriter) -> Result<(), io::Error> { - for x in self { - writer.write_u32(*x)?; - } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + writer.write_u16(*self)?; Ok(()) } } -impl IfdType for [Rational] { - type Inner = Rational; - fn byte_len() -> u32 { - 8 - } - fn field_type() -> u16 { - 5 - } +impl TiffValue for u32 { + const BYTE_LEN: u32 = 4; + const FIELD_TYPE: u16 = 4; + fn count(&self) -> u32 { - self.len() as u32 + 1 } - fn write(&self, writer: &mut TiffWriter) -> Result<(), io::Error> { - for x in self { - writer.write_u32(x.n)?; - writer.write_u32(x.d)?; - } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + writer.write_u32(*self)?; Ok(()) } } -impl IfdType for [i8] { - type Inner = i8; - fn byte_len() -> u32 { - 1 - } - fn field_type() -> u16 { - 6 - } +impl TiffValue for Rational { + const BYTE_LEN: u32 = 8; + const FIELD_TYPE: u16 = 5; + fn count(&self) -> u32 { - self.len() as u32 + 1 } - fn write(&self, writer: &mut TiffWriter) -> Result<(), io::Error> { - for x in self { - writer.write_u8(*x as u8)?; - } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + writer.write_u32(self.n)?; + writer.write_u32(self.d)?; Ok(()) } } -impl IfdType for str { - type Inner = u8; - fn byte_len() -> u32 { - 1 - } - fn field_type() -> u16 { - 2 - } +impl TiffValue for str { + const BYTE_LEN: u32 = 1; + const FIELD_TYPE: u16 = 2; + fn count(&self) -> u32 { - self.len() as u32 + self.len() as u32 + 1 } - fn write(&self, writer: &mut TiffWriter) -> Result<(), io::Error> { + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { for x in self.chars() { if x.is_ascii() { writer.write_u8(x as u8)?; @@ -135,130 +115,229 @@ impl IfdType for str { } } -pub struct TiffEncoder { - writer: TiffWriter, - // Sort tags by ascending order - ifd: BTreeMap)>, - ifd_offsets: HashMap, - strip_number: u32, + +pub trait ColorType { + type Inner: TiffValue; + const TIFF_VALUE: u8; + fn bits_per_sample() -> Vec; } -impl TiffEncoder { - pub fn new_le(writer: W) -> TiffEncoder { - TiffEncoder { - writer: TiffWriter::new_le(writer), - ifd: BTreeMap::new(), - ifd_offsets: HashMap::new(), - strip_number: 0, - } +pub struct RGB8; +impl ColorType for RGB8 { + type Inner = u8; + const TIFF_VALUE: u8 = 2; + fn bits_per_sample() -> Vec { + vec![8,8,8] } +} - pub fn new_be(writer: W) -> TiffEncoder { - TiffEncoder { - writer: TiffWriter::new_be(writer), - ifd: BTreeMap::new(), - ifd_offsets: HashMap::new(), - strip_number: 0, - } +pub struct RGBA8; +impl ColorType for RGBA8 { + type Inner = u8; + const TIFF_VALUE: u8 = 2; + fn bits_per_sample() -> Vec { + vec![8,8,8,8] } +} - pub fn write_header(&mut self) -> Result<(), io::Error> { - match self.writer.byte_order() { - ByteOrder::LittleEndian => self.writer.write_u16(0x4949)?, - ByteOrder::BigEndian => self.writer.write_u16(0x4d4d)?, + +pub struct TiffEncoder { + writer: TiffWriter, + offset: usize, +} + +impl TiffEncoder { + pub fn new(writer: W) -> TiffResult> { + let mut encoder = TiffEncoder { + writer: TiffWriter::new(writer), + offset: 0, }; - self.writer.write_u16(42)?; + + NativeEndian::write_header(&mut encoder.writer)?; - Ok(()) + Ok(encoder) } - pub fn new_ifd(&mut self) -> Result<(), io::Error> { - self.writer.pad_word_boundary()?; - let offset = self.writer.offset(); - self.writer.write_u32(offset + 4)?; + pub fn new_directory(&mut self) -> TiffResult> { + DirectoryEncoder::new(&mut self.writer) + } - self.ifd = BTreeMap::new(); + pub fn new_image(&mut self, width: u32, height: u32) -> TiffResult> { + let encoder = DirectoryEncoder::new(&mut self.writer)?; + ImageEncoder::new(encoder, width, height) + } +} - Ok(()) +pub struct DirectoryEncoder<'a, W> { + 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(), + }) } - pub fn new_ifd_entry(&mut self, tag: ifd::Tag, value: &T) { - let mut bytes: Vec = Vec::new(); + pub fn write_tag(&mut self, tag: ifd::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, self.writer.byte_order()); + let mut writer = TiffWriter::new(&mut bytes); value.write(&mut writer).unwrap(); } - self.ifd.insert(tag.to_u16(), (::field_type(), value.count(), bytes)); + + self.ifd.insert(tag.to_u16(), (::FIELD_TYPE, value.count(), bytes)); } - pub fn finish_ifd(&mut self) -> Result<(), io::Error> { - self.ifd_offsets = HashMap::new(); - self.writer.write_u16(self.ifd.len() as u16)?; + pub fn modify_tag(&mut self, tag: ifd::Tag, offset: u64, value: T) { + // TODO: handle errors + let bytes = &mut self.ifd.get_mut(&tag.to_u16()).unwrap().2; + let mut writer = TiffWriter::new(std::io::Cursor::new(bytes)); - let mut value_offset = self.writer.offset() + self.ifd.len() as u32 * 12 + 4; + writer.goto_offset(offset).unwrap(); + value.write(&mut writer).unwrap(); + } - { - let mut ifd_values = Vec::new(); - - for (tag, (type_, count, bytes)) in &self.ifd { - self.writer.write_u16(*tag)?; - self.writer.write_u16(*type_)?; - self.writer.write_u32(*count)?; - if bytes.len() <= 4 { - self.ifd_offsets.insert(*tag, self.writer.offset()); - self.writer.write_bytes(bytes)?; - for _ in 0..4 - bytes.len() { - self.writer.write_u8(0)?; - } - } - else { - self.ifd_offsets.insert(*tag, value_offset); - self.writer.write_u32(value_offset)?; - ifd_values.push(bytes); - value_offset += bytes.len() as u32; - } + 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)?; } - - self.writer.write_u32(0)?; - - for value in ifd_values { - self.writer.write_bytes(value)?; + else { + while bytes.len() < 4 { + bytes.push(0); + } } } - self.ifd = BTreeMap::new(); + let offset = self.writer.offset(); - Ok(()) - } - - pub fn update_ifd_value(&mut self, tag: ifd::Tag, idx: u32, value: &T) -> Result<(), io::Error> { - if let Some(off) = self.ifd_offsets.get(&tag.to_u16()) { - let curr_offset = self.writer.offset(); - self.writer.too_offset(off + idx * ::byte_len())?; - value.write(&mut self.writer)?; - self.writer.too_offset(curr_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(()) + Ok(offset) } - pub fn write_strip(&mut self, strip_value: &[u8]) -> Result<(), io::Error> { + pub fn write_data(&mut self, value: T) -> TiffResult { let offset = self.writer.offset(); - self.writer.write_bytes(strip_value)?; + value.write(&mut self.writer); + Ok(offset) + } + + pub fn finish(mut self) -> TiffResult<()> { + let ifd_pointer = self.write_directory()?; + let curr_pos = self.writer.offset(); - let n = self.strip_number; + 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.update_ifd_value::<[u32]>(ifd::Tag::StripOffsets, n, &[offset])?; - self.update_ifd_value::<[u32]>(ifd::Tag::StripByteCounts, n, &[strip_value.len() as u32])?; + self.dropped = true; Ok(()) } } -pub struct IfdEncoder<'a, W> { - tiff_encoder: &'a mut TiffEncoder, +impl<'a, W> Drop for DirectoryEncoder<'a, W> { + fn drop(&mut self) { + if !self.dropped { + panic!("Illegal to drop DirectoryEncoder: you should call `DirectoryEncoder::finish`") + } + } +} + +pub struct ImageEncoder<'a, W, T> { + encoder: DirectoryEncoder<'a, W>, + strip_idx: u64, + strip_count: u64, + row_samples: u64, + height: u32, + rows_per_strip: u64, + _phantom: std::marker::PhantomData, } -pub struct ImageEncoder<'a, W> { - tiff_encoder: &'a mut TiffEncoder, +impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> { + fn new(mut encoder: DirectoryEncoder<'a, W>, width: u32, height: u32) -> TiffResult> { + use self::ifd::Tag; + let row_samples = width as u64 * ::bits_per_sample().len() as u64; + let row_bytes = row_samples * ::BYTE_LEN as u64; + + // As per tiff spec each strip should be about 8k long + let rows_per_strip = (8000 + row_bytes - 1) / row_bytes; + + let strip_count = (height as u64 + 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() as &[u16]); + encoder.write_tag(Tag::PhotometricInterpretation, ::TIFF_VALUE); + + encoder.write_tag(Tag::StripOffsets, &vec![0u32; strip_count as usize] as &[u32]); + encoder.write_tag(Tag::StripByteCounts, &vec![0u32; strip_count as usize] as &[u32]); + 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, + _phantom: std::marker::PhantomData, + }) + } + + pub fn next_strip_sample_count(&self) -> u64 { + if self.strip_idx >= self.strip_count { + return 0 + } + + let start_row = std::cmp::min(self.height as u64, self.strip_idx * self.rows_per_strip); + let end_row = std::cmp::min(self.height as u64, (self.strip_idx+1)*self.rows_per_strip); + + (end_row - start_row) * self.row_samples + } + + pub fn write_strip(&mut self, value: &[T::Inner]) -> TiffResult<()> { + // TODO: Compression + let offset = self.encoder.write_data(value)?; + self.encoder.modify_tag(ifd::Tag::StripOffsets, self.strip_idx as u64 * 4, offset as u32); + self.encoder.modify_tag(ifd::Tag::StripByteCounts, self.strip_idx as u64 * 4, value.bytes() as u32); + + self.strip_idx += 1; + Ok(()) + } + + pub fn finish(mut self) -> TiffResult<()> { + self.encoder.finish() + } } diff --git a/src/encoder/writer.rs b/src/encoder/writer.rs index 1e4ac10b..6739e324 100644 --- a/src/encoder/writer.rs +++ b/src/encoder/writer.rs @@ -1,48 +1,51 @@ use std::io::{self, Write, Seek, SeekFrom}; -use byteorder::{WriteBytesExt, BigEndian, LittleEndian}; - -/// Byte order of the TIFF file. -#[derive(Clone, Copy, Debug)] -pub enum ByteOrder { - /// little endian byte order - LittleEndian, - /// big endian byte order - BigEndian +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, - byte_order: ByteOrder, - offset: u32, + offset: u64, } impl TiffWriter { - pub fn new(writer: W, byte_order: ByteOrder) -> Self { + pub fn new(writer: W) -> Self { Self { writer, - byte_order, offset: 0, } } - pub fn new_le(writer: W) -> Self { - Self::new(writer, ByteOrder::LittleEndian) - } - - pub fn new_be(writer: W) -> Self { - Self::new(writer, ByteOrder::BigEndian) - } - - pub fn byte_order(&self) -> ByteOrder { - self.byte_order - } - pub fn offset(&self) -> u32 { + 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 u32; + self.offset += bytes.len() as u64; Ok(()) } @@ -53,27 +56,26 @@ impl TiffWriter { } pub fn write_u16(&mut self, n: u16) -> Result<(), io::Error> { - match self.byte_order { - ByteOrder::LittleEndian => self.writer.write_u16::(n), - ByteOrder::BigEndian => self.writer.write_u16::(n), - }?; - + self.writer.write_u16::(n)?; self.offset += 2; Ok(()) } pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> { - match self.byte_order { - ByteOrder::LittleEndian => self.writer.write_u32::(n), - ByteOrder::BigEndian => self.writer.write_u32::(n), - }?; - + 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> { while self.offset % 4 != 0 { self.writer.write_u8(0)?; @@ -84,7 +86,7 @@ impl TiffWriter { } impl TiffWriter { - pub fn too_offset(&mut self, offset: u32) -> Result<(), io::Error> { + 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/tests/encode_images.rs b/tests/encode_images.rs index 92383c7e..2dbbf298 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -2,7 +2,7 @@ extern crate tiff; use tiff::ColorType; use tiff::decoder::{Decoder, DecodingResult, ifd::Tag, ifd::Value}; -use tiff::encoder::{TiffEncoder, Rational}; +use tiff::encoder::{TiffEncoder, RGB8}; use std::fs::File; @@ -20,26 +20,17 @@ fn encode_decode() { } { let file = std::fs::File::create("test.tiff").unwrap(); - let mut tiff = TiffEncoder::new_le(file); + let mut tiff = TiffEncoder::new(file).unwrap(); - tiff.write_header().unwrap(); - - tiff.new_ifd().unwrap(); - tiff.new_ifd_entry(Tag::ImageWidth, &[100u32][..]); - tiff.new_ifd_entry(Tag::ImageLength, &[100u32][..]); - tiff.new_ifd_entry(Tag::BitsPerSample, &[8u16,8,8][..]); - tiff.new_ifd_entry(Tag::Compression, &[1u16][..]); - tiff.new_ifd_entry(Tag::PhotometricInterpretation, &[2u16][..]); - tiff.new_ifd_entry(Tag::StripOffsets, &[0u32][..]); - tiff.new_ifd_entry(Tag::SamplesPerPixel, &[3u16][..]); - tiff.new_ifd_entry(Tag::RowsPerStrip, &[100u32][..]); - tiff.new_ifd_entry(Tag::StripByteCounts, &[0u32][..]); - tiff.new_ifd_entry(Tag::XResolution, &[Rational {n: 1, d: 1}][..]); - tiff.new_ifd_entry(Tag::YResolution, &[Rational {n: 1, d: 1}][..]); - tiff.new_ifd_entry(Tag::ResolutionUnit, &[1u16][..]); - tiff.finish_ifd().unwrap(); - - tiff.write_strip(&image_data).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(); } { let file = File::open("test.tiff").unwrap(); From fb233771b5a24f77f785bb8509f1123f537ea95e Mon Sep 17 00:00:00 2001 From: Birk Tjelmeland Date: Sat, 9 Feb 2019 20:36:40 +0100 Subject: [PATCH 04/10] Fix small errors and clean up --- Cargo.toml | 3 +++ src/encoder/mod.rs | 43 +++++++++++++++++++++--------------------- tests/encode_images.rs | 16 +++++++--------- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 90a298e7..838e0d6e 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/encoder/mod.rs b/src/encoder/mod.rs index fbfa9bae..eb28ef8b 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -1,9 +1,9 @@ -use std::io::{self, Write, Seek}; -use std::collections::{BTreeMap, HashMap}; -use byteorder::{ByteOrder, WriteBytesExt, NativeEndian}; +use std::io::{Write, Seek}; +use std::collections::BTreeMap; +use byteorder::{NativeEndian}; use crate::decoder::ifd; -use crate::error::TiffResult; +use crate::error::{TiffResult, TiffFormatError, TiffError}; mod writer; use self::writer::*; @@ -105,13 +105,14 @@ impl TiffValue for str { } fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - for x in self.chars() { - if x.is_ascii() { - writer.write_u8(x as u8)?; - } + if self.is_ascii() { + writer.write_bytes(self.as_bytes())?; + writer.write_u8(0)?; + Ok(()) + } + else { + Err(TiffError::FormatError(TiffFormatError::InvalidTag)) } - writer.write_u8(0)?; - Ok(()) } } @@ -143,14 +144,12 @@ impl ColorType for RGBA8 { pub struct TiffEncoder { writer: TiffWriter, - offset: usize, } impl TiffEncoder { pub fn new(writer: W) -> TiffResult> { let mut encoder = TiffEncoder { writer: TiffWriter::new(writer), - offset: 0, }; NativeEndian::write_header(&mut encoder.writer)?; @@ -200,13 +199,15 @@ impl<'a, W: Write + Seek> DirectoryEncoder<'a, W> { self.ifd.insert(tag.to_u16(), (::FIELD_TYPE, value.count(), bytes)); } - pub fn modify_tag(&mut self, tag: ifd::Tag, offset: u64, value: T) { - // TODO: handle errors - let bytes = &mut self.ifd.get_mut(&tag.to_u16()).unwrap().2; + pub fn modify_tag(&mut self, tag: ifd::Tag, offset: u64, value: T) -> TiffResult<()> { + let bytes = &mut self.ifd.get_mut(&tag.to_u16()) + .ok_or(TiffError::FormatError(TiffFormatError::RequiredTagNotFound(tag)))?.2; + let mut writer = TiffWriter::new(std::io::Cursor::new(bytes)); - writer.goto_offset(offset).unwrap(); - value.write(&mut writer).unwrap(); + writer.goto_offset(offset)?; + value.write(&mut writer)?; + Ok(()) } fn write_directory(&mut self) -> TiffResult { @@ -241,7 +242,7 @@ impl<'a, W: Write + Seek> DirectoryEncoder<'a, W> { pub fn write_data(&mut self, value: T) -> TiffResult { let offset = self.writer.offset(); - value.write(&mut self.writer); + value.write(&mut self.writer)?; Ok(offset) } @@ -330,14 +331,14 @@ impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> { pub fn write_strip(&mut self, value: &[T::Inner]) -> TiffResult<()> { // TODO: Compression let offset = self.encoder.write_data(value)?; - self.encoder.modify_tag(ifd::Tag::StripOffsets, self.strip_idx as u64 * 4, offset as u32); - self.encoder.modify_tag(ifd::Tag::StripByteCounts, self.strip_idx as u64 * 4, value.bytes() as u32); + self.encoder.modify_tag(ifd::Tag::StripOffsets, self.strip_idx as u64 * 4, offset as u32)?; + self.encoder.modify_tag(ifd::Tag::StripByteCounts, self.strip_idx as u64 * 4, value.bytes() as u32)?; self.strip_idx += 1; Ok(()) } - pub fn finish(mut self) -> TiffResult<()> { + pub fn finish(self) -> TiffResult<()> { self.encoder.finish() } } diff --git a/tests/encode_images.rs b/tests/encode_images.rs index 2dbbf298..c70d9099 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -1,11 +1,11 @@ extern crate tiff; +extern crate tempfile; use tiff::ColorType; -use tiff::decoder::{Decoder, DecodingResult, ifd::Tag, ifd::Value}; +use tiff::decoder::{Decoder, DecodingResult}; use tiff::encoder::{TiffEncoder, RGB8}; -use std::fs::File; - +use std::io::{Seek, SeekFrom}; #[test] fn encode_decode() { @@ -18,9 +18,9 @@ fn encode_decode() { image_data.push(val); } } + let mut file = tempfile::tempfile().unwrap(); { - let file = std::fs::File::create("test.tiff").unwrap(); - let mut tiff = TiffEncoder::new(file).unwrap(); + let mut tiff = TiffEncoder::new(&mut file).unwrap(); let mut image = tiff.new_image::(100, 100).unwrap(); @@ -33,8 +33,8 @@ fn encode_decode() { image.finish().unwrap(); } { - let file = File::open("test.tiff").unwrap(); - let mut decoder = Decoder::new(file).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() { @@ -44,6 +44,4 @@ fn encode_decode() { panic!("Wrong data type"); } } - - std::fs::remove_file("test.tiff").unwrap(); } From 503c4d29ed9f12e80a06dd67c5833fad374811e3 Mon Sep 17 00:00:00 2001 From: Birk Tjelmeland Date: Sat, 9 Feb 2019 20:40:25 +0100 Subject: [PATCH 05/10] Add function to write entire image --- src/encoder/mod.rs | 13 +++++++++++++ tests/encode_images.rs | 10 +--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index eb28ef8b..95d8a379 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -165,6 +165,19 @@ impl TiffEncoder { let encoder = DirectoryEncoder::new(&mut self.writer)?; ImageEncoder::new(encoder, width, height) } + + pub fn write_image(&mut self, width: u32, height: u32, data: &[C::Inner]) -> TiffResult<()> { + 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() + } } pub struct DirectoryEncoder<'a, W> { diff --git a/tests/encode_images.rs b/tests/encode_images.rs index c70d9099..8a6a098a 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -22,15 +22,7 @@ fn encode_decode() { { 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(); + tiff.write_image::(100, 100, &image_data).unwrap(); } { file.seek(SeekFrom::Start(0)).unwrap(); From fa540dbad6e95ba7b414723938cf652b4312f537 Mon Sep 17 00:00:00 2001 From: Birk Tjelmeland Date: Sat, 9 Feb 2019 20:51:55 +0100 Subject: [PATCH 06/10] Add more colortypes --- src/encoder/colortype.rs | 68 ++++++++++++++++++++++++++++++++++++++++ src/encoder/mod.rs | 26 ++------------- tests/encode_images.rs | 61 +++++++++++++++++++++++++++++++++-- 3 files changed, 130 insertions(+), 25 deletions(-) create mode 100644 src/encoder/colortype.rs diff --git a/src/encoder/colortype.rs b/src/encoder/colortype.rs new file mode 100644 index 00000000..92beaaf0 --- /dev/null +++ b/src/encoder/colortype.rs @@ -0,0 +1,68 @@ +pub trait ColorType { + type Inner: super::TiffValue; + const TIFF_VALUE: u8; + fn bits_per_sample() -> Vec; +} + +pub struct Grey8; +impl ColorType for Grey8 { + type Inner = u8; + const TIFF_VALUE: u8 = 1; + fn bits_per_sample() -> Vec { + vec![8] + } +} + +pub struct Grey16; +impl ColorType for Grey16 { + type Inner = u16; + const TIFF_VALUE: u8 = 1; + fn bits_per_sample() -> Vec { + vec![16] + } +} + +pub struct RGB8; +impl ColorType for RGB8 { + type Inner = u8; + const TIFF_VALUE: u8 = 2; + fn bits_per_sample() -> Vec { + vec![8,8,8] + } +} + +pub struct RGB16; +impl ColorType for RGB16 { + type Inner = u16; + const TIFF_VALUE: u8 = 2; + fn bits_per_sample() -> Vec { + vec![16,16,16] + } +} + +pub struct RGBA8; +impl ColorType for RGBA8 { + type Inner = u8; + const TIFF_VALUE: u8 = 2; + fn bits_per_sample() -> Vec { + vec![8,8,8,8] + } +} + +pub struct RGBA16; +impl ColorType for RGBA16 { + type Inner = u16; + const TIFF_VALUE: u8 = 2; + fn bits_per_sample() -> Vec { + vec![16,16,16,16] + } +} + +pub struct CMYK8; +impl ColorType for CMYK8 { + type Inner = u8; + const TIFF_VALUE: u8 = 5; + fn bits_per_sample() -> Vec { + vec![8,8,8,8] + } +} diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 95d8a379..a30c81c3 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -6,7 +6,10 @@ use crate::decoder::ifd; use crate::error::{TiffResult, TiffFormatError, TiffError}; mod writer; +pub mod colortype; + use self::writer::*; +use self::colortype::*; pub struct Rational { pub n: u32, @@ -117,29 +120,6 @@ impl TiffValue for str { } -pub trait ColorType { - type Inner: TiffValue; - const TIFF_VALUE: u8; - fn bits_per_sample() -> Vec; -} - -pub struct RGB8; -impl ColorType for RGB8 { - type Inner = u8; - const TIFF_VALUE: u8 = 2; - fn bits_per_sample() -> Vec { - vec![8,8,8] - } -} - -pub struct RGBA8; -impl ColorType for RGBA8 { - type Inner = u8; - const TIFF_VALUE: u8 = 2; - fn bits_per_sample() -> Vec { - vec![8,8,8,8] - } -} pub struct TiffEncoder { diff --git a/tests/encode_images.rs b/tests/encode_images.rs index 8a6a098a..22965b38 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -3,9 +3,10 @@ extern crate tempfile; use tiff::ColorType; use tiff::decoder::{Decoder, DecodingResult}; -use tiff::encoder::{TiffEncoder, RGB8}; +use tiff::encoder::{colortype, TiffEncoder}; use std::io::{Seek, SeekFrom}; +use std::fs::File; #[test] fn encode_decode() { @@ -22,7 +23,7 @@ fn encode_decode() { { let mut tiff = TiffEncoder::new(&mut file).unwrap(); - tiff.write_image::(100, 100, &image_data).unwrap(); + tiff.write_image::(100, 100, &image_data).unwrap(); } { file.seek(SeekFrom::Start(0)).unwrap(); @@ -37,3 +38,59 @@ fn encode_decode() { } } } + + +#[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 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(); + + 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_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()); +} From 9ca445a5ebd75c3c16e0bbd932c3c674ca9d5082 Mon Sep 17 00:00:00 2001 From: Birk Tjelmeland Date: Sat, 9 Feb 2019 20:58:28 +0100 Subject: [PATCH 07/10] Add roundtrip tests --- tests/encode_images.rs | 111 +++++++++++++++++++++++++++++++++-------- 1 file changed, 91 insertions(+), 20 deletions(-) diff --git a/tests/encode_images.rs b/tests/encode_images.rs index 22965b38..5aa31230 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -46,51 +46,122 @@ 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 img_res = decoder.read_image(); - assert!(img_res.is_ok()); + + 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() +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 img_res = decoder.read_image(); - 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 image_data = match decoder.read_image().unwrap() { + DecodingResult::U8(res) => res, + _ => panic!("Wrong data type"), + }; - 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); + 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(); } - else { - panic!("Wrong data type"); + 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() +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 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"); + } + } } #[test] -fn test_rgb_u16() +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"); + } + } } From b0d7ac9cbf233ea62602bd3120d787c6a09e3b59 Mon Sep 17 00:00:00 2001 From: Birk Tjelmeland Date: Sat, 9 Feb 2019 21:40:01 +0100 Subject: [PATCH 08/10] Add documentation and reorganize --- src/encoder/colortype.rs | 12 ++-- src/encoder/mod.rs | 146 +++++++++++++++++++++++++++++++-------- tests/encode_images.rs | 4 +- 3 files changed, 127 insertions(+), 35 deletions(-) diff --git a/src/encoder/colortype.rs b/src/encoder/colortype.rs index 92beaaf0..ebbd0d08 100644 --- a/src/encoder/colortype.rs +++ b/src/encoder/colortype.rs @@ -1,11 +1,15 @@ +/// Trait for different colortypes that can be encoded. pub trait ColorType { + /// The type of each sample of thes colortype type Inner: super::TiffValue; + /// The value of the tiff tag `PhotometricInterpretation` const TIFF_VALUE: u8; + /// The value of the tiff tag `BitsPerSample` fn bits_per_sample() -> Vec; } -pub struct Grey8; -impl ColorType for Grey8 { +pub struct Gray8; +impl ColorType for Gray8 { type Inner = u8; const TIFF_VALUE: u8 = 1; fn bits_per_sample() -> Vec { @@ -13,8 +17,8 @@ impl ColorType for Grey8 { } } -pub struct Grey16; -impl ColorType for Grey16 { +pub struct Gray16; +impl ColorType for Gray16 { type Inner = u16; const TIFF_VALUE: u8 = 1; fn bits_per_sample() -> Vec { diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index a30c81c3..d10a6f72 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -2,7 +2,7 @@ use std::io::{Write, Seek}; use std::collections::BTreeMap; use byteorder::{NativeEndian}; -use crate::decoder::ifd; +use crate::decoder::ifd::Tag; use crate::error::{TiffResult, TiffFormatError, TiffError}; mod writer; @@ -11,11 +11,13 @@ pub mod colortype; use self::writer::*; use self::colortype::*; +/// Type to represent tiff values of type `RATIONAL` pub struct Rational { pub n: u32, pub d: u32, } +/// Trait for types that can be encoded in a tiff file pub trait TiffValue { const BYTE_LEN: u32; const FIELD_TYPE: u16; @@ -119,9 +121,32 @@ impl TiffValue for str { } } - - - +/// 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 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); +/// # } +/// # } +/// use tiff::encoder::*; +/// +/// let mut tiff = TiffEncoder::new(&mut file).unwrap(); +/// +/// tiff.write_image::(100, 100, &image_data).unwrap(); +/// ``` pub struct TiffEncoder { writer: TiffWriter, } @@ -137,15 +162,18 @@ impl TiffEncoder { 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<()> { let encoder = DirectoryEncoder::new(&mut self.writer)?; let mut image: ImageEncoder = ImageEncoder::new(encoder, width, height).unwrap(); @@ -160,7 +188,11 @@ impl TiffEncoder { } } -pub struct DirectoryEncoder<'a, W> { +/// 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 @@ -181,7 +213,8 @@ impl<'a, W: Write + Seek> DirectoryEncoder<'a, W> { }) } - pub fn write_tag(&mut self, tag: ifd::Tag, value: T) { + /// 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); { @@ -192,17 +225,6 @@ impl<'a, W: Write + Seek> DirectoryEncoder<'a, W> { self.ifd.insert(tag.to_u16(), (::FIELD_TYPE, value.count(), bytes)); } - pub fn modify_tag(&mut self, tag: ifd::Tag, offset: u64, value: T) -> TiffResult<()> { - let bytes = &mut self.ifd.get_mut(&tag.to_u16()) - .ok_or(TiffError::FormatError(TiffFormatError::RequiredTagNotFound(tag)))?.2; - - let mut writer = TiffWriter::new(std::io::Cursor::new(bytes)); - - writer.goto_offset(offset)?; - value.write(&mut writer)?; - Ok(()) - } - fn write_directory(&mut self) -> TiffResult { // Start by writing out all values for (_, _, ref mut bytes) in self.ifd.values_mut() { @@ -233,13 +255,16 @@ impl<'a, W: Write + Seek> DirectoryEncoder<'a, W> { 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) } - pub fn finish(mut self) -> TiffResult<()> { + fn finish_internal(&mut self) -> TiffResult<()> { let ifd_pointer = self.write_directory()?; let curr_pos = self.writer.offset(); @@ -252,29 +277,68 @@ impl<'a, W: Write + Seek> DirectoryEncoder<'a, W> { Ok(()) } + + /// Write out the ifd directory. + pub fn finish(mut self) -> TiffResult<()> { + self.finish_internal() + } } -impl<'a, W> Drop for DirectoryEncoder<'a, W> { +impl<'a, W: Write + Seek> Drop for DirectoryEncoder<'a, W> { fn drop(&mut self) { if !self.dropped { - panic!("Illegal to drop DirectoryEncoder: you should call `DirectoryEncoder::finish`") + let _ = self.finish_internal(); } } } -pub struct ImageEncoder<'a, W, T> { + +/// 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 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); +/// # } +/// # } +/// 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, - _phantom: std::marker::PhantomData, + 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> { - use self::ifd::Tag; let row_samples = width as u64 * ::bits_per_sample().len() as u64; let row_bytes = row_samples * ::BYTE_LEN as u64; @@ -290,8 +354,6 @@ impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> { encoder.write_tag(Tag::BitsPerSample, &::bits_per_sample() as &[u16]); encoder.write_tag(Tag::PhotometricInterpretation, ::TIFF_VALUE); - encoder.write_tag(Tag::StripOffsets, &vec![0u32; strip_count as usize] as &[u32]); - encoder.write_tag(Tag::StripByteCounts, &vec![0u32; strip_count as usize] as &[u32]); encoder.write_tag(Tag::RowsPerStrip, rows_per_strip as u32); encoder.write_tag(Tag::SamplesPerPixel, ::bits_per_sample().len() as u16); @@ -306,10 +368,14 @@ impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> { 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 @@ -321,17 +387,39 @@ impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> { (end_row - start_row) * self.row_samples } + /// Write a single strip. pub fn write_strip(&mut self, value: &[T::Inner]) -> TiffResult<()> { // TODO: Compression + let samples = self.next_strip_sample_count(); + assert_eq!(value.len() as u64, samples); + let offset = self.encoder.write_data(value)?; - self.encoder.modify_tag(ifd::Tag::StripOffsets, self.strip_idx as u64 * 4, offset as u32)?; - self.encoder.modify_tag(ifd::Tag::StripByteCounts, self.strip_idx as u64 * 4, value.bytes() as u32)?; + self.strip_offsets.push(offset as u32); + self.strip_byte_count.push(value.bytes() as u32); self.strip_idx += 1; Ok(()) } - pub fn finish(self) -> TiffResult<()> { - self.encoder.finish() + 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() + } + + /// 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/tests/encode_images.rs b/tests/encode_images.rs index 5aa31230..f726f571 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -57,7 +57,7 @@ fn test_gray_u8_roundtrip() let mut tiff = TiffEncoder::new(&mut file).unwrap(); let (width, height) = decoder.dimensions().unwrap(); - tiff.write_image::(width, height, &image_data).unwrap(); + tiff.write_image::(width, height, &image_data).unwrap(); } file.seek(SeekFrom::Start(0)).unwrap(); { @@ -119,7 +119,7 @@ fn test_gray_u16_roundtrip() let mut tiff = TiffEncoder::new(&mut file).unwrap(); let (width, height) = decoder.dimensions().unwrap(); - tiff.write_image::(width, height, &image_data).unwrap(); + tiff.write_image::(width, height, &image_data).unwrap(); } file.seek(SeekFrom::Start(0)).unwrap(); { From addfaa56920dc967d2e6c5f69bf116c2bda68acc Mon Sep 17 00:00:00 2001 From: Birk Tjelmeland Date: Wed, 20 Feb 2019 23:08:40 +0100 Subject: [PATCH 09/10] Fix some small problems --- src/encoder/colortype.rs | 20 +++--- src/encoder/mod.rs | 145 +++++++++++++++++++++++++++++---------- 2 files changed, 118 insertions(+), 47 deletions(-) diff --git a/src/encoder/colortype.rs b/src/encoder/colortype.rs index ebbd0d08..3b4b739d 100644 --- a/src/encoder/colortype.rs +++ b/src/encoder/colortype.rs @@ -1,9 +1,11 @@ +use crate::decoder::PhotometricInterpretation; + /// Trait for different colortypes that can be encoded. pub trait ColorType { - /// The type of each sample of thes colortype + /// The type of each sample of this colortype type Inner: super::TiffValue; /// The value of the tiff tag `PhotometricInterpretation` - const TIFF_VALUE: u8; + const TIFF_VALUE: PhotometricInterpretation; /// The value of the tiff tag `BitsPerSample` fn bits_per_sample() -> Vec; } @@ -11,7 +13,7 @@ pub trait ColorType { pub struct Gray8; impl ColorType for Gray8 { type Inner = u8; - const TIFF_VALUE: u8 = 1; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; fn bits_per_sample() -> Vec { vec![8] } @@ -20,7 +22,7 @@ impl ColorType for Gray8 { pub struct Gray16; impl ColorType for Gray16 { type Inner = u16; - const TIFF_VALUE: u8 = 1; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; fn bits_per_sample() -> Vec { vec![16] } @@ -29,7 +31,7 @@ impl ColorType for Gray16 { pub struct RGB8; impl ColorType for RGB8 { type Inner = u8; - const TIFF_VALUE: u8 = 2; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; fn bits_per_sample() -> Vec { vec![8,8,8] } @@ -38,7 +40,7 @@ impl ColorType for RGB8 { pub struct RGB16; impl ColorType for RGB16 { type Inner = u16; - const TIFF_VALUE: u8 = 2; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; fn bits_per_sample() -> Vec { vec![16,16,16] } @@ -47,7 +49,7 @@ impl ColorType for RGB16 { pub struct RGBA8; impl ColorType for RGBA8 { type Inner = u8; - const TIFF_VALUE: u8 = 2; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; fn bits_per_sample() -> Vec { vec![8,8,8,8] } @@ -56,7 +58,7 @@ impl ColorType for RGBA8 { pub struct RGBA16; impl ColorType for RGBA16 { type Inner = u16; - const TIFF_VALUE: u8 = 2; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; fn bits_per_sample() -> Vec { vec![16,16,16,16] } @@ -65,7 +67,7 @@ impl ColorType for RGBA16 { pub struct CMYK8; impl ColorType for CMYK8 { type Inner = u8; - const TIFF_VALUE: u8 = 5; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; fn bits_per_sample() -> Vec { vec![8,8,8,8] } diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index d10a6f72..2a7a2a10 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -2,7 +2,7 @@ use std::io::{Write, Seek}; use std::collections::BTreeMap; use byteorder::{NativeEndian}; -use crate::decoder::ifd::Tag; +use crate::decoder::ifd::{self, Tag}; use crate::error::{TiffResult, TiffFormatError, TiffError}; mod writer; @@ -12,15 +12,23 @@ 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: u16; + const FIELD_TYPE: ifd::Type; fn count(&self) -> u32; fn bytes(&self) -> u32 { self.count() * Self::BYTE_LEN @@ -28,12 +36,62 @@ pub trait TiffValue { fn write(&self, writer: &mut TiffWriter) -> TiffResult<()>; } -impl TiffValue for &[T] { - const BYTE_LEN: u32 = T::BYTE_LEN; - const FIELD_TYPE: u16 = T::FIELD_TYPE; +impl TiffValue for &[u8] { + const BYTE_LEN: u32 = 1; + const FIELD_TYPE: ifd::Type = ifd::Type::BYTE; fn count(&self) -> u32 { - self.iter().map(|x| x.count()).sum() + 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<()> { @@ -46,7 +104,7 @@ impl TiffValue for &[T] { impl TiffValue for u8 { const BYTE_LEN: u32 = 1; - const FIELD_TYPE: u16 = 1; + const FIELD_TYPE: ifd::Type = ifd::Type::BYTE; fn count(&self) -> u32 { 1 @@ -60,7 +118,7 @@ impl TiffValue for u8 { impl TiffValue for u16 { const BYTE_LEN: u32 = 2; - const FIELD_TYPE: u16 = 3; + const FIELD_TYPE: ifd::Type = ifd::Type::SHORT; fn count(&self) -> u32 { 1 @@ -74,7 +132,7 @@ impl TiffValue for u16 { impl TiffValue for u32 { const BYTE_LEN: u32 = 4; - const FIELD_TYPE: u16 = 4; + const FIELD_TYPE: ifd::Type = ifd::Type::LONG; fn count(&self) -> u32 { 1 @@ -88,7 +146,7 @@ impl TiffValue for u32 { impl TiffValue for Rational { const BYTE_LEN: u32 = 8; - const FIELD_TYPE: u16 = 5; + const FIELD_TYPE: ifd::Type = ifd::Type::RATIONAL; fn count(&self) -> u32 { 1 @@ -103,7 +161,7 @@ impl TiffValue for Rational { impl TiffValue for str { const BYTE_LEN: u32 = 1; - const FIELD_TYPE: u16 = 2; + const FIELD_TYPE: ifd::Type = ifd::Type::ASCII; fn count(&self) -> u32 { self.len() as u32 + 1 @@ -132,15 +190,7 @@ impl TiffValue for str { /// ``` /// # extern crate tempfile; /// # let mut file = tempfile::tempfile().unwrap(); -/// # 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 image_data = vec![0; 100*100*3]; /// use tiff::encoder::*; /// /// let mut tiff = TiffEncoder::new(&mut file).unwrap(); @@ -174,7 +224,7 @@ impl TiffEncoder { } /// Convenience function to write an entire image from memory. - pub fn write_image(&mut self, width: u32, height: u32, data: &[C::Inner]) -> TiffResult<()> { + pub fn write_image<'a, C: ColorType>(&mut self, width: u32, height: u32, data: &'a [C::Inner]) -> TiffResult<()> where &'a [C::Inner]: TiffValue { let encoder = DirectoryEncoder::new(&mut self.writer)?; let mut image: ImageEncoder = ImageEncoder::new(encoder, width, height).unwrap(); @@ -222,7 +272,7 @@ impl<'a, W: Write + Seek> DirectoryEncoder<'a, W> { value.write(&mut writer).unwrap(); } - self.ifd.insert(tag.to_u16(), (::FIELD_TYPE, value.count(), bytes)); + self.ifd.insert(tag.to_u16(), (::FIELD_TYPE as u16, value.count(), bytes)); } fn write_directory(&mut self) -> TiffResult { @@ -302,15 +352,7 @@ impl<'a, W: Write + Seek> Drop for DirectoryEncoder<'a, W> { /// ``` /// # extern crate tempfile; /// # let mut file = tempfile::tempfile().unwrap(); -/// # 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 image_data = vec![0; 100*100*3]; /// use tiff::encoder::*; /// /// let mut tiff = TiffEncoder::new(&mut file).unwrap(); @@ -339,20 +381,20 @@ pub struct ImageEncoder<'a, W: Write + Seek, C: ColorType> { 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 = width as u64 * ::bits_per_sample().len() as u64; - let row_bytes = row_samples * ::BYTE_LEN as u64; + 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 = (height as u64 + rows_per_strip - 1) / rows_per_strip; + 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() as &[u16]); - encoder.write_tag(Tag::PhotometricInterpretation, ::TIFF_VALUE); + encoder.write_tag(Tag::PhotometricInterpretation, ::TIFF_VALUE as u16); encoder.write_tag(Tag::RowsPerStrip, rows_per_strip as u32); @@ -381,14 +423,14 @@ impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> { return 0 } - let start_row = std::cmp::min(self.height as u64, self.strip_idx * self.rows_per_strip); - let end_row = std::cmp::min(self.height as u64, (self.strip_idx+1)*self.rows_per_strip); + 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<()> { + pub fn write_strip<'b>(&mut self, value: &'b [T::Inner]) -> TiffResult<()> where &'b [T::Inner]: TiffValue { // TODO: Compression let samples = self.next_strip_sample_count(); assert_eq!(value.len() as u64, samples); @@ -401,6 +443,28 @@ impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> { 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); @@ -409,6 +473,11 @@ impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> { 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() From 098b799a62586619f9aa55494f6ad19ef05583d3 Mon Sep 17 00:00:00 2001 From: Birk Tjelmeland Date: Tue, 26 Feb 2019 10:57:28 +0100 Subject: [PATCH 10/10] Fix string encoding and clean up --- src/encoder/colortype.rs | 30 ++++++++---------------------- src/encoder/mod.rs | 35 ++++++++++++++++++++++++----------- src/encoder/writer.rs | 6 ++++-- 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/encoder/colortype.rs b/src/encoder/colortype.rs index 3b4b739d..bc52a3d6 100644 --- a/src/encoder/colortype.rs +++ b/src/encoder/colortype.rs @@ -7,68 +7,54 @@ pub trait ColorType { /// The value of the tiff tag `PhotometricInterpretation` const TIFF_VALUE: PhotometricInterpretation; /// The value of the tiff tag `BitsPerSample` - fn bits_per_sample() -> Vec; + const BITS_PER_SAMPLE: &'static [u16]; } pub struct Gray8; impl ColorType for Gray8 { type Inner = u8; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; - fn bits_per_sample() -> Vec { - vec![8] - } + const BITS_PER_SAMPLE: &'static [u16] = &[8]; } pub struct Gray16; impl ColorType for Gray16 { type Inner = u16; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; - fn bits_per_sample() -> Vec { - vec![16] - } + const BITS_PER_SAMPLE: &'static [u16] = &[16]; } pub struct RGB8; impl ColorType for RGB8 { type Inner = u8; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; - fn bits_per_sample() -> Vec { - vec![8,8,8] - } + 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; - fn bits_per_sample() -> Vec { - vec![16,16,16] - } + 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; - fn bits_per_sample() -> Vec { - vec![8,8,8,8] - } + 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; - fn bits_per_sample() -> Vec { - vec![16,16,16,16] - } + 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; - fn bits_per_sample() -> Vec { - vec![8,8,8,8] - } + const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8]; } diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 2a7a2a10..eeb9e1ed 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -36,7 +36,7 @@ pub trait TiffValue { fn write(&self, writer: &mut TiffWriter) -> TiffResult<()>; } -impl TiffValue for &[u8] { +impl TiffValue for [u8] { const BYTE_LEN: u32 = 1; const FIELD_TYPE: ifd::Type = ifd::Type::BYTE; @@ -50,7 +50,7 @@ impl TiffValue for &[u8] { } } -impl TiffValue for &[u16] { +impl TiffValue for [u16] { const BYTE_LEN: u32 = 2; const FIELD_TYPE: ifd::Type = ifd::Type::SHORT; @@ -68,7 +68,7 @@ impl TiffValue for &[u16] { } } -impl TiffValue for &[u32] { +impl TiffValue for [u32] { const BYTE_LEN: u32 = 4; const FIELD_TYPE: ifd::Type = ifd::Type::LONG; @@ -86,7 +86,7 @@ impl TiffValue for &[u32] { } } -impl TiffValue for &[Rational] { +impl TiffValue for [Rational] { const BYTE_LEN: u32 = 8; const FIELD_TYPE: ifd::Type = ifd::Type::RATIONAL; @@ -95,7 +95,7 @@ impl TiffValue for &[Rational] { } fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - for x in *self { + for x in self { x.write(writer)?; } Ok(()) @@ -168,7 +168,7 @@ impl TiffValue for str { } fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - if self.is_ascii() { + if self.is_ascii() && !self.bytes().any(|b| b == 0) { writer.write_bytes(self.as_bytes())?; writer.write_u8(0)?; Ok(()) @@ -179,6 +179,19 @@ 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; + + 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` @@ -224,7 +237,7 @@ impl TiffEncoder { } /// Convenience function to write an entire image from memory. - pub fn write_image<'a, C: ColorType>(&mut self, width: u32, height: u32, data: &'a [C::Inner]) -> TiffResult<()> where &'a [C::Inner]: TiffValue { + 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(); @@ -381,7 +394,7 @@ pub struct ImageEncoder<'a, W: Write + Seek, C: ColorType> { 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_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 @@ -393,12 +406,12 @@ impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> { encoder.write_tag(Tag::ImageLength, height); encoder.write_tag(Tag::Compression, 1u16); - encoder.write_tag(Tag::BitsPerSample, &::bits_per_sample() as &[u16]); + 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::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); @@ -430,7 +443,7 @@ impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> { } /// Write a single strip. - pub fn write_strip<'b>(&mut self, value: &'b [T::Inner]) -> TiffResult<()> where &'b [T::Inner]: TiffValue { + 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); diff --git a/src/encoder/writer.rs b/src/encoder/writer.rs index 6739e324..1f0eef17 100644 --- a/src/encoder/writer.rs +++ b/src/encoder/writer.rs @@ -77,8 +77,10 @@ impl TiffWriter { } pub fn pad_word_boundary(&mut self) -> Result<(), io::Error> { - while self.offset % 4 != 0 { - self.writer.write_u8(0)?; + 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(())