You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

912 lines
32 KiB

#include "exif.h"
#include <algorithm>
#include <cstdint>
#include <stdio.h>
#include <vector>
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;
// Sanity check: some cameras pad the JPEG image with some bytes at the end.
// Normally, we should be able to find the JPEG end marker 0xFFD9 at the end
// of the image buffer, but not always. As long as there are some bytes
// except 0xD9 at the end of the image buffer, keep decrementing len until
// an 0xFFD9 is found. If JPEG end marker 0xFFD9 is not found,
// then we can be reasonably sure that the buffer is not a JPEG.
while (len > 2) {
if (buf[len - 1] == 0xD9 && buf[len - 2] == 0xFF)
break;
len--;
}
if (len <= 2)
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 0x8822:
// Exposure Program
if (result.format() == 3 && result.val_short().size())
this->ExposureProgram = result.val_short().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 && result.val_short().size()) {
uint16_t data = result.val_short().front();
this->Flash = data & 1;
this->FlashReturnedLight = (data & 6) >> 1;
this->FlashMode = (data & 24) >> 3;
}
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 0xa210:
// EXIF Focal plane resolution unit
if (result.format() == 3 && result.val_short().size()) {
this->LensInfo.FocalPlaneResolutionUnit = result.val_short().front();
}
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;
ExposureProgram = 0;
ISOSpeedRatings = 0;
ShutterSpeedValue = 0;
ExposureBiasValue = 0;
SubjectDistance = 0;
FocalLength = 0;
FocalLengthIn35mm = 0;
Flash = 0;
FlashReturnedLight = 0;
FlashMode = 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.FocalPlaneResolutionUnit = 0;
LensInfo.Make = "";
LensInfo.Model = "";
}