diff --git a/src/lib.rs b/src/lib.rs index 00cac6b..770d47b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,6 +63,11 @@ pub use tiff::parse_exif; pub use value::Value; pub use value::{Rational, SRational}; +/// The interfaces in this module is experimental and unstable. +pub mod experimental { + pub use writer::Writer; +} + #[cfg(test)] #[macro_use] mod tmacro; diff --git a/src/tiff.rs b/src/tiff.rs index cc4c826..ce32aac 100644 --- a/src/tiff.rs +++ b/src/tiff.rs @@ -38,8 +38,8 @@ use util::atou16; const TIFF_BE: u16 = 0x4d4d; const TIFF_LE: u16 = 0x4949; const TIFF_FORTY_TWO: u16 = 0x002a; -const TIFF_BE_SIG: [u8; 4] = [0x4d, 0x4d, 0x00, 0x2a]; -const TIFF_LE_SIG: [u8; 4] = [0x49, 0x49, 0x2a, 0x00]; +pub const TIFF_BE_SIG: [u8; 4] = [0x4d, 0x4d, 0x00, 0x2a]; +pub const TIFF_LE_SIG: [u8; 4] = [0x49, 0x49, 0x2a, 0x00]; /// A TIFF field. #[derive(Debug)] diff --git a/src/writer.rs b/src/writer.rs index 7b63b28..07e300d 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -24,13 +24,389 @@ // SUCH DAMAGE. // +use std::io; +use std::io::{Seek, SeekFrom, Write}; use std::mem; use std::slice; use endian::{Endian, BigEndian, LittleEndian}; use error::Error; +use tag_priv::{Context, Tag, constants as tag}; +use tiff::{Field, TIFF_BE_SIG, TIFF_LE_SIG}; use value::Value; +/// The `Writer` struct is used to encode and write Exif data. +pub struct Writer<'a> { + tiff_fields: Vec<&'a Field<'a>>, + exif_fields: Vec<&'a Field<'a>>, + gps_fields: Vec<&'a Field<'a>>, + interop_fields: Vec<&'a Field<'a>>, + tn_tiff_fields: Vec<&'a Field<'a>>, + tn_exif_fields: Vec<&'a Field<'a>>, + tn_gps_fields: Vec<&'a Field<'a>>, + tn_interop_fields: Vec<&'a Field<'a>>, + strips: Option<&'a [&'a [u8]]>, + tn_strips: Option<&'a [&'a [u8]]>, + tn_jpeg: Option<&'a [u8]>, +} + +struct WriterState<'a> { + tiff_fields: Vec<&'a Field<'a>>, + exif_fields: Vec<&'a Field<'a>>, + gps_fields: Vec<&'a Field<'a>>, + interop_fields: Vec<&'a Field<'a>>, + tiff_ifd_offset: u32, + exif_ifd_offset: u32, + gps_ifd_offset: u32, + interop_ifd_offset: u32, + strips: Option<&'a [&'a [u8]]>, + jpeg: Option<&'a [u8]>, +} + +impl<'a> Writer<'a> { + /// Constructs an empty `Writer`. + pub fn new() -> Writer<'a> { + Writer { + tiff_fields: Vec::new(), + exif_fields: Vec::new(), + gps_fields: Vec::new(), + interop_fields: Vec::new(), + tn_tiff_fields: Vec::new(), + tn_exif_fields: Vec::new(), + tn_gps_fields: Vec::new(), + tn_interop_fields: Vec::new(), + strips: None, + tn_strips: None, + tn_jpeg: None, + } + } + + /// Appends a field to be written. + /// + /// The fields can be appended in any order. + /// Duplicate fields must not be appended. + /// + /// ExifIFDPointer, GPSInfoIFDPointer, InteropIFDPointer, + /// StripOffsets, StripByteCounts, JPEGInterchangeFormat, and + /// JPEGInterchangeFormatLength are ignored. + /// They are synthesized when needed. + pub fn push_field(&mut self, field: &'a Field) { + match *field { + // Ignore the tags for the internal data structure. + Field { tag: tag::ExifIFDPointer, .. } | + Field { tag: tag::GPSInfoIFDPointer, .. } | + Field { tag: tag::InteropIFDPointer, .. } => {}, + // These tags are synthesized from the actual strip data. + Field { tag: tag::StripOffsets, .. } | + Field { tag: tag::StripByteCounts, .. } => {}, + // These tags are synthesized from the actual JPEG thumbnail. + Field { tag: tag::JPEGInterchangeFormat, .. } | + Field { tag: tag::JPEGInterchangeFormatLength, .. } => {}, + // Other normal tags. + Field { tag: Tag(Context::Tiff, _), thumbnail: false, .. } => + self.tiff_fields.push(field), + Field { tag: Tag(Context::Exif, _), thumbnail: false, .. } => + self.exif_fields.push(field), + Field { tag: Tag(Context::Gps, _), thumbnail: false, .. } => + self.gps_fields.push(field), + Field { tag: Tag(Context::Interop, _), thumbnail: false, .. } => + self.interop_fields.push(field), + Field { tag: Tag(Context::Tiff, _), thumbnail: true, .. } => + self.tn_tiff_fields.push(field), + Field { tag: Tag(Context::Exif, _), thumbnail: true, .. } => + self.tn_exif_fields.push(field), + Field { tag: Tag(Context::Gps, _), thumbnail: true, .. } => + self.tn_gps_fields.push(field), + Field { tag: Tag(Context::Interop, _), thumbnail: true, .. } => + self.tn_interop_fields.push(field), + } + } + + /// Sets TIFF strips for the primary image. + /// If this method is called multiple times, the last one is used. + pub fn set_strips(&mut self, strips: &'a [&'a [u8]]) { + self.strips = Some(strips); + } + + /// Sets TIFF strips for the thumbnail image. + /// If this method is called multiple times, the last one is used. + pub fn set_thumbnail_strips(&mut self, strips: &'a [&'a [u8]]) { + self.tn_strips = Some(strips); + } + + /// Sets JPEG data for the thumbnail image. + /// If this method is called multiple times, the last one is used. + pub fn set_thumbnail_jpeg(&mut self, jpeg: &'a [u8]) { + self.tn_jpeg = Some(jpeg); + } + + /// Encodes Exif data and writes it into `w`. + /// + /// The write position of `w` must be set to zero before calling + /// this method. + /// + /// A new `exif::Error` variant will be introduced in the next + /// API-version bump to return write errors. `Error::InvalidFormat` + /// is used until then. + pub fn write(&mut self, w: &mut W, little_endian: bool) + -> Result<(), Error> where W: Write + Seek { + // TIFF signature and the offset of the 0th IFD. + if little_endian { + try!(w.write_all(&TIFF_LE_SIG)); + try!(LittleEndian::writeu32(w, 8)); + } else { + try!(w.write_all(&TIFF_BE_SIG)); + try!(BigEndian::writeu32(w, 8)); + } + + // Write the primary image. + let ws = WriterState { + tiff_fields: self.tiff_fields.clone(), + exif_fields: self.exif_fields.clone(), + gps_fields: self.gps_fields.clone(), + interop_fields: self.interop_fields.clone(), + tiff_ifd_offset: 0, + exif_ifd_offset: 0, + gps_ifd_offset: 0, + interop_ifd_offset: 0, + strips: self.strips, + jpeg: None, + }; + let next_ifd_offset_offset = + try!(synthesize_fields(w, ws, false, little_endian)); + + let next_ifd_offset = try!(pad_and_get_offset(w)); + let origpos = try!(w.seek(SeekFrom::Current(0))); + try!(w.seek(SeekFrom::Start(next_ifd_offset_offset as u64))); + match little_endian { + false => try!(BigEndian::writeu32(w, next_ifd_offset)), + true => try!(LittleEndian::writeu32(w, next_ifd_offset)), + } + try!(w.seek(SeekFrom::Start(origpos))); + + // Write the thumbnail image. + let ws = WriterState { + tiff_fields: self.tn_tiff_fields.clone(), + exif_fields: self.tn_exif_fields.clone(), + gps_fields: self.tn_gps_fields.clone(), + interop_fields: self.tn_interop_fields.clone(), + tiff_ifd_offset: 0, + exif_ifd_offset: 0, + gps_ifd_offset: 0, + interop_ifd_offset: 0, + strips: self.tn_strips, + jpeg: self.tn_jpeg, + }; + try!(synthesize_fields(w, ws, true, little_endian)); + + try!(w.flush()); + Ok(()) + } +} + +// Synthesizes special fields, writes an image, and returns the offset +// of the next IFD offset. +fn synthesize_fields(w: &mut W, ws: WriterState, thumbnail: bool, + little_endian: bool) + -> Result where W: Write + Seek { + let exif_in_tiff; + let gps_in_tiff; + let interop_in_exif; + let strip_offsets; + let strip_byte_counts; + let jpeg_offset; + let jpeg_length; + // Shrink the scope so that referenced fields live longer than ws. + let mut ws = ws; + + if let Some(strips) = ws.strips { + strip_offsets = Field { + tag: tag::StripOffsets, + thumbnail: thumbnail, + value: Value::Long(vec![0; strips.len()]), + }; + ws.tiff_fields.push(&strip_offsets); + strip_byte_counts = Field { + tag: tag::StripByteCounts, + thumbnail: thumbnail, + value: Value::Long( + strips.iter().map(|s| s.len() as u32).collect()), + }; + ws.tiff_fields.push(&strip_byte_counts); + } + if let Some(jpeg) = ws.jpeg { + jpeg_offset = Field { + tag: tag::JPEGInterchangeFormat, + thumbnail: thumbnail, + value: Value::Long(vec![0]), + }; + ws.tiff_fields.push(&jpeg_offset); + jpeg_length = Field { + tag: tag::JPEGInterchangeFormatLength, + thumbnail: thumbnail, + value: Value::Long(vec![jpeg.len() as u32]), + }; + ws.tiff_fields.push(&jpeg_length); + } + + let interop_fields_len = ws.interop_fields.len(); + let gps_fields_len = ws.gps_fields.len(); + let exif_fields_len = ws.exif_fields.len() + + match interop_fields_len { 0 => 0, _ => 1 }; + let tiff_fields_len = ws.tiff_fields.len() + + match gps_fields_len { 0 => 0, _ => 1 } + + match exif_fields_len { 0 => 0, _ => 1 }; + + ws.tiff_ifd_offset = try!(reserve_ifd(w, tiff_fields_len)); + if exif_fields_len > 0 { + ws.exif_ifd_offset = try!(reserve_ifd(w, exif_fields_len)); + exif_in_tiff = Field { + tag: tag::ExifIFDPointer, + thumbnail: thumbnail, + value: Value::Long(vec![ws.exif_ifd_offset]), + }; + ws.tiff_fields.push(&exif_in_tiff); + } + if gps_fields_len > 0 { + ws.gps_ifd_offset = try!(reserve_ifd(w, gps_fields_len)); + gps_in_tiff = Field { + tag: tag::GPSInfoIFDPointer, + thumbnail: thumbnail, + value: Value::Long(vec![ws.gps_ifd_offset]), + }; + ws.tiff_fields.push(&gps_in_tiff); + } + if interop_fields_len > 0 { + ws.interop_ifd_offset = try!(reserve_ifd(w, interop_fields_len)); + interop_in_exif = Field { + tag: tag::InteropIFDPointer, + thumbnail: thumbnail, + value: Value::Long(vec![ws.interop_ifd_offset]), + }; + ws.exif_fields.push(&interop_in_exif); + } + + ws.tiff_fields.sort_by_key(|f| f.tag.number()); + ws.exif_fields.sort_by_key(|f| f.tag.number()); + ws.gps_fields.sort_by_key(|f| f.tag.number()); + ws.interop_fields.sort_by_key(|f| f.tag.number()); + + match little_endian { + false => write_image::<_, BigEndian>(w, ws), + true => write_image::<_, LittleEndian>(w, ws), + } +} + +// Writes an image and returns the offset of the next IFD offset. +fn write_image(w: &mut W, ws: WriterState) + -> Result where W: Write + Seek, E: Endian { + let (next_ifd_offset_offset, strip_offsets_offset, jpeg_offset) = + try!(write_ifd_and_fields::<_, E>( + w, &ws.tiff_fields, ws.tiff_ifd_offset)); + if ws.exif_fields.len() > 0 { + try!(write_ifd_and_fields::<_, E>( + w, &ws.exif_fields, ws.exif_ifd_offset)); + } + if ws.gps_fields.len() > 0 { + try!(write_ifd_and_fields::<_, E>( + w, &ws.gps_fields, ws.gps_ifd_offset)); + } + if ws.interop_fields.len() > 0 { + try!(write_ifd_and_fields::<_, E>( + w, &ws.interop_fields, ws.interop_ifd_offset)); + } + + if let Some(strips) = ws.strips { + let mut strip_offsets = Vec::new(); + for strip in strips { + strip_offsets.push(try!(get_offset(w))); + try!(w.write_all(strip)); + } + let origpos = try!(w.seek(SeekFrom::Current(0))); + try!(w.seek(SeekFrom::Start(strip_offsets_offset as u64))); + for ofs in strip_offsets { + try!(E::writeu32(w, ofs)); + } + try!(w.seek(SeekFrom::Start(origpos))); + } + if let Some(jpeg) = ws.jpeg { + let offset = try!(get_offset(w)); + try!(w.write_all(jpeg)); + let origpos = try!(w.seek(SeekFrom::Current(0))); + try!(w.seek(SeekFrom::Start(jpeg_offset as u64))); + try!(E::writeu32(w, offset)); + try!(w.seek(SeekFrom::Start(origpos))); + } + + Ok(next_ifd_offset_offset) +} + +// Advances the write position to make a space for a new IFD and +// returns the offset of the IFD. +fn reserve_ifd(w: &mut W, count: usize) + -> Result where W: Write + Seek { + let ifdpos = try!(get_offset(w)); + assert!(ifdpos % 2 == 0); + // The number of entries (2) + array of entries (12 * n) + + // the next IFD pointer (4). + try!(w.seek(SeekFrom::Current(2 + count as i64 * 12 + 4))); + Ok(ifdpos) +} + +// Writes an IFD and its fields, and +// returns the offsets of the next IFD offset, StripOffsets value, and +// JPEGInterchangeFormat value. +fn write_ifd_and_fields( + w: &mut W, fields: &Vec<&Field>, ifd_offset: u32) + -> Result<(u32, u32, u32), Error> where W: Write + Seek, E: Endian +{ + let mut strip_offsets_offset = 0; + let mut jpeg_offset = 0; + let mut ifd = Vec::new(); + + // Write the number of entries. + try!(E::writeu16(&mut ifd, fields.len() as u16)); + // Write the fields. + for f in fields { + let (typ, cnt, mut valbuf) = try!(compose_value::(&f.value)); + if cnt as u32 as usize != cnt { + return Err(Error::InvalidFormat("Too long array")); + } + try!(E::writeu16(&mut ifd, f.tag.number())); + try!(E::writeu16(&mut ifd, typ)); + try!(E::writeu32(&mut ifd, cnt as u32)); + // Embed the value itself into the offset, or + // encode as an offset and the value. + if valbuf.len() <= 4 { + valbuf.resize(4, 0); + try!(ifd.write_all(&valbuf)); + } else { + // The value must begin on a word boundary. [TIFF6, Section 2: + // TIFF Structure, Image File Directory, IFD Entry, p. 15] + let valofs = try!(pad_and_get_offset(w)); + try!(E::writeu32(&mut ifd, valofs)); + try!(w.write_all(&valbuf)); + } + if f.tag == tag::StripOffsets { + strip_offsets_offset = match valbuf.len() { + 0...4 => ifd_offset + ifd.len() as u32 - 4, + _ => try!(get_offset(w)) - valbuf.len() as u32, + }; + } + if f.tag == tag::JPEGInterchangeFormat { + jpeg_offset = ifd_offset + ifd.len() as u32 - 4; + } + } + // Write the next IFD pointer. + let next_ifd_offset_offset = ifd_offset + ifd.len() as u32; + try!(E::writeu32(&mut ifd, 0)); + + // Write the IFD. + try!(write_at(w, &ifd, ifd_offset)); + + Ok((next_ifd_offset_offset, strip_offsets_offset, jpeg_offset)) +} + // Returns the type, count, and encoded value. fn compose_value(value: &Value) -> Result<(u16, usize, Vec), Error> where E: Endian { @@ -115,6 +491,38 @@ fn compose_value(value: &Value) } } +fn write_at(w: &mut W, buf: &[u8], offset: u32) + -> io::Result<()> where W: Write + Seek { + let orig = try!(w.seek(SeekFrom::Current(0))); + try!(w.seek(SeekFrom::Start(offset as u64))); + try!(w.write_all(buf)); + try!(w.seek(SeekFrom::Start(orig))); + Ok(()) +} + +// Aligns `w` to the two-byte (word) boundary and returns the new offset. +fn pad_and_get_offset(w: &mut W) + -> Result where W: Write + Seek { + let mut pos = try!(w.seek(SeekFrom::Current(0))); + if pos >= 1 << 32 - 1 { + return Err(Error::InvalidFormat("Offset too large")); + } + if pos % 2 != 0 { + try!(w.write_all(&[0])); + pos += 1; + } + Ok(pos as u32) +} + +fn get_offset(w: &mut W) + -> Result where W: Write + Seek { + let pos = try!(w.seek(SeekFrom::Current(0))); + if pos as u32 as u64 != pos { + return Err(Error::InvalidFormat("Offset too large")); + } + Ok(pos as u32) +} + #[cfg(test)] mod tests { use value::{Rational, SRational}; @@ -166,9 +574,9 @@ mod tests { (12, 2, b"\x00\x00\x00\x00\x00\x00\x04\x40\ \x00\x00\x00\x00\x00\x00\xe0\xbf".to_vec())), ]; - for p in patterns.into_iter() { - assert_eq!(compose_value::(&p.0).unwrap(), p.1); - assert_eq!(compose_value::(&p.0).unwrap(), p.2); + for (val, be, le) in patterns.into_iter() { + assert_eq!(compose_value::(&val).unwrap(), be); + assert_eq!(compose_value::(&val).unwrap(), le); } } }