1 #include <QDataStream>
2 #include <QFileInfo>
3 #include <QImageReader>
4 #include "common/tifffile.h"
5 #include "exifparser.h"
6 
7 
8 #define SOI_MARKER       0xFFD8
9 #define APP1_MARKER      0xFFE1
10 
11 #define GPSIFDTag        34853
12 #define ImageDescription 270
13 
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
22 
23 
text(TIFFFile & file,const IFDEntry & e) const24 QString EXIFParser::text(TIFFFile &file, const IFDEntry &e) const
25 {
26 	if (e.type != TIFF_ASCII || !e.count)
27 		return QString();
28 
29 	if (e.count <= sizeof(e.offset))
30 		return QString(QByteArray((const char *)&e.offset, sizeof(e.offset)));
31 
32 	if (!file.seek(e.offset))
33 		return QString();
34 
35 	QByteArray str(file.read(e.count));
36 	if (str.size() < (int)e.count)
37 		return QString();
38 
39 	return QString(str);
40 }
41 
time(TIFFFile & file,const IFDEntry & ts) const42 QTime EXIFParser::time(TIFFFile &file, const IFDEntry &ts) const
43 {
44 	if (!(ts.type == TIFF_RATIONAL && ts.count == 3))
45 		return QTime();
46 
47 	if (!file.seek(ts.offset))
48 		return QTime();
49 
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();
57 
58 		hms[i] = num/(double)den;
59 	}
60 
61 	return QTime((int)hms[0], (int)hms[1], (int)hms[2]);
62 }
63 
altitude(TIFFFile & file,const IFDEntry & alt,const IFDEntry & altRef) const64 double EXIFParser::altitude(TIFFFile &file, const IFDEntry &alt,
65   const IFDEntry &altRef) const
66 {
67 	if (!(alt.type == TIFF_RATIONAL && alt.count == 1))
68 		return NAN;
69 
70 	if (!file.seek(alt.offset))
71 		return NAN;
72 
73 	quint32 num, den;
74 	if (!file.readValue(num))
75 		return NAN;
76 	if (!file.readValue(den))
77 		return NAN;
78 
79 	return (altRef.type == TIFF_BYTE && altRef.count == 1 && altRef.offset)
80 	  ? -(num/(double)den) : num/(double)den;
81 }
82 
coordinate(TIFFFile & file,const IFDEntry & ll) const83 double EXIFParser::coordinate(TIFFFile &file, const IFDEntry &ll) const
84 {
85 	// Some broken image creators like NOKIA phones use a wrong (SRATIONAL)
86 	// data type
87 	if (!((ll.type == TIFF_RATIONAL || ll.type == TIFF_SRATIONAL)
88 	  && ll.count == 3))
89 		return NAN;
90 
91 	if (!file.seek(ll.offset))
92 		return NAN;
93 
94 	double dms[3];
95 	for (int i = 0; i < 3; i++) {
96 		quint32 num, den;
97 		if (!file.readValue(num))
98 			return NAN;
99 		if (!file.readValue(den))
100 			return NAN;
101 
102 		dms[i] = num/(double)den;
103 	}
104 
105 	return dms[0] + dms[1]/60 + dms[2]/3600;
106 }
107 
coordinates(TIFFFile & file,const IFDEntry & lon,const IFDEntry & lonRef,const IFDEntry & lat,const IFDEntry & latRef) const108 Coordinates EXIFParser::coordinates(TIFFFile &file, const IFDEntry &lon,
109   const IFDEntry &lonRef, const IFDEntry &lat, const IFDEntry &latRef) const
110 {
111 	if (!(latRef.type == TIFF_ASCII && latRef.count == 2
112 	  && lonRef.type == TIFF_ASCII && lonRef.count == 2))
113 		return Coordinates();
114 
115 	Coordinates c(coordinate(file, lon), coordinate(file, lat));
116 	if (!c.isValid())
117 		return Coordinates();
118 
119 	char ew = file.isBE() ? lonRef.offset >> 24 : lonRef.offset;
120 	char ns = file.isBE() ? latRef.offset >> 24 : latRef.offset;
121 
122 	if (ew == 'W')
123 		c.rlon() = -c.lon();
124 	if (ns == 'S')
125 		c.rlat() = -c.lat();
126 
127 	return c;
128 }
129 
readEntry(TIFFFile & file,const QSet<quint16> & tags,QMap<quint16,IFDEntry> & entries) const130 bool EXIFParser::readEntry(TIFFFile &file, const QSet<quint16> &tags,
131   QMap<quint16, IFDEntry> &entries) const
132 {
133 	IFDEntry entry;
134 	quint16 tag;
135 
136 	if (!file.readValue(tag))
137 		return false;
138 	if (!file.readValue(entry.type))
139 		return false;
140 	if (!file.readValue(entry.count))
141 		return false;
142 	if (!file.readValue(entry.offset))
143 		return false;
144 
145 	if (tags.contains(tag))
146 		entries.insert(tag, entry);
147 
148 	return true;
149 }
150 
readIFD(TIFFFile & file,quint32 offset,const QSet<quint16> & tags,QMap<quint16,IFDEntry> & entries) const151 bool EXIFParser::readIFD(TIFFFile &file, quint32 offset,
152   const QSet<quint16> &tags, QMap<quint16, IFDEntry> &entries) const
153 {
154 	quint16 count;
155 
156 	if (!file.seek(offset))
157 		return false;
158 	if (!file.readValue(count))
159 		return false;
160 
161 	for (quint16 i = 0; i < count; i++)
162 		if (!readEntry(file, tags, entries))
163 			return false;
164 
165 	return true;
166 }
167 
parseTIFF(QFile * file,QVector<Waypoint> & waypoints)168 bool EXIFParser::parseTIFF(QFile *file, QVector<Waypoint> &waypoints)
169 {
170 	TIFFFile tiff(file);
171 	if (!tiff.isValid()) {
172 		_errorString = "Invalid EXIF data";
173 		return false;
174 	}
175 
176 	QSet<quint16> IFD0Tags;
177 	IFD0Tags << GPSIFDTag << ImageDescription;
178 	QMap<quint16, IFDEntry> IFD0;
179 	for (quint32 ifd = tiff.ifd(); ifd; ) {
180 		if (!readIFD(tiff, ifd, IFD0Tags, IFD0) || !tiff.readValue(ifd)) {
181 			_errorString = "Invalid IFD0";
182 			return false;
183 		}
184 	}
185 	if (!IFD0.contains(GPSIFDTag)) {
186 		_errorString = "GPS IFD not found";
187 		return false;
188 	}
189 
190 	QSet<quint16> GPSIFDTags;
191 	GPSIFDTags << GPSLatitude << GPSLongitude << GPSLatitudeRef
192 	  << GPSLongitudeRef << GPSAltitude << GPSAltitudeRef << GPSDateStamp
193 	  << GPSTimeStamp;
194 	QMap<quint16, IFDEntry> GPSIFD;
195 	for (quint32 ifd = IFD0.value(GPSIFDTag).offset; ifd; ) {
196 		if (!readIFD(tiff, ifd, GPSIFDTags, GPSIFD) || !tiff.readValue(ifd)) {
197 			_errorString = "Invalid GPS IFD";
198 			return false;
199 		}
200 	}
201 
202 	Coordinates c(coordinates(tiff, GPSIFD.value(GPSLongitude),
203 	  GPSIFD.value(GPSLongitudeRef), GPSIFD.value(GPSLatitude),
204 	  GPSIFD.value(GPSLatitudeRef)));
205 	if (!c.isValid()) {
206 		_errorString = "Invalid/missing GPS coordinates";
207 		return false;
208 	}
209 
210 	file->reset();
211 	ImageInfo img(file->fileName(), QImageReader(file).size());
212 
213 	Waypoint wp(c);
214 	wp.setName(QFileInfo(file->fileName()).baseName());
215 	wp.addImage(img);
216 	wp.setElevation(altitude(tiff, GPSIFD.value(GPSAltitude),
217 	  GPSIFD.value(GPSAltitudeRef)));
218 	wp.setTimestamp(QDateTime(QDate::fromString(text(tiff,
219 	  GPSIFD.value(GPSDateStamp)), "yyyy:MM:dd"), time(tiff,
220 	  GPSIFD.value(GPSTimeStamp)), Qt::UTC));
221 	wp.setDescription(text(tiff, IFD0.value(ImageDescription)).trimmed());
222 
223 	waypoints.append(wp);
224 
225 	return true;
226 }
227 
parse(QFile * file,QList<TrackData> & tracks,QList<RouteData> & routes,QList<Area> & polygons,QVector<Waypoint> & waypoints)228 bool EXIFParser::parse(QFile *file, QList<TrackData> &tracks,
229   QList<RouteData> &routes, QList<Area> &polygons,
230   QVector<Waypoint> &waypoints)
231 {
232 	Q_UNUSED(tracks);
233 	Q_UNUSED(routes);
234 	Q_UNUSED(polygons);
235 	quint16 marker;
236 
237 	QDataStream stream(file);
238 	stream.setByteOrder(QDataStream::BigEndian);
239 	stream >> marker;
240 	if (marker != SOI_MARKER) {
241 		_errorString = "Not a JPEG file";
242 		return false;
243 	}
244 
245 	while (!stream.atEnd()) {
246 		stream >> marker;
247 		if (marker == APP1_MARKER) {
248 			quint16 size;
249 			char magic[6];
250 			stream >> size;
251 			if (stream.readRawData(magic, sizeof(magic)) == sizeof(magic) &&
252 			  !memcmp(magic, "Exif\0\0", sizeof(magic)))
253 				return parseTIFF(file, waypoints);
254 			else
255 				break;
256 		}
257 	}
258 
259 	_errorString = "No EXIF data found";
260 	return false;
261 }
262