use std::borrow::Cow;
use std::fmt;
use std::io;
use super::ifdformat::tag_value_eq;
use super::rational::*;
pub const EXIF_HEADER: &[u8] = &[b'E', b'x', b'i', b'f', 0x00, 0x00];
const INTEL_TIFF_HEADER: &[u8] = &[b'I', b'I', 0x2a, 0x00];
const MOTOROLA_TIFF_HEADER: &[u8] = &[b'M', b'M', 0x00, 0x2a];
const DATA_WIDTH: usize = 4;
#[derive(Debug, PartialEq)]
pub struct ExifData {
pub mime: &'static str,
pub entries: Vec<ExifEntry>,
pub le: bool,
}
impl ExifData {
pub fn new(mime: &'static str, entries: Vec<ExifEntry>, le: bool) -> Self {
ExifData {
mime,
entries,
le,
}
}
}
impl ExifData {
pub fn serialize(&self) -> Result<Vec<u8>, ExifError> {
let tiff_header = if self.le {
INTEL_TIFF_HEADER
} else {
MOTOROLA_TIFF_HEADER
};
let mut serialized = vec![];
serialized.extend(tiff_header);
let offset = if self.le {
(tiff_header.len() as u32 + std::mem::size_of::<u32>() as u32).to_le_bytes()
} else {
(tiff_header.len() as u32 + std::mem::size_of::<u32>() as u32).to_be_bytes()
};
serialized.extend(&offset);
let mut ifd0 = vec![];
let mut ifd1 = vec![];
let mut exif = vec![];
let mut gps = vec![];
for e in &self.entries {
match e.kind {
IfdKind::Ifd0 => ifd0.push(e),
IfdKind::Ifd1 => ifd1.push(e),
IfdKind::Exif => exif.push(e),
IfdKind::Gps => gps.push(e),
_ => {
},
}
}
assert!(ifd1.is_empty());
if self.le {
serialized.extend(&(ifd0.len() as u16).to_le_bytes());
} else {
serialized.extend(&(ifd0.len() as u16).to_be_bytes());
};
let mut exif_ifd_pointer = None;
let mut gps_ifd_pointer = None;
let mut data_patches = vec![];
for entry in ifd0 {
entry.ifd.serialize(&mut serialized, &mut data_patches)?;
if entry.tag == ExifTag::ExifOffset {
exif_ifd_pointer = Some(serialized.len() - DATA_WIDTH);
}
if entry.tag == ExifTag::GPSOffset {
gps_ifd_pointer = Some(serialized.len() - DATA_WIDTH);
}
}
if ifd1.is_empty() {
serialized.extend(&[0, 0, 0, 0]);
} else {
unimplemented!("IFD-1");
}
for patch in &data_patches {
let bytes = if self.le {
(serialized.len() as u32).to_le_bytes()
} else {
(serialized.len() as u32).to_be_bytes()
};
serialized.extend(&patch.data);
for (place, byte) in serialized.iter_mut().skip(patch.offset_pos as usize).zip(bytes.iter()) {
*place = *byte;
}
}
if !exif.is_empty() {
self.serialize_ifd(&mut serialized, exif, exif_ifd_pointer)?;
}
if !gps.is_empty() {
self.serialize_ifd(&mut serialized, gps, gps_ifd_pointer)?;
}
Ok(if self.mime == "image/jpeg" {
[EXIF_HEADER, &serialized].concat()
} else {
serialized
})
}
fn serialize_ifd(
&self,
serialized: &mut Vec<u8>,
entries: Vec<&ExifEntry>,
pos: Option<usize>,
) -> Result<(), ExifError> {
let bytes = if self.le {
(serialized.len() as u32).to_le_bytes()
} else {
(serialized.len() as u32).to_be_bytes()
};
if self.le {
serialized.extend(&(entries.len() as u16).to_le_bytes());
} else {
serialized.extend(&(entries.len() as u16).to_be_bytes());
}
let pos = pos.ok_or(ExifError::MissingExifOffset)?;
for (place, byte) in serialized.iter_mut().skip(pos).zip(bytes.iter()) {
*place = *byte;
}
let mut data_patches = vec![];
for entry in entries {
entry.ifd.serialize(serialized, &mut data_patches)?;
}
serialized.extend(&[0, 0, 0, 0]);
for patch in &data_patches {
let bytes = if self.le {
(serialized.len() as u32).to_le_bytes()
} else {
(serialized.len() as u32).to_be_bytes()
};
serialized.extend(&patch.data);
for (place, byte) in serialized.iter_mut().skip(patch.offset_pos as usize).zip(bytes.iter()) {
*place = *byte;
}
}
Ok(())
}
}
pub(super) struct Patch {
offset_pos: u32,
data: Vec<u8>,
}
impl Patch {
pub fn new(offset_pos: u32, data: &[u8]) -> Patch {
Patch {
offset_pos,
data: data.to_vec(),
}
}
}
#[derive(Debug)]
pub enum ExifError {
IoError(io::Error),
FileTypeUnknown,
JpegWithoutExif(String),
TiffTruncated,
TiffBadPreamble(String),
IfdTruncated,
ExifIfdTruncated(String),
ExifIfdEntryNotFound,
UnsupportedNamespace,
MissingExifOffset,
}
#[derive(Clone, Debug)]
pub struct IfdEntry {
pub namespace: Namespace,
pub tag: u16,
pub format: IfdFormat,
pub count: u32,
pub data: Vec<u8>,
pub ifd_data: Vec<u8>,
pub ext_data: Vec<u8>,
pub le: bool,
}
impl PartialEq for IfdEntry {
fn eq(&self, other: &IfdEntry) -> bool {
let data_eq = if self.in_ifd() && !self.tag == ExifTag::ExifOffset as u16 && !self.tag == ExifTag::GPSOffset as u16 {
self.data == other.data && self.ifd_data == other.ifd_data && self.ext_data == other.ext_data
} else {
true
};
self.namespace == other.namespace && self.tag == other.tag && self.count == other.count &&
data_eq && self.le == other.le
}
}
impl IfdEntry {
pub(crate) fn serialize(
&self,
serialized: &mut Vec<u8>,
data_patches: &mut Vec<Patch>,
) -> Result<(), ExifError> {
if self.namespace != Namespace::Standard {
return Err(ExifError::UnsupportedNamespace)
}
if self.le {
serialized.extend(&self.tag.to_le_bytes());
} else {
serialized.extend(&self.tag.to_be_bytes());
};
if self.le {
serialized.extend(&(self.format as u16).to_le_bytes());
} else {
serialized.extend(&(self.format as u16).to_be_bytes());
}
if self.le {
serialized.extend(&self.count.to_le_bytes());
} else {
serialized.extend(&self.count.to_be_bytes());
}
if self.in_ifd() {
serialized.extend(&self.data);
} else {
data_patches.push(Patch::new(serialized.len() as u32, &self.data));
serialized.extend(&[0, 0, 0, 0]);
}
Ok(())
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Namespace {
Standard = 0x0000,
Nikon = 0x0001,
Canon = 0x0002,
}
#[derive(Copy, Clone, Debug, PartialEq, Hash)]
pub enum ExifTag {
UnknownToMe = 0x0000_ffff,
ImageDescription = 0x0000_010e,
Make = 0x0000_010f,
Model = 0x0000_0110,
Orientation = 0x0000_0112,
XResolution = 0x0000_011a,
YResolution = 0x0000_011b,
ResolutionUnit = 0x0000_0128,
Software = 0x0000_0131,
DateTime = 0x0000_0132,
HostComputer = 0x0000_013c,
WhitePoint = 0x0000_013e,
PrimaryChromaticities = 0x0000_013f,
YCbCrCoefficients = 0x0000_0211,
ReferenceBlackWhite = 0x0000_0214,
Copyright = 0x0000_8298,
ExifOffset = 0x0000_8769,
GPSOffset = 0x0000_8825,
ExposureTime = 0x0000_829a,
FNumber = 0x0000_829d,
ExposureProgram = 0x0000_8822,
SpectralSensitivity = 0x0000_8824,
ISOSpeedRatings = 0x0000_8827,
OECF = 0x0000_8828,
SensitivityType = 0x0000_8830,
ExifVersion = 0x0000_9000,
DateTimeOriginal = 0x0000_9003,
DateTimeDigitized = 0x0000_9004,
ShutterSpeedValue = 0x0000_9201,
ApertureValue = 0x0000_9202,
BrightnessValue = 0x0000_9203,
ExposureBiasValue = 0x0000_9204,
MaxApertureValue = 0x0000_9205,
SubjectDistance = 0x0000_9206,
MeteringMode = 0x0000_9207,
LightSource = 0x0000_9208,
Flash = 0x0000_9209,
FocalLength = 0x0000_920a,
SubjectArea = 0x0000_9214,
MakerNote = 0x0000_927c,
UserComment = 0x0000_9286,
FlashPixVersion = 0x0000_a000,
ColorSpace = 0x0000_a001,
RelatedSoundFile = 0x0000_a004,
FlashEnergy = 0x0000_a20b,
FocalPlaneXResolution = 0x0000_a20e,
FocalPlaneYResolution = 0x0000_a20f,
FocalPlaneResolutionUnit = 0x0000_a210,
SubjectLocation = 0x0000_a214,
ExposureIndex = 0x0000_a215,
SensingMethod = 0x0000_a217,
FileSource = 0x0000_a300,
SceneType = 0x0000_a301,
CFAPattern = 0x0000_a302,
CustomRendered = 0x0000_a401,
ExposureMode = 0x0000_a402,
WhiteBalanceMode = 0x0000_a403,
DigitalZoomRatio = 0x0000_a404,
FocalLengthIn35mmFilm = 0x0000_a405,
SceneCaptureType = 0x0000_a406,
GainControl = 0x0000_a407,
Contrast = 0x0000_a408,
Saturation = 0x0000_a409,
Sharpness = 0x0000_a40a,
DeviceSettingDescription = 0x0000_a40b,
SubjectDistanceRange = 0x0000_a40c,
ImageUniqueID = 0x0000_a420,
LensSpecification = 0x0000_a432,
LensMake = 0x0000_a433,
LensModel = 0x0000_a434,
Gamma = 0xa500,
GPSVersionID = 0x00000,
GPSLatitudeRef = 0x00001,
GPSLatitude = 0x00002,
GPSLongitudeRef = 0x00003,
GPSLongitude = 0x00004,
GPSAltitudeRef = 0x00005,
GPSAltitude = 0x00006,
GPSTimeStamp = 0x00007,
GPSSatellites = 0x00008,
GPSStatus = 0x00009,
GPSMeasureMode = 0x0000a,
GPSDOP = 0x0000b,
GPSSpeedRef = 0x0000c,
GPSSpeed = 0x0000d,
GPSTrackRef = 0x0000e,
GPSTrack = 0x0000f,
GPSImgDirectionRef = 0x0000_0010,
GPSImgDirection = 0x0000_0011,
GPSMapDatum = 0x0000_0012,
GPSDestLatitudeRef = 0x0000_0013,
GPSDestLatitude = 0x0000_0014,
GPSDestLongitudeRef = 0x0000_0015,
GPSDestLongitude = 0x0000_0016,
GPSDestBearingRef = 0x0000_0017,
GPSDestBearing = 0x0000_0018,
GPSDestDistanceRef = 0x0000_0019,
GPSDestDistance = 0x0000_001a,
GPSProcessingMethod = 0x0000_001b,
GPSAreaInformation = 0x0000_001c,
GPSDateStamp = 0x0000_001d,
GPSDifferential = 0x0000_001e,
}
impl Eq for ExifTag {}
impl fmt::Display for ExifTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match *self {
ExifTag::ImageDescription => "Image Description",
ExifTag::Make => "Manufacturer",
ExifTag::HostComputer => "Host computer",
ExifTag::Model => "Model",
ExifTag::Orientation => "Orientation",
ExifTag::XResolution => "X Resolution",
ExifTag::YResolution => "Y Resolution",
ExifTag::ResolutionUnit => "Resolution Unit",
ExifTag::Software => "Software",
ExifTag::DateTime => "Image date",
ExifTag::WhitePoint => "White Point",
ExifTag::PrimaryChromaticities => "Primary Chromaticities",
ExifTag::YCbCrCoefficients => "YCbCr Coefficients",
ExifTag::ReferenceBlackWhite => "Reference Black/White",
ExifTag::Copyright => "Copyright",
ExifTag::ExifOffset => "This image has an Exif SubIFD",
ExifTag::GPSOffset => "This image has a GPS SubIFD",
ExifTag::ExposureTime => "Exposure time",
ExifTag::SensitivityType => "Sensitivity type",
ExifTag::FNumber => "Aperture",
ExifTag::ExposureProgram => "Exposure program",
ExifTag::SpectralSensitivity => "Spectral sensitivity",
ExifTag::ISOSpeedRatings => "ISO speed ratings",
ExifTag::OECF => "OECF",
ExifTag::ExifVersion => "Exif version",
ExifTag::DateTimeOriginal => "Date of original image",
ExifTag::DateTimeDigitized => "Date of image digitalization",
ExifTag::ShutterSpeedValue => "Shutter speed",
ExifTag::ApertureValue => "Aperture value",
ExifTag::BrightnessValue => "Brightness value",
ExifTag::ExposureBiasValue => "Exposure bias value",
ExifTag::MaxApertureValue => "Maximum aperture value",
ExifTag::SubjectDistance => "Subject distance",
ExifTag::MeteringMode => "Meteting mode",
ExifTag::LightSource => "Light source",
ExifTag::Flash => "Flash",
ExifTag::FocalLength => "Focal length",
ExifTag::SubjectArea => "Subject area",
ExifTag::MakerNote => "Maker note",
ExifTag::UserComment => "User comment",
ExifTag::FlashPixVersion => "Flashpix version",
ExifTag::ColorSpace => "Color space",
ExifTag::FlashEnergy => "Flash energy",
ExifTag::RelatedSoundFile => "Related sound file",
ExifTag::FocalPlaneXResolution => "Focal plane X resolution",
ExifTag::FocalPlaneYResolution => "Focal plane Y resolution",
ExifTag::FocalPlaneResolutionUnit => "Focal plane resolution unit",
ExifTag::SubjectLocation => "Subject location",
ExifTag::ExposureIndex => "Exposure index",
ExifTag::SensingMethod => "Sensing method",
ExifTag::FileSource => "File source",
ExifTag::SceneType => "Scene type",
ExifTag::CFAPattern => "CFA Pattern",
ExifTag::CustomRendered => "Custom rendered",
ExifTag::ExposureMode => "Exposure mode",
ExifTag::WhiteBalanceMode => "White balance mode",
ExifTag::DigitalZoomRatio => "Digital zoom ratio",
ExifTag::FocalLengthIn35mmFilm => "Equivalent focal length in 35mm",
ExifTag::SceneCaptureType => "Scene capture type",
ExifTag::GainControl => "Gain control",
ExifTag::Contrast => "Contrast",
ExifTag::Saturation => "Saturation",
ExifTag::Sharpness => "Sharpness",
ExifTag::LensSpecification => "Lens specification",
ExifTag::LensMake => "Lens manufacturer",
ExifTag::LensModel => "Lens model",
ExifTag::Gamma => "Gamma",
ExifTag::DeviceSettingDescription => "Device setting description",
ExifTag::SubjectDistanceRange => "Subject distance range",
ExifTag::ImageUniqueID => "Image unique ID",
ExifTag::GPSVersionID => "GPS version ID",
ExifTag::GPSLatitudeRef => "GPS latitude ref",
ExifTag::GPSLatitude => "GPS latitude",
ExifTag::GPSLongitudeRef => "GPS longitude ref",
ExifTag::GPSLongitude => "GPS longitude",
ExifTag::GPSAltitudeRef => "GPS altitude ref",
ExifTag::GPSAltitude => "GPS altitude",
ExifTag::GPSTimeStamp => "GPS timestamp",
ExifTag::GPSSatellites => "GPS satellites",
ExifTag::GPSStatus => "GPS status",
ExifTag::GPSMeasureMode => "GPS measure mode",
ExifTag::GPSDOP => "GPS Data Degree of Precision (DOP)",
ExifTag::GPSSpeedRef => "GPS speed ref",
ExifTag::GPSSpeed => "GPS speed",
ExifTag::GPSTrackRef => "GPS track ref",
ExifTag::GPSTrack => "GPS track",
ExifTag::GPSImgDirectionRef => "GPS image direction ref",
ExifTag::GPSImgDirection => "GPS image direction",
ExifTag::GPSMapDatum => "GPS map datum",
ExifTag::GPSDestLatitudeRef => "GPS destination latitude ref",
ExifTag::GPSDestLatitude => "GPS destination latitude",
ExifTag::GPSDestLongitudeRef => "GPS destination longitude ref",
ExifTag::GPSDestLongitude => "GPS destination longitude",
ExifTag::GPSDestBearingRef => "GPS destination bearing ref",
ExifTag::GPSDestBearing => "GPS destination bearing",
ExifTag::GPSDestDistanceRef => "GPS destination distance ref",
ExifTag::GPSDestDistance => "GPS destination distance",
ExifTag::GPSProcessingMethod => "GPS processing method",
ExifTag::GPSAreaInformation => "GPS area information",
ExifTag::GPSDateStamp => "GPS date stamp",
ExifTag::GPSDifferential => "GPS differential",
ExifTag::UnknownToMe => "Unknown to this library, or manufacturer-specific",
}
)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum IfdFormat {
Unknown = 0,
U8 = 1,
Ascii = 2,
U16 = 3,
U32 = 4,
URational = 5,
I8 = 6,
Undefined = 7,
I16 = 8,
I32 = 9,
IRational = 10,
F32 = 11,
F64 = 12,
}
#[derive(Clone, Debug)]
pub struct ExifEntry {
pub namespace: Namespace,
pub ifd: IfdEntry,
pub tag: ExifTag,
pub value: TagValue,
pub unit: Cow<'static, str>,
pub value_more_readable: Cow<'static, str>,
pub kind: IfdKind,
}
impl PartialEq for ExifEntry {
fn eq(&self, other: &ExifEntry) -> bool {
let value_eq = match self.tag {
ExifTag::ExifOffset | ExifTag::GPSOffset => true,
_ => {
self.value_more_readable == other.value_more_readable && tag_value_eq(&self.value, &other.value)
},
};
self.namespace == other.namespace && self.ifd == other.ifd && self.tag == other.tag &&
self.unit == other.unit && self.kind == other.kind && value_eq
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum TagValue {
U8(Vec<u8>),
Ascii(String),
U16(Vec<u16>),
U32(Vec<u32>),
URational(Vec<URational>),
I8(Vec<i8>),
Undefined(Vec<u8>, bool),
I16(Vec<i16>),
I32(Vec<i32>),
IRational(Vec<IRational>),
F32(Vec<f32>),
F64(Vec<f64>),
Unknown(Vec<u8>, bool),
Invalid(Vec<u8>, bool, u16, u32),
}
impl TagValue {
pub fn to_i64(&self, index: usize) -> Option<i64> {
match *self {
TagValue::U8(ref v) => v.get(index).cloned().map(From::from),
TagValue::U16(ref v) => v.get(index).cloned().map(From::from),
TagValue::U32(ref v) => v.get(index).cloned().map(From::from),
TagValue::I8(ref v) => v.get(index).cloned().map(From::from),
TagValue::I16(ref v) => v.get(index).cloned().map(From::from),
TagValue::I32(ref v) => v.get(index).cloned().map(From::from),
_ => None,
}
}
pub fn to_f64(&self, index: usize) -> Option<f64> {
match *self {
TagValue::U8(ref v) => v.get(index).cloned().map(From::from),
TagValue::U16(ref v) => v.get(index).cloned().map(From::from),
TagValue::U32(ref v) => v.get(index).cloned().map(From::from),
TagValue::I8(ref v) => v.get(index).cloned().map(From::from),
TagValue::I16(ref v) => v.get(index).cloned().map(From::from),
TagValue::I32(ref v) => v.get(index).cloned().map(From::from),
TagValue::F32(ref v) => v.get(index).cloned().map(From::from),
TagValue::F64(ref v) => v.get(index).cloned().map(From::from),
TagValue::IRational(ref v) => v.get(index).cloned().map(|v| v.value()),
TagValue::URational(ref v) => v.get(index).cloned().map(|v| v.value()),
_ => None,
}
}
}
pub type ExifResult = Result<ExifData, ExifError>;
pub type ExifEntryResult = Result<Vec<ExifEntry>, ExifError>;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum IfdKind {
Ifd0,
Ifd1,
Exif,
Gps,
Makernote,
Interoperability,
}