Add struct In (IFD number) to indicate primary/thumbnail images.
This commit is contained in:
parent
6c3ddddc96
commit
b9575efd0b
|
@ -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<_>>());
|
||||||
|
|
|
@ -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]) {
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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]");
|
||||||
}
|
}
|
||||||
|
|
117
src/tiff.rs
117
src/tiff.rs
|
@ -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()]),
|
||||||
};
|
};
|
||||||
|
|
|
@ -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();
|
||||||
|
|
BIN
tests/exif.tif
BIN
tests/exif.tif
Binary file not shown.
|
@ -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),
|
||||||
|
|
Loading…
Reference in New Issue