Fixed lat/lon reference parsing in big endian EXIF entries
[GPXSee.git] / src / data / exifparser.cpp
blob80200efcea81bccf477e4214c16726d3a8a09b4d
1 #include <QDataStream>
2 #include <QFileInfo>
3 #include <QImageReader>
4 #include "common/tifffile.h"
5 #include "exifparser.h"
8 #define SOI_MARKER 0xFFD8
9 #define APP1_MARKER 0xFFE1
11 #define GPSIFDTag 34853
12 #define ImageDescription 270
14 #define GPSLatitudeRef 1
15 #define GPSLatitude 2
16 #define GPSLongitudeRef 3
17 #define GPSLongitude 4
18 #define GPSAltitudeRef 5
19 #define GPSAltitude 6
20 #define GPSTimeStamp 7
21 #define GPSDateStamp 29
24 QString EXIFParser::text(TIFFFile &file, const IFDEntry &e) const
26 if (e.type != TIFF_ASCII || !e.count)
27 return QString();
29 if (e.count <= sizeof(e.offset))
30 return QString(QByteArray((const char *)&e.offset, sizeof(e.offset)));
32 if (!file.seek(e.offset))
33 return QString();
35 QByteArray str(file.read(e.count));
36 if (str.size() < (int)e.count)
37 return QString();
39 return QString(str);
42 QTime EXIFParser::time(TIFFFile &file, const IFDEntry &ts) const
44 if (!(ts.type == TIFF_RATIONAL && ts.count == 3))
45 return QTime();
47 if (!file.seek(ts.offset))
48 return QTime();
50 double hms[3];
51 for (int i = 0; i < 3; i++) {
52 quint32 num, den;
53 if (!file.readValue(num))
54 return QTime();
55 if (!file.readValue(den))
56 return QTime();
58 hms[i] = num/(double)den;
61 return QTime((int)hms[0], (int)hms[1], (int)hms[2]);
64 double EXIFParser::altitude(TIFFFile &file, const IFDEntry &alt,
65 const IFDEntry &altRef) const
67 if (!(alt.type == TIFF_RATIONAL && alt.count == 1))
68 return NAN;
70 if (!file.seek(alt.offset))
71 return NAN;
73 quint32 num, den;
74 if (!file.readValue(num))
75 return NAN;
76 if (!file.readValue(den))
77 return NAN;
79 return (altRef.type == TIFF_BYTE && altRef.count == 1 && altRef.offset)
80 ? -(num/(double)den) : num/(double)den;
83 double EXIFParser::coordinate(TIFFFile &file, const IFDEntry &ll) const
85 if (!(ll.type == TIFF_RATIONAL && ll.count == 3))
86 return NAN;
88 if (!file.seek(ll.offset))
89 return NAN;
91 double dms[3];
92 for (int i = 0; i < 3; i++) {
93 quint32 num, den;
94 if (!file.readValue(num))
95 return NAN;
96 if (!file.readValue(den))
97 return NAN;
99 dms[i] = num/(double)den;
102 return dms[0] + dms[1]/60 + dms[2]/3600;
105 Coordinates EXIFParser::coordinates(TIFFFile &file, const IFDEntry &lon,
106 const IFDEntry &lonRef, const IFDEntry &lat, const IFDEntry &latRef) const
108 if (!(latRef.type == TIFF_ASCII && latRef.count == 2
109 && lonRef.type == TIFF_ASCII && lonRef.count == 2))
110 return Coordinates();
112 Coordinates c(coordinate(file, lon), coordinate(file, lat));
113 if (!c.isValid())
114 return Coordinates();
116 char ew = file.isBE() ? lonRef.offset >> 24 : lonRef.offset;
117 char ns = file.isBE() ? latRef.offset >> 24 : latRef.offset;
119 if (ew == 'W')
120 c.rlon() = -c.lon();
121 if (ns == 'S')
122 c.rlat() = -c.lat();
124 return c;
127 bool EXIFParser::readEntry(TIFFFile &file, const QSet<quint16> &tags,
128 QMap<quint16, IFDEntry> &entries) const
130 IFDEntry entry;
131 quint16 tag;
133 if (!file.readValue(tag))
134 return false;
135 if (!file.readValue(entry.type))
136 return false;
137 if (!file.readValue(entry.count))
138 return false;
139 if (!file.readValue(entry.offset))
140 return false;
142 if (tags.contains(tag))
143 entries.insert(tag, entry);
145 return true;
148 bool EXIFParser::readIFD(TIFFFile &file, quint32 offset,
149 const QSet<quint16> &tags, QMap<quint16, IFDEntry> &entries) const
151 quint16 count;
153 if (!file.seek(offset))
154 return false;
155 if (!file.readValue(count))
156 return false;
158 for (quint16 i = 0; i < count; i++)
159 if (!readEntry(file, tags, entries))
160 return false;
162 return true;
165 bool EXIFParser::parseTIFF(QFile *file, QVector<Waypoint> &waypoints)
167 TIFFFile tiff(file);
168 if (!tiff.isValid()) {
169 _errorString = "Invalid EXIF data";
170 return false;
173 QSet<quint16> IFD0Tags;
174 IFD0Tags << GPSIFDTag << ImageDescription;
175 QMap<quint16, IFDEntry> IFD0;
176 for (quint32 ifd = tiff.ifd(); ifd; ) {
177 if (!readIFD(tiff, ifd, IFD0Tags, IFD0) || !tiff.readValue(ifd)) {
178 _errorString = "Invalid IFD0";
179 return false;
182 if (!IFD0.contains(GPSIFDTag)) {
183 _errorString = "GPS IFD not found";
184 return false;
187 QSet<quint16> GPSIFDTags;
188 GPSIFDTags << GPSLatitude << GPSLongitude << GPSLatitudeRef
189 << GPSLongitudeRef << GPSAltitude << GPSAltitudeRef << GPSDateStamp
190 << GPSTimeStamp;
191 QMap<quint16, IFDEntry> GPSIFD;
192 for (quint32 ifd = IFD0.value(GPSIFDTag).offset; ifd; ) {
193 if (!readIFD(tiff, ifd, GPSIFDTags, GPSIFD) || !tiff.readValue(ifd)) {
194 _errorString = "Invalid GPS IFD";
195 return false;
199 Coordinates c(coordinates(tiff, GPSIFD.value(GPSLongitude),
200 GPSIFD.value(GPSLongitudeRef), GPSIFD.value(GPSLatitude),
201 GPSIFD.value(GPSLatitudeRef)));
202 if (!c.isValid()) {
203 _errorString = "Invalid/missing GPS coordinates";
204 return false;
207 file->reset();
208 ImageInfo img(file->fileName(), QImageReader(file).size());
210 Waypoint wp(c);
211 wp.setName(QFileInfo(file->fileName()).baseName());
212 wp.setImage(img);
213 wp.setElevation(altitude(tiff, GPSIFD.value(GPSAltitude),
214 GPSIFD.value(GPSAltitudeRef)));
215 wp.setTimestamp(QDateTime(QDate::fromString(text(tiff,
216 GPSIFD.value(GPSDateStamp)), "yyyy:MM:dd"), time(tiff,
217 GPSIFD.value(GPSTimeStamp)), Qt::UTC));
218 wp.setDescription(text(tiff, IFD0.value(ImageDescription)).trimmed());
220 waypoints.append(wp);
222 return true;
225 bool EXIFParser::parse(QFile *file, QList<TrackData> &tracks,
226 QList<RouteData> &routes, QList<Area> &polygons,
227 QVector<Waypoint> &waypoints)
229 Q_UNUSED(tracks);
230 Q_UNUSED(routes);
231 Q_UNUSED(polygons);
232 quint16 marker;
234 QDataStream stream(file);
235 stream.setByteOrder(QDataStream::BigEndian);
236 stream >> marker;
237 if (marker != SOI_MARKER) {
238 _errorString = "Not a JPEG file";
239 return false;
242 while (!stream.atEnd()) {
243 stream >> marker;
244 if (marker == APP1_MARKER) {
245 quint16 size;
246 char magic[6];
247 stream >> size;
248 if (stream.readRawData(magic, sizeof(magic)) == sizeof(magic) &&
249 !memcmp(magic, "Exif\0\0", sizeof(magic)))
250 return parseTIFF(file, waypoints);
251 else
252 break;
256 _errorString = "No EXIF data found";
257 return false;