Add an experimental Writer that encodes and writes Exif data.

This commit is contained in:
KAMADA Ken'ichi 2017-07-01 19:17:37 +09:00
parent 132c9e9d18
commit 73bced6cbf
3 changed files with 418 additions and 5 deletions

View File

@ -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;

View File

@ -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)]

View File

@ -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<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 {
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>(w: &mut W, ws: WriterState, thumbnail: bool,
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 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, E>(w: &mut W, ws: WriterState)
-> Result<u32, Error> 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>(w: &mut W, count: usize)
-> Result<u32, Error> 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, E>(
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::<E>(&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<E>(value: &Value)
-> Result<(u16, usize, Vec<u8>), Error> where E: Endian {
@ -115,6 +491,38 @@ fn compose_value<E>(value: &Value)
}
}
fn write_at<W>(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>(w: &mut W)
-> Result<u32, Error> 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>(w: &mut W)
-> Result<u32, Error> 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::<BigEndian>(&p.0).unwrap(), p.1);
assert_eq!(compose_value::<LittleEndian>(&p.0).unwrap(), p.2);
for (val, be, le) in patterns.into_iter() {
assert_eq!(compose_value::<BigEndian>(&val).unwrap(), be);
assert_eq!(compose_value::<LittleEndian>(&val).unwrap(), le);
}
}
}