1use super::ifdformat::tag_value_eq;
2use super::rational::{IRational, URational};
3use std::borrow::Cow;
4use std::{fmt, io};
5
6pub const EXIF_HEADER: &[u8] = &[b'E', b'x', b'i', b'f', 0x00, 0x00];
8const INTEL_TIFF_HEADER: &[u8] = &[b'I', b'I', 0x2a, 0x00];
9const MOTOROLA_TIFF_HEADER: &[u8] = &[b'M', b'M', 0x00, 0x2a];
10const DATA_WIDTH: usize = 4;
11
12#[derive(Debug, PartialEq)]
14pub struct ExifData {
15 pub mime: &'static str,
17 pub entries: Vec<ExifEntry>,
19 pub le: bool,
21}
22
23impl ExifData {
24 #[must_use]
25 pub fn new(mime: &'static str, entries: Vec<ExifEntry>, le: bool) -> Self {
26 Self { mime, entries, le }
27 }
28}
29
30impl ExifData {
31 pub fn serialize(&self) -> Result<Vec<u8>, ExifError> {
36 let tiff_header = if self.le {
38 INTEL_TIFF_HEADER
39 } else {
40 MOTOROLA_TIFF_HEADER
41 };
42
43 let mut serialized = vec![];
45
46 serialized.extend(tiff_header);
48
49 let offset = if self.le {
52 (tiff_header.len() as u32 + std::mem::size_of::<u32>() as u32).to_le_bytes()
53 } else {
54 (tiff_header.len() as u32 + std::mem::size_of::<u32>() as u32).to_be_bytes()
55 };
56 serialized.extend(&offset);
57
58 let mut ifd0 = vec![];
59 let mut ifd1 = vec![];
60 let mut exif = vec![];
61 let mut gps = vec![];
62
63 for e in &self.entries {
64 match e.kind {
65 IfdKind::Ifd0 => ifd0.push(e),
66 IfdKind::Ifd1 => ifd1.push(e),
67 IfdKind::Exif => exif.push(e),
68 IfdKind::Gps => gps.push(e),
69 _ => {
70 },
72 }
73 }
74
75 assert!(ifd1.is_empty());
78
79 if self.le {
81 serialized.extend(&(ifd0.len() as u16).to_le_bytes());
82 } else {
83 serialized.extend(&(ifd0.len() as u16).to_be_bytes());
84 };
85
86 let mut exif_ifd_pointer = None;
88
89 let mut gps_ifd_pointer = None;
91
92 let mut data_patches = vec![];
95 for entry in ifd0 {
96 entry.ifd.serialize(&mut serialized, &mut data_patches)?;
97
98 if entry.tag == ExifTag::ExifOffset {
104 exif_ifd_pointer = Some(serialized.len() - DATA_WIDTH);
105 }
106 if entry.tag == ExifTag::GPSOffset {
107 gps_ifd_pointer = Some(serialized.len() - DATA_WIDTH);
108 }
109 }
110
111 if ifd1.is_empty() {
112 serialized.extend(&[0, 0, 0, 0]);
113 } else {
114 unimplemented!("IFD-1");
117 }
118
119 for patch in &data_patches {
121 let bytes = if self.le {
123 (serialized.len() as u32).to_le_bytes()
124 } else {
125 (serialized.len() as u32).to_be_bytes()
126 };
127
128 serialized.extend(patch.data);
129 for (place, byte) in serialized.iter_mut().skip(patch.offset_pos as usize).zip(bytes.iter()) {
130 *place = *byte;
131 }
132 }
133
134 if !exif.is_empty() {
135 self.serialize_ifd(&mut serialized, exif, exif_ifd_pointer)?;
136 }
137
138 if !gps.is_empty() {
139 self.serialize_ifd(&mut serialized, gps, gps_ifd_pointer)?;
140 }
141
142 Ok(if self.mime == "image/jpeg" {
145 [EXIF_HEADER, &serialized].concat()
146 } else {
147 serialized
148 })
149 }
150
151 fn serialize_ifd(
153 &self,
154 serialized: &mut Vec<u8>,
155 entries: Vec<&ExifEntry>,
156 pos: Option<usize>,
157 ) -> Result<(), ExifError> {
158 let bytes = if self.le {
159 (serialized.len() as u32).to_le_bytes()
160 } else {
161 (serialized.len() as u32).to_be_bytes()
162 };
163
164 if self.le {
166 serialized.extend(&(entries.len() as u16).to_le_bytes());
167 } else {
168 serialized.extend(&(entries.len() as u16).to_be_bytes());
169 }
170
171 let pos = pos.ok_or(ExifError::MissingExifOffset)?;
173 for (place, byte) in serialized.iter_mut().skip(pos).zip(bytes.iter()) {
174 *place = *byte;
175 }
176
177 let mut data_patches = vec![];
178
179 for entry in entries {
180 entry.ifd.serialize(serialized, &mut data_patches)?;
181 }
182
183 serialized.extend(&[0, 0, 0, 0]);
184 for patch in &data_patches {
185 let bytes = if self.le {
187 (serialized.len() as u32).to_le_bytes()
188 } else {
189 (serialized.len() as u32).to_be_bytes()
190 };
191 serialized.extend(patch.data);
192 for (place, byte) in serialized.iter_mut().skip(patch.offset_pos as usize).zip(bytes.iter()) {
193 *place = *byte;
194 }
195 }
196 Ok(())
197 }
198}
199
200pub(super) struct Patch<'a> {
201 offset_pos: u32,
203 data: &'a [u8],
205}
206
207impl Patch<'_> {
208 #[must_use]
209 pub const fn new(offset_pos: u32, data: &[u8]) -> Patch {
210 Patch {
211 offset_pos,
212 data,
213 }
214 }
215}
216
217#[derive(Debug)]
219pub enum ExifError {
220 IoError(io::Error),
221 FileTypeUnknown,
222 JpegWithoutExif(String),
223 TiffTruncated,
224 TiffBadPreamble(String),
225 IfdTruncated,
226 ExifIfdTruncated(String),
227 ExifIfdEntryNotFound,
228 UnsupportedNamespace,
229 MissingExifOffset,
230}
231
232#[derive(Clone, Debug)]
234pub struct IfdEntry {
235 pub namespace: Namespace,
239 pub tag: u16,
241 pub format: IfdFormat,
243 pub count: u32,
245 pub data: Vec<u8>,
248 pub ifd_data: Vec<u8>,
251 pub ext_data: Vec<u8>,
254 pub le: bool,
260}
261
262impl PartialEq for IfdEntry {
266 fn eq(&self, other: &Self) -> bool {
267 let data_eq = if self.in_ifd() && !self.tag == ExifTag::ExifOffset as u16 && !self.tag == ExifTag::GPSOffset as u16 {
268 self.data == other.data && self.ifd_data == other.ifd_data && self.ext_data == other.ext_data
269 } else {
270 true
271 };
272
273 self.namespace == other.namespace && self.tag == other.tag && self.count == other.count &&
274 data_eq && self.le == other.le
275 }
276}
277
278impl IfdEntry {
279 pub(crate) fn serialize<'a>(
280 &'a self,
281 serialized: &mut Vec<u8>,
282 data_patches: &mut Vec<Patch<'a>>,
283 ) -> Result<(), ExifError> {
284 if self.namespace != Namespace::Standard {
286 return Err(ExifError::UnsupportedNamespace);
287 }
288
289 if self.le {
291 serialized.extend(&self.tag.to_le_bytes());
292 } else {
293 serialized.extend(&self.tag.to_be_bytes());
294 };
295
296 if self.le {
298 serialized.extend(&(self.format as u16).to_le_bytes());
299 } else {
300 serialized.extend(&(self.format as u16).to_be_bytes());
301 }
302
303 if self.le {
305 serialized.extend(&self.count.to_le_bytes());
306 } else {
307 serialized.extend(&self.count.to_be_bytes());
308 }
309
310 if self.in_ifd() {
312 serialized.extend(&self.data);
313 } else {
314 data_patches.push(Patch::new(serialized.len() as u32, &self.data));
315 serialized.extend(&[0, 0, 0, 0]);
317 }
318 Ok(())
319 }
320}
321
322#[derive(Copy, Clone, Debug, PartialEq)]
326pub enum Namespace {
327 Standard = 0x0000,
328 Nikon = 0x0001,
329 Canon = 0x0002,
330}
331
332#[derive(Copy, Clone, Debug, PartialEq, Hash)]
343#[repr(u32)]
344pub enum ExifTag {
345 UnknownToMe = 0x0000_ffff,
348 ImageDescription = 0x0000_010e,
349 Make = 0x0000_010f,
350 Model = 0x0000_0110,
351 Orientation = 0x0000_0112,
352 XResolution = 0x0000_011a,
353 YResolution = 0x0000_011b,
354 ResolutionUnit = 0x0000_0128,
355 Software = 0x0000_0131,
356 DateTime = 0x0000_0132,
357 HostComputer = 0x0000_013c,
358 WhitePoint = 0x0000_013e,
359 PrimaryChromaticities = 0x0000_013f,
360 YCbCrCoefficients = 0x0000_0211,
361 ReferenceBlackWhite = 0x0000_0214,
362 Copyright = 0x0000_8298,
363 ExifOffset = 0x0000_8769,
364 GPSOffset = 0x0000_8825,
365
366 ExposureTime = 0x0000_829a,
367 FNumber = 0x0000_829d,
368 ExposureProgram = 0x0000_8822,
369 SpectralSensitivity = 0x0000_8824,
370 ISOSpeedRatings = 0x0000_8827,
371 OECF = 0x0000_8828,
372 SensitivityType = 0x0000_8830,
373 ExifVersion = 0x0000_9000,
374 DateTimeOriginal = 0x0000_9003,
375 DateTimeDigitized = 0x0000_9004,
376 ShutterSpeedValue = 0x0000_9201,
377 ApertureValue = 0x0000_9202,
378 BrightnessValue = 0x0000_9203,
379 ExposureBiasValue = 0x0000_9204,
380 MaxApertureValue = 0x0000_9205,
381 SubjectDistance = 0x0000_9206,
382 MeteringMode = 0x0000_9207,
383 LightSource = 0x0000_9208,
384 Flash = 0x0000_9209,
385 FocalLength = 0x0000_920a,
386 SubjectArea = 0x0000_9214,
387 MakerNote = 0x0000_927c,
388 UserComment = 0x0000_9286,
389 FlashPixVersion = 0x0000_a000,
390 ColorSpace = 0x0000_a001,
391 RelatedSoundFile = 0x0000_a004,
392 FlashEnergy = 0x0000_a20b,
393 FocalPlaneXResolution = 0x0000_a20e,
394 FocalPlaneYResolution = 0x0000_a20f,
395 FocalPlaneResolutionUnit = 0x0000_a210,
396 SubjectLocation = 0x0000_a214,
397 ExposureIndex = 0x0000_a215,
398 SensingMethod = 0x0000_a217,
399 FileSource = 0x0000_a300,
400 SceneType = 0x0000_a301,
401 CFAPattern = 0x0000_a302,
402 CustomRendered = 0x0000_a401,
403 ExposureMode = 0x0000_a402,
404 WhiteBalanceMode = 0x0000_a403,
405 DigitalZoomRatio = 0x0000_a404,
406 FocalLengthIn35mmFilm = 0x0000_a405,
407 SceneCaptureType = 0x0000_a406,
408 GainControl = 0x0000_a407,
409 Contrast = 0x0000_a408,
410 Saturation = 0x0000_a409,
411 Sharpness = 0x0000_a40a,
412 DeviceSettingDescription = 0x0000_a40b,
413 SubjectDistanceRange = 0x0000_a40c,
414 ImageUniqueID = 0x0000_a420,
415 LensSpecification = 0x0000_a432,
416 LensMake = 0x0000_a433,
417 LensModel = 0x0000_a434,
418 Gamma = 0xa500,
419
420 GPSVersionID = 0x00000,
421 GPSLatitudeRef = 0x00001,
422 GPSLatitude = 0x00002,
423 GPSLongitudeRef = 0x00003,
424 GPSLongitude = 0x00004,
425 GPSAltitudeRef = 0x00005,
426 GPSAltitude = 0x00006,
427 GPSTimeStamp = 0x00007,
428 GPSSatellites = 0x00008,
429 GPSStatus = 0x00009,
430 GPSMeasureMode = 0x0000a,
431 GPSDOP = 0x0000b,
432 GPSSpeedRef = 0x0000c,
433 GPSSpeed = 0x0000d,
434 GPSTrackRef = 0x0000e,
435 GPSTrack = 0x0000f,
436 GPSImgDirectionRef = 0x0000_0010,
437 GPSImgDirection = 0x0000_0011,
438 GPSMapDatum = 0x0000_0012,
439 GPSDestLatitudeRef = 0x0000_0013,
440 GPSDestLatitude = 0x0000_0014,
441 GPSDestLongitudeRef = 0x0000_0015,
442 GPSDestLongitude = 0x0000_0016,
443 GPSDestBearingRef = 0x0000_0017,
444 GPSDestBearing = 0x0000_0018,
445 GPSDestDistanceRef = 0x0000_0019,
446 GPSDestDistance = 0x0000_001a,
447 GPSProcessingMethod = 0x0000_001b,
448 GPSAreaInformation = 0x0000_001c,
449 GPSDateStamp = 0x0000_001d,
450 GPSDifferential = 0x0000_001e,
451}
452
453impl Eq for ExifTag {}
454
455impl fmt::Display for ExifTag {
456 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
457 write!(
458 f,
459 "{}",
460 match *self {
461 ExifTag::ImageDescription => "Image Description",
462 ExifTag::Make => "Manufacturer",
463 ExifTag::HostComputer => "Host computer",
464 ExifTag::Model => "Model",
465 ExifTag::Orientation => "Orientation",
466 ExifTag::XResolution => "X Resolution",
467 ExifTag::YResolution => "Y Resolution",
468 ExifTag::ResolutionUnit => "Resolution Unit",
469 ExifTag::Software => "Software",
470 ExifTag::DateTime => "Image date",
471 ExifTag::WhitePoint => "White Point",
472 ExifTag::PrimaryChromaticities => "Primary Chromaticities",
473 ExifTag::YCbCrCoefficients => "YCbCr Coefficients",
474 ExifTag::ReferenceBlackWhite => "Reference Black/White",
475 ExifTag::Copyright => "Copyright",
476 ExifTag::ExifOffset => "This image has an Exif SubIFD",
477 ExifTag::GPSOffset => "This image has a GPS SubIFD",
478 ExifTag::ExposureTime => "Exposure time",
479 ExifTag::SensitivityType => "Sensitivity type",
480 ExifTag::FNumber => "Aperture",
481 ExifTag::ExposureProgram => "Exposure program",
482 ExifTag::SpectralSensitivity => "Spectral sensitivity",
483 ExifTag::ISOSpeedRatings => "ISO speed ratings",
484 ExifTag::OECF => "OECF",
485 ExifTag::ExifVersion => "Exif version",
486 ExifTag::DateTimeOriginal => "Date of original image",
487 ExifTag::DateTimeDigitized => "Date of image digitalization",
488 ExifTag::ShutterSpeedValue => "Shutter speed",
489 ExifTag::ApertureValue => "Aperture value",
490 ExifTag::BrightnessValue => "Brightness value",
491 ExifTag::ExposureBiasValue => "Exposure bias value",
492 ExifTag::MaxApertureValue => "Maximum aperture value",
493 ExifTag::SubjectDistance => "Subject distance",
494 ExifTag::MeteringMode => "Meteting mode",
495 ExifTag::LightSource => "Light source",
496 ExifTag::Flash => "Flash",
497 ExifTag::FocalLength => "Focal length",
498 ExifTag::SubjectArea => "Subject area",
499 ExifTag::MakerNote => "Maker note",
500 ExifTag::UserComment => "User comment",
501 ExifTag::FlashPixVersion => "Flashpix version",
502 ExifTag::ColorSpace => "Color space",
503 ExifTag::FlashEnergy => "Flash energy",
504 ExifTag::RelatedSoundFile => "Related sound file",
505 ExifTag::FocalPlaneXResolution => "Focal plane X resolution",
506 ExifTag::FocalPlaneYResolution => "Focal plane Y resolution",
507 ExifTag::FocalPlaneResolutionUnit => "Focal plane resolution unit",
508 ExifTag::SubjectLocation => "Subject location",
509 ExifTag::ExposureIndex => "Exposure index",
510 ExifTag::SensingMethod => "Sensing method",
511 ExifTag::FileSource => "File source",
512 ExifTag::SceneType => "Scene type",
513 ExifTag::CFAPattern => "CFA Pattern",
514 ExifTag::CustomRendered => "Custom rendered",
515 ExifTag::ExposureMode => "Exposure mode",
516 ExifTag::WhiteBalanceMode => "White balance mode",
517 ExifTag::DigitalZoomRatio => "Digital zoom ratio",
518 ExifTag::FocalLengthIn35mmFilm => "Equivalent focal length in 35mm",
519 ExifTag::SceneCaptureType => "Scene capture type",
520 ExifTag::GainControl => "Gain control",
521 ExifTag::Contrast => "Contrast",
522 ExifTag::Saturation => "Saturation",
523 ExifTag::Sharpness => "Sharpness",
524 ExifTag::LensSpecification => "Lens specification",
525 ExifTag::LensMake => "Lens manufacturer",
526 ExifTag::LensModel => "Lens model",
527 ExifTag::Gamma => "Gamma",
528 ExifTag::DeviceSettingDescription => "Device setting description",
529 ExifTag::SubjectDistanceRange => "Subject distance range",
530 ExifTag::ImageUniqueID => "Image unique ID",
531 ExifTag::GPSVersionID => "GPS version ID",
532 ExifTag::GPSLatitudeRef => "GPS latitude ref",
533 ExifTag::GPSLatitude => "GPS latitude",
534 ExifTag::GPSLongitudeRef => "GPS longitude ref",
535 ExifTag::GPSLongitude => "GPS longitude",
536 ExifTag::GPSAltitudeRef => "GPS altitude ref",
537 ExifTag::GPSAltitude => "GPS altitude",
538 ExifTag::GPSTimeStamp => "GPS timestamp",
539 ExifTag::GPSSatellites => "GPS satellites",
540 ExifTag::GPSStatus => "GPS status",
541 ExifTag::GPSMeasureMode => "GPS measure mode",
542 ExifTag::GPSDOP => "GPS Data Degree of Precision (DOP)",
543 ExifTag::GPSSpeedRef => "GPS speed ref",
544 ExifTag::GPSSpeed => "GPS speed",
545 ExifTag::GPSTrackRef => "GPS track ref",
546 ExifTag::GPSTrack => "GPS track",
547 ExifTag::GPSImgDirectionRef => "GPS image direction ref",
548 ExifTag::GPSImgDirection => "GPS image direction",
549 ExifTag::GPSMapDatum => "GPS map datum",
550 ExifTag::GPSDestLatitudeRef => "GPS destination latitude ref",
551 ExifTag::GPSDestLatitude => "GPS destination latitude",
552 ExifTag::GPSDestLongitudeRef => "GPS destination longitude ref",
553 ExifTag::GPSDestLongitude => "GPS destination longitude",
554 ExifTag::GPSDestBearingRef => "GPS destination bearing ref",
555 ExifTag::GPSDestBearing => "GPS destination bearing",
556 ExifTag::GPSDestDistanceRef => "GPS destination distance ref",
557 ExifTag::GPSDestDistance => "GPS destination distance",
558 ExifTag::GPSProcessingMethod => "GPS processing method",
559 ExifTag::GPSAreaInformation => "GPS area information",
560 ExifTag::GPSDateStamp => "GPS date stamp",
561 ExifTag::GPSDifferential => "GPS differential",
562 ExifTag::UnknownToMe => "Unknown to this library, or manufacturer-specific",
563 }
564 )
565 }
566}
567
568#[derive(Copy, Clone, Debug, PartialEq)]
573pub enum IfdFormat {
574 Unknown = 0,
575 U8 = 1,
576 Ascii = 2,
577 U16 = 3,
578 U32 = 4,
579 URational = 5,
580 I8 = 6,
581 Undefined = 7, I16 = 8,
583 I32 = 9,
584 IRational = 10,
585 F32 = 11,
586 F64 = 12,
587}
588
589#[derive(Clone, Debug)]
591pub struct ExifEntry {
592 pub namespace: Namespace,
596 pub ifd: IfdEntry,
599 pub tag: ExifTag,
603 pub value: TagValue,
605 pub unit: Cow<'static, str>,
614 pub value_more_readable: Cow<'static, str>,
619 pub kind: IfdKind,
620}
621
622impl PartialEq for ExifEntry {
623 fn eq(&self, other: &Self) -> bool {
624 let value_eq = match self.tag {
627 ExifTag::ExifOffset | ExifTag::GPSOffset => true,
628 _ => {
629 self.value_more_readable == other.value_more_readable && tag_value_eq(&self.value, &other.value)
630 },
631 };
632
633 self.namespace == other.namespace && self.ifd == other.ifd && self.tag == other.tag &&
634 self.unit == other.unit && self.kind == other.kind && value_eq
635 }
636}
637
638#[derive(Clone, Debug, PartialEq)]
642pub enum TagValue {
643 U8(Vec<u8>),
645 Ascii(String),
647 U16(Vec<u16>),
648 U32(Vec<u32>),
649 URational(Vec<URational>),
651 I8(Vec<i8>),
652 Undefined(Vec<u8>, bool),
662 I16(Vec<i16>),
663 I32(Vec<i32>),
664 IRational(Vec<IRational>),
666 F32(Vec<f32>),
668 F64(Vec<f64>),
670 Unknown(Vec<u8>, bool),
680 Invalid(Vec<u8>, bool, u16, u32),
684}
685
686impl TagValue {
687 pub fn to_i64(&self, index: usize) -> Option<i64> {
690 match *self {
691 TagValue::U8(ref v) => v.get(index).copied().map(From::from),
692 TagValue::U16(ref v) => v.get(index).copied().map(From::from),
693 TagValue::U32(ref v) => v.get(index).copied().map(From::from),
694 TagValue::I8(ref v) => v.get(index).copied().map(From::from),
695 TagValue::I16(ref v) => v.get(index).copied().map(From::from),
696 TagValue::I32(ref v) => v.get(index).copied().map(From::from),
697 _ => None,
698 }
699 }
700
701 pub fn to_f64(&self, index: usize) -> Option<f64> {
704 match *self {
705 TagValue::U8(ref v) => v.get(index).copied().map(From::from),
706 TagValue::U16(ref v) => v.get(index).copied().map(From::from),
707 TagValue::U32(ref v) => v.get(index).copied().map(From::from),
708 TagValue::I8(ref v) => v.get(index).copied().map(From::from),
709 TagValue::I16(ref v) => v.get(index).copied().map(From::from),
710 TagValue::I32(ref v) => v.get(index).copied().map(From::from),
711 TagValue::F32(ref v) => v.get(index).copied().map(From::from),
712 TagValue::F64(ref v) => v.get(index).copied().map(From::from),
713 TagValue::IRational(ref v) => v.get(index).copied().map(|v| v.value()),
714 TagValue::URational(ref v) => v.get(index).copied().map(|v| v.value()),
715 _ => None,
716 }
717 }
718}
719
720pub type ExifResult = Result<ExifData, ExifError>;
722
723pub type ExifEntryResult = Result<Vec<ExifEntry>, ExifError>;
725
726#[derive(Copy, Clone, Debug, PartialEq)]
727pub enum IfdKind {
728 Ifd0,
729 Ifd1,
730 Exif,
731 Gps,
732 Makernote,
733 Interoperability,
734}