use std::fmt;
use crate::error::Error;
use crate::value;
use crate::value::Value;
use crate::util::atou16;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Tag(pub Context, pub u16);
impl Tag {
#[inline]
pub fn context(self) -> Context {
self.0
}
#[inline]
pub fn number(self) -> u16 {
self.1
}
#[inline]
pub fn description(&self) -> Option<&str> {
get_tag_info(*self).map(|ti| ti.desc)
}
#[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)),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum Context {
Tiff, Exif, Gps, Interop, }
#[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| $(
$( #[$attr:meta] )*
($name:ident, $num:expr, $defval:expr, $dispval:ident, $unit:expr,
$desc:expr)
),+, )+
) => (
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,
}
}
)
}
generate_well_known_tag_constants!(
|Context::Tiff|
#[doc(hidden)]
(ExifIFDPointer, 0x8769, DefaultValue::None, d_default,
unit![],
"Exif IFD pointer"),
#[doc(hidden)]
(GPSInfoIFDPointer, 0x8825, DefaultValue::None, d_default,
unit![],
"GPS Info IFD pointer"),
|Context::Exif|
#[doc(hidden)]
(InteropIFDPointer, 0xa005, DefaultValue::None, d_default,
unit![],
"Interoperability IFD pointer"),
|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"),
(TileOffsets, 0x144, DefaultValue::None, d_default,
unit![],
"Tiled image data location"),
(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"),
|Context::Exif|
(ExposureTime, 0x829a, DefaultValue::None, d_exptime,
unit!["s"],
"Exposure time"),
(FNumber, 0x829d, DefaultValue::None, d_decimal,
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"),
(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"),
|Context::Gps|
(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"),
|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"),
);
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 },
}
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
fn d_ycbcrsubsamp(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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),
}
}
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)
}
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)
}
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)
}
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)
}
}
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)
}
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),
}
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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),
}
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
fn d_lensspec(w: &mut dyn fmt::Write, value: &Value) -> fmt::Result {
match value.rational().and_then(|x| x.get(..4)) {
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),
}
}
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)
}
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),
}
}
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),
}
}
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),
}
}
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),
}
}
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)
}
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),
}
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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)
}
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::*;
#[test]
fn tag_constant_in_pattern() {
match Tag(Context::Tiff, 0x132) {
Tag(Context::Tiff, 0x132) => {},
_ => panic!("failed to match Tag"),
}
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""#);
}
}