// // Copyright (c) 2017 KAMADA Ken'ichi. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // use std::io; use std::io::{Seek, SeekFrom, Write}; use crate::endian::{Endian, BigEndian, LittleEndian}; use crate::error::Error; use crate::tag::{Context, Tag}; use crate::tiff::{Field, In, TIFF_BE_SIG, TIFF_LE_SIG}; use crate::value::Value; /// The `Writer` struct is used to encode and write Exif data. /// /// # Examples /// /// ``` /// # fn main() -> Result<(), Box> { /// use exif::{Field, In, Tag, Value}; /// use exif::experimental::Writer; /// let image_desc = Field { /// tag: Tag::ImageDescription, /// ifd_num: In::PRIMARY, /// value: Value::Ascii(vec![b"Sample".to_vec()]), /// }; /// let mut writer = Writer::new(); /// let mut buf = std::io::Cursor::new(Vec::new()); /// writer.push_field(&image_desc); /// writer.write(&mut buf, false)?; /// static expected: &[u8] = /// b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ /// \x00\x01\x01\x0e\x00\x02\x00\x00\x00\x07\x00\x00\x00\x1a\ /// \x00\x00\x00\x00\ /// Sample\0"; /// assert_eq!(buf.into_inner(), expected); /// # Ok(()) } /// ``` #[derive(Debug)] pub struct Writer<'a> { ifd_list: Vec>, } #[derive(Debug, Default)] struct Ifd<'a> { tiff_fields: Vec<&'a Field>, exif_fields: Vec<&'a Field>, gps_fields: Vec<&'a Field>, interop_fields: Vec<&'a Field>, strips: Option<&'a [&'a [u8]]>, tiles: Option<&'a [&'a [u8]]>, jpeg: Option<&'a [u8]>, } impl<'a> Ifd<'a> { fn is_empty(&self) -> bool { self.tiff_fields.is_empty() && self.exif_fields.is_empty() && self.gps_fields.is_empty() && self.interop_fields.is_empty() && self.strips.is_none() && self.tiles.is_none() && self.jpeg.is_none() } } struct WriterState<'a> { tiff_fields: Vec<&'a Field>, exif_fields: Vec<&'a Field>, gps_fields: Vec<&'a Field>, interop_fields: Vec<&'a Field>, tiff_ifd_offset: u32, exif_ifd_offset: u32, gps_ifd_offset: u32, interop_ifd_offset: u32, } impl<'a> Writer<'a> { /// Constructs an empty `Writer`. pub fn new() -> Writer<'a> { Writer { ifd_list: Vec::new(), } } /// Appends a field to be written. /// /// The fields can be appended in any order. /// Duplicate fields must not be appended. /// /// The following fields are ignored and synthesized when needed: /// ExifIFDPointer, GPSInfoIFDPointer, InteropIFDPointer, /// StripOffsets, StripByteCounts, TileOffsets, TileByteCounts, /// JPEGInterchangeFormat, and JPEGInterchangeFormatLength. 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/tile data. Field { tag: Tag::StripOffsets, .. } | Field { tag: Tag::StripByteCounts, .. } | Field { tag: Tag::TileOffsets, .. } | Field { tag: Tag::TileByteCounts, .. } => {}, // These tags are synthesized from the actual JPEG thumbnail. Field { tag: Tag::JPEGInterchangeFormat, .. } | Field { tag: Tag::JPEGInterchangeFormatLength, .. } => {}, // Other normal tags. Field { tag: Tag(ctx, _), ifd_num, .. } => { let ifd = self.pick_ifd(ifd_num); match ctx { Context::Tiff => ifd.tiff_fields.push(field), Context::Exif => ifd.exif_fields.push(field), Context::Gps => ifd.gps_fields.push(field), Context::Interop => ifd.interop_fields.push(field), } }, } } /// Sets TIFF strips for the specified IFD. /// If this method is called multiple times, the last one is used. pub fn set_strips(&mut self, strips: &'a [&'a [u8]], ifd_num: In) { self.pick_ifd(ifd_num).strips = Some(strips); } /// Sets TIFF tiles for the specified IFD. /// If this method is called multiple times, the last one is used. pub fn set_tiles(&mut self, tiles: &'a [&'a [u8]], ifd_num: In) { self.pick_ifd(ifd_num).tiles = Some(tiles); } /// Sets JPEG data for the specified IFD. /// If this method is called multiple times, the last one is used. pub fn set_jpeg(&mut self, jpeg: &'a [u8], ifd_num: In) { self.pick_ifd(ifd_num).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. 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 { w.write_all(&TIFF_LE_SIG)?; LittleEndian::writeu32(w, 8)?; } else { w.write_all(&TIFF_BE_SIG)?; BigEndian::writeu32(w, 8)?; } // There must be at least 1 IFD in a TIFF file [TIFF6, Section 2, // Image File Directory]. if self.ifd_list.is_empty() { return Err(Error::InvalidFormat("At least one IFD must exist")); } let mut ifd_num_ck = Some(0); let mut next_ifd_offset_offset = 4; for ifd in &self.ifd_list { // Each IFD must have at least one entry [TIFF6, Section 2, // Image File Directory]. if ifd.is_empty() { return Err(Error::InvalidFormat("IFD must not be empty")); } let ifd_num = ifd_num_ck.ok_or(Error::InvalidFormat("Too many IFDs"))?; if ifd_num > 0 { let next_ifd_offset = pad_and_get_offset(w)?; let origpos = w.seek(SeekFrom::Current(0))?; w.seek(SeekFrom::Start(next_ifd_offset_offset as u64))?; match little_endian { false => BigEndian::writeu32(w, next_ifd_offset)?, true => LittleEndian::writeu32(w, next_ifd_offset)?, } w.seek(SeekFrom::Start(origpos))?; } next_ifd_offset_offset = synthesize_fields(w, ifd, In(ifd_num), little_endian)?; ifd_num_ck = ifd_num.checked_add(1); } w.flush()?; Ok(()) } fn pick_ifd(&mut self, ifd_num: In) -> &mut Ifd<'a> { let ifd_num = ifd_num.index() as usize; if self.ifd_list.len() <= ifd_num { self.ifd_list.resize_with(ifd_num + 1, Default::default); } &mut self.ifd_list[ifd_num] } } // Synthesizes special fields, writes an image, and returns the offset // of the next IFD offset. fn synthesize_fields(w: &mut W, ifd: &Ifd, ifd_num: In, 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 tile_offsets; let tile_byte_counts; let jpeg_offset; let jpeg_length; let mut ws = WriterState { tiff_fields: ifd.tiff_fields.clone(), exif_fields: ifd.exif_fields.clone(), gps_fields: ifd.gps_fields.clone(), interop_fields: ifd.interop_fields.clone(), tiff_ifd_offset: 0, exif_ifd_offset: 0, gps_ifd_offset: 0, interop_ifd_offset: 0, }; if let Some(strips) = ifd.strips { strip_offsets = Field { tag: Tag::StripOffsets, ifd_num: ifd_num, value: Value::Long(vec![0; strips.len()]), }; ws.tiff_fields.push(&strip_offsets); strip_byte_counts = Field { tag: Tag::StripByteCounts, ifd_num: ifd_num, value: Value::Long( strips.iter().map(|s| s.len() as u32).collect()), }; ws.tiff_fields.push(&strip_byte_counts); } if let Some(tiles) = ifd.tiles { tile_offsets = Field { tag: Tag::TileOffsets, ifd_num: ifd_num, value: Value::Long(vec![0; tiles.len()]), }; ws.tiff_fields.push(&tile_offsets); tile_byte_counts = Field { tag: Tag::TileByteCounts, ifd_num: ifd_num, value: Value::Long( tiles.iter().map(|s| s.len() as u32).collect()), }; ws.tiff_fields.push(&tile_byte_counts); } if let Some(jpeg) = ifd.jpeg { jpeg_offset = Field { tag: Tag::JPEGInterchangeFormat, ifd_num: ifd_num, value: Value::Long(vec![0]), }; ws.tiff_fields.push(&jpeg_offset); jpeg_length = Field { tag: Tag::JPEGInterchangeFormatLength, ifd_num: ifd_num, 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 }; assert_ne!(tiff_fields_len, 0); ws.tiff_ifd_offset = reserve_ifd(w, tiff_fields_len)?; if exif_fields_len > 0 { ws.exif_ifd_offset = reserve_ifd(w, exif_fields_len)?; exif_in_tiff = Field { tag: Tag::ExifIFDPointer, ifd_num: ifd_num, value: Value::Long(vec![ws.exif_ifd_offset]), }; ws.tiff_fields.push(&exif_in_tiff); } if gps_fields_len > 0 { ws.gps_ifd_offset = reserve_ifd(w, gps_fields_len)?; gps_in_tiff = Field { tag: Tag::GPSInfoIFDPointer, ifd_num: ifd_num, value: Value::Long(vec![ws.gps_ifd_offset]), }; ws.tiff_fields.push(&gps_in_tiff); } if interop_fields_len > 0 { ws.interop_ifd_offset = reserve_ifd(w, interop_fields_len)?; interop_in_exif = Field { tag: Tag::InteropIFDPointer, ifd_num: ifd_num, 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, ifd), true => write_image::<_, LittleEndian>(w, &ws, ifd), } } // Writes an image and returns the offset of the next IFD offset. fn write_image(w: &mut W, ws: &WriterState, ifd: &Ifd) -> Result where W: Write + Seek, E: Endian { let (next_ifd_offset_offset, strip_offsets_offset, tile_offsets_offset, jpeg_offset) = write_ifd_and_fields::<_, E>( w, &ws.tiff_fields, ws.tiff_ifd_offset)?; if ws.exif_fields.len() > 0 { write_ifd_and_fields::<_, E>( w, &ws.exif_fields, ws.exif_ifd_offset)?; } if ws.gps_fields.len() > 0 { write_ifd_and_fields::<_, E>( w, &ws.gps_fields, ws.gps_ifd_offset)?; } if ws.interop_fields.len() > 0 { write_ifd_and_fields::<_, E>( w, &ws.interop_fields, ws.interop_ifd_offset)?; } if let Some(strips) = ifd.strips { let mut strip_offsets = Vec::new(); for strip in strips { strip_offsets.push(get_offset(w)?); w.write_all(strip)?; } let origpos = w.seek(SeekFrom::Current(0))?; w.seek(SeekFrom::Start(strip_offsets_offset as u64))?; for ofs in strip_offsets { E::writeu32(w, ofs)?; } w.seek(SeekFrom::Start(origpos))?; } if let Some(tiles) = ifd.tiles { let mut tile_offsets = Vec::new(); for tile in tiles { tile_offsets.push(get_offset(w)?); w.write_all(tile)?; } let origpos = w.seek(SeekFrom::Current(0))?; w.seek(SeekFrom::Start(tile_offsets_offset as u64))?; for ofs in tile_offsets { E::writeu32(w, ofs)?; } w.seek(SeekFrom::Start(origpos))?; } if let Some(jpeg) = ifd.jpeg { let offset = get_offset(w)?; w.write_all(jpeg)?; let origpos = w.seek(SeekFrom::Current(0))?; w.seek(SeekFrom::Start(jpeg_offset as u64))?; E::writeu32(w, offset)?; 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 = get_offset(w)?; assert!(ifdpos % 2 == 0); // The number of entries (2) + array of entries (12 * n) + // the next IFD pointer (4). 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, // TileOffsets value, and JPEGInterchangeFormat value. fn write_ifd_and_fields( w: &mut W, fields: &Vec<&Field>, ifd_offset: u32) -> Result<(u32, u32, u32, u32), Error> where W: Write + Seek, E: Endian { let mut strip_offsets_offset = 0; let mut tile_offsets_offset = 0; let mut jpeg_offset = 0; let mut ifd = Vec::new(); // Write the number of entries. E::writeu16(&mut ifd, fields.len() as u16)?; // Write the fields. for f in fields { let (typ, cnt, mut valbuf) = compose_value::(&f.value)?; if cnt as u32 as usize != cnt { return Err(Error::TooBig("Too long array")); } E::writeu16(&mut ifd, f.tag.number())?; E::writeu16(&mut ifd, typ)?; 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); 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 = pad_and_get_offset(w)?; E::writeu32(&mut ifd, valofs)?; w.write_all(&valbuf)?; } if f.tag == Tag::StripOffsets { strip_offsets_offset = match valbuf.len() { 0..=4 => ifd_offset + ifd.len() as u32 - 4, _ => get_offset(w)? - valbuf.len() as u32, }; } if f.tag == Tag::TileOffsets { tile_offsets_offset = match valbuf.len() { 0..=4 => ifd_offset + ifd.len() as u32 - 4, _ => 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; E::writeu32(&mut ifd, 0)?; // Write the IFD. write_at(w, &ifd, ifd_offset)?; Ok((next_ifd_offset_offset, strip_offsets_offset, tile_offsets_offset, jpeg_offset)) } // Returns the type, count, and encoded value. fn compose_value(value: &Value) -> Result<(u16, usize, Vec), Error> where E: Endian { match *value { Value::Byte(ref vec) => Ok((1, vec.len(), vec.clone())), Value::Ascii(ref vec) => { let mut buf = Vec::new(); for x in vec { buf.extend_from_slice(x); buf.push(0); } Ok((2, buf.len(), buf)) }, Value::Short(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu16(&mut buf, v)?; } Ok((3, vec.len(), buf)) }, Value::Long(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu32(&mut buf, v)?; } Ok((4, vec.len(), buf)) }, Value::Rational(ref vec) => { let mut buf = Vec::new(); for v in vec { E::writeu32(&mut buf, v.num)?; E::writeu32(&mut buf, v.denom)?; } Ok((5, vec.len(), buf)) }, Value::SByte(ref vec) => { let bytes = vec.iter().map(|x| *x as u8).collect(); Ok((6, vec.len(), bytes)) }, Value::Undefined(ref s, _) => Ok((7, s.len(), s.to_vec())), Value::SShort(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu16(&mut buf, v as u16)?; } Ok((8, vec.len(), buf)) }, Value::SLong(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu32(&mut buf, v as u32)?; } Ok((9, vec.len(), buf)) }, Value::SRational(ref vec) => { let mut buf = Vec::new(); for v in vec { E::writeu32(&mut buf, v.num as u32)?; E::writeu32(&mut buf, v.denom as u32)?; } Ok((10, vec.len(), buf)) }, Value::Float(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu32(&mut buf, v.to_bits())?; } Ok((11, vec.len(), buf)) }, Value::Double(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu64(&mut buf, v.to_bits())?; } Ok((12, vec.len(), buf)) }, Value::Unknown(_, _, _) => Err(Error::NotSupported("Cannot write unknown field types")), } } fn write_at(w: &mut W, buf: &[u8], offset: u32) -> io::Result<()> where W: Write + Seek { let orig = w.seek(SeekFrom::Current(0))?; w.seek(SeekFrom::Start(offset as u64))?; w.write_all(buf)?; 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 = w.seek(SeekFrom::Current(0))?; if pos >= (1 << 32) - 1 { return Err(Error::TooBig("Offset too large")); } if pos % 2 != 0 { w.write_all(&[0])?; pos += 1; } Ok(pos as u32) } fn get_offset(w: &mut W) -> Result where W: Write + Seek { let pos = w.seek(SeekFrom::Current(0))?; if pos as u32 as u64 != pos { return Err(Error::TooBig("Offset too large")); } Ok(pos as u32) } #[cfg(test)] mod tests { use std::io::Cursor; use super::*; #[test] fn primary() { let image_desc = Field { tag: Tag::ImageDescription, ifd_num: In::PRIMARY, value: Value::Ascii(vec![b"Sample".to_vec()]), }; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.push_field(&image_desc); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x01\x01\x0e\x00\x02\x00\x00\x00\x07\x00\x00\x00\x1a\ \x00\x00\x00\x00\ Sample\0"; assert_eq!(buf.into_inner(), expected); } #[test] fn primary_exif_only() { let exif_ver = Field { tag: Tag::ExifVersion, ifd_num: In::PRIMARY, value: Value::Undefined(b"0231".to_vec(), 0), }; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.push_field(&exif_ver); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x01\x87\x69\x00\x04\x00\x00\x00\x01\x00\x00\x00\x1a\ \x00\x00\x00\x00\ \x00\x01\x90\x00\x00\x07\x00\x00\x00\x040231\ \x00\x00\x00\x00"; assert_eq!(buf.into_inner(), expected); } #[test] fn primary_tiff_tiled() { // This is not a valid TIFF tile (only for testing). let tiles: &[&[u8]] = &[b"TILE"]; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.set_tiles(tiles, In::PRIMARY); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x02\x01\x44\x00\x04\x00\x00\x00\x01\x00\x00\x00\x26\ \x01\x45\x00\x04\x00\x00\x00\x01\x00\x00\x00\x04\ \x00\x00\x00\x00\ TILE"; assert_eq!(buf.into_inner(), expected); } #[test] fn thumbnail_jpeg() { // This is not a valid JPEG data (only for testing). let jpeg = b"JPEG"; let desc = Field { tag: Tag::ImageDescription, ifd_num: In::PRIMARY, value: Value::Ascii(vec![b"jpg".to_vec()]), }; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.push_field(&desc); writer.set_jpeg(jpeg, In::THUMBNAIL); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x01\x01\x0e\x00\x02\x00\x00\x00\x04jpg\x00\ \x00\x00\x00\x1a\ \x00\x02\x02\x01\x00\x04\x00\x00\x00\x01\x00\x00\x00\x38\ \x02\x02\x00\x04\x00\x00\x00\x01\x00\x00\x00\x04\ \x00\x00\x00\x00\ JPEG"; assert_eq!(buf.into_inner(), expected); } #[test] fn thumbnail_tiff() { // This is not a valid TIFF strip (only for testing). let desc = Field { tag: Tag::ImageDescription, ifd_num: In::PRIMARY, value: Value::Ascii(vec![b"tif".to_vec()]), }; let strips: &[&[u8]] = &[b"STRIP"]; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.push_field(&desc); writer.set_strips(strips, In::THUMBNAIL); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x01\x01\x0e\x00\x02\x00\x00\x00\x04tif\x00\ \x00\x00\x00\x1a\ \x00\x02\x01\x11\x00\x04\x00\x00\x00\x01\x00\x00\x00\x38\ \x01\x17\x00\x04\x00\x00\x00\x01\x00\x00\x00\x05\ \x00\x00\x00\x00\ STRIP"; assert_eq!(buf.into_inner(), expected); } #[test] fn primary_and_thumbnail() { let image_desc = Field { tag: Tag::ImageDescription, ifd_num: In::PRIMARY, value: Value::Ascii(vec![b"Sample".to_vec()]), }; let exif_ver = Field { tag: Tag::ExifVersion, ifd_num: In::PRIMARY, value: Value::Undefined(b"0231".to_vec(), 0), }; let gps_ver = Field { tag: Tag::GPSVersionID, ifd_num: In::PRIMARY, value: Value::Byte(vec![2, 3, 0, 0]), }; let interop_index = Field { tag: Tag::InteroperabilityIndex, ifd_num: In::PRIMARY, value: Value::Ascii(vec![b"ABC".to_vec()]), }; let jpeg = b"JPEG"; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.push_field(&image_desc); writer.push_field(&exif_ver); writer.push_field(&gps_ver); writer.push_field(&interop_index); writer.set_jpeg(jpeg, In::THUMBNAIL); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x03\x01\x0e\x00\x02\x00\x00\x00\x07\x00\x00\x00\x74\ \x87\x69\x00\x04\x00\x00\x00\x01\x00\x00\x00\x32\ \x88\x25\x00\x04\x00\x00\x00\x01\x00\x00\x00\x50\ \x00\x00\x00\x7c\ \x00\x02\x90\x00\x00\x07\x00\x00\x00\x040231\ \xa0\x05\x00\x04\x00\x00\x00\x01\x00\x00\x00\x62\ \x00\x00\x00\x00\ \x00\x01\x00\x00\x00\x01\x00\x00\x00\x04\x02\x03\x00\x00\ \x00\x00\x00\x00\ \x00\x01\x00\x01\x00\x02\x00\x00\x00\x04ABC\0\ \x00\x00\x00\x00\ Sample\0\0\ \x00\x02\x02\x01\x00\x04\x00\x00\x00\x01\x00\x00\x00\x9a\ \x02\x02\x00\x04\x00\x00\x00\x01\x00\x00\x00\x04\ \x00\x00\x00\x00\ JPEG"; assert_eq!(buf.into_inner(), expected); } #[test] fn primary_thumbnail_and_2nd() { let desc0 = Field { tag: Tag::ImageDescription, ifd_num: In::PRIMARY, value: Value::Ascii(vec![b"p".to_vec()]), }; let desc1 = Field { tag: Tag::ImageDescription, ifd_num: In::THUMBNAIL, value: Value::Ascii(vec![b"t".to_vec()]), }; let desc2 = Field { tag: Tag::ImageDescription, ifd_num: In(2), value: Value::Ascii(vec![b"2".to_vec()]), }; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.push_field(&desc0); writer.push_field(&desc1); writer.push_field(&desc2); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x01\x01\x0e\x00\x02\x00\x00\x00\x02p\x00\x00\x00\ \x00\x00\x00\x1a\ \x00\x01\x01\x0e\x00\x02\x00\x00\x00\x02t\x00\x00\x00\ \x00\x00\x00\x2c\ \x00\x01\x01\x0e\x00\x02\x00\x00\x00\x022\x00\x00\x00\ \x00\x00\x00\x00"; assert_eq!(buf.into_inner(), expected); } #[test] fn empty_file() { let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); assert_pat!(writer.write(&mut buf, false), Err(Error::InvalidFormat("At least one IFD must exist"))); } #[test] fn missing_primary() { let jpeg = b"JPEG"; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.set_jpeg(jpeg, In::THUMBNAIL); assert_pat!(writer.write(&mut buf, false), Err(Error::InvalidFormat("IFD must not be empty"))); } #[test] fn write_twice() { let image_desc = Field { tag: Tag::ImageDescription, ifd_num: In::PRIMARY, value: Value::Ascii(vec![b"Sample".to_vec()]), }; let mut writer = Writer::new(); writer.push_field(&image_desc); let mut buf1 = Cursor::new(Vec::new()); writer.write(&mut buf1, false).unwrap(); let mut buf2 = Cursor::new(Vec::new()); writer.write(&mut buf2, false).unwrap(); assert_eq!(buf1.into_inner(), buf2.into_inner()); } #[test] fn compose_field_value() { let patterns = vec![ (Value::Byte(vec![1, 2]), (1, 2, vec![1, 2]), (1, 2, vec![1, 2])), (Value::Ascii(vec![b"a".to_vec(), b"b".to_vec()]), (2, 4, b"a\0b\0".to_vec()), (2, 4, b"a\0b\0".to_vec())), (Value::Short(vec![0x0102, 0x0304]), (3, 2, b"\x01\x02\x03\x04".to_vec()), (3, 2, b"\x02\x01\x04\x03".to_vec())), (Value::Long(vec![0x01020304, 0x05060708]), (4, 2, b"\x01\x02\x03\x04\x05\x06\x07\x08".to_vec()), (4, 2, b"\x04\x03\x02\x01\x08\x07\x06\x05".to_vec())), (Value::Rational(vec![(1, 2).into(), (3, 4).into()]), (5, 2, b"\0\0\0\x01\0\0\0\x02\0\0\0\x03\0\0\0\x04".to_vec()), (5, 2, b"\x01\0\0\0\x02\0\0\0\x03\0\0\0\x04\0\0\0".to_vec())), (Value::SByte(vec![-2, -128]), (6, 2, b"\xfe\x80".to_vec()), (6, 2, b"\xfe\x80".to_vec())), (Value::Undefined(b"abc".to_vec(), 0), (7, 3, b"abc".to_vec()), (7, 3, b"abc".to_vec())), (Value::SShort(vec![-2, -0x8000]), (8, 2, b"\xff\xfe\x80\x00".to_vec()), (8, 2, b"\xfe\xff\x00\x80".to_vec())), (Value::SLong(vec![-2, -0x80000000]), (9, 2, b"\xff\xff\xff\xfe\x80\x00\x00\x00".to_vec()), (9, 2, b"\xfe\xff\xff\xff\x00\x00\x00\x80".to_vec())), (Value::SRational(vec![(-1, -2).into(), (-3, -4).into()]), (10, 2, b"\xff\xff\xff\xff\xff\xff\xff\xfe\ \xff\xff\xff\xfd\xff\xff\xff\xfc".to_vec()), (10, 2, b"\xff\xff\xff\xff\xfe\xff\xff\xff\ \xfd\xff\xff\xff\xfc\xff\xff\xff".to_vec())), (Value::Float(vec![2.5, -0.5]), (11, 2, b"\x40\x20\x00\x00\xbf\x00\x00\x00".to_vec()), (11, 2, b"\x00\x00\x20\x40\x00\x00\x00\xbf".to_vec())), (Value::Double(vec![2.5, -0.5]), (12, 2, b"\x40\x04\x00\x00\x00\x00\x00\x00\ \xbf\xe0\x00\x00\x00\x00\x00\x00".to_vec()), (12, 2, b"\x00\x00\x00\x00\x00\x00\x04\x40\ \x00\x00\x00\x00\x00\x00\xe0\xbf".to_vec())), ]; for (val, be, le) in patterns.into_iter() { assert_eq!(compose_value::(&val).unwrap(), be); assert_eq!(compose_value::(&val).unwrap(), le); } } }