exif-rs/src/tag.rs

1556 lines
53 KiB
Rust

//
// Copyright (c) 2016 KAMADA Ken'ichi.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
use std::fmt;
use crate::error::Error;
use crate::value;
use crate::value::Value;
use crate::util::atou16;
/// A tag of a TIFF/Exif field.
///
/// Some well-known tags are provided as associated constants of
/// this type. The constant names follow the Exif specification
/// but not the Rust naming conventions.
///
/// A non-predefined tag can also be specified
/// by the context and the number as in `Tag(Context::Tiff, 0x100)`.
//
// This is not an enum to keep safety and API stability, while
// supporting unknown tag numbers. This comment is based on the
// behavior of Rust 1.12.
// Storing unknown values in a repr(u16) enum is unsafe. The compiler
// assumes that there is no undefined discriminant even with a C-like
// enum, so the exhaustiveness check of a match expression will break.
// Storing unknown values in a special variant such as Unknown(u16)
// tends to break backward compatibility. When Tag::VariantFoo is
// defined in a new version of the library, the old codes using
// Tag::Unknown(Foo's tag number) will break.
//
// Use of constants is restricted in patterns. As of Rust 1.12,
// PartialEq and Eq need to be _automatically derived_ for Tag to
// emulate structural equivalency.
// <https://github.com/rust-lang/rfcs/pull/1445>
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Tag(pub Context, pub u16);
impl Tag {
/// Returns the context of the tag.
///
/// # Examples
/// ```
/// use exif::{Context, Tag};
/// assert_eq!(Tag::XResolution.context(), Context::Tiff);
/// assert_eq!(Tag::DateTimeOriginal.context(), Context::Exif);
/// ```
#[inline]
pub fn context(self) -> Context {
self.0
}
/// Returns the tag number.
///
/// # Examples
/// ```
/// use exif::Tag;
/// assert_eq!(Tag::XResolution.number(), 0x11a);
/// assert_eq!(Tag::DateTimeOriginal.number(), 0x9003);
/// ```
#[inline]
pub fn number(self) -> u16 {
self.1
}
/// Returns the description of the tag.
#[inline]
pub fn description(&self) -> Option<&str> {
get_tag_info(*self).map(|ti| ti.desc)
}
/// Returns the default value of the tag. `None` is returned if
/// it is not defined in the standard or it depends on another tag.
#[inline]
pub fn default_value(&self) -> Option<Value> {
get_tag_info(*self).and_then(|ti| (&ti.default).into())
}
pub(crate) fn unit(self) -> Option<&'static [UnitPiece]> {
get_tag_info(self).and_then(|ti| ti.unit)
}
}
impl fmt::Display for Tag {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match get_tag_info(*self) {
Some(ti) => f.pad(ti.name),
None => f.pad(&format!("{:?}", self)),
}
}
}
/// An enum that indicates how a tag number is interpreted.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum Context {
/// TIFF attributes defined in the TIFF Rev. 6.0 specification.
Tiff, // 0th/1st IFD (toplevel)
/// Exif attributes.
Exif, // -- Exif IFD
/// GPS attributes.
Gps, // -- GPS IFD
/// Interoperability attributes.
Interop, // -- 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, $unit:expr,
$desc:expr)
),+, )+
) => (
// This is not relevant for associated constants, because
// they cannot be imported even with "uniform paths".
// /// It is not recommended to import the constants directly into
// /// your namespace; import the module and use with the module name
// /// like `tag::DateTime`. The constant names follow the Exif
// /// specification but not the Rust naming conventions, and a user
// /// of the constants will get the non_upper_case_globals warning
// /// if a bare constant is used in a match arm.
// // This is discussed in
// // <https://github.com/rust-lang/rust/issues/25207>.
impl Tag {
$($(
$( #[$attr] )*
#[doc = $desc]
#[allow(non_upper_case_globals)]
pub const $name: Tag = Tag($ctx, $num);
)+)+
}
mod tag_info {
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 dyn fmt::Write, &Value) -> fmt::Result,
pub unit: Option<&'static [UnitPiece]>,
}
$($(
#[allow(non_upper_case_globals)]
pub static $name: TagInfo = TagInfo {
name: stringify!($name),
desc: $desc,
default: $defval,
dispval: super::$dispval,
unit: $unit,
};
)+)+
}
fn get_tag_info(tag: Tag) -> Option<&'static tag_info::TagInfo> {
match tag {
$($(
Tag::$name => Some(&tag_info::$name),
)+)+
_ => None,
}
}
)
}
// Tag constant names do not follow the Rust naming conventions but
// the Exif field names: camel cases and all-capital acronyms.
generate_well_known_tag_constants!(
// Exif-specific IFDs [EXIF23 4.6.3].
|Context::Tiff|
/// A pointer to the Exif IFD. This is used for the internal structure
/// of Exif data and will not be returned to the user.
#[doc(hidden)]
(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.
#[doc(hidden)]
(GPSInfoIFDPointer, 0x8825, DefaultValue::None, d_default,
unit![],
"GPS Info IFD pointer"),
|Context::Exif|
/// A pointer to the interoperability IFD. This is used for the internal
/// structure of Exif data and will not be returned to the user.
#[doc(hidden)]
(InteropIFDPointer, 0xa005, DefaultValue::None, d_default,
unit![],
"Interoperability IFD pointer"),
// TIFF primary and thumbnail attributes [EXIF23 4.6.4 Table 4,
// 4.6.8 Table 17, and 4.6.8 Table 21].
|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_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_optdecimal,
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"),
(CompositeImage, 0xa460, DefaultValue::Short(&[0]), d_cpstimg,
unit![],
"Composite image"),
(SourceImageNumberOfCompositeImage, 0xa461, DefaultValue::None, d_numcpstimg,
unit![],
"Source image number of composite image"),
(SourceExposureTimesOfCompositeImage, 0xa462, DefaultValue::None, d_default,
unit![],
"Source exposure times of composite image"),
(Gamma, 0xa500, DefaultValue::None, d_decimal,
unit![],
"Gamma"),
// GPS attributes [EXIF23 4.6.6 Table 15 and 4.6.8 Table 19].
|Context::Gps|
// 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]
// [DCF20 4.4.5.3, 4.5.4.3, and 4.6.4.3].
|Context::Interop|
(InteroperabilityIndex, 0x1, DefaultValue::None, d_default,
unit![],
"Interoperability identification"),
(InteroperabilityVersion, 0x2, DefaultValue::None, d_interopver,
unit![],
"Interoperability version"),
(RelatedImageFileFormat, 0x1000, DefaultValue::None, d_default,
unit![],
"Related image file format"),
(RelatedImageWidth, 0x1001, DefaultValue::None, d_default,
unit!["pixels"],
"Related image width"),
(RelatedImageLength, 0x1002, DefaultValue::None, d_default,
unit!["pixels"],
"Related image height"),
);
// For Value::display_as().
pub fn display_value_as<'a>(value: &'a Value, tag: Tag) -> value::Display<'a> {
match get_tag_info(tag) {
Some(ti) => value::Display { fmt: ti.dispval, value: value },
None => value::Display { fmt: d_default, value: value },
}
}
// Compression (TIFF 0x103)
fn d_compression(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(1) => "uncompressed",
Some(2) => "Modified Huffman",
Some(6) => "JPEG",
Some(32773) => "PackBits",
_ => return d_reserved(w, value, "compression"),
};
w.write_str(s)
}
// PhotometricInterpretation (TIFF 0x106)
fn d_photointp(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "white is zero",
Some(1) => "black is zero",
Some(2) => "RGB",
Some(3) => "palette color",
Some(4) => "transparency mask",
Some(6) => "YCbCr",
_ => return d_reserved(w, value, "photometric interpretation"),
};
w.write_str(s)
}
// Orientation (TIFF 0x112)
fn d_orientation(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(1) => "row 0 at top and column 0 at left",
Some(2) => "row 0 at top and column 0 at right",
Some(3) => "row 0 at bottom and column 0 at right",
Some(4) => "row 0 at bottom and column 0 at left",
Some(5) => "row 0 at left and column 0 at top",
Some(6) => "row 0 at right and column 0 at top",
Some(7) => "row 0 at right and column 0 at bottom",
Some(8) => "row 0 at left and column 0 at bottom",
_ => return d_reserved(w, value, "orientation"),
};
w.write_str(s)
}
// PlanarConfiguration (TIFF 0x11c)
fn d_planarcfg(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(1) => "chunky",
Some(2) => "planar",
_ => return d_reserved(w, value, "planar configuration"),
};
w.write_str(s)
}
// ResolutionUnit (TIFF 0x128)
// FocalPlaneResolutionUnit (Exif 0xa210)
fn d_resunit(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(1) => "no absolute unit",
Some(2) => "inch",
Some(3) => "cm",
_ => return d_reserved(w, value, "resolution unit"),
};
w.write_str(s)
}
// DateTime (TIFF 0x132), DateTimeOriginal (Exif 0x9003), and
// DateTimeDigitized (Exif 0x9004)
fn d_datetime(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
if let Some(dt) = value.ascii().and_then(|x| x.first()) {
match crate::tiff::DateTime::from_ascii(dt) {
Ok(dt) => return write!(w, "{}", dt),
Err(Error::BlankValue(_)) => return w.write_str("unknown"),
_ => {},
}
}
d_default(w, value)
}
// YCbCrSubSampling (TIFF 0x212)
fn d_ycbcrsubsamp(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
// 0 is used to go to d_default in the match below.
let horiz = value.get_uint(0).unwrap_or(0);
let vert = value.get_uint(1).unwrap_or(0);
let s = match (horiz, vert) {
(1, 1) => "full horizontally, full vertically (4:4:4)",
(2, 1) => "half horizontally, full vertically (4:2:2)",
(2, 2) => "half horizontally, half vertically (4:2:0)",
(4, 1) => "quarter horizontally, full vertically (4:1:1)",
(4, 2) => "quarter horizontally, half vertically",
(4, 4) => "quarter horizontally, quarter vertically",
_ => return d_default(w, value),
};
w.write_str(s)
}
// YCbCrPositioning (TIFF 0x213)
fn d_ycbcrpos(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(1) => "centered",
Some(2) => "co-sited",
_ => return d_reserved(w, value, "YCbCr positioning"),
};
w.write_str(s)
}
// ExposureTime (Exif 0x829a)
fn d_exptime(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
if let Some(et) = value.rational().and_then(|x| x.first()) {
if et.num >= et.denom {
return write!(w, "{}", et.to_f64());
} else if et.num != 0 {
return write!(w, "1/{}", et.denom as f64 / et.num as f64);
}
}
d_default(w, value)
}
// ExposureProgram (Exif 0x8822)
fn d_expprog(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "not defined",
Some(1) => "manual",
Some(2) => "normal program",
Some(3) => "aperture priority",
Some(4) => "shutter priority",
Some(5) => "creative program",
Some(6) => "action program",
Some(7) => "portrait mode",
Some(8) => "landscape mode",
_ => return d_reserved(w, value, "exposure program"),
};
w.write_str(s)
}
// SensitivityType (Exif 0x8830)
fn d_sensitivitytype(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "unknown",
Some(1) => "SOS",
Some(2) => "REI",
Some(3) => "ISO speed",
Some(4) => "SOS/REI",
Some(5) => "SOS/ISO speed",
Some(6) => "REI/ISO speed",
Some(7) => "SOS/REI/ISO speed",
_ => return d_reserved(w, value, "sensitivity type"),
};
w.write_str(s)
}
// ExifVersion (Exif 0x9000), FlashpixVersion (Exif 0xa000)
fn d_exifver(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
if let Some(s) = value.undefined().filter(|s| s.len() == 4) {
if let Ok(major) = atou16(&s[0..2]) {
if let Ok(minor) = atou16(&s[2..4]) {
if minor % 10 == 0 {
return write!(w, "{}.{}", major, minor / 10);
} else {
return write!(w, "{}.{:02}", major, minor);
}
}
}
}
d_default(w, value)
}
// ComponentsConfiguration (Exif 0x9101)
fn d_cpntcfg(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
match value.undefined() {
Some(s) => s.iter().try_for_each(|x| match x {
0 => w.write_char('_'),
1 => w.write_char('Y'),
2 => w.write_str("Cb"),
3 => w.write_str("Cr"),
4 => w.write_char('R'),
5 => w.write_char('G'),
6 => w.write_char('B'),
_ => w.write_char('?'),
}),
None => d_default(w, value),
}
}
// SubjectDistance (Exif 0x9206)
fn d_subjdist(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
if let Some(dist) = value.rational().and_then(|x| x.first()) {
if dist.num == 0 {
return w.write_str("unknown");
} else if dist.num == 0xffffffff {
return w.write_str("infinity");
}
}
d_decimal(w, value)
}
// MeteringMode (Exif 0x9207)
fn d_metering(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "unknown",
Some(1) => "average",
Some(2) => "center-weighted average",
Some(3) => "spot",
Some(4) => "multi-spot",
Some(5) => "pattern",
Some(6) => "partial",
Some(255) => "other",
_ => return d_reserved(w, value, "metering mode"),
};
w.write_str(s)
}
// LightSource (Exif 0x9208)
fn d_lightsrc(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "unknown",
Some(1) => "daylight",
Some(2) => "fluorescent",
Some(3) => "tungsten",
Some(4) => "flash",
Some(9) => "fine weather",
Some(10) => "cloudy weather",
Some(11) => "shade",
Some(12) => "daylight fluorescent (D 5700-7100K)",
Some(13) => "day white fluorescent (N 4600-5500K)",
Some(14) => "cool white fluorescent (W 3800-4500K)",
Some(15) => "white fluorescent (WW 3250-3800K)",
Some(16) => "warm white fluorescent (L 2600-3250K)",
Some(17) => "standard light A",
Some(18) => "standard light B",
Some(19) => "standard light C",
Some(20) => "D55",
Some(21) => "D65",
Some(22) => "D75",
Some(23) => "D50",
Some(24) => "ISO studio tungsten",
Some(255) => "other",
_ => return d_reserved(w, value, "light source"),
};
w.write_str(s)
}
// Flash (Exif 0x9209)
fn d_flash(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
const FIRED: &[&str] = &["not fired", "fired"];
const RETURN: &[&str] = &[
", no return light detection function",
", return light status 1 (reserved)",
", return light not detected",
", return light detected",
];
const AUTO: &[&str] = &[
", auto mode 0 (unknown)", ", forced", ", suppressed", ", auto"];
const FUNCTION: &[&str] = &["", ", no function present"];
const RED_EYE: &[&str] = &["", ", red-eye reduction"];
if let Some(v) = value.get_uint(0) {
write!(w, "{}{}{}{}{}{}",
FIRED[v as usize & 1],
RETURN[v as usize >> 1 & 3],
AUTO[v as usize >> 3 & 3],
FUNCTION[v as usize >> 5 & 1],
RED_EYE[v as usize >> 6 & 1],
if v >> 7 != 0 { ", unknown MSB bits" } else { "" })
} else {
d_default(w, value)
}
}
// SubjectArea (Exif 0x9214), SubjectLocation (Exif 0xa214)
// Only (x, y) case is valid for SubjectLocation.
fn d_subjarea(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
if let Some(x) = value.get_uint(0) {
if let Some(y) = value.get_uint(1) {
if let Some(d) = value.get_uint(2) {
if let Some(h) = value.get_uint(3) {
return write!(w, "rectangle (x={}, y={}, w={}, h={})",
x, y, d, h);
}
return write!(w, "circle (x={}, y={}, d={})", x, y, d);
}
return write!(w, "point (x={}, y={})", x, y);
}
}
d_default(w, value)
}
// Rational/SRational with 0xffffffff being unknown.
// BrightnessValue (Exif 0x9203),
// Temperature (Exif 0x9400), Humidity (Exif 0x9401),
// Pressure (Exif 0x9402), WaterDepth (Exif 0x9403),
// Acceleration (Exif 0x9404), CameraElevationAngle (Exif 0x9405)
fn d_optdecimal(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
match *value {
Value::Rational(ref v) if v.len() > 0 =>
if v[0].denom != 0xffffffff {
write!(w, "{}", v[0].to_f64())
} else {
w.write_str("unknown")
},
Value::SRational(ref v) if v.len() > 0 =>
if v[0].denom != -1 {
write!(w, "{}", v[0].to_f64())
} else {
w.write_str("unknown")
},
_ => d_decimal(w, value),
}
}
// ColorSpace (Exif 0xa001)
fn d_cspace(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(1) => "sRGB",
Some(0xffff) => "uncalibrated",
_ => return d_reserved(w, value, "color space"),
};
w.write_str(s)
}
// SensingMethod (Exif 0xa217)
fn d_sensingmethod(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(1) => "not defined",
Some(2) => "one-chip color area sensor",
Some(3) => "two-chip color area sensor",
Some(4) => "three-chip color area sensor",
Some(5) => "color sequential area sensor",
Some(7) => "trilinear sensor",
Some(8) => "color sequential linear sensor",
_ => return d_reserved(w, value, "sensing method"),
};
w.write_str(s)
}
// FileSource (Exif 0xa300)
fn d_filesrc(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.undefined().and_then(|x| x.first().copied()) {
Some(0) => "others",
Some(1) => "transparency scanner",
Some(2) => "reflective scanner",
Some(3) => "digital still camera",
_ => return d_reserved(w, value, "file source"),
};
w.write_str(s)
}
// SceneType (Exif 0xa301)
fn d_scenetype(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.undefined().and_then(|x| x.first().copied()) {
Some(1) => "directly photographed image",
_ => return d_reserved(w, value, "scene type"),
};
w.write_str(s)
}
// CustomRendered (Exif 0xa401)
fn d_customrendered(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "normal process",
Some(1) => "custom process",
_ => return d_reserved(w, value, "custom rendered"),
};
w.write_str(s)
}
// ExposureMode (Exif 0xa402)
fn d_expmode(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "auto exposure",
Some(1) => "manual exposure",
Some(2) => "auto bracket",
_ => return d_reserved(w, value, "exposure mode"),
};
w.write_str(s)
}
// WhiteBalance (Exif 0xa403)
fn d_whitebalance(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "auto white balance",
Some(1) => "manual white balance",
_ => return d_reserved(w, value, "white balance mode"),
};
w.write_str(s)
}
// DigitalZoomRatio (Exif 0xa404)
fn d_dzoomratio(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
if value.rational().and_then(|x| x.first()).map(|x| x.num) == Some(0) {
return w.write_str("unused");
}
d_decimal(w, value)
}
// FocalLengthIn35mmFilm (Exif 0xa405)
fn d_focallen35(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
match value.get_uint(0) {
Some(0) => w.write_str("unknown"),
_ => d_default(w, value),
}
}
// SceneCaptureType (Exif 0xa406)
fn d_scenecaptype(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "standard",
Some(1) => "landscape",
Some(2) => "portrait",
Some(3) => "night scene",
_ => return d_reserved(w, value, "scene capture type"),
};
w.write_str(s)
}
// GainControl (Exif 0xa407)
fn d_gainctrl(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "none",
Some(1) => "low gain up",
Some(2) => "high gain up",
Some(3) => "low gain down",
Some(4) => "high gain down",
_ => return d_reserved(w, value, "gain control"),
};
w.write_str(s)
}
// Contrast (Exif 0xa408)
fn d_contrast(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "normal",
Some(1) => "soft",
Some(2) => "hard",
_ => return d_reserved(w, value, "contrast processing"),
};
w.write_str(s)
}
// Saturation (Exif 0xa409)
fn d_saturation(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "normal",
Some(1) => "low saturation",
Some(2) => "high saturation",
_ => return d_reserved(w, value, "saturation processing"),
};
w.write_str(s)
}
// Sharpness (Exif 0xa40a)
fn d_sharpness(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "normal",
Some(1) => "soft",
Some(2) => "hard",
_ => return d_reserved(w, value, "sharpness processing"),
};
w.write_str(s)
}
// SubjectDistanceRange (Exif 0xa40c)
fn d_subjdistrange(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "unknown",
Some(1) => "macro",
Some(2) => "close view",
Some(3) => "distant view",
_ => return d_reserved(w, value, "subject distance range"),
};
w.write_str(s)
}
// LensSpecification (Exif 0xa432)
fn d_lensspec(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
match value.rational().and_then(|x| x.get(..4)) {
// There are several notations: "F1.4" in Japan, "f/1.4"
// in the U.S., and so on.
Some(s) => write!(w, "{}-{} mm, f/{}-{}",
s[0].to_f64(), s[1].to_f64(),
s[2].to_f64(), s[3].to_f64()),
_ => d_default(w, value),
}
}
// CompositeImage (Exif 0xa460)
fn d_cpstimg(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "unknown",
Some(1) => "non-composite",
Some(2) => "composite (general)",
Some(3) => "composite (at the moment of shooting)",
_ => return d_reserved(w, value, "composite image"),
};
w.write_str(s)
}
// SourceImageNumberOfCompositeImage (Exif 0xa461)
fn d_numcpstimg(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
match (value.get_uint(0), value.get_uint(1)) {
(Some(t), Some(u)) => write!(w, "total {}, used {}", t, u),
_ => d_default(w, value),
}
}
// GPSVersionID (GPS 0x0)
fn d_gpsver(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
match value.byte().and_then(|x| x.get(..4)) {
Some(s) => write!(w, "{}.{}.{}.{}", s[0], s[1], s[2], s[3]),
_ => d_default(w, value),
}
}
// GPSLatitudeRef (GPS 0x1), GPSLongitudeRef (GPS 0x3)
// GPSDestLatitudeRef (GPS 0x13), GPSDestLongitudeRef (GPS 0x15)
fn d_gpslatlongref(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
match value.ascii().and_then(|x| x.first()) {
Some([c]) if c.is_ascii_uppercase() => w.write_char(*c as char),
_ => d_default(w, value),
}
}
// GPSLatitude (GPS 0x2), GPSLongitude (GPS 0x4),
// GPSDestLatitude (GPS 0x14), GPSDestLongitude (GPS 0x16)
fn d_gpsdms(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
match value.rational().and_then(|x| x.get(..3)) {
Some(s) => write!(w, "{} deg {} min {} sec",
s[0].to_f64(), s[1].to_f64(), s[2].to_f64()),
_ => d_default(w, value),
}
}
// GPSAltitudeRef (GPS 0x5)
fn d_gpsaltref(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "above sea level",
Some(1) => "below sea level",
_ => return d_reserved(w, value, "GPS altitude ref"),
};
w.write_str(s)
}
// GPSTimeStamp (GPS 0x7)
fn d_gpstimestamp(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
match value.rational().and_then(|x| x.get(..3)) {
Some(s) => {
let (h, m, s) = (s[0].to_f64(), s[1].to_f64(), s[2].to_f64());
write!(w, "{}{}:{}{}:{}{}",
if h < 10.0 { "0" } else { "" }, h,
if m < 10.0 { "0" } else { "" }, m,
if s < 10.0 { "0" } else { "" }, s)
},
_ => d_default(w, value),
}
}
// GPSStatus (GPS 0x9)
fn d_gpsstatus(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.ascii().and_then(|x| x.first()) {
Some(b"A") => "measurement in progress",
Some(b"V") => "measurement interrupted",
_ => return d_reserved(w, value, "GPS status"),
};
w.write_str(s)
}
// GPSMeasureMode (GPS 0xa)
fn d_gpsmeasuremode(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.ascii().and_then(|x| x.first()) {
Some(b"2") => "2-dimensional measurement",
Some(b"3") => "3-dimensional measurement",
_ => return d_reserved(w, value, "GPS measurement mode"),
};
w.write_str(s)
}
// GPSSpeedRef (GPS 0xc)
fn d_gpsspeedref(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.ascii().and_then(|x| x.first()) {
Some(b"K") => "km/h",
Some(b"M") => "mph",
Some(b"N") => "knots",
_ => return d_reserved(w, value, "GPS speed ref"),
};
w.write_str(s)
}
// GPSTrackRef (GPS 0xe), GPSImgDirectionRef (GPS 0x10),
// GPSDestBearingRef (GPS 0x17)
fn d_gpsdirref(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.ascii().and_then(|x| x.first()) {
Some(b"T") => "true direction",
Some(b"M") => "magnetic direction",
_ => return d_reserved(w, value, "GPS direction ref"),
};
w.write_str(s)
}
// GPSDestDistanceRef (GPS 0x19)
fn d_gpsdistref(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.ascii().and_then(|x| x.first()) {
Some(b"K") => "km",
Some(b"M") => "miles",
Some(b"N") => "nautical miles",
_ => return d_reserved(w, value, "GPS distance ref"),
};
w.write_str(s)
}
// GPSDateStamp (GPS 0x1d)
fn d_gpsdatestamp(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
if let Some(data) = value.ascii().and_then(|x| x.first()) {
if data.len() >= 10 && data[4] == b':' && data[7] == b':' {
if let Ok(year) = atou16(&data[0..4]) {
if let Ok(month) = atou16(&data[5..7]) {
if let Ok(day) = atou16(&data[8..10]) {
return write!(w, "{:04}-{:02}-{:02}",
year, month, day);
}
}
}
}
}
d_default(w, value)
}
// GPSDifferential (GPS 0x1e)
fn d_gpsdifferential(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
let s = match value.get_uint(0) {
Some(0) => "no differential correction",
Some(1) => "differential correction applied",
_ => return d_reserved(w, value, "GPS differential correction"),
};
w.write_str(s)
}
// InteroperabilityVersion (Interoperability 0x2)
fn d_interopver(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
if let Some(s) = value.undefined().filter(|s| s.len() == 4) {
if let Ok(major) = atou16(&s[0..2]) {
if let Ok(minor) = atou16(&s[2..4]) {
return write!(w, "{}.{:02}", major, minor);
}
}
}
d_default(w, value)
}
fn d_ascii_in_undef(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
match *value {
Value::Undefined(ref v, _) => d_sub_ascii(w, v),
_ => d_default(w, value),
}
}
fn d_decimal(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
match *value {
Value::Rational(ref v) =>
d_sub_comma(w, v.iter().map(|x| x.to_f64())),
Value::SRational(ref v) =>
d_sub_comma(w, v.iter().map(|x| x.to_f64())),
_ => d_default(w, value),
}
}
#[inline(never)]
fn d_reserved(w: &mut dyn fmt::Write, value: &Value, name: &str)
-> fmt::Result {
write!(w, "[reserved {} ", name)?;
d_default(w, value)?;
w.write_char(']')
}
fn d_default(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
match *value {
Value::Byte(ref v) => d_sub_comma(w, v),
Value::Ascii(ref v) =>
d_sub_comma(w, v.iter().map(|x| AsciiDisplay(x))),
Value::Short(ref v) => d_sub_comma(w, v),
Value::Long(ref v) => d_sub_comma(w, v),
Value::Rational(ref v) => d_sub_comma(w, v),
Value::SByte(ref v) => d_sub_comma(w, v),
Value::Undefined(ref v, _) => d_sub_hex(w, v),
Value::SShort(ref v) => d_sub_comma(w, v),
Value::SLong(ref v) => d_sub_comma(w, v),
Value::SRational(ref v) => d_sub_comma(w, v),
Value::Float(ref v) => d_sub_comma(w, v),
Value::Double(ref v) => d_sub_comma(w, v),
Value::Unknown(t, c, o) =>
write!(w, "unknown value (type={}, count={}, offset={:#x})",
t, c, o),
}
}
fn d_sub_comma<I, T>(w: &mut dyn fmt::Write, itit: I) -> fmt::Result
where I: IntoIterator<Item = T>, T: fmt::Display {
let mut first = true;
for x in itit {
match first {
true => write!(w, "{}", x),
false => write!(w, ", {}", x),
}?;
first = false;
}
Ok(())
}
struct AsciiDisplay<'a>(&'a [u8]);
impl<'a> fmt::Display for AsciiDisplay<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
d_sub_ascii(f, self.0)
}
}
fn d_sub_hex(w: &mut dyn fmt::Write, bytes: &[u8]) -> fmt::Result {
w.write_str("0x")?;
for x in bytes {
write!(w, "{:02x}", x)?;
}
Ok(())
}
fn d_sub_ascii(w: &mut dyn fmt::Write, bytes: &[u8]) -> fmt::Result {
w.write_char('"')?;
for &c in bytes {
match c {
b'\\' | b'"' => {
w.write_char('\\')?;
w.write_char(c as char)?;
},
0x20..=0x7e => w.write_char(c as char)?,
_ => write!(w, "\\x{:02x}", c)?,
}
}
w.write_char('"')
}
#[cfg(test)]
mod tests {
use value::Rational;
use super::*;
// This test checks if Tag constants can be used in patterns.
#[test]
fn tag_constant_in_pattern() {
// Destructuring, which will always work.
match Tag(Context::Tiff, 0x132) {
Tag(Context::Tiff, 0x132) => {},
_ => panic!("failed to match Tag"),
}
// Matching against a constant. Test if this compiles.
match Tag(Context::Tiff, 0x132) {
Tag::DateTime => {},
_ => panic!("failed to match Tag"),
}
}
#[test]
fn default_value() {
assert_pat!(Tag::DateTime.default_value(), None);
match Tag::BitsPerSample.default_value() {
Some(Value::Short(v)) => assert_eq!(v, &[8, 8, 8]),
_ => panic!(),
}
match Tag::XResolution.default_value() {
Some(Value::Rational(v)) => {
assert_eq!(v.len(), 1);
assert_eq!(v[0].num, 72);
assert_eq!(v[0].denom, 1);
},
_ => panic!(),
}
match Tag::FileSource.default_value() {
Some(Value::Undefined(v, _)) => assert_eq!(v, &[3]),
_ => panic!(),
}
match Tag::GPSAltitudeRef.default_value() {
Some(Value::Byte(v)) => assert_eq!(v, &[0]),
_ => panic!(),
}
match Tag::GPSSpeedRef.default_value() {
Some(Value::Ascii(v)) => assert_eq!(v, &[b"K"]),
_ => panic!(),
}
}
#[test]
fn tag_fmt_display() {
let tag1 = Tag(Context::Tiff, 0x132);
assert_eq!(format!("{:15}", tag1), "DateTime ");
assert_eq!(format!("{:>15}", tag1), " DateTime");
assert_eq!(format!("{:5.6}", tag1), "DateTi");
let tag2 = Tag(Context::Exif, 0);
assert_eq!(format!("{:15}", tag2), "Tag(Exif, 0) ");
assert_eq!(format!("{:>15}", tag2), " Tag(Exif, 0)");
assert_eq!(format!("{:5.6}", tag2), "Tag(Ex");
}
#[test]
fn disp_val_sub() {
let mut buf = String::new();
d_sub_comma(&mut buf, &[0u16, 1, 2]).unwrap();
assert_eq!(buf, "0, 1, 2");
let mut buf = String::new();
d_sub_comma(&mut buf, &[Rational::from((3, 5))]).unwrap();
assert_eq!(buf, "3/5");
let mut buf = String::new();
let list = &[Rational::from((1, 2))];
d_sub_comma(&mut buf, list.iter().map(|x| x.to_f64())).unwrap();
assert_eq!(buf, "0.5");
let mut buf = String::new();
d_sub_hex(&mut buf, b"abc\x00\xff").unwrap();
assert_eq!(buf, "0x61626300ff");
let mut buf = String::new();
d_sub_ascii(&mut buf, b"a \"\\b\"\n").unwrap();
assert_eq!(buf, r#""a \"\\b\"\x0a""#);
}
}