1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
use std::borrow::Cow;
use std::fmt;
use std::io;
use super::ifdformat::tag_value_eq;
use super::rational::*;

/// The value of the Exif header.
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;

/// Top-level structure that contains all parsed metadata inside an image
#[derive(Debug, PartialEq)]
pub struct ExifData {
    /// MIME type of the parsed image. It may be "image/jpeg", "image/tiff", or empty if unrecognized.
    pub mime: &'static str,
    /// Collection of EXIF entries found in the image
    pub entries: Vec<ExifEntry>,
    /// If `true`, this uses little-endian byte ordering for the raw bytes. Otherwise, it uses big-endian ordering.
    pub le: bool,
}

impl ExifData {
    pub fn new(mime: &'static str, entries: Vec<ExifEntry>, le: bool) -> Self {
        ExifData {
            mime,
            entries,
            le,
        }
    }
}

impl ExifData {
    /// Serialize the metadata entries, and return the result.
    ///
    /// *Note*: this serializes the metadata according to its original endianness (specified
    /// through the `le` attribute).
    pub fn serialize(&self) -> Result<Vec<u8>, ExifError> {
        // Select the right TIFF header based on the endianness.
        let tiff_header = if self.le {
            INTEL_TIFF_HEADER
        } else {
            MOTOROLA_TIFF_HEADER
        };

        // The result buffer.
        let mut serialized = vec![];

        // Generate the TIFF header.
        serialized.extend(tiff_header);

        // The offset to IFD-0. IFD-0 follows immediately after the TIFF header.
        // The offset is a 4-byte value - serialize it to bytes:
        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),
                _ => {
                    // XXX Silently ignore Makernote and Interoperability IFDs
                },
            }
        }

        // IFD-1 contains the thumbnail. For now, the parser discards IFD-1, so its serialization
        // has not yet been implemented.
        assert!(ifd1.is_empty());

        // Serialize the number of directory entries in this IFD.
        if self.le {
            serialized.extend(&(ifd0.len() as u16).to_le_bytes());
        } else {
            serialized.extend(&(ifd0.len() as u16).to_be_bytes());
        };

        // The position of the data in an Exif Offset entry.
        let mut exif_ifd_pointer = None;

        // The position of the data in an GPS Offset entry.
        let mut gps_ifd_pointer = None;

        // The positions which contain offsets pointing to values in the data section of IFD-0.
        // These offsets will be filled out (patched) later.
        let mut data_patches = vec![];
        for entry in ifd0 {
            entry.ifd.serialize(&mut serialized, &mut data_patches)?;

            // If IFD-0 points to an Exif/GPS sub-IFD, the offset of the sub-IFD must be serialized
            // inside IFD-0. Subtract `DATA_WIDTH` from the length, because the pointer to the
            // sub-IFD will be written in the data section of the previously serialized entry
            // (which is of type ExifOffset/GPSOffset precisely because its data section contains
            // an offset to a sub-IFD).
            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 {
            // Otherwise, serialize the pointer to IFD-1 (which is just the offset of IFD-1 in the
            // file).
            unimplemented!("IFD-1");
        }

        // Patch the offsets serialized above.
        for patch in &data_patches {
            // The position of the data pointed to by the IFD entries serialized above.
            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)?;
        }

        // TODO Makernote, Interoperability IFD, Thumbnail image

        Ok(if self.mime == "image/jpeg" {
            [EXIF_HEADER, &serialized].concat()
        } else {
            serialized
        })
    }

    /// Serialize GPS/Exif IFD entries.
    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()
        };

        // Serialize the number of directory entries in this IFD
        if self.le {
            serialized.extend(&(entries.len() as u16).to_le_bytes());
        } else {
            serialized.extend(&(entries.len() as u16).to_be_bytes());
        }

        // Write the offset of this IFD in IFD-0.
        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 {
            // The position of the data pointed to by the IFD entries serialized above.
            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 {
    /// The position where to write the offset in the file where the data will be located.
    offset_pos: u32,
    /// The data to add to the data section of the current IFD.
    data: Vec<u8>,
}

impl Patch {
    pub fn new(offset_pos: u32, data: &[u8]) -> Patch {
        Patch {
            offset_pos,
            data: data.to_vec(),
        }
    }
}

/// Possible fatal errors that may happen when an image is parsed.
#[derive(Debug)]
pub enum ExifError {
    IoError(io::Error),
    FileTypeUnknown,
    JpegWithoutExif(String),
    TiffTruncated,
    TiffBadPreamble(String),
    IfdTruncated,
    ExifIfdTruncated(String),
    ExifIfdEntryNotFound,
    UnsupportedNamespace,
    MissingExifOffset,
}

/// Structure that represents a parsed IFD entry of a TIFF image
#[derive(Clone, Debug)]
pub struct IfdEntry {
    /// Namespace of the entry. Standard is a tag found in normal TIFF IFD structure,
    /// other namespaces are entries found e.g. within MarkerNote blobs that are
    /// manufacturer-specific.
    pub namespace: Namespace,
    /// IFD tag value, may or not be an EXIF tag
    pub tag: u16,
    /// IFD data format
    pub format: IfdFormat,
    /// Number of items, each one in the data format specified by format
    pub count: u32,
    /// Raw data as a vector of bytes. Length is sizeof(format) * count.
    /// Depending on its size, it came from different parts of the image file.
    pub data: Vec<u8>,
    /// Raw data contained within the IFD structure. If count * sizeof(format) >= 4,
    /// this item contains the offset where the actual data can be found
    pub ifd_data: Vec<u8>,
    /// Raw data contained outside of the IFD structure and pointed by ifd_data,
    /// if data would not fit within the IFD structure
    pub ext_data: Vec<u8>,
    /// If true, integer and offset formats must be parsed from raw data as little-endian.
    /// If false, integer and offset formats must be parsed from raw data as big-endian.
    ///
    /// It is important to have 'endianess' per IFD entry, because some manufacturer-specific
    /// entries may have fixed endianess (regardeless of TIFF container's general endianess).
    pub le: bool,
}

// Do not include `ifd_data` in the comparison, as it may in fact contain the offset to the data,
// and two Exif entries may contain the same data, but at different offsets. In that case, the
// entries should still be considered equal.
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> {
        // Serialize the entry
        if self.namespace != Namespace::Standard {
            return Err(ExifError::UnsupportedNamespace)
        }

        // Serialize the tag (2 bytes)
        if self.le {
            serialized.extend(&self.tag.to_le_bytes());
        } else {
            serialized.extend(&self.tag.to_be_bytes());
        };

        // Serialize the data format (2 bytes)
        if self.le {
            serialized.extend(&(self.format as u16).to_le_bytes());
        } else {
            serialized.extend(&(self.format as u16).to_be_bytes());
        }

        // Serialize the number of components (4 bytes)
        if self.le {
            serialized.extend(&self.count.to_le_bytes());
        } else {
            serialized.extend(&self.count.to_be_bytes());
        }

        // Serialize the data value/offset to data value (4 bytes)
        if self.in_ifd() {
            serialized.extend(&self.data);
        } else {
            data_patches.push(Patch::new(serialized.len() as u32, &self.data));
            // 4 bytes that will be filled out later
            serialized.extend(&[0, 0, 0, 0]);
        }
        Ok(())
    }
}

/// Enumeration that represent EXIF tag namespaces. Namespaces exist to
/// accomodate future parsing of the manufacturer-specific tags embedded within
/// the MarkerNote tag.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Namespace {
    Standard = 0x0000,
    Nikon = 0x0001,
    Canon = 0x0002,
}

/// Enumeration that represents recognized EXIF tags found in TIFF IFDs.
///
/// Items can be cast to u32 in order to get the namespace (most significant word)
/// and tag code (least significant word). The tag code matches the Exif, or the
/// Makernote standard, depending on the namespace that the tag belongs to.
///
/// On the other hand, the namespace code is arbitrary, it only matches
/// the `Namespace` enumeration. The namespace is 0 for standard Exif tags.
/// The non-standard namespaces exist to accomodate future parsing of the
/// MarkerNote tag, that contains embedded manufacturer-specific tags.
#[derive(Copy, Clone, Debug, PartialEq, Hash)]
pub enum ExifTag {
    /// Tag not recognized are partially parsed. The client may still try to interpret
    /// the tag by reading into the IfdFormat structure.
    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",
            }
        )
    }
}

/// Enumeration that represents the possible data formats of an IFD entry.
///
/// Any enumeration item can be cast to u16 to get the low-level format code
/// as defined by the TIFF format.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum IfdFormat {
    Unknown = 0,
    U8 = 1,
    Ascii = 2,
    U16 = 3,
    U32 = 4,
    URational = 5,
    I8 = 6,
    Undefined = 7, // u8
    I16 = 8,
    I32 = 9,
    IRational = 10,
    F32 = 11,
    F64 = 12,
}

/// Structure that represents a parsed EXIF tag.
#[derive(Clone, Debug)]
pub struct ExifEntry {
    /// Namespace of the tag. If Standard (0x0000), it is an EXIF tag defined in the
    /// official standard. Other namespaces accomodate manufacturer-specific tags that
    /// may be embedded in MarkerNote blob tag.
    pub namespace: Namespace,
    /// Low-level IFD entry that contains the EXIF tag. The client may look into this
    /// structure to get tag's raw data, or to parse the tag herself if `tag` is `UnknownToMe`.
    pub ifd: IfdEntry,
    /// EXIF tag type as an enumeration. If `UnknownToMe`, the crate did not know the
    /// tag in detail, and parsing will be incomplete. The client may read into
    /// `ifd` to discover more about the unparsed tag.
    pub tag: ExifTag,
    /// EXIF tag value as an enumeration. Behaves as a "variant" value
    pub value: TagValue,
    /// Unit of the value, if applicable. If tag is `UnknownToMe`, unit will be empty.
    /// If the tag has been parsed and it is indeed unitless, it will be `"none"`.
    ///
    /// Note that
    /// unit refers to the contents of `value`, not to the readable string. For example,
    /// a GPS latitude is a triplet of rational values, so unit is D/M/S, even though
    /// `value_more_readable` contains a single string with all three parts
    /// combined.
    pub unit: Cow<'static, str>,
    /// Human-readable and "pretty" version of `value`.
    /// Enumerations and tuples are interpreted and combined. If `value`
    /// has a unit, it is also added.
    /// If tag is `UnknownToMe`,
    /// this member contains the same string as `value_readable`.
    pub value_more_readable: Cow<'static, str>,
    pub kind: IfdKind,
}

impl PartialEq for ExifEntry {
    fn eq(&self, other: &ExifEntry) -> bool {
        // If the ExifEntry is an ExifOffset or a GPSOffset, the value it contains is an offset.
        // Two entries can be equal even if they do not point to the same offset.
        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
    }
}

/// Tag value enumeration. It works as a variant type. Each value is
/// actually a vector because many EXIF tags are collections of values.
/// Exif tags with single values are represented as single-item vectors.
#[derive(Clone, Debug, PartialEq)]
pub enum TagValue {
    /// Array of unsigned byte integers
    U8(Vec<u8>),
    /// ASCII string. (The standard specifies 7-bit ASCII, but this parser accepts UTF-8 strings.)
    Ascii(String),
    U16(Vec<u16>),
    U32(Vec<u32>),
    /// Array of `URational` structures (tuples with integer numerator and denominator)
    URational(Vec<URational>),
    I8(Vec<i8>),
    /// Array of bytes with opaque internal structure. Used by manufacturer-specific
    /// tags, SIG-specific tags, tags that contain Unicode (UCS-2) or Japanese (JIS)
    /// strings (i.e. strings that are not 7-bit-clean), tags that contain
    /// dissimilar or variant types, etc.
    ///
    /// This item has a "little endian"
    /// boolean parameter that reports the whole TIFF's endianness.
    /// Any sort of internal structure that is sensitive to endianess
    /// should be interpreted accordignly to this parameter (true=LE, false=BE).
    Undefined(Vec<u8>, bool),
    I16(Vec<i16>),
    I32(Vec<i32>),
    /// Array of `IRational` structures (tuples with signed integer numerator and denominator)
    IRational(Vec<IRational>),
    /// Array of IEEE 754 floating-points
    F32(Vec<f32>),
    /// Array of IEEE 754 floating-points
    F64(Vec<f64>),
    /// Array of bytes with unknown internal structure.
    /// This is different from `Undefined` because `Undefined` is actually a specified
    /// format, while `Unknown` is an unexpected format type. A tag of `Unknown` format
    /// is most likely a corrupted tag.
    ///
    /// This variant has a "little endian"
    /// boolean parameter that reports the whole TIFF's endianness.
    /// Any sort of internal structure that is sensitive to endianess
    /// should be interpreted accordignly to this parameter (true=LE, false=BE).
    Unknown(Vec<u8>, bool),
    /// Type that could not be parsed due to some sort of error (e.g. buffer too
    /// short for the count and type size). Variant contains raw data, LE/BE,
    /// format (as u16) and count.
    Invalid(Vec<u8>, bool, u16, u32),
}

impl TagValue {
    /// Get value as an integer
    /// Out of bounds indexes and invalid types return `None`
    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,
        }
    }

    /// Get value as a floating-point number
    /// Out of bounds indexes and invalid types return `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,
        }
    }
}

/// Type returned by image file parsing
pub type ExifResult = Result<ExifData, ExifError>;

/// Type resturned by lower-level parsing functions
pub type ExifEntryResult = Result<Vec<ExifEntry>, ExifError>;

#[derive(Copy, Clone, Debug, PartialEq)]
pub enum IfdKind {
    Ifd0,
    Ifd1,
    Exif,
    Gps,
    Makernote,
    Interoperability,
}