Add struct In (IFD number) to indicate primary/thumbnail images.

This commit is contained in:
KAMADA Ken'ichi 2019-04-20 23:16:01 +09:00
parent 6c3ddddc96
commit b9575efd0b
8 changed files with 174 additions and 110 deletions

View File

@ -46,9 +46,9 @@ fn dump_file(path: &Path) -> Result<(), exif::Error> {
println!("{}", path.display()); println!("{}", path.display());
for f in reader.fields() { for f in reader.fields() {
let thumb = if f.thumbnail { "1/" } else { "0/" }; println!(" {}/{}: {}",
println!(" {}{}: {}", f.ifd_num.index(), f.tag,
thumb, f.tag, f.display_value().with_unit(&reader)); f.display_value().with_unit(&reader));
if let exif::Value::Ascii(ref s) = f.value { if let exif::Value::Ascii(ref s) = f.value {
println!(" Ascii({:?})", println!(" Ascii({:?})",
s.iter().map(escape).collect::<Vec<_>>()); s.iter().map(escape).collect::<Vec<_>>());

View File

@ -29,7 +29,7 @@ extern crate exif;
use std::fs::File; use std::fs::File;
use std::io::BufReader; use std::io::BufReader;
use exif::{DateTime, Reader, Value, Tag}; use exif::{DateTime, In, Reader, Value, Tag};
fn main() { fn main() {
let file = File::open("tests/exif.jpg").unwrap(); let file = File::open("tests/exif.jpg").unwrap();
@ -44,7 +44,7 @@ fn main() {
Tag::ImageDescription, Tag::ImageDescription,
Tag::DateTime]; Tag::DateTime];
for &tag in tag_list.iter() { for &tag in tag_list.iter() {
if let Some(field) = reader.get_field(tag, false) { if let Some(field) = reader.get_field(tag, In::PRIMARY) {
println!("{}: {}", println!("{}: {}",
field.tag, field.display_value().with_unit(&reader)); field.tag, field.display_value().with_unit(&reader));
} }
@ -52,7 +52,7 @@ fn main() {
// To get unsigned integer value(s) from either of BYTE, SHORT, // To get unsigned integer value(s) from either of BYTE, SHORT,
// or LONG, `Value::get_uint` or `Value::iter_uint` can be used. // or LONG, `Value::get_uint` or `Value::iter_uint` can be used.
if let Some(field) = reader.get_field(Tag::PixelXDimension, false) { if let Some(field) = reader.get_field(Tag::PixelXDimension, In::PRIMARY) {
if let Some(width) = field.value.get_uint(0) { if let Some(width) = field.value.get_uint(0) {
println!("Valid width of the image is {}.", width); println!("Valid width of the image is {}.", width);
} }
@ -60,7 +60,7 @@ fn main() {
// To convert a Rational or SRational to an f64, `Rational::to_f64` // To convert a Rational or SRational to an f64, `Rational::to_f64`
// or `SRational::to_f64` can be used. // or `SRational::to_f64` can be used.
if let Some(field) = reader.get_field(Tag::XResolution, false) { if let Some(field) = reader.get_field(Tag::XResolution, In::PRIMARY) {
match field.value { match field.value {
Value::Rational(ref vec) if !vec.is_empty() => Value::Rational(ref vec) if !vec.is_empty() =>
println!("X resolution is {}.", vec[0].to_f64()), println!("X resolution is {}.", vec[0].to_f64()),
@ -69,7 +69,7 @@ fn main() {
} }
// To parse a DateTime-like field, `DateTime::from_ascii` can be used. // To parse a DateTime-like field, `DateTime::from_ascii` can be used.
if let Some(field) = reader.get_field(Tag::DateTime, false) { if let Some(field) = reader.get_field(Tag::DateTime, In::PRIMARY) {
match field.value { match field.value {
Value::Ascii(ref vec) if !vec.is_empty() => { Value::Ascii(ref vec) if !vec.is_empty() => {
if let Ok(datetime) = DateTime::from_ascii(vec[0]) { if let Ok(datetime) = DateTime::from_ascii(vec[0]) {

View File

@ -39,7 +39,7 @@
//! &mut std::io::BufReader::new(&file)).unwrap(); //! &mut std::io::BufReader::new(&file)).unwrap();
//! for f in reader.fields() { //! for f in reader.fields() {
//! println!("{} {} {}", //! println!("{} {} {}",
//! f.tag, f.thumbnail, f.display_value().with_unit(&reader)); //! f.tag, f.ifd_num, f.display_value().with_unit(&reader));
//! } //! }
//! } //! }
//! ``` //! ```
@ -50,12 +50,16 @@
//! //!
//! * The constants in tag module (`tag::TagName`) have been removed. //! * The constants in tag module (`tag::TagName`) have been removed.
//! Use `Tag::TagName` instead. //! Use `Tag::TagName` instead.
//! * Sturct `In` (IFD number) has been added to indicate primary/thumbnail
//! images, which were distinguished by `bool` previously. Function
//! parameters and struct members now take `In`s instead of `bool`s.
//! `Field::thumbnail` was renamed to `Field::ifd_num` accordingly.
pub use error::Error; pub use error::Error;
pub use jpeg::get_exif_attr as get_exif_attr_from_jpeg; pub use jpeg::get_exif_attr as get_exif_attr_from_jpeg;
pub use reader::Reader; pub use reader::Reader;
pub use tag::{Context, Tag}; pub use tag::{Context, Tag};
pub use tiff::{DateTime, Field}; pub use tiff::{DateTime, Field, In};
pub use tiff::parse_exif; pub use tiff::parse_exif;
pub use value::Value; pub use value::Value;
pub use value::{Rational, SRational}; pub use value::{Rational, SRational};

View File

@ -33,17 +33,17 @@ use crate::error::Error;
use crate::jpeg; use crate::jpeg;
use crate::tag::Tag; use crate::tag::Tag;
use crate::tiff; use crate::tiff;
use crate::tiff::{Field, ProvideUnit}; use crate::tiff::{Field, In, ProvideUnit};
/// The `Reader` struct reads a JPEG or TIFF image, /// The `Reader` struct reads a JPEG or TIFF image,
/// parses the Exif attributes in it, and holds the results. /// parses the Exif attributes in it, and holds the results.
/// ///
/// # Examples /// # Examples
/// ``` /// ```
/// use exif::{In, Reader, Tag};
/// let file = std::fs::File::open("tests/exif.jpg").unwrap(); /// let file = std::fs::File::open("tests/exif.jpg").unwrap();
/// let reader = exif::Reader::new( /// let reader = Reader::new(&mut std::io::BufReader::new(&file)).unwrap();
/// &mut std::io::BufReader::new(&file)).unwrap(); /// let xres = reader.get_field(Tag::XResolution, In::PRIMARY).unwrap();
/// let xres = reader.get_field(exif::Tag::XResolution, false).unwrap();
/// assert_eq!(xres.display_value().with_unit(&reader).to_string(), /// assert_eq!(xres.display_value().with_unit(&reader).to_string(),
/// "72 pixels per inch"); /// "72 pixels per inch");
/// ``` /// ```
@ -68,7 +68,7 @@ pub struct Reader {
// True if the TIFF data is little endian. // True if the TIFF data is little endian.
little_endian: bool, little_endian: bool,
// HashMap to find a field quickly. // HashMap to find a field quickly.
field_map: HashMap<(Tag, bool), &'static Field<'static>>, field_map: HashMap<(Tag, In), &'static Field<'static>>,
} }
impl Reader { impl Reader {
@ -98,7 +98,7 @@ impl Reader {
// Initialize the HashMap of all fields. // Initialize the HashMap of all fields.
let mut field_map = HashMap::new(); let mut field_map = HashMap::new();
for f in &fields { for f in &fields {
field_map.insert((f.tag, f.thumbnail), field_map.insert((f.tag, f.ifd_num),
unsafe { mem::transmute::<&Field, &Field>(f) }); unsafe { mem::transmute::<&Field, &Field>(f) });
} }
@ -129,16 +129,16 @@ impl Reader {
} }
/// Returns a reference to the Exif field specified by the tag /// Returns a reference to the Exif field specified by the tag
/// and the thumbnail flag. /// and the IFD number.
#[inline] #[inline]
pub fn get_field(&self, tag: Tag, thumbnail: bool) -> Option<&Field> { pub fn get_field(&self, tag: Tag, ifd_num: In) -> Option<&Field> {
self.field_map.get(&(tag, thumbnail)).map(|&f| f) self.field_map.get(&(tag, ifd_num)).map(|&f| f)
} }
} }
impl<'a> ProvideUnit<'a> for &'a Reader { impl<'a> ProvideUnit<'a> for &'a Reader {
fn get_field(self, tag: Tag, thumbnail: bool) -> Option<&'a Field<'a>> { fn get_field(self, tag: Tag, ifd_num: In) -> Option<&'a Field<'a>> {
self.get_field(tag, thumbnail) self.get_field(tag, ifd_num)
} }
} }
@ -199,10 +199,16 @@ mod tests {
#[test] #[test]
fn get_field() { fn get_field() {
let file = File::open("tests/exif.jpg").unwrap(); let file = File::open("tests/exif.tif").unwrap();
let reader = Reader::new(&mut BufReader::new(&file)).unwrap(); let reader = Reader::new(&mut BufReader::new(&file)).unwrap();
assert_pat!(reader.get_field(Tag::ExifVersion, false).unwrap().value, match reader.get_field(Tag::ImageDescription, In(0)).unwrap().value {
Value::Undefined(b"0230", _)); Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test image"]),
ref v => panic!("wrong variant {:?}", v)
}
match reader.get_field(Tag::ImageDescription, In(1)).unwrap().value {
Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test thumbnail"]),
ref v => panic!("wrong variant {:?}", v)
}
} }
#[test] #[test]
@ -210,23 +216,23 @@ mod tests {
let file = File::open("tests/unit.tif").unwrap(); let file = File::open("tests/unit.tif").unwrap();
let reader = Reader::new(&mut BufReader::new(&file)).unwrap(); let reader = Reader::new(&mut BufReader::new(&file)).unwrap();
// No unit. // No unit.
let exifver = reader.get_field(Tag::ExifVersion, false).unwrap(); let exifver = reader.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
assert_eq!(exifver.display_value().with_unit(&reader).to_string(), assert_eq!(exifver.display_value().with_unit(&reader).to_string(),
"2.31"); "2.31");
// Fixed string. // Fixed string.
let width = reader.get_field(Tag::ImageWidth, false).unwrap(); let width = reader.get_field(Tag::ImageWidth, In::PRIMARY).unwrap();
assert_eq!(width.display_value().with_unit(&reader).to_string(), assert_eq!(width.display_value().with_unit(&reader).to_string(),
"15 pixels"); "15 pixels");
// Unit tag (with a non-default value). // Unit tag (with a non-default value).
let gpsalt = reader.get_field(Tag::GPSAltitude, false).unwrap(); let gpsalt = reader.get_field(Tag::GPSAltitude, In::PRIMARY).unwrap();
assert_eq!(gpsalt.display_value().with_unit(&reader).to_string(), assert_eq!(gpsalt.display_value().with_unit(&reader).to_string(),
"0.5 meters below sea level"); "0.5 meters below sea level");
// Unit tag is missing but the default is specified. // Unit tag is missing but the default is specified.
let xres = reader.get_field(Tag::XResolution, false).unwrap(); let xres = reader.get_field(Tag::XResolution, In::PRIMARY).unwrap();
assert_eq!(xres.display_value().with_unit(&reader).to_string(), assert_eq!(xres.display_value().with_unit(&reader).to_string(),
"72 pixels per inch"); "72 pixels per inch");
// Unit tag is missing and the default is not specified. // Unit tag is missing and the default is not specified.
let gpslat = reader.get_field(Tag::GPSLatitude, false).unwrap(); let gpslat = reader.get_field(Tag::GPSLatitude, In::PRIMARY).unwrap();
assert_eq!(gpslat.display_value().with_unit(&reader).to_string(), assert_eq!(gpslat.display_value().with_unit(&reader).to_string(),
"10 deg 0 min 0 sec [GPSLatitudeRef missing]"); "10 deg 0 min 0 sec [GPSLatitudeRef missing]");
} }

View File

@ -46,12 +46,48 @@ pub const TIFF_LE_SIG: [u8; 4] = [0x49, 0x49, 0x2a, 0x00];
pub struct Field<'a> { pub struct Field<'a> {
/// The tag of this field. /// The tag of this field.
pub tag: Tag, pub tag: Tag,
/// False for the primary image and true for the thumbnail. /// The index of the IFD to which this field belongs.
pub thumbnail: bool, pub ifd_num: In,
/// The value of this field. /// The value of this field.
pub value: Value<'a>, pub value: Value<'a>,
} }
/// The IFD number.
///
/// The IFDs are indexed from 0. The 0th IFD is for the primary image
/// and the 1st one is for the thumbnail. Two associated constants,
/// `In::PRIMARY` and `In::THUMBNAIL`, are defined for them respectively.
///
/// # Examples
/// ```
/// use exif::In;
/// assert_eq!(In::PRIMARY.index(), 0);
/// assert_eq!(In::THUMBNAIL.index(), 1);
/// ```
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct In(pub u16);
impl In {
pub const PRIMARY: In = In(0);
pub const THUMBNAIL: In = In(1);
/// Returns the IFD number.
#[inline]
pub fn index(self) -> u16 {
self.0
}
}
impl fmt::Display for In {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
0 => f.pad("primary"),
1 => f.pad("thumbnail"),
n => f.pad(&format!("IFD{}", n)),
}
}
}
/// Parse the Exif attributes in the TIFF format. /// Parse the Exif attributes in the TIFF format.
/// ///
/// Returns a Vec of Exif fields and a bool. /// Returns a Vec of Exif fields and a bool.
@ -77,13 +113,13 @@ fn parse_exif_sub<E>(data: &[u8])
} }
let ifd_offset = E::loadu32(data, 4) as usize; let ifd_offset = E::loadu32(data, 4) as usize;
let mut fields = Vec::new(); let mut fields = Vec::new();
parse_ifd::<E>(&mut fields, data, ifd_offset, Context::Tiff, false)?; parse_ifd::<E>(&mut fields, data, ifd_offset, Context::Tiff, 0)?;
Ok(fields) Ok(fields)
} }
// Parse IFD [EXIF23 4.6.2]. // Parse IFD [EXIF23 4.6.2].
fn parse_ifd<'a, E>(fields: &mut Vec<Field<'a>>, data: &'a [u8], fn parse_ifd<'a, E>(fields: &mut Vec<Field<'a>>, data: &'a [u8],
offset: usize, ctx: Context, thumbnail: bool) offset: usize, ctx: Context, ifd_num: u16)
-> Result<(), Error> where E: Endian { -> Result<(), Error> where E: Endian {
// Count (the number of the entries). // Count (the number of the entries).
if data.len() < offset || data.len() - offset < 2 { if data.len() < offset || data.len() - offset < 2 {
@ -121,13 +157,13 @@ fn parse_ifd<'a, E>(fields: &mut Vec<Field<'a>>, data: &'a [u8],
let tag = Tag(ctx, tag); let tag = Tag(ctx, tag);
match tag { match tag {
Tag::ExifIFDPointer => parse_child_ifd::<E>( Tag::ExifIFDPointer => parse_child_ifd::<E>(
fields, data, &val, Context::Exif, thumbnail)?, fields, data, &val, Context::Exif, ifd_num)?,
Tag::GPSInfoIFDPointer => parse_child_ifd::<E>( Tag::GPSInfoIFDPointer => parse_child_ifd::<E>(
fields, data, &val, Context::Gps, thumbnail)?, fields, data, &val, Context::Gps, ifd_num)?,
Tag::InteropIFDPointer => parse_child_ifd::<E>( Tag::InteropIFDPointer => parse_child_ifd::<E>(
fields, data, &val, Context::Interop, thumbnail)?, fields, data, &val, Context::Interop, ifd_num)?,
_ => fields.push(Field { tag: tag, thumbnail: thumbnail, _ => fields.push(Field {
value: val }), tag: tag, ifd_num: In(ifd_num), value: val }),
} }
} }
@ -137,24 +173,24 @@ fn parse_ifd<'a, E>(fields: &mut Vec<Field<'a>>, data: &'a [u8],
} }
let next_ifd_offset = E::loadu32(data, offset + 2 + count * 12) as usize; let next_ifd_offset = E::loadu32(data, offset + 2 + count * 12) as usize;
// Ignore IFDs after IFD1 (thumbnail) for now. // Ignore IFDs after IFD1 (thumbnail) for now.
if next_ifd_offset == 0 || thumbnail { if next_ifd_offset == 0 || ifd_num > 0 {
return Ok(()); return Ok(());
} }
if ctx != Context::Tiff { if ctx != Context::Tiff {
return Err(Error::InvalidFormat("Unexpected next IFD")); return Err(Error::InvalidFormat("Unexpected next IFD"));
} }
parse_ifd::<E>(fields, data, next_ifd_offset, Context::Tiff, true) parse_ifd::<E>(fields, data, next_ifd_offset, Context::Tiff, 1)
} }
fn parse_child_ifd<'a, E>(fields: &mut Vec<Field<'a>>, data: &'a [u8], fn parse_child_ifd<'a, E>(fields: &mut Vec<Field<'a>>, data: &'a [u8],
pointer: &Value, ctx: Context, thumbnail: bool) pointer: &Value, ctx: Context, ifd_num: u16)
-> Result<(), Error> where E: Endian { -> Result<(), Error> where E: Endian {
// A pointer field has type == LONG and count == 1, so the // A pointer field has type == LONG and count == 1, so the
// value (IFD offset) must be embedded in the "value offset" // value (IFD offset) must be embedded in the "value offset"
// element of the field. // element of the field.
let ofs = pointer.get_uint(0).ok_or( let ofs = pointer.get_uint(0).ok_or(
Error::InvalidFormat("Invalid pointer"))? as usize; Error::InvalidFormat("Invalid pointer"))? as usize;
parse_ifd::<E>(fields, data, ofs, ctx, thumbnail) parse_ifd::<E>(fields, data, ofs, ctx, ifd_num)
} }
pub fn is_tiff(buf: &[u8]) -> bool { pub fn is_tiff(buf: &[u8]) -> bool {
@ -278,16 +314,16 @@ impl<'a> Field<'a> {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use exif::{Field, Tag, Value}; /// use exif::{Field, In, Tag, Value};
/// ///
/// let xres = Field { /// let xres = Field {
/// tag: Tag::XResolution, /// tag: Tag::XResolution,
/// thumbnail: false, /// ifd_num: In::PRIMARY,
/// value: Value::Rational(vec![(72, 1).into()]), /// value: Value::Rational(vec![(72, 1).into()]),
/// }; /// };
/// let cm = Field { /// let cm = Field {
/// tag: Tag::ResolutionUnit, /// tag: Tag::ResolutionUnit,
/// thumbnail: false, /// ifd_num: In::PRIMARY,
/// value: Value::Short(vec![3]), /// value: Value::Short(vec![3]),
/// }; /// };
/// assert_eq!(xres.display_value().to_string(), "72"); /// assert_eq!(xres.display_value().to_string(), "72");
@ -303,7 +339,7 @@ impl<'a> Field<'a> {
/// ///
/// let flen = Field { /// let flen = Field {
/// tag: Tag::FocalLengthIn35mmFilm, /// tag: Tag::FocalLengthIn35mmFilm,
/// thumbnail: false, /// ifd_num: In::PRIMARY,
/// value: Value::Short(vec![24]), /// value: Value::Short(vec![24]),
/// }; /// };
/// // The unit of the focal length is always mm, so the argument /// // The unit of the focal length is always mm, so the argument
@ -315,7 +351,7 @@ impl<'a> Field<'a> {
pub fn display_value(&self) -> DisplayValue { pub fn display_value(&self) -> DisplayValue {
DisplayValue { DisplayValue {
tag: self.tag, tag: self.tag,
thumbnail: self.thumbnail, ifd_num: self.ifd_num,
value_display: self.value.display_as(self.tag), value_display: self.value.display_as(self.tag),
} }
} }
@ -324,7 +360,7 @@ impl<'a> Field<'a> {
/// Helper struct for printing a value in a tag-specific format. /// Helper struct for printing a value in a tag-specific format.
pub struct DisplayValue<'a> { pub struct DisplayValue<'a> {
tag: Tag, tag: Tag,
thumbnail: bool, ifd_num: In,
value_display: value::Display<'a>, value_display: value::Display<'a>,
} }
@ -333,7 +369,7 @@ impl<'a> DisplayValue<'a> {
pub fn with_unit<'b, T>(&'b self, unit_provider: T) pub fn with_unit<'b, T>(&'b self, unit_provider: T)
-> DisplayValueUnit<T> where T: ProvideUnit<'b> { -> DisplayValueUnit<T> where T: ProvideUnit<'b> {
DisplayValueUnit { DisplayValueUnit {
thumbnail: self.thumbnail, ifd_num: self.ifd_num,
value_display: self.value_display, value_display: self.value_display,
unit: self.tag.unit(), unit: self.tag.unit(),
unit_provider: unit_provider, unit_provider: unit_provider,
@ -350,7 +386,7 @@ impl<'a> fmt::Display for DisplayValue<'a> {
/// Helper struct for printing a value with its unit. /// Helper struct for printing a value with its unit.
pub struct DisplayValueUnit<'a, T> where T: ProvideUnit<'a> { pub struct DisplayValueUnit<'a, T> where T: ProvideUnit<'a> {
thumbnail: bool, ifd_num: In,
value_display: value::Display<'a>, value_display: value::Display<'a>,
unit: Option<&'static [UnitPiece]>, unit: Option<&'static [UnitPiece]>,
unit_provider: T, unit_provider: T,
@ -366,7 +402,7 @@ impl<'a, T> fmt::Display for DisplayValueUnit<'a, T> where T: ProvideUnit<'a> {
UnitPiece::Str(s) => f.write_str(s), UnitPiece::Str(s) => f.write_str(s),
UnitPiece::Tag(tag) => UnitPiece::Tag(tag) =>
if let Some(x) = self.unit_provider.get_field( if let Some(x) = self.unit_provider.get_field(
tag, self.thumbnail) { tag, self.ifd_num) {
x.value.display_as(tag).fmt(f) x.value.display_as(tag).fmt(f)
} else if let Some(x) = tag.default_value() { } else if let Some(x) = tag.default_value() {
x.display_as(tag).fmt(f) x.display_as(tag).fmt(f)
@ -383,18 +419,18 @@ impl<'a, T> fmt::Display for DisplayValueUnit<'a, T> where T: ProvideUnit<'a> {
} }
pub trait ProvideUnit<'a>: Copy { pub trait ProvideUnit<'a>: Copy {
fn get_field(self, tag: Tag, thumbnail: bool) -> Option<&'a Field<'a>>; fn get_field(self, tag: Tag, ifd_num: In) -> Option<&'a Field<'a>>;
} }
impl<'a> ProvideUnit<'a> for () { impl<'a> ProvideUnit<'a> for () {
fn get_field(self, _tag: Tag, _thumbnail: bool) -> Option<&'a Field<'a>> { fn get_field(self, _tag: Tag, _ifd_num: In) -> Option<&'a Field<'a>> {
None None
} }
} }
impl<'a> ProvideUnit<'a> for &'a Field<'a> { impl<'a> ProvideUnit<'a> for &'a Field<'a> {
fn get_field(self, tag: Tag, thumbnail: bool) -> Option<&'a Field<'a>> { fn get_field(self, tag: Tag, ifd_num: In) -> Option<&'a Field<'a>> {
Some(self).filter(|x| x.tag == tag && x.thumbnail == thumbnail) Some(self).filter(|x| x.tag == tag && x.ifd_num == ifd_num)
} }
} }
@ -402,6 +438,23 @@ impl<'a> ProvideUnit<'a> for &'a Field<'a> {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn in_convert() {
assert_eq!(In::PRIMARY.index(), 0);
assert_eq!(In::THUMBNAIL.index(), 1);
assert_eq!(In(2).index(), 2);
assert_eq!(In(65535).index(), 65535);
assert_eq!(In::PRIMARY, In(0));
}
#[test]
fn in_display() {
assert_eq!(format!("{:10}", In::PRIMARY), "primary ");
assert_eq!(format!("{:>10}", In::THUMBNAIL), " thumbnail");
assert_eq!(format!("{:10}", In(2)), "IFD2 ");
assert_eq!(format!("{:^10}", In(65535)), " IFD65535 ");
}
// Before the error is returned, the IFD is parsed twice as the // Before the error is returned, the IFD is parsed twice as the
// 0th and 1st IFDs. // 0th and 1st IFDs.
#[test] #[test]
@ -464,18 +517,18 @@ mod tests {
fn display_value_with_unit() { fn display_value_with_unit() {
let cm = Field { let cm = Field {
tag: Tag::ResolutionUnit, tag: Tag::ResolutionUnit,
thumbnail: false, ifd_num: In::PRIMARY,
value: Value::Short(vec![3]), value: Value::Short(vec![3]),
}; };
let cm_tn = Field { let cm_tn = Field {
tag: Tag::ResolutionUnit, tag: Tag::ResolutionUnit,
thumbnail: true, ifd_num: In::THUMBNAIL,
value: Value::Short(vec![3]), value: Value::Short(vec![3]),
}; };
// No unit. // No unit.
let exifver = Field { let exifver = Field {
tag: Tag::ExifVersion, tag: Tag::ExifVersion,
thumbnail: false, ifd_num: In::PRIMARY,
value: Value::Undefined(b"0231", 0), value: Value::Undefined(b"0231", 0),
}; };
assert_eq!(exifver.display_value().to_string(), assert_eq!(exifver.display_value().to_string(),
@ -487,7 +540,7 @@ mod tests {
// Fixed string. // Fixed string.
let width = Field { let width = Field {
tag: Tag::ImageWidth, tag: Tag::ImageWidth,
thumbnail: false, ifd_num: In::PRIMARY,
value: Value::Short(vec![257]), value: Value::Short(vec![257]),
}; };
assert_eq!(width.display_value().to_string(), assert_eq!(width.display_value().to_string(),
@ -500,7 +553,7 @@ mod tests {
// Unit tag is missing but the default is specified. // Unit tag is missing but the default is specified.
let xres = Field { let xres = Field {
tag: Tag::XResolution, tag: Tag::XResolution,
thumbnail: false, ifd_num: In::PRIMARY,
value: Value::Rational(vec![(300, 1).into()]), value: Value::Rational(vec![(300, 1).into()]),
}; };
assert_eq!(xres.display_value().to_string(), assert_eq!(xres.display_value().to_string(),
@ -514,7 +567,7 @@ mod tests {
// Unit tag is missing and the default is not specified. // Unit tag is missing and the default is not specified.
let gpslat = Field { let gpslat = Field {
tag: Tag::GPSLatitude, tag: Tag::GPSLatitude,
thumbnail: false, ifd_num: In::PRIMARY,
value: Value::Rational(vec![ value: Value::Rational(vec![
(10, 1).into(), (0, 1).into(), (1, 10).into()]), (10, 1).into(), (0, 1).into(), (1, 10).into()]),
}; };

View File

@ -30,7 +30,7 @@ use std::io::{Seek, SeekFrom, Write};
use crate::endian::{Endian, BigEndian, LittleEndian}; use crate::endian::{Endian, BigEndian, LittleEndian};
use crate::error::Error; use crate::error::Error;
use crate::tag::{Context, Tag}; use crate::tag::{Context, Tag};
use crate::tiff::{Field, TIFF_BE_SIG, TIFF_LE_SIG}; use crate::tiff::{Field, In, TIFF_BE_SIG, TIFF_LE_SIG};
use crate::value::Value; use crate::value::Value;
/// The `Writer` struct is used to encode and write Exif data. /// The `Writer` struct is used to encode and write Exif data.
@ -38,11 +38,11 @@ use crate::value::Value;
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use exif::{Field, Value, Tag}; /// use exif::{Field, In, Tag, Value};
/// use exif::experimental::Writer; /// use exif::experimental::Writer;
/// let image_desc = Field { /// let image_desc = Field {
/// tag: Tag::ImageDescription, /// tag: Tag::ImageDescription,
/// thumbnail: false, /// ifd_num: In::PRIMARY,
/// value: Value::Ascii(vec![b"Sample"]), /// value: Value::Ascii(vec![b"Sample"]),
/// }; /// };
/// let mut writer = Writer::new(); /// let mut writer = Writer::new();
@ -129,22 +129,23 @@ impl<'a> Writer<'a> {
Field { tag: Tag::JPEGInterchangeFormat, .. } | Field { tag: Tag::JPEGInterchangeFormat, .. } |
Field { tag: Tag::JPEGInterchangeFormatLength, .. } => {}, Field { tag: Tag::JPEGInterchangeFormatLength, .. } => {},
// Other normal tags. // Other normal tags.
Field { tag: Tag(Context::Tiff, _), thumbnail: false, .. } => Field { tag: Tag(Context::Tiff, _), ifd_num: In::PRIMARY, .. } =>
self.tiff_fields.push(field), self.tiff_fields.push(field),
Field { tag: Tag(Context::Exif, _), thumbnail: false, .. } => Field { tag: Tag(Context::Exif, _), ifd_num: In::PRIMARY, .. } =>
self.exif_fields.push(field), self.exif_fields.push(field),
Field { tag: Tag(Context::Gps, _), thumbnail: false, .. } => Field { tag: Tag(Context::Gps, _), ifd_num: In::PRIMARY, .. } =>
self.gps_fields.push(field), self.gps_fields.push(field),
Field { tag: Tag(Context::Interop, _), thumbnail: false, .. } => Field { tag: Tag(Context::Interop, _), ifd_num: In::PRIMARY, .. } =>
self.interop_fields.push(field), self.interop_fields.push(field),
Field { tag: Tag(Context::Tiff, _), thumbnail: true, .. } => Field { tag: Tag(Context::Tiff, _), ifd_num: In::THUMBNAIL, .. } =>
self.tn_tiff_fields.push(field), self.tn_tiff_fields.push(field),
Field { tag: Tag(Context::Exif, _), thumbnail: true, .. } => Field { tag: Tag(Context::Exif, _), ifd_num: In::THUMBNAIL, .. } =>
self.tn_exif_fields.push(field), self.tn_exif_fields.push(field),
Field { tag: Tag(Context::Gps, _), thumbnail: true, .. } => Field { tag: Tag(Context::Gps, _), ifd_num: In::THUMBNAIL, .. } =>
self.tn_gps_fields.push(field), self.tn_gps_fields.push(field),
Field { tag: Tag(Context::Interop, _), thumbnail: true, .. } => Field { tag: Tag(Context::Interop, _), ifd_num: In::THUMBNAIL, .. } =>
self.tn_interop_fields.push(field), self.tn_interop_fields.push(field),
_ => unimplemented!(),
} }
} }
@ -202,7 +203,7 @@ impl<'a> Writer<'a> {
jpeg: None, jpeg: None,
}; };
let next_ifd_offset_offset = let next_ifd_offset_offset =
synthesize_fields(w, ws, false, little_endian)?; synthesize_fields(w, ws, In::PRIMARY, little_endian)?;
// Do not output the thumbnail IFD if there are no data in it. // Do not output the thumbnail IFD if there are no data in it.
let thumbnail_absent = let thumbnail_absent =
@ -240,7 +241,7 @@ impl<'a> Writer<'a> {
tiles: None, tiles: None,
jpeg: self.tn_jpeg, jpeg: self.tn_jpeg,
}; };
synthesize_fields(w, ws, true, little_endian)?; synthesize_fields(w, ws, In::THUMBNAIL, little_endian)?;
w.flush()?; w.flush()?;
Ok(()) Ok(())
@ -249,7 +250,7 @@ impl<'a> Writer<'a> {
// Synthesizes special fields, writes an image, and returns the offset // Synthesizes special fields, writes an image, and returns the offset
// of the next IFD offset. // of the next IFD offset.
fn synthesize_fields<W>(w: &mut W, ws: WriterState, thumbnail: bool, fn synthesize_fields<W>(w: &mut W, ws: WriterState, ifd_num: In,
little_endian: bool) little_endian: bool)
-> Result<u32, Error> where W: Write + Seek { -> Result<u32, Error> where W: Write + Seek {
let exif_in_tiff; let exif_in_tiff;
@ -267,13 +268,13 @@ fn synthesize_fields<W>(w: &mut W, ws: WriterState, thumbnail: bool,
if let Some(strips) = ws.strips { if let Some(strips) = ws.strips {
strip_offsets = Field { strip_offsets = Field {
tag: Tag::StripOffsets, tag: Tag::StripOffsets,
thumbnail: thumbnail, ifd_num: ifd_num,
value: Value::Long(vec![0; strips.len()]), value: Value::Long(vec![0; strips.len()]),
}; };
ws.tiff_fields.push(&strip_offsets); ws.tiff_fields.push(&strip_offsets);
strip_byte_counts = Field { strip_byte_counts = Field {
tag: Tag::StripByteCounts, tag: Tag::StripByteCounts,
thumbnail: thumbnail, ifd_num: ifd_num,
value: Value::Long( value: Value::Long(
strips.iter().map(|s| s.len() as u32).collect()), strips.iter().map(|s| s.len() as u32).collect()),
}; };
@ -282,13 +283,13 @@ fn synthesize_fields<W>(w: &mut W, ws: WriterState, thumbnail: bool,
if let Some(tiles) = ws.tiles { if let Some(tiles) = ws.tiles {
tile_offsets = Field { tile_offsets = Field {
tag: Tag::TileOffsets, tag: Tag::TileOffsets,
thumbnail: thumbnail, ifd_num: ifd_num,
value: Value::Long(vec![0; tiles.len()]), value: Value::Long(vec![0; tiles.len()]),
}; };
ws.tiff_fields.push(&tile_offsets); ws.tiff_fields.push(&tile_offsets);
tile_byte_counts = Field { tile_byte_counts = Field {
tag: Tag::TileByteCounts, tag: Tag::TileByteCounts,
thumbnail: thumbnail, ifd_num: ifd_num,
value: Value::Long( value: Value::Long(
tiles.iter().map(|s| s.len() as u32).collect()), tiles.iter().map(|s| s.len() as u32).collect()),
}; };
@ -297,13 +298,13 @@ fn synthesize_fields<W>(w: &mut W, ws: WriterState, thumbnail: bool,
if let Some(jpeg) = ws.jpeg { if let Some(jpeg) = ws.jpeg {
jpeg_offset = Field { jpeg_offset = Field {
tag: Tag::JPEGInterchangeFormat, tag: Tag::JPEGInterchangeFormat,
thumbnail: thumbnail, ifd_num: ifd_num,
value: Value::Long(vec![0]), value: Value::Long(vec![0]),
}; };
ws.tiff_fields.push(&jpeg_offset); ws.tiff_fields.push(&jpeg_offset);
jpeg_length = Field { jpeg_length = Field {
tag: Tag::JPEGInterchangeFormatLength, tag: Tag::JPEGInterchangeFormatLength,
thumbnail: thumbnail, ifd_num: ifd_num,
value: Value::Long(vec![jpeg.len() as u32]), value: Value::Long(vec![jpeg.len() as u32]),
}; };
ws.tiff_fields.push(&jpeg_length); ws.tiff_fields.push(&jpeg_length);
@ -322,7 +323,7 @@ fn synthesize_fields<W>(w: &mut W, ws: WriterState, thumbnail: bool,
ws.exif_ifd_offset = reserve_ifd(w, exif_fields_len)?; ws.exif_ifd_offset = reserve_ifd(w, exif_fields_len)?;
exif_in_tiff = Field { exif_in_tiff = Field {
tag: Tag::ExifIFDPointer, tag: Tag::ExifIFDPointer,
thumbnail: thumbnail, ifd_num: ifd_num,
value: Value::Long(vec![ws.exif_ifd_offset]), value: Value::Long(vec![ws.exif_ifd_offset]),
}; };
ws.tiff_fields.push(&exif_in_tiff); ws.tiff_fields.push(&exif_in_tiff);
@ -331,7 +332,7 @@ fn synthesize_fields<W>(w: &mut W, ws: WriterState, thumbnail: bool,
ws.gps_ifd_offset = reserve_ifd(w, gps_fields_len)?; ws.gps_ifd_offset = reserve_ifd(w, gps_fields_len)?;
gps_in_tiff = Field { gps_in_tiff = Field {
tag: Tag::GPSInfoIFDPointer, tag: Tag::GPSInfoIFDPointer,
thumbnail: thumbnail, ifd_num: ifd_num,
value: Value::Long(vec![ws.gps_ifd_offset]), value: Value::Long(vec![ws.gps_ifd_offset]),
}; };
ws.tiff_fields.push(&gps_in_tiff); ws.tiff_fields.push(&gps_in_tiff);
@ -340,7 +341,7 @@ fn synthesize_fields<W>(w: &mut W, ws: WriterState, thumbnail: bool,
ws.interop_ifd_offset = reserve_ifd(w, interop_fields_len)?; ws.interop_ifd_offset = reserve_ifd(w, interop_fields_len)?;
interop_in_exif = Field { interop_in_exif = Field {
tag: Tag::InteropIFDPointer, tag: Tag::InteropIFDPointer,
thumbnail: thumbnail, ifd_num: ifd_num,
value: Value::Long(vec![ws.interop_ifd_offset]), value: Value::Long(vec![ws.interop_ifd_offset]),
}; };
ws.exif_fields.push(&interop_in_exif); ws.exif_fields.push(&interop_in_exif);
@ -615,7 +616,7 @@ mod tests {
fn primary() { fn primary() {
let image_desc = Field { let image_desc = Field {
tag: Tag::ImageDescription, tag: Tag::ImageDescription,
thumbnail: false, ifd_num: In::PRIMARY,
value: Value::Ascii(vec![b"Sample"]), value: Value::Ascii(vec![b"Sample"]),
}; };
let mut writer = Writer::new(); let mut writer = Writer::new();
@ -634,7 +635,7 @@ mod tests {
fn primary_exif_only() { fn primary_exif_only() {
let exif_ver = Field { let exif_ver = Field {
tag: Tag::ExifVersion, tag: Tag::ExifVersion,
thumbnail: false, ifd_num: In::PRIMARY,
value: Value::Undefined(b"0231", 0), value: Value::Undefined(b"0231", 0),
}; };
let mut writer = Writer::new(); let mut writer = Writer::new();
@ -707,22 +708,22 @@ mod tests {
fn primary_and_thumbnail() { fn primary_and_thumbnail() {
let image_desc = Field { let image_desc = Field {
tag: Tag::ImageDescription, tag: Tag::ImageDescription,
thumbnail: false, ifd_num: In::PRIMARY,
value: Value::Ascii(vec![b"Sample"]), value: Value::Ascii(vec![b"Sample"]),
}; };
let exif_ver = Field { let exif_ver = Field {
tag: Tag::ExifVersion, tag: Tag::ExifVersion,
thumbnail: false, ifd_num: In::PRIMARY,
value: Value::Undefined(b"0231", 0), value: Value::Undefined(b"0231", 0),
}; };
let gps_ver = Field { let gps_ver = Field {
tag: Tag::GPSVersionID, tag: Tag::GPSVersionID,
thumbnail: false, ifd_num: In::PRIMARY,
value: Value::Byte(vec![2, 3, 0, 0]), value: Value::Byte(vec![2, 3, 0, 0]),
}; };
let interop_index = Field { let interop_index = Field {
tag: Tag::InteroperabilityIndex, tag: Tag::InteroperabilityIndex,
thumbnail: false, ifd_num: In::PRIMARY,
value: Value::Ascii(vec![b"ABC"]), value: Value::Ascii(vec![b"ABC"]),
}; };
let jpeg = b"JPEG"; let jpeg = b"JPEG";
@ -759,7 +760,7 @@ mod tests {
fn write_twice() { fn write_twice() {
let image_desc = Field { let image_desc = Field {
tag: Tag::ImageDescription, tag: Tag::ImageDescription,
thumbnail: false, ifd_num: In::PRIMARY,
value: Value::Ascii(vec![b"Sample"]), value: Value::Ascii(vec![b"Sample"]),
}; };
let mut writer = Writer::new(); let mut writer = Writer::new();

Binary file not shown.

View File

@ -37,7 +37,7 @@ use std::path::Path;
#[cfg(not(test))] #[cfg(not(test))]
use exif::Error; use exif::Error;
use exif::{Reader, Value, Tag}; use exif::{In, Reader, Value, Tag};
use exif::experimental::Writer; use exif::experimental::Writer;
#[test] #[test]
@ -71,10 +71,10 @@ fn rwr_compare<P>(path: P) where P: AsRef<Path> {
return; return;
}, },
}; };
let strips = get_strips(&reader1, false); let strips = get_strips(&reader1, In::PRIMARY);
let tn_strips = get_strips(&reader1, true); let tn_strips = get_strips(&reader1, In::THUMBNAIL);
let tiles = get_tiles(&reader1, false); let tiles = get_tiles(&reader1, In::PRIMARY);
let tn_jpeg = get_jpeg(&reader1, true); let tn_jpeg = get_jpeg(&reader1, In::THUMBNAIL);
// Write. // Write.
let mut writer = Writer::new(); let mut writer = Writer::new();
@ -120,7 +120,7 @@ fn rwr_compare<P>(path: P) where P: AsRef<Path> {
assert_eq!(reader1.fields().len(), reader2.fields().len()); assert_eq!(reader1.fields().len(), reader2.fields().len());
for (f1, f2) in fields1.iter().zip(fields2.iter()) { for (f1, f2) in fields1.iter().zip(fields2.iter()) {
assert_eq!(f1.tag, f2.tag); assert_eq!(f1.tag, f2.tag);
assert_eq!(f1.thumbnail, f2.thumbnail); assert_eq!(f1.ifd_num, f2.ifd_num);
match f1.tag { match f1.tag {
Tag::StripOffsets | Tag::TileOffsets | Tag::StripOffsets | Tag::TileOffsets |
Tag::JPEGInterchangeFormat => continue, Tag::JPEGInterchangeFormat => continue,
@ -128,10 +128,10 @@ fn rwr_compare<P>(path: P) where P: AsRef<Path> {
} }
compare_field_value(&f1.value, &f2.value); compare_field_value(&f1.value, &f2.value);
} }
assert_eq!(get_strips(&reader2, false), strips); assert_eq!(get_strips(&reader2, In::PRIMARY), strips);
assert_eq!(get_strips(&reader2, true), tn_strips); assert_eq!(get_strips(&reader2, In::THUMBNAIL), tn_strips);
assert_eq!(get_tiles(&reader2, false), tiles); assert_eq!(get_tiles(&reader2, In::PRIMARY), tiles);
assert_eq!(get_jpeg(&reader2, true), tn_jpeg); assert_eq!(get_jpeg(&reader2, In::THUMBNAIL), tn_jpeg);
} }
// Compare field values. // Compare field values.
@ -176,10 +176,10 @@ fn compare_field_value(value1: &Value, value2: &Value) {
} }
} }
fn get_strips(reader: &Reader, thumbnail: bool) -> Option<Vec<&[u8]>> { fn get_strips(reader: &Reader, ifd_num: In) -> Option<Vec<&[u8]>> {
let offsets = reader.get_field(Tag::StripOffsets, thumbnail) let offsets = reader.get_field(Tag::StripOffsets, ifd_num)
.and_then(|f| f.value.iter_uint()); .and_then(|f| f.value.iter_uint());
let counts = reader.get_field(Tag::StripByteCounts, thumbnail) let counts = reader.get_field(Tag::StripByteCounts, ifd_num)
.and_then(|f| f.value.iter_uint()); .and_then(|f| f.value.iter_uint());
let (offsets, counts) = match (offsets, counts) { let (offsets, counts) = match (offsets, counts) {
(Some(offsets), Some(counts)) => (offsets, counts), (Some(offsets), Some(counts)) => (offsets, counts),
@ -193,10 +193,10 @@ fn get_strips(reader: &Reader, thumbnail: bool) -> Option<Vec<&[u8]>> {
Some(strips) Some(strips)
} }
fn get_tiles(reader: &Reader, thumbnail: bool) -> Option<Vec<&[u8]>> { fn get_tiles(reader: &Reader, ifd_num: In) -> Option<Vec<&[u8]>> {
let offsets = reader.get_field(Tag::TileOffsets, thumbnail) let offsets = reader.get_field(Tag::TileOffsets, ifd_num)
.and_then(|f| f.value.iter_uint()); .and_then(|f| f.value.iter_uint());
let counts = reader.get_field(Tag::TileByteCounts, thumbnail) let counts = reader.get_field(Tag::TileByteCounts, ifd_num)
.and_then(|f| f.value.iter_uint()); .and_then(|f| f.value.iter_uint());
let (offsets, counts) = match (offsets, counts) { let (offsets, counts) = match (offsets, counts) {
(Some(offsets), Some(counts)) => (offsets, counts), (Some(offsets), Some(counts)) => (offsets, counts),
@ -210,10 +210,10 @@ fn get_tiles(reader: &Reader, thumbnail: bool) -> Option<Vec<&[u8]>> {
Some(strips) Some(strips)
} }
fn get_jpeg(reader: &Reader, thumbnail: bool) -> Option<&[u8]> { fn get_jpeg(reader: &Reader, ifd_num: In) -> Option<&[u8]> {
let offset = reader.get_field(Tag::JPEGInterchangeFormat, thumbnail) let offset = reader.get_field(Tag::JPEGInterchangeFormat, ifd_num)
.and_then(|f| f.value.get_uint(0)); .and_then(|f| f.value.get_uint(0));
let len = reader.get_field(Tag::JPEGInterchangeFormatLength, thumbnail) let len = reader.get_field(Tag::JPEGInterchangeFormatLength, ifd_num)
.and_then(|f| f.value.get_uint(0)); .and_then(|f| f.value.get_uint(0));
let (offset, len) = match (offset, len) { let (offset, len) = match (offset, len) {
(Some(offset), Some(len)) => (offset as usize, len as usize), (Some(offset), Some(len)) => (offset as usize, len as usize),