Support displaying a value with its unit.

- Field::display_value and DisplayValue::with_unit were introduced.
- The string "centimeter" was changed to "cm" for consistency
  with "m", "mm", and "km".
- F-number was changed to be prefixed by "f/" only when with_unit() is
  called, though it is actually not a unit.
This commit is contained in:
KAMADA Ken'ichi 2019-04-07 21:11:14 +09:00
parent eb4723a8a9
commit 2ffa449d9d
8 changed files with 448 additions and 18 deletions

View File

@ -47,7 +47,8 @@ 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/" }; let thumb = if f.thumbnail { "1/" } else { "0/" };
println!(" {}{}: {}", thumb, f.tag, f.value.display_as(f.tag)); println!(" {}{}: {}",
thumb, f.tag, 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

@ -35,8 +35,9 @@ fn main() {
let file = File::open("tests/exif.jpg").unwrap(); let file = File::open("tests/exif.jpg").unwrap();
let reader = Reader::new(&mut BufReader::new(&file)).unwrap(); let reader = Reader::new(&mut BufReader::new(&file)).unwrap();
// To obtain a string representation, `Value::display_as` can be used // To obtain a string representation, `Value::display_as`
// for any tag. // or `Field::display_value` can be used. To display a value with its
// unit, call `with_unit` on the return value of `Field::display_value`.
let tag_list = [Tag::ExifVersion, let tag_list = [Tag::ExifVersion,
Tag::PixelXDimension, Tag::PixelXDimension,
Tag::XResolution, Tag::XResolution,
@ -44,7 +45,8 @@ fn main() {
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, false) {
println!("{}: {}", field.tag, field.value.display_as(field.tag)); println!("{}: {}",
field.tag, field.display_value().with_unit(&reader));
} }
} }

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.value.display_as(f.tag)); //! f.tag, f.thumbnail, f.display_value().with_unit(&reader));
//! } //! }
//! } //! }
//! ``` //! ```

View File

@ -33,10 +33,20 @@ 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; use crate::tiff::{Field, 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
/// ```
/// let file = std::fs::File::open("tests/exif.jpg").unwrap();
/// let reader = exif::Reader::new(
/// &mut std::io::BufReader::new(&file)).unwrap();
/// let xres = reader.get_field(exif::Tag::XResolution, false).unwrap();
/// assert_eq!(format!("{}", xres.display_value().with_unit(&reader)),
/// "72 pixels per inch");
/// ```
// //
// The struct Reader is self-contained, which means that it does not // The struct Reader is self-contained, which means that it does not
// have any external reference. The `fields` field actually refers to // have any external reference. The `fields` field actually refers to
@ -126,6 +136,12 @@ impl Reader {
} }
} }
impl<'a> ProvideUnit<'a> for &'a Reader {
fn get_field(self, tag: Tag, thumbnail: bool) -> Option<&'a Field<'a>> {
self.get_field(tag, thumbnail)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::fs::File; use std::fs::File;
@ -188,4 +204,30 @@ mod tests {
assert_pat!(reader.get_field(Tag::ExifVersion, false).unwrap().value, assert_pat!(reader.get_field(Tag::ExifVersion, false).unwrap().value,
Value::Undefined(b"0230", _)); Value::Undefined(b"0230", _));
} }
#[test]
fn display_value_with_unit() {
let file = File::open("tests/unit.tif").unwrap();
let reader = Reader::new(&mut BufReader::new(&file)).unwrap();
// No unit.
let exifver = reader.get_field(Tag::ExifVersion, false).unwrap();
assert_eq!(format!("{}", exifver.display_value().with_unit(&reader)),
"2.31");
// Fixed string.
let width = reader.get_field(Tag::ImageWidth, false).unwrap();
assert_eq!(format!("{}", width.display_value().with_unit(&reader)),
"15 pixels");
// Unit tag (with a non-default value).
let gpsalt = reader.get_field(Tag::GPSAltitude, false).unwrap();
assert_eq!(format!("{}", gpsalt.display_value().with_unit(&reader)),
"0.5 meters below sea level");
// Unit tag is missing but the default is specified.
let xres = reader.get_field(Tag::XResolution, false).unwrap();
assert_eq!(format!("{}", xres.display_value().with_unit(&reader)),
"72 pixels per inch");
// Unit tag is missing and the default is not specified.
let gpslat = reader.get_field(Tag::GPSLatitude, false).unwrap();
assert_eq!(format!("{}", gpslat.display_value().with_unit(&reader)),
"10 deg 0 min 0 sec [GPSLatitudeRef missing]");
}
} }

View File

@ -92,6 +92,11 @@ impl Tag {
pub fn default_value(&self) -> Option<Value> { pub fn default_value(&self) -> Option<Value> {
get_tag_info(*self).and_then(|ti| (&ti.default).into()) get_tag_info(*self).and_then(|ti| (&ti.default).into())
} }
#[inline]
pub(crate) fn unit(self) -> Option<&'static [UnitPiece]> {
get_tag_info(self).and_then(|ti| ti.unit)
}
} }
impl fmt::Display for Tag { impl fmt::Display for Tag {
@ -116,12 +121,40 @@ pub enum Context {
Interop, // 0th/1st IFD -- Exif IFD -- Interoperability IFD Interop, // 0th/1st IFD -- Exif IFD -- Interoperability IFD
} }
#[derive(Debug)]
pub enum UnitPiece {
Value,
Str(&'static str),
Tag(Tag),
}
macro_rules! unit {
() => ( None );
( $str:literal ) => ( unit![V, concat!(" ", $str)] );
( Tag::$tag:ident ) => ( unit![V, " ", Tag::$tag] );
( $($tokens:tt)* ) => ( Some(unit_expand!( ; $($tokens)* , )) );
}
macro_rules! unit_expand {
( $($built:expr),* ; ) => ( &[$($built),*] );
( $($built:expr),* ; , ) => ( &[$($built),*] );
( $($built:expr),* ; V, $($rest:tt)* ) => (
unit_expand!($($built,)* UnitPiece::Value ; $($rest)*) );
( $($built:expr),* ; $str:literal, $($rest:tt)* ) => (
unit_expand!($($built,)* UnitPiece::Str($str) ; $($rest)*) );
( $($built:expr),* ; concat!($($strs:literal),*), $($rest:tt)* ) => (
unit_expand!($($built,)* UnitPiece::Str(concat!($($strs),*)) ; $($rest)*) );
( $($built:expr),* ; Tag::$tag:ident, $($rest:tt)* ) => (
unit_expand!($($built,)* UnitPiece::Tag(Tag::$tag) ; $($rest)*) );
}
macro_rules! generate_well_known_tag_constants { macro_rules! generate_well_known_tag_constants {
( (
$( |$ctx:path| $( $( |$ctx:path| $(
// Copy the doc attribute to the actual definition. // Copy the doc attribute to the actual definition.
$( #[$attr:meta] )* $( #[$attr:meta] )*
($name:ident, $num:expr, $defval:expr, $dispval:ident, $desc:expr) ($name:ident, $num:expr, $defval:expr, $dispval:ident, $unit:expr,
$desc:expr)
),+, )+ ),+, )+
) => ( ) => (
// This is not relevant for associated constants, because // This is not relevant for associated constants, because
@ -146,12 +179,14 @@ macro_rules! generate_well_known_tag_constants {
use std::fmt; use std::fmt;
use crate::value::Value; use crate::value::Value;
use crate::value::DefaultValue; use crate::value::DefaultValue;
use super::{Tag, UnitPiece};
pub struct TagInfo { pub struct TagInfo {
pub name: &'static str, pub name: &'static str,
pub desc: &'static str, pub desc: &'static str,
pub default: DefaultValue, pub default: DefaultValue,
pub dispval: fn(&mut fmt::Write, &Value) -> fmt::Result, pub dispval: fn(&mut fmt::Write, &Value) -> fmt::Result,
pub unit: Option<&'static [UnitPiece]>,
} }
$($( $($(
@ -161,6 +196,7 @@ macro_rules! generate_well_known_tag_constants {
desc: $desc, desc: $desc,
default: $defval, default: $defval,
dispval: super::$dispval, dispval: super::$dispval,
unit: $unit,
}; };
)+)+ )+)+
} }
@ -185,10 +221,12 @@ generate_well_known_tag_constants!(
/// A pointer to the Exif IFD. This is used for the internal structure /// A pointer to the Exif IFD. This is used for the internal structure
/// of Exif data and will not be returned to the user. /// of Exif data and will not be returned to the user.
(ExifIFDPointer, 0x8769, DefaultValue::None, d_default, (ExifIFDPointer, 0x8769, DefaultValue::None, d_default,
unit![],
"Exif IFD pointer"), "Exif IFD pointer"),
/// A pointer to the GPS IFD. This is used for the internal structure /// A pointer to the GPS IFD. This is used for the internal structure
/// of Exif data and will not be returned to the user. /// of Exif data and will not be returned to the user.
(GPSInfoIFDPointer, 0x8825, DefaultValue::None, d_default, (GPSInfoIFDPointer, 0x8825, DefaultValue::None, d_default,
unit![],
"GPS Info IFD pointer"), "GPS Info IFD pointer"),
|Context::Exif| |Context::Exif|
@ -196,6 +234,7 @@ generate_well_known_tag_constants!(
/// A pointer to the interoperability IFD. This is used for the internal /// A pointer to the interoperability IFD. This is used for the internal
/// structure of Exif data and will not be returned to the user. /// structure of Exif data and will not be returned to the user.
(InteropIFDPointer, 0xa005, DefaultValue::None, d_default, (InteropIFDPointer, 0xa005, DefaultValue::None, d_default,
unit![],
"Interoperability IFD pointer"), "Interoperability IFD pointer"),
// TIFF primary and thumbnail attributes [EXIF23 4.6.4 Table 4, // TIFF primary and thumbnail attributes [EXIF23 4.6.4 Table 4,
@ -203,232 +242,344 @@ generate_well_known_tag_constants!(
|Context::Tiff| |Context::Tiff|
(ImageWidth, 0x100, DefaultValue::None, d_default, (ImageWidth, 0x100, DefaultValue::None, d_default,
unit!["pixels"],
"Image width"), "Image width"),
(ImageLength, 0x101, DefaultValue::None, d_default, (ImageLength, 0x101, DefaultValue::None, d_default,
unit!["pixels"],
"Image height"), "Image height"),
(BitsPerSample, 0x102, DefaultValue::Short(&[8, 8, 8]), d_default, (BitsPerSample, 0x102, DefaultValue::Short(&[8, 8, 8]), d_default,
unit![],
"Number of bits per component"), "Number of bits per component"),
(Compression, 0x103, DefaultValue::None, d_compression, (Compression, 0x103, DefaultValue::None, d_compression,
unit![],
"Compression scheme"), "Compression scheme"),
(PhotometricInterpretation, 0x106, DefaultValue::None, d_photointp, (PhotometricInterpretation, 0x106, DefaultValue::None, d_photointp,
unit![],
"Pixel composition"), "Pixel composition"),
(ImageDescription, 0x10e, DefaultValue::None, d_default, (ImageDescription, 0x10e, DefaultValue::None, d_default,
unit![],
"Image title"), "Image title"),
(Make, 0x10f, DefaultValue::None, d_default, (Make, 0x10f, DefaultValue::None, d_default,
unit![],
"Manufacturer of image input equipment"), "Manufacturer of image input equipment"),
(Model, 0x110, DefaultValue::None, d_default, (Model, 0x110, DefaultValue::None, d_default,
unit![],
"Model of image input equipment"), "Model of image input equipment"),
(StripOffsets, 0x111, DefaultValue::None, d_default, (StripOffsets, 0x111, DefaultValue::None, d_default,
unit![],
"Image data location"), "Image data location"),
(Orientation, 0x112, DefaultValue::Short(&[1]), d_orientation, (Orientation, 0x112, DefaultValue::Short(&[1]), d_orientation,
unit![],
"Orientation of image"), "Orientation of image"),
(SamplesPerPixel, 0x115, DefaultValue::Short(&[3]), d_default, (SamplesPerPixel, 0x115, DefaultValue::Short(&[3]), d_default,
unit![],
"Number of components"), "Number of components"),
(RowsPerStrip, 0x116, DefaultValue::None, d_default, (RowsPerStrip, 0x116, DefaultValue::None, d_default,
unit![],
"Number of rows per strip"), "Number of rows per strip"),
(StripByteCounts, 0x117, DefaultValue::None, d_default, (StripByteCounts, 0x117, DefaultValue::None, d_default,
unit![],
"Bytes per compressed strip"), "Bytes per compressed strip"),
(XResolution, 0x11a, DefaultValue::Rational(&[(72, 1)]), d_decimal, (XResolution, 0x11a, DefaultValue::Rational(&[(72, 1)]), d_decimal,
unit![V, " pixels per ", Tag::ResolutionUnit],
"Image resolution in width direction"), "Image resolution in width direction"),
(YResolution, 0x11b, DefaultValue::Rational(&[(72, 1)]), d_decimal, (YResolution, 0x11b, DefaultValue::Rational(&[(72, 1)]), d_decimal,
unit![V, " pixels per ", Tag::ResolutionUnit],
"Image resolution in height direction"), "Image resolution in height direction"),
(PlanarConfiguration, 0x11c, DefaultValue::Short(&[1]), d_planarcfg, (PlanarConfiguration, 0x11c, DefaultValue::Short(&[1]), d_planarcfg,
unit![],
"Image data arrangement"), "Image data arrangement"),
(ResolutionUnit, 0x128, DefaultValue::Short(&[2]), d_resunit, (ResolutionUnit, 0x128, DefaultValue::Short(&[2]), d_resunit,
unit![],
"Unit of X and Y resolution"), "Unit of X and Y resolution"),
(TransferFunction, 0x12d, DefaultValue::None, d_default, (TransferFunction, 0x12d, DefaultValue::None, d_default,
unit![],
"Transfer function"), "Transfer function"),
(Software, 0x131, DefaultValue::None, d_default, (Software, 0x131, DefaultValue::None, d_default,
unit![],
"Software used"), "Software used"),
(DateTime, 0x132, DefaultValue::None, d_datetime, (DateTime, 0x132, DefaultValue::None, d_datetime,
unit![],
"File change date and time"), "File change date and time"),
(Artist, 0x13b, DefaultValue::None, d_default, (Artist, 0x13b, DefaultValue::None, d_default,
unit![],
"Person who created the image"), "Person who created the image"),
(WhitePoint, 0x13e, DefaultValue::None, d_decimal, (WhitePoint, 0x13e, DefaultValue::None, d_decimal,
unit![],
"White point chromaticity"), "White point chromaticity"),
(PrimaryChromaticities, 0x13f, DefaultValue::None, d_decimal, (PrimaryChromaticities, 0x13f, DefaultValue::None, d_decimal,
unit![],
"Chromaticities of primaries"), "Chromaticities of primaries"),
// Not referenced in Exif. // Not referenced in Exif.
(TileOffsets, 0x144, DefaultValue::None, d_default, (TileOffsets, 0x144, DefaultValue::None, d_default,
unit![],
"Tiled image data location"), "Tiled image data location"),
// Not referenced in Exif. // Not referenced in Exif.
(TileByteCounts, 0x145, DefaultValue::None, d_default, (TileByteCounts, 0x145, DefaultValue::None, d_default,
unit![],
"Bytes per compressed tile"), "Bytes per compressed tile"),
(JPEGInterchangeFormat, 0x201, DefaultValue::None, d_default, (JPEGInterchangeFormat, 0x201, DefaultValue::None, d_default,
unit![],
"Offset to JPEG SOI"), "Offset to JPEG SOI"),
(JPEGInterchangeFormatLength, 0x202, DefaultValue::None, d_default, (JPEGInterchangeFormatLength, 0x202, DefaultValue::None, d_default,
unit![],
"Bytes of JPEG data"), "Bytes of JPEG data"),
(YCbCrCoefficients, 0x211, DefaultValue::Unspecified, d_decimal, (YCbCrCoefficients, 0x211, DefaultValue::Unspecified, d_decimal,
unit![],
"Color space transformation matrix coefficients"), "Color space transformation matrix coefficients"),
(YCbCrSubSampling, 0x212, DefaultValue::None, d_ycbcrsubsamp, (YCbCrSubSampling, 0x212, DefaultValue::None, d_ycbcrsubsamp,
unit![],
"Subsampling ratio of Y to C"), "Subsampling ratio of Y to C"),
(YCbCrPositioning, 0x213, DefaultValue::Short(&[1]), d_ycbcrpos, (YCbCrPositioning, 0x213, DefaultValue::Short(&[1]), d_ycbcrpos,
unit![],
"Y and C positioning"), "Y and C positioning"),
(ReferenceBlackWhite, 0x214, DefaultValue::ContextDependent, d_decimal, (ReferenceBlackWhite, 0x214, DefaultValue::ContextDependent, d_decimal,
unit![],
"Pair of black and white reference values"), "Pair of black and white reference values"),
(Copyright, 0x8298, DefaultValue::None, d_default, (Copyright, 0x8298, DefaultValue::None, d_default,
unit![],
"Copyright holder"), "Copyright holder"),
// Exif IFD attributes [EXIF23 4.6.5 Table 7 and 4.6.8 Table 18]. // Exif IFD attributes [EXIF23 4.6.5 Table 7 and 4.6.8 Table 18].
|Context::Exif| |Context::Exif|
(ExposureTime, 0x829a, DefaultValue::None, d_exptime, (ExposureTime, 0x829a, DefaultValue::None, d_exptime,
unit!["s"],
"Exposure time"), "Exposure time"),
(FNumber, 0x829d, DefaultValue::None, d_fnumber, (FNumber, 0x829d, DefaultValue::None, d_decimal,
// F-number is dimensionless, but usually prefixed with "F" in Japan,
// "f/" in the U.S., and so on.
unit!["f/", V],
"F number"), "F number"),
(ExposureProgram, 0x8822, DefaultValue::None, d_expprog, (ExposureProgram, 0x8822, DefaultValue::None, d_expprog,
unit![],
"Exposure program"), "Exposure program"),
(SpectralSensitivity, 0x8824, DefaultValue::None, d_default, (SpectralSensitivity, 0x8824, DefaultValue::None, d_default,
unit![],
"Spectral sensitivity"), "Spectral sensitivity"),
(PhotographicSensitivity, 0x8827, DefaultValue::None, d_default, (PhotographicSensitivity, 0x8827, DefaultValue::None, d_default,
unit![],
"Photographic sensitivity"), "Photographic sensitivity"),
(OECF, 0x8828, DefaultValue::None, d_default, (OECF, 0x8828, DefaultValue::None, d_default,
unit![],
"Optoelectric conversion factor"), "Optoelectric conversion factor"),
(SensitivityType, 0x8830, DefaultValue::None, d_sensitivitytype, (SensitivityType, 0x8830, DefaultValue::None, d_sensitivitytype,
unit![],
"Sensitivity type"), "Sensitivity type"),
(StandardOutputSensitivity, 0x8831, DefaultValue::None, d_default, (StandardOutputSensitivity, 0x8831, DefaultValue::None, d_default,
unit![],
"Standard output sensitivity"), "Standard output sensitivity"),
(RecommendedExposureIndex, 0x8832, DefaultValue::None, d_default, (RecommendedExposureIndex, 0x8832, DefaultValue::None, d_default,
unit![],
"Recommended exposure index"), "Recommended exposure index"),
(ISOSpeed, 0x8833, DefaultValue::None, d_default, (ISOSpeed, 0x8833, DefaultValue::None, d_default,
unit![],
"ISO speed"), "ISO speed"),
(ISOSpeedLatitudeyyy, 0x8834, DefaultValue::None, d_default, (ISOSpeedLatitudeyyy, 0x8834, DefaultValue::None, d_default,
unit![],
"ISO speed latitude yyy"), "ISO speed latitude yyy"),
(ISOSpeedLatitudezzz, 0x8835, DefaultValue::None, d_default, (ISOSpeedLatitudezzz, 0x8835, DefaultValue::None, d_default,
unit![],
"ISO speed latitude zzz"), "ISO speed latitude zzz"),
// The absence of this field means non-conformance to Exif, so the default // The absence of this field means non-conformance to Exif, so the default
// value specified in the standard (e.g., "0231") should not apply. // value specified in the standard (e.g., "0231") should not apply.
(ExifVersion, 0x9000, DefaultValue::None, d_exifver, (ExifVersion, 0x9000, DefaultValue::None, d_exifver,
unit![],
"Exif version"), "Exif version"),
(DateTimeOriginal, 0x9003, DefaultValue::None, d_datetime, (DateTimeOriginal, 0x9003, DefaultValue::None, d_datetime,
unit![],
"Date and time of original data generation"), "Date and time of original data generation"),
(DateTimeDigitized, 0x9004, DefaultValue::None, d_datetime, (DateTimeDigitized, 0x9004, DefaultValue::None, d_datetime,
unit![],
"Date and time of digital data generation"), "Date and time of digital data generation"),
(OffsetTime, 0x9010, DefaultValue::None, d_default, (OffsetTime, 0x9010, DefaultValue::None, d_default,
unit![],
"Offset data of DateTime"), "Offset data of DateTime"),
(OffsetTimeOriginal, 0x9011, DefaultValue::None, d_default, (OffsetTimeOriginal, 0x9011, DefaultValue::None, d_default,
unit![],
"Offset data of DateTimeOriginal"), "Offset data of DateTimeOriginal"),
(OffsetTimeDigitized, 0x9012, DefaultValue::None, d_default, (OffsetTimeDigitized, 0x9012, DefaultValue::None, d_default,
unit![],
"Offset data of DateTimeDigitized"), "Offset data of DateTimeDigitized"),
(ComponentsConfiguration, 0x9101, DefaultValue::ContextDependent, d_cpntcfg, (ComponentsConfiguration, 0x9101, DefaultValue::ContextDependent, d_cpntcfg,
unit![],
"Meaning of each component"), "Meaning of each component"),
(CompressedBitsPerPixel, 0x9102, DefaultValue::None, d_decimal, (CompressedBitsPerPixel, 0x9102, DefaultValue::None, d_decimal,
unit![],
"Image compression mode"), "Image compression mode"),
(ShutterSpeedValue, 0x9201, DefaultValue::None, d_decimal, (ShutterSpeedValue, 0x9201, DefaultValue::None, d_decimal,
unit!["EV"],
"Shutter speed"), "Shutter speed"),
(ApertureValue, 0x9202, DefaultValue::None, d_decimal, (ApertureValue, 0x9202, DefaultValue::None, d_decimal,
unit!["EV"],
"Aperture"), "Aperture"),
(BrightnessValue, 0x9203, DefaultValue::None, d_decimal, (BrightnessValue, 0x9203, DefaultValue::None, d_decimal,
unit!["EV"],
"Brightness"), "Brightness"),
(ExposureBiasValue, 0x9204, DefaultValue::None, d_decimal, (ExposureBiasValue, 0x9204, DefaultValue::None, d_decimal,
unit!["EV"],
"Exposure bias"), "Exposure bias"),
(MaxApertureValue, 0x9205, DefaultValue::None, d_decimal, (MaxApertureValue, 0x9205, DefaultValue::None, d_decimal,
unit!["EV"],
"Maximum lens aperture"), "Maximum lens aperture"),
(SubjectDistance, 0x9206, DefaultValue::None, d_subjdist, (SubjectDistance, 0x9206, DefaultValue::None, d_subjdist,
unit!["m"],
"Subject distance"), "Subject distance"),
(MeteringMode, 0x9207, DefaultValue::Short(&[0]), d_metering, (MeteringMode, 0x9207, DefaultValue::Short(&[0]), d_metering,
unit![],
"Metering mode"), "Metering mode"),
(LightSource, 0x9208, DefaultValue::Short(&[0]), d_lightsrc, (LightSource, 0x9208, DefaultValue::Short(&[0]), d_lightsrc,
unit![],
"Light source"), "Light source"),
(Flash, 0x9209, DefaultValue::Unspecified, d_flash, (Flash, 0x9209, DefaultValue::Unspecified, d_flash,
unit![],
"Flash"), "Flash"),
(FocalLength, 0x920a, DefaultValue::None, d_decimal, (FocalLength, 0x920a, DefaultValue::None, d_decimal,
unit!["mm"],
"Lens focal length"), "Lens focal length"),
(SubjectArea, 0x9214, DefaultValue::None, d_subjarea, (SubjectArea, 0x9214, DefaultValue::None, d_subjarea,
unit![],
"Subject area"), "Subject area"),
(MakerNote, 0x927c, DefaultValue::None, d_default, (MakerNote, 0x927c, DefaultValue::None, d_default,
unit![],
"Manufacturer notes"), "Manufacturer notes"),
(UserComment, 0x9286, DefaultValue::None, d_default, (UserComment, 0x9286, DefaultValue::None, d_default,
unit![],
"User comments"), "User comments"),
(SubSecTime, 0x9290, DefaultValue::None, d_default, (SubSecTime, 0x9290, DefaultValue::None, d_default,
unit![],
"DateTime subseconds"), "DateTime subseconds"),
(SubSecTimeOriginal, 0x9291, DefaultValue::None, d_default, (SubSecTimeOriginal, 0x9291, DefaultValue::None, d_default,
unit![],
"DateTimeOriginal subseconds"), "DateTimeOriginal subseconds"),
(SubSecTimeDigitized, 0x9292, DefaultValue::None, d_default, (SubSecTimeDigitized, 0x9292, DefaultValue::None, d_default,
unit![],
"DateTimeDigitized subseconds"), "DateTimeDigitized subseconds"),
(Temperature, 0x9400, DefaultValue::None, d_optdecimal, (Temperature, 0x9400, DefaultValue::None, d_optdecimal,
unit!["degC"],
"Temperature"), "Temperature"),
(Humidity, 0x9401, DefaultValue::None, d_optdecimal, (Humidity, 0x9401, DefaultValue::None, d_optdecimal,
unit!["%"],
"Humidity"), "Humidity"),
(Pressure, 0x9402, DefaultValue::None, d_optdecimal, (Pressure, 0x9402, DefaultValue::None, d_optdecimal,
unit!["hPa"],
"Pressure"), "Pressure"),
(WaterDepth, 0x9403, DefaultValue::None, d_optdecimal, (WaterDepth, 0x9403, DefaultValue::None, d_optdecimal,
unit!["m"],
"Water depth"), "Water depth"),
(Acceleration, 0x9404, DefaultValue::None, d_optdecimal, (Acceleration, 0x9404, DefaultValue::None, d_optdecimal,
unit!["mGal"],
"Acceleration"), "Acceleration"),
(CameraElevationAngle, 0x9405, DefaultValue::None, d_optdecimal, (CameraElevationAngle, 0x9405, DefaultValue::None, d_optdecimal,
unit!["deg"],
"Camera elevation angle"), "Camera elevation angle"),
(FlashpixVersion, 0xa000, DefaultValue::Undefined(b"0100"), d_exifver, (FlashpixVersion, 0xa000, DefaultValue::Undefined(b"0100"), d_exifver,
unit![],
"Supported Flashpix version"), "Supported Flashpix version"),
(ColorSpace, 0xa001, DefaultValue::Unspecified, d_cspace, (ColorSpace, 0xa001, DefaultValue::Unspecified, d_cspace,
unit![],
"Color space information"), "Color space information"),
(PixelXDimension, 0xa002, DefaultValue::None, d_default, (PixelXDimension, 0xa002, DefaultValue::None, d_default,
unit!["pixels"],
"Valid image width"), "Valid image width"),
(PixelYDimension, 0xa003, DefaultValue::Unspecified, d_default, (PixelYDimension, 0xa003, DefaultValue::Unspecified, d_default,
unit!["pixels"],
"Valid image height"), "Valid image height"),
(RelatedSoundFile, 0xa004, DefaultValue::None, d_default, (RelatedSoundFile, 0xa004, DefaultValue::None, d_default,
unit![],
"Related audio file"), "Related audio file"),
(FlashEnergy, 0xa20b, DefaultValue::None, d_decimal, (FlashEnergy, 0xa20b, DefaultValue::None, d_decimal,
unit!["BCPS"],
"Flash energy"), "Flash energy"),
(SpatialFrequencyResponse, 0xa20c, DefaultValue::None, d_default, (SpatialFrequencyResponse, 0xa20c, DefaultValue::None, d_default,
unit![],
"Spatial frequency response"), "Spatial frequency response"),
(FocalPlaneXResolution, 0xa20e, DefaultValue::None, d_decimal, (FocalPlaneXResolution, 0xa20e, DefaultValue::None, d_decimal,
unit![V, " pixels per ", Tag::FocalPlaneResolutionUnit],
"Focal plane X resolution"), "Focal plane X resolution"),
(FocalPlaneYResolution, 0xa20f, DefaultValue::None, d_decimal, (FocalPlaneYResolution, 0xa20f, DefaultValue::None, d_decimal,
unit![V, " pixels per ", Tag::FocalPlaneResolutionUnit],
"Focal plane Y resolution"), "Focal plane Y resolution"),
(FocalPlaneResolutionUnit, 0xa210, DefaultValue::Short(&[2]), d_resunit, (FocalPlaneResolutionUnit, 0xa210, DefaultValue::Short(&[2]), d_resunit,
unit![],
"Focal plane resolution unit"), "Focal plane resolution unit"),
(SubjectLocation, 0xa214, DefaultValue::None, d_subjarea, (SubjectLocation, 0xa214, DefaultValue::None, d_subjarea,
unit![],
"Subject location"), "Subject location"),
(ExposureIndex, 0xa215, DefaultValue::None, d_decimal, (ExposureIndex, 0xa215, DefaultValue::None, d_decimal,
unit![],
"Exposure index"), "Exposure index"),
(SensingMethod, 0xa217, DefaultValue::None, d_sensingmethod, (SensingMethod, 0xa217, DefaultValue::None, d_sensingmethod,
unit![],
"Sensing method"), "Sensing method"),
(FileSource, 0xa300, DefaultValue::Undefined(&[3]), d_filesrc, (FileSource, 0xa300, DefaultValue::Undefined(&[3]), d_filesrc,
unit![],
"File source"), "File source"),
(SceneType, 0xa301, DefaultValue::Undefined(&[1]), d_scenetype, (SceneType, 0xa301, DefaultValue::Undefined(&[1]), d_scenetype,
unit![],
"Scene type"), "Scene type"),
(CFAPattern, 0xa302, DefaultValue::None, d_default, (CFAPattern, 0xa302, DefaultValue::None, d_default,
unit![],
"CFA pattern"), "CFA pattern"),
(CustomRendered, 0xa401, DefaultValue::Short(&[0]), d_customrendered, (CustomRendered, 0xa401, DefaultValue::Short(&[0]), d_customrendered,
unit![],
"Custom image processing"), "Custom image processing"),
(ExposureMode, 0xa402, DefaultValue::None, d_expmode, (ExposureMode, 0xa402, DefaultValue::None, d_expmode,
unit![],
"Exposure mode"), "Exposure mode"),
(WhiteBalance, 0xa403, DefaultValue::None, d_whitebalance, (WhiteBalance, 0xa403, DefaultValue::None, d_whitebalance,
unit![],
"White balance"), "White balance"),
(DigitalZoomRatio, 0xa404, DefaultValue::None, d_dzoomratio, (DigitalZoomRatio, 0xa404, DefaultValue::None, d_dzoomratio,
unit![],
"Digital zoom ratio"), "Digital zoom ratio"),
(FocalLengthIn35mmFilm, 0xa405, DefaultValue::None, d_focallen35, (FocalLengthIn35mmFilm, 0xa405, DefaultValue::None, d_focallen35,
unit!["mm"],
"Focal length in 35 mm film"), "Focal length in 35 mm film"),
(SceneCaptureType, 0xa406, DefaultValue::Short(&[0]), d_scenecaptype, (SceneCaptureType, 0xa406, DefaultValue::Short(&[0]), d_scenecaptype,
unit![],
"Scene capture type"), "Scene capture type"),
(GainControl, 0xa407, DefaultValue::None, d_gainctrl, (GainControl, 0xa407, DefaultValue::None, d_gainctrl,
unit![],
"Gain control"), "Gain control"),
(Contrast, 0xa408, DefaultValue::Short(&[0]), d_contrast, (Contrast, 0xa408, DefaultValue::Short(&[0]), d_contrast,
unit![],
"Contrast"), "Contrast"),
(Saturation, 0xa409, DefaultValue::Short(&[0]), d_saturation, (Saturation, 0xa409, DefaultValue::Short(&[0]), d_saturation,
unit![],
"Saturation"), "Saturation"),
(Sharpness, 0xa40a, DefaultValue::Short(&[0]), d_sharpness, (Sharpness, 0xa40a, DefaultValue::Short(&[0]), d_sharpness,
unit![],
"Sharpness"), "Sharpness"),
(DeviceSettingDescription, 0xa40b, DefaultValue::None, d_default, (DeviceSettingDescription, 0xa40b, DefaultValue::None, d_default,
unit![],
"Device settings description"), "Device settings description"),
(SubjectDistanceRange, 0xa40c, DefaultValue::None, d_subjdistrange, (SubjectDistanceRange, 0xa40c, DefaultValue::None, d_subjdistrange,
unit![],
"Subject distance range"), "Subject distance range"),
(ImageUniqueID, 0xa420, DefaultValue::None, d_default, (ImageUniqueID, 0xa420, DefaultValue::None, d_default,
unit![],
"Unique image ID"), "Unique image ID"),
(CameraOwnerName, 0xa430, DefaultValue::None, d_default, (CameraOwnerName, 0xa430, DefaultValue::None, d_default,
unit![],
"Camera owner name"), "Camera owner name"),
(BodySerialNumber, 0xa431, DefaultValue::None, d_default, (BodySerialNumber, 0xa431, DefaultValue::None, d_default,
unit![],
"Body serial number"), "Body serial number"),
(LensSpecification, 0xa432, DefaultValue::None, d_lensspec, (LensSpecification, 0xa432, DefaultValue::None, d_lensspec,
unit![],
"Lens specification"), "Lens specification"),
(LensMake, 0xa433, DefaultValue::None, d_default, (LensMake, 0xa433, DefaultValue::None, d_default,
unit![],
"Lens make"), "Lens make"),
(LensModel, 0xa434, DefaultValue::None, d_default, (LensModel, 0xa434, DefaultValue::None, d_default,
unit![],
"Lens model"), "Lens model"),
(LensSerialNumber, 0xa435, DefaultValue::None, d_default, (LensSerialNumber, 0xa435, DefaultValue::None, d_default,
unit![],
"Lens serial number"), "Lens serial number"),
(Gamma, 0xa500, DefaultValue::None, d_decimal, (Gamma, 0xa500, DefaultValue::None, d_decimal,
unit![],
"Gamma"), "Gamma"),
// GPS attributes [EXIF23 4.6.6 Table 15 and 4.6.8 Table 19]. // GPS attributes [EXIF23 4.6.6 Table 15 and 4.6.8 Table 19].
@ -436,74 +587,107 @@ generate_well_known_tag_constants!(
// Depends on the Exif version. // Depends on the Exif version.
(GPSVersionID, 0x0, DefaultValue::ContextDependent, d_gpsver, (GPSVersionID, 0x0, DefaultValue::ContextDependent, d_gpsver,
unit![],
"GPS tag version"), "GPS tag version"),
(GPSLatitudeRef, 0x1, DefaultValue::None, d_gpslatlongref, (GPSLatitudeRef, 0x1, DefaultValue::None, d_gpslatlongref,
unit![],
"North or south latitude"), "North or south latitude"),
(GPSLatitude, 0x2, DefaultValue::None, d_gpsdms, (GPSLatitude, 0x2, DefaultValue::None, d_gpsdms,
unit![Tag::GPSLatitudeRef],
"Latitude"), "Latitude"),
(GPSLongitudeRef, 0x3, DefaultValue::None, d_gpslatlongref, (GPSLongitudeRef, 0x3, DefaultValue::None, d_gpslatlongref,
unit![],
"East or West Longitude"), "East or West Longitude"),
(GPSLongitude, 0x4, DefaultValue::None, d_gpsdms, (GPSLongitude, 0x4, DefaultValue::None, d_gpsdms,
unit![Tag::GPSLongitudeRef],
"Longitude"), "Longitude"),
(GPSAltitudeRef, 0x5, DefaultValue::Byte(&[0]), d_gpsaltref, (GPSAltitudeRef, 0x5, DefaultValue::Byte(&[0]), d_gpsaltref,
unit![],
"Altitude reference"), "Altitude reference"),
(GPSAltitude, 0x6, DefaultValue::None, d_decimal, (GPSAltitude, 0x6, DefaultValue::None, d_decimal,
unit![V, " meters ", Tag::GPSAltitudeRef],
"Altitude"), "Altitude"),
(GPSTimeStamp, 0x7, DefaultValue::None, d_gpstimestamp, (GPSTimeStamp, 0x7, DefaultValue::None, d_gpstimestamp,
unit![],
"GPS time (atomic clock)"), "GPS time (atomic clock)"),
(GPSSatellites, 0x8, DefaultValue::None, d_default, (GPSSatellites, 0x8, DefaultValue::None, d_default,
unit![],
"GPS satellites used for measurement"), "GPS satellites used for measurement"),
(GPSStatus, 0x9, DefaultValue::None, d_gpsstatus, (GPSStatus, 0x9, DefaultValue::None, d_gpsstatus,
unit![],
"GPS receiver status"), "GPS receiver status"),
(GPSMeasureMode, 0xa, DefaultValue::None, d_gpsmeasuremode, (GPSMeasureMode, 0xa, DefaultValue::None, d_gpsmeasuremode,
unit![],
"GPS measurement mode"), "GPS measurement mode"),
(GPSDOP, 0xb, DefaultValue::None, d_decimal, (GPSDOP, 0xb, DefaultValue::None, d_decimal,
unit![],
"Measurement precision"), "Measurement precision"),
(GPSSpeedRef, 0xc, DefaultValue::Ascii(&[b"K"]), d_gpsspeedref, (GPSSpeedRef, 0xc, DefaultValue::Ascii(&[b"K"]), d_gpsspeedref,
unit![],
"Speed unit"), "Speed unit"),
(GPSSpeed, 0xd, DefaultValue::None, d_decimal, (GPSSpeed, 0xd, DefaultValue::None, d_decimal,
unit![Tag::GPSSpeedRef],
"Speed of GPS receiver"), "Speed of GPS receiver"),
(GPSTrackRef, 0xe, DefaultValue::Ascii(&[b"T"]), d_gpsdirref, (GPSTrackRef, 0xe, DefaultValue::Ascii(&[b"T"]), d_gpsdirref,
unit![],
"Reference for direction of movement"), "Reference for direction of movement"),
(GPSTrack, 0xf, DefaultValue::None, d_decimal, (GPSTrack, 0xf, DefaultValue::None, d_decimal,
unit![V, " degrees in ", Tag::GPSTrackRef],
"Direction of movement"), "Direction of movement"),
(GPSImgDirectionRef, 0x10, DefaultValue::Ascii(&[b"T"]), d_gpsdirref, (GPSImgDirectionRef, 0x10, DefaultValue::Ascii(&[b"T"]), d_gpsdirref,
unit![],
"Reference for direction of image"), "Reference for direction of image"),
(GPSImgDirection, 0x11, DefaultValue::None, d_decimal, (GPSImgDirection, 0x11, DefaultValue::None, d_decimal,
unit![V, " degrees in ", Tag::GPSImgDirectionRef],
"Direction of image"), "Direction of image"),
(GPSMapDatum, 0x12, DefaultValue::None, d_default, (GPSMapDatum, 0x12, DefaultValue::None, d_default,
unit![],
"Geodetic survey data used"), "Geodetic survey data used"),
(GPSDestLatitudeRef, 0x13, DefaultValue::None, d_gpslatlongref, (GPSDestLatitudeRef, 0x13, DefaultValue::None, d_gpslatlongref,
unit![],
"Reference for latitude of destination"), "Reference for latitude of destination"),
(GPSDestLatitude, 0x14, DefaultValue::None, d_gpsdms, (GPSDestLatitude, 0x14, DefaultValue::None, d_gpsdms,
unit![Tag::GPSDestLatitudeRef],
"Latitude of destination"), "Latitude of destination"),
(GPSDestLongitudeRef, 0x15, DefaultValue::None, d_gpslatlongref, (GPSDestLongitudeRef, 0x15, DefaultValue::None, d_gpslatlongref,
unit![],
"Reference for longitude of destination"), "Reference for longitude of destination"),
(GPSDestLongitude, 0x16, DefaultValue::None, d_gpsdms, (GPSDestLongitude, 0x16, DefaultValue::None, d_gpsdms,
unit![Tag::GPSDestLongitudeRef],
"Longitude of destination"), "Longitude of destination"),
(GPSDestBearingRef, 0x17, DefaultValue::Ascii(&[b"T"]), d_gpsdirref, (GPSDestBearingRef, 0x17, DefaultValue::Ascii(&[b"T"]), d_gpsdirref,
unit![],
"Reference for bearing of destination"), "Reference for bearing of destination"),
(GPSDestBearing, 0x18, DefaultValue::None, d_decimal, (GPSDestBearing, 0x18, DefaultValue::None, d_decimal,
unit![V, " degrees in ", Tag::GPSDestBearingRef],
"Bearing of destination"), "Bearing of destination"),
(GPSDestDistanceRef, 0x19, DefaultValue::Ascii(&[b"K"]), d_gpsdistref, (GPSDestDistanceRef, 0x19, DefaultValue::Ascii(&[b"K"]), d_gpsdistref,
unit![],
"Reference for distance to destination"), "Reference for distance to destination"),
(GPSDestDistance, 0x1a, DefaultValue::None, d_decimal, (GPSDestDistance, 0x1a, DefaultValue::None, d_decimal,
unit![Tag::GPSDestDistanceRef],
"Distance to destination"), "Distance to destination"),
(GPSProcessingMethod, 0x1b, DefaultValue::None, d_ascii_in_undef, (GPSProcessingMethod, 0x1b, DefaultValue::None, d_ascii_in_undef,
unit![],
"Name of GPS processing method"), "Name of GPS processing method"),
(GPSAreaInformation, 0x1c, DefaultValue::None, d_default, (GPSAreaInformation, 0x1c, DefaultValue::None, d_default,
unit![],
"Name of GPS area"), "Name of GPS area"),
(GPSDateStamp, 0x1d, DefaultValue::None, d_gpsdatestamp, (GPSDateStamp, 0x1d, DefaultValue::None, d_gpsdatestamp,
unit![],
"GPS date"), "GPS date"),
(GPSDifferential, 0x1e, DefaultValue::None, d_gpsdifferential, (GPSDifferential, 0x1e, DefaultValue::None, d_gpsdifferential,
unit![],
"GPS differential correction"), "GPS differential correction"),
(GPSHPositioningError, 0x1f, DefaultValue::None, d_decimal, (GPSHPositioningError, 0x1f, DefaultValue::None, d_decimal,
unit!["m"],
"Horizontal positioning error"), "Horizontal positioning error"),
// Interoperability attributes [EXIF23 4.6.7 Table 16 and 4.6.8 Table 20]. // Interoperability attributes [EXIF23 4.6.7 Table 16 and 4.6.8 Table 20].
|Context::Interop| |Context::Interop|
(InteroperabilityIndex, 0x1, DefaultValue::None, d_default, (InteroperabilityIndex, 0x1, DefaultValue::None, d_default,
unit![],
"Interoperability identification"), "Interoperability identification"),
); );
@ -571,8 +755,8 @@ fn d_planarcfg(w: &mut fmt::Write, value: &Value) -> fmt::Result {
fn d_resunit(w: &mut fmt::Write, value: &Value) -> fmt::Result { fn d_resunit(w: &mut fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) { let s = match value.get_uint(0) {
Some(1) => "no absolute unit", Some(1) => "no absolute unit",
Some(2) => "pixels per inch", Some(2) => "inch",
Some(3) => "pixels per centimeter", Some(3) => "cm",
_ => return d_unknown(w, value, "unknown unit "), _ => return d_unknown(w, value, "unknown unit "),
}; };
w.write_str(s) w.write_str(s)
@ -634,12 +818,6 @@ fn d_exptime(w: &mut fmt::Write, value: &Value) -> fmt::Result {
d_default(w, value) d_default(w, value)
} }
// FNumber (Exif 0x829d)
fn d_fnumber(w: &mut fmt::Write, value: &Value) -> fmt::Result {
w.write_str("f/")?;
d_decimal(w, value)
}
// ExposureProgram (Exif 0x8822) // ExposureProgram (Exif 0x8822)
fn d_expprog(w: &mut fmt::Write, value: &Value) -> fmt::Result { fn d_expprog(w: &mut fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) { let s = match value.get_uint(0) {

View File

@ -28,7 +28,8 @@ use std::fmt;
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, UnitPiece};
use crate::value;
use crate::value::Value; use crate::value::Value;
use crate::value::get_type_info; use crate::value::get_type_info;
use crate::util::{atou16, ctou32}; use crate::util::{atou16, ctou32};
@ -264,9 +265,143 @@ impl fmt::Display for DateTime {
} }
} }
impl<'a> Field<'a> {
/// Returns an object that implements `std::fmt::Display` for
/// printing the value of this field in a tag-specific format.
///
/// To print the value with the unit, call `with_unit` method on the
/// returned object. It takes a parameter, which is either `()`,
/// `&Field`, or `&Reader` and provides the unit information.
/// If the unit does not depend on another field, `()` can be used.
/// Otherwise, `&Field` or `&Reader` should be used.
///
/// # Examples
///
/// ```
/// use exif::{Field, Rational, Tag, Value};
///
/// let xres = Field {
/// tag: Tag::XResolution,
/// thumbnail: false,
/// value: Value::Rational(vec![Rational { num: 72, denom: 1 }]),
/// };
/// let cm = Field {
/// tag: Tag::ResolutionUnit,
/// thumbnail: false,
/// value: Value::Short(vec![3]),
/// };
/// assert_eq!(format!("{}", xres.display_value()), "72");
/// assert_eq!(format!("{}", cm.display_value()), "cm");
/// // The unit of XResolution is indicated by ResolutionUnit.
/// assert_eq!(format!("{}", xres.display_value().with_unit(&cm)),
/// "72 pixels per cm");
/// // If ResolutionUnit is not given, the default value is used.
/// assert_eq!(format!("{}", xres.display_value().with_unit(())),
/// "72 pixels per inch");
/// assert_eq!(format!("{}", xres.display_value().with_unit(&xres)),
/// "72 pixels per inch");
///
/// let flen = Field {
/// tag: Tag::FocalLengthIn35mmFilm,
/// thumbnail: false,
/// value: Value::Short(vec![24]),
/// };
/// // The unit of the focal length is always mm, so the argument
/// // has nothing to do with the result.
/// assert_eq!(format!("{}", flen.display_value().with_unit(())), "24 mm");
/// assert_eq!(format!("{}", flen.display_value().with_unit(&cm)), "24 mm");
/// ```
#[inline]
pub fn display_value(&self) -> DisplayValue {
DisplayValue {
tag: self.tag,
thumbnail: self.thumbnail,
value_display: self.value.display_as(self.tag),
}
}
}
/// Helper struct for printing a value in a tag-specific format.
pub struct DisplayValue<'a> {
tag: Tag,
thumbnail: bool,
value_display: value::Display<'a>,
}
impl<'a> DisplayValue<'a> {
#[inline]
pub fn with_unit<'b, T>(&'b self, unit_provider: T)
-> DisplayValueUnit<T> where T: ProvideUnit<'b> {
DisplayValueUnit {
thumbnail: self.thumbnail,
value_display: self.value_display,
unit: self.tag.unit(),
unit_provider: unit_provider,
}
}
}
impl<'a> fmt::Display for DisplayValue<'a> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.value_display.fmt(f)
}
}
/// Helper struct for printing a value with its unit.
pub struct DisplayValueUnit<'a, T> where T: ProvideUnit<'a> {
thumbnail: bool,
value_display: value::Display<'a>,
unit: Option<&'static [UnitPiece]>,
unit_provider: T,
}
impl<'a, T> fmt::Display for DisplayValueUnit<'a, T> where T: ProvideUnit<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(unit) = self.unit {
assert!(!unit.is_empty());
for piece in unit {
match *piece {
UnitPiece::Value => self.value_display.fmt(f),
UnitPiece::Str(s) => f.write_str(s),
UnitPiece::Tag(tag) =>
if let Some(x) = self.unit_provider.get_field(
tag, self.thumbnail) {
x.value.display_as(tag).fmt(f)
} else if let Some(x) = tag.default_value() {
x.display_as(tag).fmt(f)
} else {
write!(f, "[{} missing]", tag)
},
}?
}
Ok(())
} else {
self.value_display.fmt(f)
}
}
}
pub trait ProvideUnit<'a>: Copy {
fn get_field(self, tag: Tag, thumbnail: bool) -> Option<&'a Field<'a>>;
}
impl<'a> ProvideUnit<'a> for () {
fn get_field(self, _tag: Tag, _thumbnail: bool) -> Option<&'a Field<'a>> {
None
}
}
impl<'a> ProvideUnit<'a> for &'a Field<'a> {
fn get_field(self, tag: Tag, thumbnail: bool) -> Option<&'a Field<'a>> {
Some(self).filter(|x| x.tag == tag && x.thumbnail == thumbnail)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::value::Rational;
// 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.
@ -325,4 +460,73 @@ mod tests {
assert_err_pat!(dt.parse_offset(b" : "), Error::BlankValue(_)); assert_err_pat!(dt.parse_offset(b" : "), Error::BlankValue(_));
assert_err_pat!(dt.parse_offset(b" "), Error::BlankValue(_)); assert_err_pat!(dt.parse_offset(b" "), Error::BlankValue(_));
} }
#[test]
fn display_value_with_unit() {
let cm = Field {
tag: Tag::ResolutionUnit,
thumbnail: false,
value: Value::Short(vec![3]),
};
let cm_tn = Field {
tag: Tag::ResolutionUnit,
thumbnail: true,
value: Value::Short(vec![3]),
};
// No unit.
let exifver = Field {
tag: Tag::ExifVersion,
thumbnail: false,
value: Value::Undefined(b"0231", 0),
};
assert_eq!(format!("{}", exifver.display_value()),
"2.31");
assert_eq!(format!("{}", exifver.display_value().with_unit(())),
"2.31");
assert_eq!(format!("{}", exifver.display_value().with_unit(&cm)),
"2.31");
// Fixed string.
let width = Field {
tag: Tag::ImageWidth,
thumbnail: false,
value: Value::Short(vec![257]),
};
assert_eq!(format!("{}", width.display_value()),
"257");
assert_eq!(format!("{}", width.display_value().with_unit(())),
"257 pixels");
assert_eq!(format!("{}", width.display_value().with_unit(&cm)),
"257 pixels");
// Unit tag (with a non-default value).
// Unit tag is missing but the default is specified.
let xres = Field {
tag: Tag::XResolution,
thumbnail: false,
value: Value::Rational(vec![Rational { num: 300, denom: 1 }]),
};
assert_eq!(format!("{}", xres.display_value()),
"300");
assert_eq!(format!("{}", xres.display_value().with_unit(())),
"300 pixels per inch");
assert_eq!(format!("{}", xres.display_value().with_unit(&cm)),
"300 pixels per cm");
assert_eq!(format!("{}", xres.display_value().with_unit(&cm_tn)),
"300 pixels per inch");
// Unit tag is missing and the default is not specified.
let gpslat = Field {
tag: Tag::GPSLatitude,
thumbnail: false,
value: Value::Rational(vec![
Rational { num: 10, denom: 1 },
Rational { num: 0, denom: 1 },
Rational { num: 1, denom: 10 },
]),
};
assert_eq!(format!("{}", gpslat.display_value()),
"10 deg 0 min 0.1 sec");
assert_eq!(format!("{}", gpslat.display_value().with_unit(())),
"10 deg 0 min 0.1 sec [GPSLatitudeRef missing]");
assert_eq!(format!("{}", gpslat.display_value().with_unit(&cm)),
"10 deg 0 min 0.1 sec [GPSLatitudeRef missing]");
}
} }

View File

@ -77,6 +77,8 @@ impl<'a> Value<'a> {
/// printing a value in a tag-specific format. /// printing a value in a tag-specific format.
/// The tag of the value is specified as the argument. /// The tag of the value is specified as the argument.
/// ///
/// If you want to display with the unit, use `Field::display_value`.
///
/// # Examples /// # Examples
/// ///
/// ``` /// ```
@ -86,7 +88,7 @@ impl<'a> Value<'a> {
/// "2.31"); /// "2.31");
/// let val = Value::Short(vec![2]); /// let val = Value::Short(vec![2]);
/// assert_eq!(format!("{}", val.display_as(Tag::ResolutionUnit)), /// assert_eq!(format!("{}", val.display_as(Tag::ResolutionUnit)),
/// "pixels per inch"); /// "inch");
/// ``` /// ```
#[inline] #[inline]
pub fn display_as(&self, tag: crate::tag::Tag) -> Display { pub fn display_as(&self, tag: crate::tag::Tag) -> Display {
@ -146,6 +148,7 @@ impl<'a> Iterator for UIntIter<'a> {
impl<'a> ExactSizeIterator for UIntIter<'a> {} impl<'a> ExactSizeIterator for UIntIter<'a> {}
/// Helper struct for printing a value in a tag-specific format. /// Helper struct for printing a value in a tag-specific format.
#[derive(Copy, Clone)]
pub struct Display<'a> { pub struct Display<'a> {
pub fmt: fn(&mut fmt::Write, &Value) -> fmt::Result, pub fmt: fn(&mut fmt::Write, &Value) -> fmt::Result,
pub value: &'a Value<'a>, pub value: &'a Value<'a>,

BIN
tests/unit.tif Normal file

Binary file not shown.