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