12 Commits
1.0.1 ... 1.0.2

Author SHA1 Message Date
cDc
c57a5fec1a fix tinyxml2 namespace 2019-01-16 15:26:30 +02:00
cDc
5bbfe8f70c add Sentera camera support to XMP 2019-01-11 15:28:25 +02:00
cDc
cb40ffa537 add test images 2019-01-11 15:24:23 +02:00
cDc
405b8a1693 parse XMP stored inside EXIF 2018-10-09 12:20:43 +03:00
cDc
22ba2704c9 parse XMP for more DJI cameras 2018-09-24 13:18:40 +03:00
cDc
8b383a2809 fix rational/s-rational EXIF parsing 2018-09-24 13:18:05 +03:00
cDc
ac39905f11 add maker check for DJI too 2018-09-24 12:33:57 +03:00
cDc
98dc256e0a add PARROT camera support to XMP 2018-08-15 13:44:36 +03:00
cDc
e9c19435c3 parse GPS accuracy 2018-07-18 15:41:35 +03:00
cDc
04969097a4 add MakerNote support for DJI 2018-07-10 10:00:24 +03:00
cDc
8e3316cb41 extract calibration information 2018-07-04 18:30:26 +03:00
cDc
c02203795b add senseFly support 2018-07-04 18:29:53 +03:00
38 changed files with 309 additions and 41 deletions

40
.gitignore vendored Normal file
View File

@@ -0,0 +1,40 @@
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Custom
*.tmp
*.exif.txt
*.exif.txt.txt
.DS_Store
CMakeSettings.json
.vs/
.idea/
.vscode/
bin/
binaries/

View File

@@ -84,7 +84,7 @@ if(BUILD_SHARED_LIBS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4251") # needs to have dll-interface
endif()
target_link_libraries(TinyEXIF tinyxml2)
target_link_libraries(TinyEXIF tinyxml2::tinyxml2)
set_target_properties(TinyEXIF PROPERTIES
COMPILE_DEFINITIONS "TINYEXIF_EXPORT"
VERSION "${GENERIC_LIB_VERSION}"
@@ -121,7 +121,7 @@ endif()
if(BUILD_STATIC_LIBS)
add_library(TinyEXIFstatic STATIC TinyEXIF.cpp TinyEXIF.h)
target_link_libraries(TinyEXIFstatic tinyxml2)
target_link_libraries(TinyEXIFstatic tinyxml2::tinyxml2)
set_target_properties(TinyEXIFstatic PROPERTIES
OUTPUT_NAME TinyEXIF
VERSION "${GENERIC_LIB_VERSION}"
@@ -162,7 +162,7 @@ if(BUILD_DEMO)
target_compile_definitions(TinyEXIFdemo PRIVATE TINYEXIF_IMPORT)
else(BUILD_STATIC_LIBS)
add_dependencies(TinyEXIFdemo TinyEXIFstatic)
target_link_libraries(TinyEXIFdemo TinyEXIFstatic tinyxml2)
target_link_libraries(TinyEXIFdemo TinyEXIFstatic tinyxml2::tinyxml2)
endif()
endif()

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

BIN
Samples/1103806289718.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

BIN
Samples/20160108-162501.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

BIN
Samples/ARTstorXMP.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
Samples/Anafi.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 KiB

BIN
Samples/Bebop_2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

BIN
Samples/Bebop_2_1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

BIN
Samples/GettyVilla0001.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

BIN
Samples/IPTCpanel.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
Samples/VRAexample012.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
Samples/bb-android.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
Samples/calib.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

BIN
Samples/crosa.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

BIN
Samples/down-mirrored.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

BIN
Samples/evil1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
Samples/example005.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

BIN
Samples/lens_info.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

BIN
Samples/lukas12p.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
Samples/ok.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

BIN
Samples/problem.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 KiB

BIN
Samples/right.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
Samples/sensefly.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

BIN
Samples/short-ascii-II.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 KiB

BIN
Samples/short-ascii-MM.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
Samples/sony-alpha-6000.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

BIN
Samples/test1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
Samples/test2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
Samples/test3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

18
TestSamples.py Normal file
View File

@@ -0,0 +1,18 @@
import os
import sys
import subprocess
source = os.path.join(os.path.dirname(sys.argv[0]), 'Samples')
TinyEXIF = os.path.join(source, 'TinyEXIF')
exiftool = 'exiftool'
ext = 'exif.txt'
if len(sys.argv) > 1:
ext = sys.argv[1]
for root, dirs, filenames in os.walk(source):
for f in filenames:
if f[-4:].lower() == '.jpg':
fullpath = os.path.join(source, f)
imgexif = fullpath[:-3] + ext
print('parse ' + fullpath + ' to ' + imgexif)
subprocess.Popen(TinyEXIF + ' ' + fullpath + ' > ' + imgexif, shell=True)
subprocess.Popen(exiftool + ' -n -s -G ' + fullpath + ' > ' + imgexif + '.txt', shell=True)

View File

@@ -49,6 +49,43 @@
#endif
namespace Tools {
// search string inside a string, case sensitive
static const char* strrnstr(const char* haystack, const char* needle, size_t len) {
const size_t needle_len(strlen(needle));
if (0 == needle_len)
return haystack;
if (len <= needle_len)
return NULL;
for (size_t i=len-needle_len; i-- > 0; ) {
if (haystack[0] == needle[0] &&
0 == _tcsncmp(haystack, needle, needle_len))
return haystack;
haystack++;
}
return NULL;
}
// split an input string with a delimiter and fill a string vector
static void strSplit(const std::string& str, char delim, std::vector<std::string>& values) {
values.clear();
std::string::size_type start(0), end(0);
while (end != std::string::npos) {
end = str.find(delim, start);
values.emplace_back(str.substr(start, end-start));
start = end + 1;
}
}
// make sure the given degrees value is between -180 and 180
static double NormD180(double d) {
return (d = fmod(d+180.0, 360.0)) < 0 ? d+180.0 : d-180.0;
}
} // namespace Tools
namespace TinyEXIF {
enum JPEG_MARKERS {
@@ -145,6 +182,10 @@ public:
length = parse32(buf + offs + 4, alignIntel);
}
const uint8_t* GetBuffer() const { return buf; }
unsigned GetOffset() const { return offs; }
bool IsIntelAligned() const { return alignIntel; }
uint16_t GetTag() const { return tag; }
uint32_t GetLength() const { return length; }
uint32_t GetData() const { return parse32(buf + offs + 8, alignIntel); }
@@ -153,11 +194,17 @@ public:
bool IsShort() const { return format == 3; }
bool IsLong() const { return format == 4; }
bool IsRational() const { return format == 5 || format == 10; }
bool IsSRational() const { return format == 10; }
bool IsFloat() const { return format == 11; }
bool IsUndefined() const { return format == 7; }
std::string FetchString() const {
return parseString(buf, length, GetData(), tiff_header_start, len, alignIntel);
}
bool Fetch(std::string& val) const {
if (format != 2 || length == 0)
return false;
val = parseEXIFString(buf, length, GetData(), tiff_header_start, len, alignIntel);
val = FetchString();
return true;
}
bool Fetch(uint8_t& val) const {
@@ -184,16 +231,30 @@ public:
val = parse32(buf + offs + 8, alignIntel);
return true;
}
bool Fetch(float& val) const {
if (!IsFloat() || length == 0)
return false;
val = parseFloat(buf + offs + 8, alignIntel);
return true;
}
bool Fetch(double& val) const {
if (!IsRational() || length == 0)
return false;
val = parseEXIFRational(buf + GetSubIFD(), alignIntel);
val = parseRational(buf + GetSubIFD(), alignIntel, IsSRational());
return true;
}
bool Fetch(double& val, uint32_t idx) const {
if (!IsRational() || length <= idx)
return false;
val = parseEXIFRational(buf + GetSubIFD() + idx*8, alignIntel);
val = parseRational(buf + GetSubIFD() + idx*8, alignIntel, IsSRational());
return true;
}
bool FetchFloat(double& val) const {
float _val;
if (!Fetch(_val))
return false;
val = _val;
return true;
}
@@ -217,14 +278,24 @@ public:
((uint32_t)buf[2]<<8) |
buf[3];
}
static double parseEXIFRational(const uint8_t* buf, bool intel) {
static float parseFloat(const uint8_t* buf, bool intel) {
union {
uint32_t i;
float f;
} i2f;
i2f.i = parse32(buf, intel);
return i2f.f;
}
static double parseRational(const uint8_t* buf, bool intel, bool isSigned) {
const uint32_t denominator = parse32(buf+4, intel);
if (denominator == 0)
return 0.0;
const uint32_t numerator = parse32(buf, intel);
return (double)(int32_t)numerator/(double)(int32_t)denominator;
return isSigned ?
(double)(int32_t)numerator/(double)(int32_t)denominator :
(double)numerator/(double)denominator;
}
static std::string parseEXIFString(const uint8_t* buf,
static std::string parseString(const uint8_t* buf,
unsigned num_components,
unsigned data,
unsigned base,
@@ -362,6 +433,14 @@ void EXIFInfo::parseIFDImage(EntryParser& parser, unsigned& exif_sub_ifd_offset,
// Parse tag as Exif IFD
void EXIFInfo::parseIFDExif(EntryParser& parser) {
switch (parser.GetTag()) {
case 0x02bc:
// XMP Metadata (Adobe technote 9-14-02)
if (parser.IsUndefined()) {
const std::string strXML(parser.FetchString());
parseFromXMPSegmentXML(strXML.c_str(), (unsigned)strXML.length());
}
break;
case 0x829a:
// Exposure time in seconds
parser.Fetch(ExposureTime);
@@ -448,6 +527,11 @@ void EXIFInfo::parseIFDExif(EntryParser& parser) {
}
break;
case 0x927c:
// MakerNote
parseIFDMakerNote(parser);
break;
case 0x9291:
// Fractions of seconds for DateTimeOriginal
parser.Fetch(SubSecTimeOriginal);
@@ -534,6 +618,60 @@ void EXIFInfo::parseIFDExif(EntryParser& parser) {
}
}
// Parse tag as MakerNote IFD
void EXIFInfo::parseIFDMakerNote(EntryParser& parser) {
const unsigned startOff = parser.GetOffset();
const uint32_t off = parser.GetSubIFD();
if (0 != _tcsicmp(Make.c_str(), "DJI"))
return;
int num_entries = EntryParser::parse16(parser.GetBuffer()+off, parser.IsIntelAligned());
if (uint32_t(2 + 12 * num_entries) > parser.GetLength())
return;
parser.Init(off+2);
parser.ParseTag();
--num_entries;
std::string maker;
if (parser.GetTag() == 1 && parser.Fetch(maker)) {
if (0 == _tcsicmp(maker.c_str(), "DJI")) {
while (--num_entries >= 0) {
parser.ParseTag();
switch (parser.GetTag()) {
case 3:
// SpeedX
parser.FetchFloat(GeoLocation.SpeedX);
break;
case 4:
// SpeedY
parser.FetchFloat(GeoLocation.SpeedY);
break;
case 5:
// SpeedZ
parser.FetchFloat(GeoLocation.SpeedZ);
break;
case 9:
// Camera Pitch
parser.FetchFloat(GeoLocation.PitchDegree);
break;
case 10:
// Camera Yaw
parser.FetchFloat(GeoLocation.YawDegree);
break;
case 11:
// Camera Roll
parser.FetchFloat(GeoLocation.RollDegree);
break;
}
}
}
}
parser.Init(startOff+12);
}
// Parse tag as GPS IFD
void EXIFInfo::parseIFDGPS(EntryParser& parser) {
switch (parser.GetTag()) {
@@ -842,23 +980,6 @@ int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
// PARAM: 'len' length of buffer
//
int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
struct Tools {
static const char* strrnstr(const char* haystack, const char* needle, size_t len) {
const size_t needle_len(strlen(needle));
if (0 == needle_len)
return haystack;
if (len <= needle_len)
return NULL;
for (size_t i=len-needle_len; i-- > 0; ) {
if (haystack[0] == needle[0] &&
0 == _tcsncmp(haystack, needle, needle_len))
return haystack;
haystack++;
}
return NULL;
}
};
unsigned offs = 29; // current offset into buffer
if (!buf || len < offs)
return PARSE_ABSENT_DATA;
@@ -866,17 +987,18 @@ int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
return PARSE_ABSENT_DATA;
if (offs >= len)
return PARSE_CORRUPT_DATA;
len -= offs;
return parseFromXMPSegmentXML((const char*)(buf + offs), len - offs);
}
int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, unsigned len) {
// Skip xpacket end section so that tinyxml2 lib parses the section correctly.
const char* const strXMP((const char*)(buf + offs)), *strEnd;
if ((strEnd=Tools::strrnstr(strXMP, "<?xpacket end=", len)) != NULL)
len = (unsigned)(strEnd - strXMP);
const char* szEnd(Tools::strrnstr(szXML, "<?xpacket end=", len));
if (szEnd != NULL)
len = (unsigned)(szEnd - szXML);
// Try parsing the XML packet.
tinyxml2::XMLDocument doc;
const tinyxml2::XMLElement* document;
if (doc.Parse(strXMP, len) != tinyxml2::XML_SUCCESS ||
if (doc.Parse(szXML, len) != tinyxml2::XML_SUCCESS ||
((document=doc.FirstChildElement("x:xmpmeta")) == NULL && (document=doc.FirstChildElement("xmp:xmpmeta")) == NULL) ||
(document=document->FirstChildElement("rdf:RDF")) == NULL ||
(document=document->FirstChildElement("rdf:Description")) == NULL)
@@ -909,19 +1031,67 @@ int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
if (szProjectionType != NULL) {
if (0 == _tcsicmp(szProjectionType, "perspective"))
ProjectionType = 1;
else if (0 == _tcsicmp(szProjectionType, "equirectangular") ||
0 == _tcsicmp(szProjectionType, "spherical"))
else
if (0 == _tcsicmp(szProjectionType, "equirectangular") ||
0 == _tcsicmp(szProjectionType, "spherical"))
ProjectionType = 2;
}
}
}
// Try parsing the XMP content for DJI info.
document->QueryDoubleAttribute("drone-dji:AbsoluteAltitude", &GeoLocation.Altitude);
document->QueryDoubleAttribute("drone-dji:RelativeAltitude", &GeoLocation.RelativeAltitude);
document->QueryDoubleAttribute("drone-dji:GimbalRollDegree", &GeoLocation.RollDegree);
document->QueryDoubleAttribute("drone-dji:GimbalPitchDegree", &GeoLocation.PitchDegree);
document->QueryDoubleAttribute("drone-dji:GimbalYawDegree", &GeoLocation.YawDegree);
// Try parsing the XMP content for supported maker's info.
struct ParseXMP {
// try yo fetch the value both from the attribute and child element
// and parse if needed rational numbers stored as string fraction
static bool Value(const tinyxml2::XMLElement* document, const char* name, double& value) {
const char* szAttribute = document->Attribute(name);
if (szAttribute == NULL) {
const tinyxml2::XMLElement* const element(document->FirstChildElement(name));
if (element == NULL || (szAttribute=element->GetText()) == NULL)
return false;
}
std::vector<std::string> values;
Tools::strSplit(szAttribute, '/', values);
switch (values.size()) {
case 1: value = strtod(values.front().c_str(), NULL); return true;
case 2: value = strtod(values.front().c_str(), NULL)/strtod(values.back().c_str(), NULL); return true;
}
return false;
}
};
const char* szAbout(document->Attribute("rdf:about"));
if (0 == _tcsicmp(Make.c_str(), "DJI") || (szAbout != NULL && 0 == _tcsicmp(szAbout, "DJI Meta Data"))) {
ParseXMP::Value(document, "drone-dji:AbsoluteAltitude", GeoLocation.Altitude);
ParseXMP::Value(document, "drone-dji:RelativeAltitude", GeoLocation.RelativeAltitude);
ParseXMP::Value(document, "drone-dji:GimbalRollDegree", GeoLocation.RollDegree);
ParseXMP::Value(document, "drone-dji:GimbalPitchDegree", GeoLocation.PitchDegree);
ParseXMP::Value(document, "drone-dji:GimbalYawDegree", GeoLocation.YawDegree);
ParseXMP::Value(document, "drone-dji:CalibratedFocalLength", Calibration.FocalLength);
ParseXMP::Value(document, "drone-dji:CalibratedOpticalCenterX", Calibration.OpticalCenterX);
ParseXMP::Value(document, "drone-dji:CalibratedOpticalCenterY", Calibration.OpticalCenterY);
} else
if (0 == _tcsicmp(Make.c_str(), "senseFly") || 0 == _tcsicmp(Make.c_str(), "Sentera")) {
ParseXMP::Value(document, "Camera:Roll", GeoLocation.RollDegree);
if (ParseXMP::Value(document, "Camera:Pitch", GeoLocation.PitchDegree)) {
// convert to DJI format: senseFly uses pitch 0 as NADIR, whereas DJI -90
GeoLocation.PitchDegree = Tools::NormD180(GeoLocation.PitchDegree-90.0);
}
ParseXMP::Value(document, "Camera:Yaw", GeoLocation.YawDegree);
ParseXMP::Value(document, "Camera:GPSXYAccuracy", GeoLocation.AccuracyXY);
ParseXMP::Value(document, "Camera:GPSZAccuracy", GeoLocation.AccuracyZ);
} else
if (0 == _tcsicmp(Make.c_str(), "PARROT")) {
ParseXMP::Value(document, "Camera:Roll", GeoLocation.RollDegree) ||
ParseXMP::Value(document, "drone-parrot:CameraRollDegree", GeoLocation.RollDegree);
if (ParseXMP::Value(document, "Camera:Pitch", GeoLocation.PitchDegree) ||
ParseXMP::Value(document, "drone-parrot:CameraPitchDegree", GeoLocation.PitchDegree)) {
// convert to DJI format: senseFly uses pitch 0 as NADIR, whereas DJI -90
GeoLocation.PitchDegree = Tools::NormD180(GeoLocation.PitchDegree-90.0);
}
ParseXMP::Value(document, "Camera:Yaw", GeoLocation.YawDegree) ||
ParseXMP::Value(document, "drone-parrot:CameraYawDegree", GeoLocation.YawDegree);
ParseXMP::Value(document, "Camera:AboveGroundAltitude", GeoLocation.RelativeAltitude);
}
return PARSE_SUCCESS;
}
@@ -969,6 +1139,9 @@ bool EXIFInfo::Geolocation_t::hasRelativeAltitude() const {
bool EXIFInfo::Geolocation_t::hasOrientation() const {
return RollDegree != DBL_MAX && PitchDegree != DBL_MAX && YawDegree != DBL_MAX;
}
bool EXIFInfo::Geolocation_t::hasSpeed() const {
return SpeedX != DBL_MAX && SpeedY != DBL_MAX && SpeedZ != DBL_MAX;
}
void EXIFInfo::clear() {
@@ -1012,6 +1185,11 @@ void EXIFInfo::clear() {
ProjectionType = 0;
SubjectArea.clear();
// Calibration
Calibration.FocalLength = 0;
Calibration.OpticalCenterX = 0;
Calibration.OpticalCenterY = 0;
// LensInfo
LensInfo.FocalLengthMax = 0;
LensInfo.FocalLengthMin = 0;
@@ -1034,6 +1212,11 @@ void EXIFInfo::clear() {
GeoLocation.RollDegree = DBL_MAX;
GeoLocation.PitchDegree = DBL_MAX;
GeoLocation.YawDegree = DBL_MAX;
GeoLocation.SpeedX = DBL_MAX;
GeoLocation.SpeedY = DBL_MAX;
GeoLocation.SpeedZ = DBL_MAX;
GeoLocation.AccuracyXY = 0;
GeoLocation.AccuracyZ = 0;
GeoLocation.GPSDOP = 0;
GeoLocation.GPSDifferential = 0;
GeoLocation.GPSMapDatum = "";

View File

@@ -121,6 +121,7 @@ public:
// but can be called for special cases where only the XMP section is
// available (i.e., a blob starting with the bytes "http://ns.adobe.com/xap/1.0/\0").
int parseFromXMPSegment(const uint8_t* buf, unsigned len);
int parseFromXMPSegmentXML(const char* szXML, unsigned len);
// Set all data members to default values.
// Should be called before parsing a new stream.
@@ -133,6 +134,8 @@ private:
void parseIFDExif(EntryParser&);
// Parse tag as GPS IFD.
void parseIFDGPS(EntryParser&);
// Parse tag as MakerNote IFD.
void parseIFDMakerNote(EntryParser&);
public:
// Data fields
@@ -239,6 +242,11 @@ public:
// 2: location of the main subject as coordinates (first value is the X coordinate and second is the Y coordinate)
// 3: area of the main subject as a circle (first value is the center X coordinate, second is the center Y coordinate, and third is the diameter)
// 4: area of the main subject as a rectangle (first value is the center X coordinate, second is the center Y coordinate, third is the width of the area, and fourth is the height of the area)
struct TINYEXIF_LIB Calibration_t { // Camera calibration information
double FocalLength; // Focal length (pixels)
double OpticalCenterX; // Principal point X (pixels)
double OpticalCenterY; // Principal point Y (pixels)
} Calibration;
struct TINYEXIF_LIB LensInfo_t { // Lens information
double FStopMin; // Min aperture (f-stop)
double FStopMax; // Max aperture (f-stop)
@@ -265,6 +273,11 @@ public:
double RollDegree; // Flight roll in degrees
double PitchDegree; // Flight pitch in degrees
double YawDegree; // Flight yaw in degrees
double SpeedX; // Flight speed on X in meters/second
double SpeedY; // Flight speed on Y in meters/second
double SpeedZ; // Flight speed on Z in meters/second
double AccuracyXY; // GPS accuracy on XY in meters
double AccuracyZ; // GPS accuracy on Z in meters
double GPSDOP; // GPS DOP (data degree of precision)
uint16_t GPSDifferential; // Differential correction applied to the GPS receiver (may not exist)
// 0: measurement without differential correction
@@ -283,6 +296,7 @@ public:
bool hasAltitude() const; // Return true if (alt) is available
bool hasRelativeAltitude()const;// Return true if (rel_alt) is available
bool hasOrientation() const; // Return true if (roll,yaw,pitch) is available
bool hasSpeed() const; // Return true if (speedX,speedY,speedZ) is available
} GeoLocation;
};

View File

@@ -100,6 +100,12 @@ int main(int argc, const char** argv)
std::cout << "MeteringMode " << imageEXIF.MeteringMode << "\n";
std::cout << "LightSource " << imageEXIF.LightSource << "\n";
std::cout << "ProjectionType " << imageEXIF.ProjectionType << "\n";
if (imageEXIF.Calibration.FocalLength != 0)
std::cout << "Calibration.FocalLength " << imageEXIF.Calibration.FocalLength << " pixels" << "\n";
if (imageEXIF.Calibration.OpticalCenterX != 0)
std::cout << "Calibration.OpticalCenterX " << imageEXIF.Calibration.OpticalCenterX << " pixels" << "\n";
if (imageEXIF.Calibration.OpticalCenterY != 0)
std::cout << "Calibration.OpticalCenterY " << imageEXIF.Calibration.OpticalCenterY << " pixels" << "\n";
std::cout << "LensInfo.FStopMin " << imageEXIF.LensInfo.FStopMin << "\n";
std::cout << "LensInfo.FStopMax " << imageEXIF.LensInfo.FStopMax << "\n";
std::cout << "LensInfo.FocalLengthMin " << imageEXIF.LensInfo.FocalLengthMin << " mm" << "\n";
@@ -120,12 +126,19 @@ int main(int argc, const char** argv)
std::cout << "GeoLocation.AltitudeRef " << (int)imageEXIF.GeoLocation.AltitudeRef << "\n";
}
if (imageEXIF.GeoLocation.hasRelativeAltitude())
std::cout << "GeoLocation.RelativeAltitude " << imageEXIF.GeoLocation.RelativeAltitude << "\n";
std::cout << "GeoLocation.RelativeAltitude " << imageEXIF.GeoLocation.RelativeAltitude << " m" << "\n";
if (imageEXIF.GeoLocation.hasOrientation()) {
std::cout << "GeoLocation.RollDegree " << imageEXIF.GeoLocation.RollDegree << "\n";
std::cout << "GeoLocation.PitchDegree " << imageEXIF.GeoLocation.PitchDegree << "\n";
std::cout << "GeoLocation.YawDegree " << imageEXIF.GeoLocation.YawDegree << "\n";
}
if (imageEXIF.GeoLocation.hasSpeed()) {
std::cout << "GeoLocation.SpeedX " << imageEXIF.GeoLocation.SpeedX << " m/s" << "\n";
std::cout << "GeoLocation.SpeedY " << imageEXIF.GeoLocation.SpeedY << " m/s" << "\n";
std::cout << "GeoLocation.SpeedZ " << imageEXIF.GeoLocation.SpeedZ << " m/s" << "\n";
}
if (imageEXIF.GeoLocation.AccuracyXY > 0 || imageEXIF.GeoLocation.AccuracyZ > 0)
std::cout << "GeoLocation.GPSAccuracy XY " << imageEXIF.GeoLocation.AccuracyXY << " m" << " Z " << imageEXIF.GeoLocation.AccuracyZ << " m" << "\n";
std::cout << "GeoLocation.GPSDOP " << imageEXIF.GeoLocation.GPSDOP << "\n";
std::cout << "GeoLocation.GPSDifferential " << imageEXIF.GeoLocation.GPSDifferential << "\n";
if (!imageEXIF.GeoLocation.GPSMapDatum.empty())