866 lines
31 KiB
Rust
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);
|
|
}
|
|
}
|
|
}
|