summaryrefslogtreecommitdiffstats
path: root/core/exif.cpp
diff options
context:
space:
mode:
authorGravatar Robert C. Helling <helling@atdotde.de>2017-02-19 10:27:27 +0100
committerGravatar Dirk Hohndel <dirk@hohndel.org>2017-02-19 15:29:40 -0800
commitdc55ee4a23bfbd013bc7085e005ab22beb2dd1a1 (patch)
treeb8cc54aae5d4f52e8916ff4b5380d385993bdc80 /core/exif.cpp
parent4a550e4d7d5ec11640bb511d28097fa7644cdd3b (diff)
downloadsubsurface-dc55ee4a23bfbd013bc7085e005ab22beb2dd1a1.tar.gz
Update exif.cpp from upstream
Signed-off-by: Robert C. Helling <helling@atdotde.de>
Diffstat (limited to 'core/exif.cpp')
-rw-r--r--core/exif.cpp1387
1 files changed, 859 insertions, 528 deletions
diff --git a/core/exif.cpp b/core/exif.cpp
index 0b1cda2bc..b2423ba20 100644
--- a/core/exif.cpp
+++ b/core/exif.cpp
@@ -1,9 +1,8 @@
-#include <stdio.h>
/**************************************************************************
exif.cpp -- A simple ISO C++ library to parse basic EXIF
- information from a JPEG file.
+ information from a JPEG file.
- Copyright (c) 2010-2013 Mayank Lahiri
+ Copyright (c) 2010-2015 Mayank Lahiri
mlahiri@gmail.com
All rights reserved (BSD License).
@@ -18,182 +17,455 @@
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.
+ 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"
-#include "exif.h"
using std::string;
namespace {
- // IF Entry
- struct IFEntry {
- // Raw fields
- unsigned short tag;
- unsigned short format;
- unsigned data;
- unsigned length;
-
- // Parsed fields
- string val_string;
- unsigned short val_16;
- unsigned val_32;
- double val_rational;
- unsigned char val_byte;
- };
-
- // Helper functions
- unsigned int parse32(const unsigned char *buf, bool intel)
- {
- if (intel)
- return ((unsigned)buf[3] << 24) |
- ((unsigned)buf[2] << 16) |
- ((unsigned)buf[1] << 8) |
- buf[0];
-
- return ((unsigned)buf[0] << 24) |
- ((unsigned)buf[1] << 16) |
- ((unsigned)buf[2] << 8) |
- buf[3];
- }
-
- unsigned short parse16(const unsigned char *buf, bool intel)
- {
- if (intel)
- return ((unsigned)buf[1] << 8) | buf[0];
- return ((unsigned)buf[0] << 8) | buf[1];
- }
-
- string parseEXIFString(const unsigned char *buf,
- const unsigned num_components,
- const unsigned data,
- const unsigned base,
- const unsigned len)
- {
- string value;
- if (num_components <= 4)
- value.assign((const char *)&data, num_components);
- else {
- if (base + data + num_components <= len)
- value.assign((const char *)(buf + base + data), num_components);
- }
- return value;
- }
-
- double parseEXIFRational(const unsigned char *buf, bool intel)
- {
- double numerator = 0;
- double denominator = 1;
-
- numerator = (double)parse32(buf, intel);
- denominator = (double)parse32(buf + 4, intel);
- if (denominator < 1e-20)
- return 0;
- return numerator / denominator;
- }
-
- IFEntry parseIFEntry(const unsigned char *buf,
- const unsigned offs,
- const bool alignIntel,
- const unsigned base,
- const unsigned len)
- {
- IFEntry result;
-
- // 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
- result.tag = parse16(buf + offs, alignIntel);
- result.format = parse16(buf + offs + 2, alignIntel);
- result.length = parse32(buf + offs + 4, alignIntel);
- result.data = parse32(buf + offs + 8, alignIntel);
-
- // Parse value in specified format
- switch (result.format) {
- case 1:
- result.val_byte = (unsigned char)*(buf + offs + 8);
- break;
- case 2:
- result.val_string = parseEXIFString(buf, result.length, result.data, base, len);
- break;
- case 3:
- result.val_16 = parse16((const unsigned char *)buf + offs + 8, alignIntel);
- break;
- case 4:
- result.val_32 = result.data;
- break;
- case 5:
- if (base + result.data + 8 <= len)
- result.val_rational = parseEXIFRational(buf + base + result.data, alignIntel);
- break;
- case 7:
- case 9:
- case 10:
- break;
- default:
- result.tag = 0xFF;
- }
- return result;
- }
+
+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 EXIFInfo::parseFrom(const unsigned char *buf, unsigned len)
-{
- // Sanity check: all JPEG files start with 0xFFD8 and end with 0xFFD9
- // This check also ensures that the user has supplied a correct value for len.
- if (!buf || len < 4)
- return PARSE_EXIF_ERROR_NO_EXIF;
- if (buf[0] != 0xFF || buf[1] != 0xD8)
- return PARSE_EXIF_ERROR_NO_JPEG;
- if (buf[len - 2] != 0xFF || buf[len - 1] != 0xD9)
- 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 = parse16(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 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;
+
+ // Sanity check: some cameras pad the JPEG image with null bytes at the end.
+ // Normally, we should able to find the JPEG end marker 0xFFD9 at the end
+ // of the image, but not always. As long as there are null/0xFF bytes at the
+ // end of the image buffer, keep decrementing len until an 0xFFD9 is found,
+ // or some other bytes are. If the first non-zero/0xFF bytes from the end are
+ // not 0xFFD9, then we can be reasonably sure that the buffer is not a JPEG.
+ while (len > 2) {
+ if (buf[len - 1] == 0 || buf[len - 1] == 0xFF) {
+ len--;
+ } else {
+ if (buf[len - 1] != 0xD9 || buf[len - 2] != 0xFF) {
+ return PARSE_EXIF_ERROR_NO_JPEG;
+ } else {
+ break;
+ }
+ }
+ }
+ 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 EXIFInfo::parseFrom(const string &data)
-{
- return parseFrom((const unsigned char *)data.data(), data.length());
+int easyexif::EXIFInfo::parseFrom(const string &data) {
+ return parseFrom(
+ reinterpret_cast<const unsigned char *>(data.data()), static_cast<unsigned>(data.length()));
}
//
@@ -202,373 +474,431 @@ int EXIFInfo::parseFrom(const string &data)
// PARAM: 'buf' start of the EXIF TIFF, which must be the bytes "Exif\0\0".
// PARAM: 'len' length of buffer
//
-int 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 != parse16(buf + offs, alignIntel))
- return PARSE_EXIF_ERROR_CORRUPT;
- offs += 2;
- unsigned first_ifd_offset = parse32(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 = parse16(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)
- this->BitsPerSample = result.val_16;
- 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)
- this->Orientation = result.val_16;
- 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 = parse16(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)
- this->ExposureTime = result.val_rational;
- break;
-
- case 0x829d:
- // FNumber
- if (result.format == 5)
- this->FNumber = result.val_rational;
- break;
-
- case 0x8827:
- // ISO Speed Rating
- if (result.format == 3)
- this->ISOSpeedRatings = result.val_16;
- 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)
- this->ShutterSpeedValue = result.val_rational;
- break;
-
- case 0x9204:
- // Exposure bias value
- if (result.format == 5)
- this->ExposureBiasValue = result.val_rational;
- break;
-
- case 0x9206:
- // Subject distance
- if (result.format == 5)
- this->SubjectDistance = result.val_rational;
- break;
-
- case 0x9209:
- // Flash used
- if (result.format == 3)
- this->Flash = result.data ? 1 : 0;
- break;
-
- case 0x920a:
- // Focal length
- if (result.format == 5)
- this->FocalLength = result.val_rational;
- break;
-
- case 0x9207:
- // Metering mode
- if (result.format == 3)
- this->MeteringMode = result.val_16;
- 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)
- this->ImageWidth = result.val_32;
- if (result.format == 3)
- this->ImageWidth = result.val_16;
- break;
-
- case 0xa003:
- // EXIF Image height
- if (result.format == 4)
- this->ImageHeight = result.val_32;
- if (result.format == 3)
- this->ImageHeight = result.val_16;
- break;
-
- case 0xa405:
- // Focal length in 35mm film
- if (result.format == 3)
- this->FocalLengthIn35mm = result.val_16;
- 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 = parse16(buf + offs, alignIntel);
- if (offs + 6 + 12 * num_entries > len)
- return PARSE_EXIF_ERROR_CORRUPT;
- offs += 2;
- while (--num_entries >= 0) {
- unsigned short tag = parse16(buf + offs, alignIntel);
- unsigned short format = parse16(buf + offs + 2, alignIntel);
- unsigned length = parse32(buf + offs + 4, alignIntel);
- unsigned data = parse32(buf + offs + 8, alignIntel);
- switch (tag) {
- case 1:
- // GPS north or south
- this->GeoLocation.LatComponents.direction = *(buf + offs + 8);
- if ('S' == this->GeoLocation.LatComponents.direction)
- this->GeoLocation.Latitude = -this->GeoLocation.Latitude;
- break;
-
- case 2:
- // GPS latitude
- if (format == 5 && length == 3) {
- this->GeoLocation.LatComponents.degrees =
- parseEXIFRational(buf + data + tiff_header_start, alignIntel);
- this->GeoLocation.LatComponents.minutes =
- parseEXIFRational(buf + data + tiff_header_start + 8, alignIntel);
- this->GeoLocation.LatComponents.seconds =
- parseEXIFRational(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 ('W' == this->GeoLocation.LonComponents.direction)
- this->GeoLocation.Longitude = -this->GeoLocation.Longitude;
- break;
-
- case 4:
- // GPS longitude
- if (format == 5 && length == 3) {
- this->GeoLocation.LonComponents.degrees =
- parseEXIFRational(buf + data + tiff_header_start, alignIntel);
- this->GeoLocation.LonComponents.minutes =
- parseEXIFRational(buf + data + tiff_header_start + 8, alignIntel);
- this->GeoLocation.LonComponents.seconds =
- parseEXIFRational(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 reference
- if (format == 5) {
- this->GeoLocation.Altitude =
- parseEXIFRational(buf + data + tiff_header_start, alignIntel);
- if (1 == this->GeoLocation.AltitudeRef)
- this->GeoLocation.Altitude = -this->GeoLocation.Altitude;
- }
- break;
- }
- offs += 12;
- }
- }
-
- return PARSE_EXIF_SUCCESS;
+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 EXIFInfo::clear()
-{
- // Strings
- ImageDescription.clear();
- Make.clear();
- Model.clear();
- Software.clear();
- DateTime.clear();
- DateTimeOriginal.clear();
- DateTimeDigitized.clear();
- SubSecTimeOriginal.clear();
- Copyright.clear();
-
- // 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.LatComponents.degrees = 0;
- GeoLocation.LatComponents.minutes = 0;
- GeoLocation.LatComponents.seconds = 0;
- GeoLocation.LatComponents.direction = 0;
- GeoLocation.LonComponents.degrees = 0;
- GeoLocation.LonComponents.minutes = 0;
- GeoLocation.LonComponents.seconds = 0;
- GeoLocation.LonComponents.direction = 0;
+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 EXIFInfo::epoch()
+time_t easyexif::EXIFInfo::epoch()
{
struct tm tm;
int year, month, day, hour, min, sec;
@@ -585,3 +915,4 @@ time_t EXIFInfo::epoch()
tm.tm_sec = sec;
return (utc_mktime(&tm));
}
+