exif-rs/src/writer.rs

866 lines
31 KiB
Rust

//
// 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<dyn std::error::Error>> {
/// 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<Ifd<'a>>,
}
#[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<W>(&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>(w: &mut W, ifd: &Ifd, ifd_num: In,
little_endian: bool)
-> Result<u32, Error> 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, E>(w: &mut W, ws: &WriterState, ifd: &Ifd)
-> Result<u32, Error> 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>(w: &mut W, count: usize)
-> Result<u32, Error> 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, E>(
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::<E>(&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<E>(value: &Value)
-> Result<(u16, usize, Vec<u8>), 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>(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>(w: &mut W)
-> Result<u32, Error> 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>(w: &mut W)
-> Result<u32, Error> 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::<BigEndian>(&val).unwrap(), be);
assert_eq!(compose_value::<LittleEndian>(&val).unwrap(), le);
}
}
}