mirror of
				https://github.com/subsurface/subsurface.git
				synced 2025-02-19 22:16:15 +00:00 
			
		
		
		
	Changes to the "Add pictures to dive" function: - Make Exif handling more tolerant by removing the JPG sanity check for EOI - Give info to user if exif.cpp can't identify a Exif date/time - Restrict file dialog filter for correct picture time by DC photo to JPG because Exif is only available from JPG Signed-off-by: Stefan Fuchs <sfuchs@gmx.de>
		
			
				
	
	
		
			902 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			902 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // SPDX-License-Identifier: BSD-2-CLAUSE
 | |
| /**************************************************************************
 | |
|   exif.cpp  -- A simple ISO C++ library to parse basic EXIF
 | |
|                information from a JPEG file.
 | |
| 
 | |
|   Copyright (c) 2010-2015 Mayank Lahiri
 | |
|   mlahiri@gmail.com
 | |
|   All rights reserved (BSD License).
 | |
| 
 | |
|   See exif.h for version history.
 | |
| 
 | |
|   Redistribution and use in source and binary forms, with or without
 | |
|   modification, are permitted provided that the following conditions are met:
 | |
| 
 | |
|   -- Redistributions of source code must retain the above copyright notice,
 | |
|      this list of conditions and the following disclaimer.
 | |
|   -- Redistributions in binary form must reproduce the above copyright notice,
 | |
|      this list of conditions and the following disclaimer in the documentation
 | |
|      and/or other materials provided with the distribution.
 | |
| 
 | |
|   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESS
 | |
|   OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 | |
|   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 | |
|   NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 | |
|   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 | |
|   BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 | |
|   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 | |
|   OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 | |
|   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 | |
|   EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | |
| */
 | |
| #include "exif.h"
 | |
| 
 | |
| #include <algorithm>
 | |
| #include <cstdint>
 | |
| #include <stdio.h>
 | |
| #include <vector>
 | |
| #include "dive.h"
 | |
| 
 | |
| using std::string;
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| struct Rational {
 | |
|   uint32_t numerator, denominator;
 | |
|   operator double() const {
 | |
|     if (denominator < 1e-20) {
 | |
|       return 0;
 | |
|     }
 | |
|     return static_cast<double>(numerator) / static_cast<double>(denominator);
 | |
|   }
 | |
| };
 | |
| 
 | |
| // IF Entry
 | |
| class IFEntry {
 | |
|  public:
 | |
|   using byte_vector = std::vector<uint8_t>;
 | |
|   using ascii_vector = std::string;
 | |
|   using short_vector = std::vector<uint16_t>;
 | |
|   using long_vector = std::vector<uint32_t>;
 | |
|   using rational_vector = std::vector<Rational>;
 | |
| 
 | |
|   IFEntry()
 | |
|       : tag_(0xFF), format_(0xFF), data_(0), length_(0), val_byte_(nullptr) {}
 | |
|   IFEntry(const IFEntry &) = delete;
 | |
|   IFEntry &operator=(const IFEntry &) = delete;
 | |
|   IFEntry(IFEntry &&other)
 | |
|       : tag_(other.tag_),
 | |
|         format_(other.format_),
 | |
|         data_(other.data_),
 | |
|         length_(other.length_),
 | |
|         val_byte_(other.val_byte_) {
 | |
|     other.tag_ = 0xFF;
 | |
|     other.format_ = 0xFF;
 | |
|     other.data_ = 0;
 | |
|     other.length_ = 0;
 | |
|     other.val_byte_ = nullptr;
 | |
|   }
 | |
|   ~IFEntry() { delete_union(); }
 | |
|   unsigned short tag() const { return tag_; }
 | |
|   void tag(unsigned short tag) { tag_ = tag; }
 | |
|   unsigned short format() const { return format_; }
 | |
|   bool format(unsigned short format) {
 | |
|     switch (format) {
 | |
|       case 0x01:
 | |
|       case 0x02:
 | |
|       case 0x03:
 | |
|       case 0x04:
 | |
|       case 0x05:
 | |
|       case 0x07:
 | |
|       case 0x09:
 | |
|       case 0x0a:
 | |
|       case 0xff:
 | |
|         break;
 | |
|       default:
 | |
|         return false;
 | |
|     }
 | |
|     delete_union();
 | |
|     format_ = format;
 | |
|     new_union();
 | |
|     return true;
 | |
|   }
 | |
|   unsigned data() const { return data_; }
 | |
|   void data(unsigned data) { data_ = data; }
 | |
|   unsigned length() const { return length_; }
 | |
|   void length(unsigned length) { length_ = length; }
 | |
| 
 | |
|   // functions to access the data
 | |
|   //
 | |
|   // !! it's CALLER responsibility to check that format !!
 | |
|   // !! is correct before accessing it's field          !!
 | |
|   //
 | |
|   // - getters are use here to allow future addition
 | |
|   //   of checks if format is correct
 | |
|   byte_vector &val_byte() { return *val_byte_; }
 | |
|   ascii_vector &val_string() { return *val_string_; }
 | |
|   short_vector &val_short() { return *val_short_; }
 | |
|   long_vector &val_long() { return *val_long_; }
 | |
|   rational_vector &val_rational() { return *val_rational_; }
 | |
| 
 | |
|  private:
 | |
|   // Raw fields
 | |
|   unsigned short tag_;
 | |
|   unsigned short format_;
 | |
|   unsigned data_;
 | |
|   unsigned length_;
 | |
| 
 | |
|   // Parsed fields
 | |
|   union {
 | |
|     byte_vector *val_byte_;
 | |
|     ascii_vector *val_string_;
 | |
|     short_vector *val_short_;
 | |
|     long_vector *val_long_;
 | |
|     rational_vector *val_rational_;
 | |
|   };
 | |
| 
 | |
|   void delete_union() {
 | |
|     switch (format_) {
 | |
|       case 0x1:
 | |
|         delete val_byte_;
 | |
|         val_byte_ = nullptr;
 | |
|         break;
 | |
|       case 0x2:
 | |
|         delete val_string_;
 | |
|         val_string_ = nullptr;
 | |
|         break;
 | |
|       case 0x3:
 | |
|         delete val_short_;
 | |
|         val_short_ = nullptr;
 | |
|         break;
 | |
|       case 0x4:
 | |
|         delete val_long_;
 | |
|         val_long_ = nullptr;
 | |
|         break;
 | |
|       case 0x5:
 | |
|         delete val_rational_;
 | |
|         val_rational_ = nullptr;
 | |
|         break;
 | |
|       case 0xff:
 | |
|         break;
 | |
|       default:
 | |
|         // should not get here
 | |
|         // should I throw an exception or ...?
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
|   void new_union() {
 | |
|     switch (format_) {
 | |
|       case 0x1:
 | |
|         val_byte_ = new byte_vector();
 | |
|         break;
 | |
|       case 0x2:
 | |
|         val_string_ = new ascii_vector();
 | |
|         break;
 | |
|       case 0x3:
 | |
|         val_short_ = new short_vector();
 | |
|         break;
 | |
|       case 0x4:
 | |
|         val_long_ = new long_vector();
 | |
|         break;
 | |
|       case 0x5:
 | |
|         val_rational_ = new rational_vector();
 | |
|         break;
 | |
|       case 0xff:
 | |
|         break;
 | |
|       default:
 | |
|         // should not get here
 | |
|         // should I throw an exception or ...?
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| // Helper functions
 | |
| template <typename T, bool alignIntel>
 | |
| T parse(const unsigned char *buf);
 | |
| 
 | |
| template <>
 | |
| uint8_t parse<uint8_t, false>(const unsigned char *buf) {
 | |
|   return *buf;
 | |
| }
 | |
| 
 | |
| template <>
 | |
| uint8_t parse<uint8_t, true>(const unsigned char *buf) {
 | |
|   return *buf;
 | |
| }
 | |
| 
 | |
| template <>
 | |
| uint16_t parse<uint16_t, false>(const unsigned char *buf) {
 | |
|   return (static_cast<uint16_t>(buf[0]) << 8) | buf[1];
 | |
| }
 | |
| 
 | |
| template <>
 | |
| uint16_t parse<uint16_t, true>(const unsigned char *buf) {
 | |
|   return (static_cast<uint16_t>(buf[1]) << 8) | buf[0];
 | |
| }
 | |
| 
 | |
| template <>
 | |
| uint32_t parse<uint32_t, false>(const unsigned char *buf) {
 | |
|   return (static_cast<uint32_t>(buf[0]) << 24) |
 | |
|          (static_cast<uint32_t>(buf[1]) << 16) |
 | |
|          (static_cast<uint32_t>(buf[2]) << 8) | buf[3];
 | |
| }
 | |
| 
 | |
| template <>
 | |
| uint32_t parse<uint32_t, true>(const unsigned char *buf) {
 | |
|   return (static_cast<uint32_t>(buf[3]) << 24) |
 | |
|          (static_cast<uint32_t>(buf[2]) << 16) |
 | |
|          (static_cast<uint32_t>(buf[1]) << 8) | buf[0];
 | |
| }
 | |
| 
 | |
| template <>
 | |
| Rational parse<Rational, true>(const unsigned char *buf) {
 | |
|   Rational r;
 | |
|   r.numerator = parse<uint32_t, true>(buf);
 | |
|   r.denominator = parse<uint32_t, true>(buf + 4);
 | |
|   return r;
 | |
| }
 | |
| 
 | |
| template <>
 | |
| Rational parse<Rational, false>(const unsigned char *buf) {
 | |
|   Rational r;
 | |
|   r.numerator = parse<uint32_t, false>(buf);
 | |
|   r.denominator = parse<uint32_t, false>(buf + 4);
 | |
|   return r;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Try to read entry.length() values for this entry.
 | |
|  *
 | |
|  * Returns:
 | |
|  *  true  - entry.length() values were read
 | |
|  *  false - something went wrong, vec's content was not touched
 | |
|  */
 | |
| template <typename T, bool alignIntel, typename C>
 | |
| bool extract_values(C &container, const unsigned char *buf, const unsigned base,
 | |
|                     const unsigned len, const IFEntry &entry) {
 | |
|   const unsigned char *data;
 | |
|   uint32_t reversed_data;
 | |
|   // if data fits into 4 bytes, they are stored directly in
 | |
|   // the data field in IFEntry
 | |
|   if (sizeof(T) * entry.length() <= 4) {
 | |
|     if (alignIntel) {
 | |
|       reversed_data = entry.data();
 | |
|     } else {
 | |
|       reversed_data = entry.data();
 | |
|       // this reversing works, but is ugly
 | |
|       unsigned char *data = reinterpret_cast<unsigned char *>(&reversed_data);
 | |
|       unsigned char tmp;
 | |
|       tmp = data[0];
 | |
|       data[0] = data[3];
 | |
|       data[3] = tmp;
 | |
|       tmp = data[1];
 | |
|       data[1] = data[2];
 | |
|       data[2] = tmp;
 | |
|     }
 | |
|     data = reinterpret_cast<const unsigned char *>(&(reversed_data));
 | |
|   } else {
 | |
|     data = buf + base + entry.data();
 | |
|     if (data + sizeof(T) * entry.length() > buf + len) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
|   container.resize(entry.length());
 | |
|   for (size_t i = 0; i < entry.length(); ++i) {
 | |
|     container[i] = parse<T, alignIntel>(data + sizeof(T) * i);
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| template <bool alignIntel>
 | |
| void parseIFEntryHeader(const unsigned char *buf, unsigned short &tag,
 | |
|                         unsigned short &format, unsigned &length,
 | |
|                         unsigned &data) {
 | |
|   // Each directory entry is composed of:
 | |
|   // 2 bytes: tag number (data field)
 | |
|   // 2 bytes: data format
 | |
|   // 4 bytes: number of components
 | |
|   // 4 bytes: data value or offset to data value
 | |
|   tag = parse<uint16_t, alignIntel>(buf);
 | |
|   format = parse<uint16_t, alignIntel>(buf + 2);
 | |
|   length = parse<uint32_t, alignIntel>(buf + 4);
 | |
|   data = parse<uint32_t, alignIntel>(buf + 8);
 | |
| }
 | |
| 
 | |
| template <bool alignIntel>
 | |
| void parseIFEntryHeader(const unsigned char *buf, IFEntry &result) {
 | |
|   unsigned short tag;
 | |
|   unsigned short format;
 | |
|   unsigned length;
 | |
|   unsigned data;
 | |
| 
 | |
|   parseIFEntryHeader<alignIntel>(buf, tag, format, length, data);
 | |
| 
 | |
|   result.tag(tag);
 | |
|   result.format(format);
 | |
|   result.length(length);
 | |
|   result.data(data);
 | |
| }
 | |
| 
 | |
| template <bool alignIntel>
 | |
| IFEntry parseIFEntry_temp(const unsigned char *buf, const unsigned offs,
 | |
|                           const unsigned base, const unsigned len) {
 | |
|   IFEntry result;
 | |
| 
 | |
|   // check if there even is enough data for IFEntry in the buffer
 | |
|   if (buf + offs + 12 > buf + len) {
 | |
|     result.tag(0xFF);
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   parseIFEntryHeader<alignIntel>(buf + offs, result);
 | |
| 
 | |
|   // Parse value in specified format
 | |
|   switch (result.format()) {
 | |
|     case 1:
 | |
|       if (!extract_values<uint8_t, alignIntel>(result.val_byte(), buf, base,
 | |
|                                                len, result)) {
 | |
|         result.tag(0xFF);
 | |
|       }
 | |
|       break;
 | |
|     case 2:
 | |
|       // string is basically sequence of uint8_t (well, according to EXIF even
 | |
|       // uint7_t, but
 | |
|       // we don't have that), so just read it as bytes
 | |
|       if (!extract_values<uint8_t, alignIntel>(result.val_string(), buf, base,
 | |
|                                                len, result)) {
 | |
|         result.tag(0xFF);
 | |
|       }
 | |
|       // and cut zero byte at the end, since we don't want that in the
 | |
|       // std::string
 | |
|       if (result.val_string()[result.val_string().length() - 1] == '\0') {
 | |
|         result.val_string().resize(result.val_string().length() - 1);
 | |
|       }
 | |
|       break;
 | |
|     case 3:
 | |
|       if (!extract_values<uint16_t, alignIntel>(result.val_short(), buf, base,
 | |
|                                                 len, result)) {
 | |
|         result.tag(0xFF);
 | |
|       }
 | |
|       break;
 | |
|     case 4:
 | |
|       if (!extract_values<uint32_t, alignIntel>(result.val_long(), buf, base,
 | |
|                                                 len, result)) {
 | |
|         result.tag(0xFF);
 | |
|       }
 | |
|       break;
 | |
|     case 5:
 | |
|       if (!extract_values<Rational, alignIntel>(result.val_rational(), buf,
 | |
|                                                 base, len, result)) {
 | |
|         result.tag(0xFF);
 | |
|       }
 | |
|       break;
 | |
|     case 7:
 | |
|     case 9:
 | |
|     case 10:
 | |
|       break;
 | |
|     default:
 | |
|       result.tag(0xFF);
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| // helper functions for convinience
 | |
| template <typename T>
 | |
| T parse_value(const unsigned char *buf, bool alignIntel) {
 | |
|   if (alignIntel) {
 | |
|     return parse<T, true>(buf);
 | |
|   } else {
 | |
|     return parse<T, false>(buf);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void parseIFEntryHeader(const unsigned char *buf, bool alignIntel,
 | |
|                         unsigned short &tag, unsigned short &format,
 | |
|                         unsigned &length, unsigned &data) {
 | |
|   if (alignIntel) {
 | |
|     parseIFEntryHeader<true>(buf, tag, format, length, data);
 | |
|   } else {
 | |
|     parseIFEntryHeader<false>(buf, tag, format, length, data);
 | |
|   }
 | |
| }
 | |
| 
 | |
| IFEntry parseIFEntry(const unsigned char *buf, const unsigned offs,
 | |
|                      const bool alignIntel, const unsigned base,
 | |
|                      const unsigned len) {
 | |
|   if (alignIntel) {
 | |
|     return parseIFEntry_temp<true>(buf, offs, base, len);
 | |
|   } else {
 | |
|     return parseIFEntry_temp<false>(buf, offs, base, len);
 | |
|   }
 | |
| }
 | |
| }
 | |
| 
 | |
| //
 | |
| // Locates the EXIF segment and parses it using parseFromEXIFSegment
 | |
| //
 | |
| int easyexif::EXIFInfo::parseFrom(const unsigned char *buf, unsigned len) {
 | |
|   // Sanity check: all JPEG files start with 0xFFD8.
 | |
|   if (!buf || len < 4) return PARSE_EXIF_ERROR_NO_JPEG;
 | |
|   if (buf[0] != 0xFF || buf[1] != 0xD8) return PARSE_EXIF_ERROR_NO_JPEG;
 | |
| 
 | |
|   clear();
 | |
| 
 | |
|   // Scan for EXIF header (bytes 0xFF 0xE1) and do a sanity check by
 | |
|   // looking for bytes "Exif\0\0". The marker length data is in Motorola
 | |
|   // byte order, which results in the 'false' parameter to parse16().
 | |
|   // The marker has to contain at least the TIFF header, otherwise the
 | |
|   // EXIF data is corrupt. So the minimum length specified here has to be:
 | |
|   //   2 bytes: section size
 | |
|   //   6 bytes: "Exif\0\0" string
 | |
|   //   2 bytes: TIFF header (either "II" or "MM" string)
 | |
|   //   2 bytes: TIFF magic (short 0x2a00 in Motorola byte order)
 | |
|   //   4 bytes: Offset to first IFD
 | |
|   // =========
 | |
|   //  16 bytes
 | |
|   unsigned offs = 0;  // current offset into buffer
 | |
|   for (offs = 0; offs < len - 1; offs++)
 | |
|     if (buf[offs] == 0xFF && buf[offs + 1] == 0xE1) break;
 | |
|   if (offs + 4 > len) return PARSE_EXIF_ERROR_NO_EXIF;
 | |
|   offs += 2;
 | |
|   unsigned short section_length = parse_value<uint16_t>(buf + offs, false);
 | |
|   if (offs + section_length > len || section_length < 16)
 | |
|     return PARSE_EXIF_ERROR_CORRUPT;
 | |
|   offs += 2;
 | |
| 
 | |
|   return parseFromEXIFSegment(buf + offs, len - offs);
 | |
| }
 | |
| 
 | |
| int easyexif::EXIFInfo::parseFrom(const string &data) {
 | |
|   return parseFrom(
 | |
|       reinterpret_cast<const unsigned char *>(data.data()), static_cast<unsigned>(data.length()));
 | |
| }
 | |
| 
 | |
| //
 | |
| // Main parsing function for an EXIF segment.
 | |
| //
 | |
| // PARAM: 'buf' start of the EXIF TIFF, which must be the bytes "Exif\0\0".
 | |
| // PARAM: 'len' length of buffer
 | |
| //
 | |
| int easyexif::EXIFInfo::parseFromEXIFSegment(const unsigned char *buf,
 | |
|                                              unsigned len) {
 | |
|   bool alignIntel = true;  // byte alignment (defined in EXIF header)
 | |
|   unsigned offs = 0;       // current offset into buffer
 | |
|   if (!buf || len < 6) return PARSE_EXIF_ERROR_NO_EXIF;
 | |
| 
 | |
|   if (!std::equal(buf, buf + 6, "Exif\0\0")) return PARSE_EXIF_ERROR_NO_EXIF;
 | |
|   offs += 6;
 | |
| 
 | |
|   // Now parsing the TIFF header. The first two bytes are either "II" or
 | |
|   // "MM" for Intel or Motorola byte alignment. Sanity check by parsing
 | |
|   // the unsigned short that follows, making sure it equals 0x2a. The
 | |
|   // last 4 bytes are an offset into the first IFD, which are added to
 | |
|   // the global offset counter. For this block, we expect the following
 | |
|   // minimum size:
 | |
|   //  2 bytes: 'II' or 'MM'
 | |
|   //  2 bytes: 0x002a
 | |
|   //  4 bytes: offset to first IDF
 | |
|   // -----------------------------
 | |
|   //  8 bytes
 | |
|   if (offs + 8 > len) return PARSE_EXIF_ERROR_CORRUPT;
 | |
|   unsigned tiff_header_start = offs;
 | |
|   if (buf[offs] == 'I' && buf[offs + 1] == 'I')
 | |
|     alignIntel = true;
 | |
|   else {
 | |
|     if (buf[offs] == 'M' && buf[offs + 1] == 'M')
 | |
|       alignIntel = false;
 | |
|     else
 | |
|       return PARSE_EXIF_ERROR_UNKNOWN_BYTEALIGN;
 | |
|   }
 | |
|   this->ByteAlign = alignIntel;
 | |
|   offs += 2;
 | |
|   if (0x2a != parse_value<uint16_t>(buf + offs, alignIntel))
 | |
|     return PARSE_EXIF_ERROR_CORRUPT;
 | |
|   offs += 2;
 | |
|   unsigned first_ifd_offset = parse_value<uint32_t>(buf + offs, alignIntel);
 | |
|   offs += first_ifd_offset - 4;
 | |
|   if (offs >= len) return PARSE_EXIF_ERROR_CORRUPT;
 | |
| 
 | |
|   // Now parsing the first Image File Directory (IFD0, for the main image).
 | |
|   // An IFD consists of a variable number of 12-byte directory entries. The
 | |
|   // first two bytes of the IFD section contain the number of directory
 | |
|   // entries in the section. The last 4 bytes of the IFD contain an offset
 | |
|   // to the next IFD, which means this IFD must contain exactly 6 + 12 * num
 | |
|   // bytes of data.
 | |
|   if (offs + 2 > len) return PARSE_EXIF_ERROR_CORRUPT;
 | |
|   int num_entries = parse_value<uint16_t>(buf + offs, alignIntel);
 | |
|   if (offs + 6 + 12 * num_entries > len) return PARSE_EXIF_ERROR_CORRUPT;
 | |
|   offs += 2;
 | |
|   unsigned exif_sub_ifd_offset = len;
 | |
|   unsigned gps_sub_ifd_offset = len;
 | |
|   while (--num_entries >= 0) {
 | |
|     IFEntry result =
 | |
|         parseIFEntry(buf, offs, alignIntel, tiff_header_start, len);
 | |
|     offs += 12;
 | |
|     switch (result.tag()) {
 | |
|       case 0x102:
 | |
|         // Bits per sample
 | |
|         if (result.format() == 3 && result.val_short().size())
 | |
|           this->BitsPerSample = result.val_short().front();
 | |
|         break;
 | |
| 
 | |
|       case 0x10E:
 | |
|         // Image description
 | |
|         if (result.format() == 2) this->ImageDescription = result.val_string();
 | |
|         break;
 | |
| 
 | |
|       case 0x10F:
 | |
|         // Digicam make
 | |
|         if (result.format() == 2) this->Make = result.val_string();
 | |
|         break;
 | |
| 
 | |
|       case 0x110:
 | |
|         // Digicam model
 | |
|         if (result.format() == 2) this->Model = result.val_string();
 | |
|         break;
 | |
| 
 | |
|       case 0x112:
 | |
|         // Orientation of image
 | |
|         if (result.format() == 3 && result.val_short().size())
 | |
|           this->Orientation = result.val_short().front();
 | |
|         break;
 | |
| 
 | |
|       case 0x131:
 | |
|         // Software used for image
 | |
|         if (result.format() == 2) this->Software = result.val_string();
 | |
|         break;
 | |
| 
 | |
|       case 0x132:
 | |
|         // EXIF/TIFF date/time of image modification
 | |
|         if (result.format() == 2) this->DateTime = result.val_string();
 | |
|         break;
 | |
| 
 | |
|       case 0x8298:
 | |
|         // Copyright information
 | |
|         if (result.format() == 2) this->Copyright = result.val_string();
 | |
|         break;
 | |
| 
 | |
|       case 0x8825:
 | |
|         // GPS IFS offset
 | |
|         gps_sub_ifd_offset = tiff_header_start + result.data();
 | |
|         break;
 | |
| 
 | |
|       case 0x8769:
 | |
|         // EXIF SubIFD offset
 | |
|         exif_sub_ifd_offset = tiff_header_start + result.data();
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Jump to the EXIF SubIFD if it exists and parse all the information
 | |
|   // there. Note that it's possible that the EXIF SubIFD doesn't exist.
 | |
|   // The EXIF SubIFD contains most of the interesting information that a
 | |
|   // typical user might want.
 | |
|   if (exif_sub_ifd_offset + 4 <= len) {
 | |
|     offs = exif_sub_ifd_offset;
 | |
|     int num_entries = parse_value<uint16_t>(buf + offs, alignIntel);
 | |
|     if (offs + 6 + 12 * num_entries > len) return PARSE_EXIF_ERROR_CORRUPT;
 | |
|     offs += 2;
 | |
|     while (--num_entries >= 0) {
 | |
|       IFEntry result =
 | |
|           parseIFEntry(buf, offs, alignIntel, tiff_header_start, len);
 | |
|       switch (result.tag()) {
 | |
|         case 0x829a:
 | |
|           // Exposure time in seconds
 | |
|           if (result.format() == 5 && result.val_rational().size())
 | |
|             this->ExposureTime = result.val_rational().front();
 | |
|           break;
 | |
| 
 | |
|         case 0x829d:
 | |
|           // FNumber
 | |
|           if (result.format() == 5 && result.val_rational().size())
 | |
|             this->FNumber = result.val_rational().front();
 | |
|           break;
 | |
| 
 | |
|         case 0x8827:
 | |
|           // ISO Speed Rating
 | |
|           if (result.format() == 3 && result.val_short().size())
 | |
|             this->ISOSpeedRatings = result.val_short().front();
 | |
|           break;
 | |
| 
 | |
|         case 0x9003:
 | |
|           // Original date and time
 | |
|           if (result.format() == 2)
 | |
|             this->DateTimeOriginal = result.val_string();
 | |
|           break;
 | |
| 
 | |
|         case 0x9004:
 | |
|           // Digitization date and time
 | |
|           if (result.format() == 2)
 | |
|             this->DateTimeDigitized = result.val_string();
 | |
|           break;
 | |
| 
 | |
|         case 0x9201:
 | |
|           // Shutter speed value
 | |
|           if (result.format() == 5 && result.val_rational().size())
 | |
|             this->ShutterSpeedValue = result.val_rational().front();
 | |
|           break;
 | |
| 
 | |
|         case 0x9204:
 | |
|           // Exposure bias value
 | |
|           if (result.format() == 5 && result.val_rational().size())
 | |
|             this->ExposureBiasValue = result.val_rational().front();
 | |
|           break;
 | |
| 
 | |
|         case 0x9206:
 | |
|           // Subject distance
 | |
|           if (result.format() == 5 && result.val_rational().size())
 | |
|             this->SubjectDistance = result.val_rational().front();
 | |
|           break;
 | |
| 
 | |
|         case 0x9209:
 | |
|           // Flash used
 | |
|           if (result.format() == 3) this->Flash = result.data() ? 1 : 0;
 | |
|           break;
 | |
| 
 | |
|         case 0x920a:
 | |
|           // Focal length
 | |
|           if (result.format() == 5 && result.val_rational().size())
 | |
|             this->FocalLength = result.val_rational().front();
 | |
|           break;
 | |
| 
 | |
|         case 0x9207:
 | |
|           // Metering mode
 | |
|           if (result.format() == 3 && result.val_short().size())
 | |
|             this->MeteringMode = result.val_short().front();
 | |
|           break;
 | |
| 
 | |
|         case 0x9291:
 | |
|           // Subsecond original time
 | |
|           if (result.format() == 2)
 | |
|             this->SubSecTimeOriginal = result.val_string();
 | |
|           break;
 | |
| 
 | |
|         case 0xa002:
 | |
|           // EXIF Image width
 | |
|           if (result.format() == 4 && result.val_long().size())
 | |
|             this->ImageWidth = result.val_long().front();
 | |
|           if (result.format() == 3 && result.val_short().size())
 | |
|             this->ImageWidth = result.val_short().front();
 | |
|           break;
 | |
| 
 | |
|         case 0xa003:
 | |
|           // EXIF Image height
 | |
|           if (result.format() == 4 && result.val_long().size())
 | |
|             this->ImageHeight = result.val_long().front();
 | |
|           if (result.format() == 3 && result.val_short().size())
 | |
|             this->ImageHeight = result.val_short().front();
 | |
|           break;
 | |
| 
 | |
|         case 0xa20e:
 | |
|           // EXIF Focal plane X-resolution
 | |
|           if (result.format() == 5) {
 | |
|             this->LensInfo.FocalPlaneXResolution = result.val_rational()[0];
 | |
|           }
 | |
|           break;
 | |
| 
 | |
|         case 0xa20f:
 | |
|           // EXIF Focal plane Y-resolution
 | |
|           if (result.format() == 5) {
 | |
|             this->LensInfo.FocalPlaneYResolution = result.val_rational()[0];
 | |
|           }
 | |
|           break;
 | |
| 
 | |
|         case 0xa405:
 | |
|           // Focal length in 35mm film
 | |
|           if (result.format() == 3 && result.val_short().size())
 | |
|             this->FocalLengthIn35mm = result.val_short().front();
 | |
|           break;
 | |
| 
 | |
|         case 0xa432:
 | |
|           // Focal length and FStop.
 | |
|           if (result.format() == 5) {
 | |
|             int sz = static_cast<unsigned>(result.val_rational().size());
 | |
|             if (sz)
 | |
|               this->LensInfo.FocalLengthMin = result.val_rational()[0];
 | |
|             if (sz > 1)
 | |
|               this->LensInfo.FocalLengthMax = result.val_rational()[1];
 | |
|             if (sz > 2)
 | |
|               this->LensInfo.FStopMin = result.val_rational()[2];
 | |
|             if (sz > 3)
 | |
|               this->LensInfo.FStopMax = result.val_rational()[3];
 | |
|           }
 | |
|           break;
 | |
| 
 | |
|         case 0xa433:
 | |
|           // Lens make.
 | |
|           if (result.format() == 2) {
 | |
|             this->LensInfo.Make = result.val_string();
 | |
|           }
 | |
|           break;
 | |
| 
 | |
|         case 0xa434:
 | |
|           // Lens model.
 | |
|           if (result.format() == 2) {
 | |
|             this->LensInfo.Model = result.val_string();
 | |
|           }
 | |
|           break;
 | |
|       }
 | |
|       offs += 12;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Jump to the GPS SubIFD if it exists and parse all the information
 | |
|   // there. Note that it's possible that the GPS SubIFD doesn't exist.
 | |
|   if (gps_sub_ifd_offset + 4 <= len) {
 | |
|     offs = gps_sub_ifd_offset;
 | |
|     int num_entries = parse_value<uint16_t>(buf + offs, alignIntel);
 | |
|     if (offs + 6 + 12 * num_entries > len) return PARSE_EXIF_ERROR_CORRUPT;
 | |
|     offs += 2;
 | |
|     while (--num_entries >= 0) {
 | |
|       unsigned short tag, format;
 | |
|       unsigned length, data;
 | |
|       parseIFEntryHeader(buf + offs, alignIntel, tag, format, length, data);
 | |
|       switch (tag) {
 | |
|         case 1:
 | |
|           // GPS north or south
 | |
|           this->GeoLocation.LatComponents.direction = *(buf + offs + 8);
 | |
|           if (this->GeoLocation.LatComponents.direction == 0) {
 | |
|             this->GeoLocation.LatComponents.direction = '?';
 | |
|           }
 | |
|           if ('S' == this->GeoLocation.LatComponents.direction) {
 | |
|             this->GeoLocation.Latitude = -this->GeoLocation.Latitude;
 | |
|           }
 | |
|           break;
 | |
| 
 | |
|         case 2:
 | |
|           // GPS latitude
 | |
|           if ((format == 5 || format == 10) && length == 3) {
 | |
|             this->GeoLocation.LatComponents.degrees = parse_value<Rational>(
 | |
|                 buf + data + tiff_header_start, alignIntel);
 | |
|             this->GeoLocation.LatComponents.minutes = parse_value<Rational>(
 | |
|                 buf + data + tiff_header_start + 8, alignIntel);
 | |
|             this->GeoLocation.LatComponents.seconds = parse_value<Rational>(
 | |
|                 buf + data + tiff_header_start + 16, alignIntel);
 | |
|             this->GeoLocation.Latitude =
 | |
|                 this->GeoLocation.LatComponents.degrees +
 | |
|                 this->GeoLocation.LatComponents.minutes / 60 +
 | |
|                 this->GeoLocation.LatComponents.seconds / 3600;
 | |
|             if ('S' == this->GeoLocation.LatComponents.direction) {
 | |
|               this->GeoLocation.Latitude = -this->GeoLocation.Latitude;
 | |
|             }
 | |
|           }
 | |
|           break;
 | |
| 
 | |
|         case 3:
 | |
|           // GPS east or west
 | |
|           this->GeoLocation.LonComponents.direction = *(buf + offs + 8);
 | |
|           if (this->GeoLocation.LonComponents.direction == 0) {
 | |
|             this->GeoLocation.LonComponents.direction = '?';
 | |
|           }
 | |
|           if ('W' == this->GeoLocation.LonComponents.direction) {
 | |
|             this->GeoLocation.Longitude = -this->GeoLocation.Longitude;
 | |
|           }
 | |
|           break;
 | |
| 
 | |
|         case 4:
 | |
|           // GPS longitude
 | |
|           if ((format == 5 || format == 10) && length == 3) {
 | |
|             this->GeoLocation.LonComponents.degrees = parse_value<Rational>(
 | |
|                 buf + data + tiff_header_start, alignIntel);
 | |
|             this->GeoLocation.LonComponents.minutes = parse_value<Rational>(
 | |
|                 buf + data + tiff_header_start + 8, alignIntel);
 | |
|             this->GeoLocation.LonComponents.seconds = parse_value<Rational>(
 | |
|                 buf + data + tiff_header_start + 16, alignIntel);
 | |
|             this->GeoLocation.Longitude =
 | |
|                 this->GeoLocation.LonComponents.degrees +
 | |
|                 this->GeoLocation.LonComponents.minutes / 60 +
 | |
|                 this->GeoLocation.LonComponents.seconds / 3600;
 | |
|             if ('W' == this->GeoLocation.LonComponents.direction)
 | |
|               this->GeoLocation.Longitude = -this->GeoLocation.Longitude;
 | |
|           }
 | |
|           break;
 | |
| 
 | |
|         case 5:
 | |
|           // GPS altitude reference (below or above sea level)
 | |
|           this->GeoLocation.AltitudeRef = *(buf + offs + 8);
 | |
|           if (1 == this->GeoLocation.AltitudeRef) {
 | |
|             this->GeoLocation.Altitude = -this->GeoLocation.Altitude;
 | |
|           }
 | |
|           break;
 | |
| 
 | |
|         case 6:
 | |
|           // GPS altitude
 | |
|           if ((format == 5 || format == 10)) {
 | |
|             this->GeoLocation.Altitude = parse_value<Rational>(
 | |
|                 buf + data + tiff_header_start, alignIntel);
 | |
|             if (1 == this->GeoLocation.AltitudeRef) {
 | |
|               this->GeoLocation.Altitude = -this->GeoLocation.Altitude;
 | |
|             }
 | |
|           }
 | |
|           break;
 | |
| 
 | |
|         case 11:
 | |
|           // GPS degree of precision (DOP)
 | |
|           if ((format == 5 || format == 10)) {
 | |
|             this->GeoLocation.DOP = parse_value<Rational>(
 | |
|                 buf + data + tiff_header_start, alignIntel);
 | |
|           }
 | |
|           break;
 | |
|       }
 | |
|       offs += 12;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return PARSE_EXIF_SUCCESS;
 | |
| }
 | |
| 
 | |
| void easyexif::EXIFInfo::clear() {
 | |
|   // Strings
 | |
|   ImageDescription = "";
 | |
|   Make = "";
 | |
|   Model = "";
 | |
|   Software = "";
 | |
|   DateTime = "";
 | |
|   DateTimeOriginal = "";
 | |
|   DateTimeDigitized = "";
 | |
|   SubSecTimeOriginal = "";
 | |
|   Copyright = "";
 | |
| 
 | |
|   // Shorts / unsigned / double
 | |
|   ByteAlign = 0;
 | |
|   Orientation = 0;
 | |
| 
 | |
|   BitsPerSample = 0;
 | |
|   ExposureTime = 0;
 | |
|   FNumber = 0;
 | |
|   ISOSpeedRatings = 0;
 | |
|   ShutterSpeedValue = 0;
 | |
|   ExposureBiasValue = 0;
 | |
|   SubjectDistance = 0;
 | |
|   FocalLength = 0;
 | |
|   FocalLengthIn35mm = 0;
 | |
|   Flash = 0;
 | |
|   MeteringMode = 0;
 | |
|   ImageWidth = 0;
 | |
|   ImageHeight = 0;
 | |
| 
 | |
|   // Geolocation
 | |
|   GeoLocation.Latitude = 0;
 | |
|   GeoLocation.Longitude = 0;
 | |
|   GeoLocation.Altitude = 0;
 | |
|   GeoLocation.AltitudeRef = 0;
 | |
|   GeoLocation.DOP = 0;
 | |
|   GeoLocation.LatComponents.degrees = 0;
 | |
|   GeoLocation.LatComponents.minutes = 0;
 | |
|   GeoLocation.LatComponents.seconds = 0;
 | |
|   GeoLocation.LatComponents.direction = '?';
 | |
|   GeoLocation.LonComponents.degrees = 0;
 | |
|   GeoLocation.LonComponents.minutes = 0;
 | |
|   GeoLocation.LonComponents.seconds = 0;
 | |
|   GeoLocation.LonComponents.direction = '?';
 | |
| 
 | |
|   // LensInfo
 | |
|   LensInfo.FocalLengthMax = 0;
 | |
|   LensInfo.FocalLengthMin = 0;
 | |
|   LensInfo.FStopMax = 0;
 | |
|   LensInfo.FStopMin = 0;
 | |
|   LensInfo.FocalPlaneYResolution = 0;
 | |
|   LensInfo.FocalPlaneXResolution = 0;
 | |
|   LensInfo.Make = "";
 | |
|   LensInfo.Model = "";
 | |
| }
 | |
| 
 | |
| time_t easyexif::EXIFInfo::epoch()
 | |
| {
 | |
| 	struct tm tm;
 | |
| 	int year, month, day, hour, min, sec;
 | |
| 
 | |
| 	if (DateTimeOriginal.size())
 | |
| 		sscanf(DateTimeOriginal.c_str(), "%d:%d:%d %d:%d:%d", &year, &month, &day, &hour, &min, &sec);
 | |
| 	else
 | |
| 		sscanf(DateTime.c_str(), "%d:%d:%d %d:%d:%d", &year, &month, &day, &hour, &min, &sec);
 | |
| 	tm.tm_year = year;
 | |
| 	tm.tm_mon = month - 1;
 | |
| 	tm.tm_mday = day;
 | |
| 	tm.tm_hour = hour;
 | |
| 	tm.tm_min = min;
 | |
| 	tm.tm_sec = sec;
 | |
| 	return (utc_mktime(&tm));
 | |
| }
 | |
| 
 |