1555 lines
53 KiB
Rust
1555 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] )*
|
|
#[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""#);
|
|
}
|
|
}
|