3 #include "common/tifffile.h"
4 #include "common/util.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
16 #define GPSLongitudeRef 3
17 #define GPSLongitude 4
18 #define GPSAltitudeRef 5
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
)
29 if (e
.count
<= sizeof(e
.offset
))
30 return QString(QByteArray((const char *)&e
.offset
, sizeof(e
.offset
)));
32 if (!file
.seek(e
.offset
))
35 QByteArray
str(file
.read(e
.count
));
36 if (str
.size() < (int)e
.count
)
42 QTime
EXIFParser::time(TIFFFile
&file
, const IFDEntry
&ts
) const
44 if (!(ts
.type
== TIFF_RATIONAL
&& ts
.count
== 3))
47 if (!file
.seek(ts
.offset
))
51 for (int i
= 0; i
< 3; i
++) {
53 if (!file
.readValue(num
))
55 if (!file
.readValue(den
))
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))
70 if (!file
.seek(alt
.offset
))
74 if (!file
.readValue(num
))
76 if (!file
.readValue(den
))
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 // Some broken image creators like NOKIA phones use a wrong (SRATIONAL)
87 if (!((ll
.type
== TIFF_RATIONAL
|| ll
.type
== TIFF_SRATIONAL
)
91 if (!file
.seek(ll
.offset
))
95 for (int i
= 0; i
< 3; i
++) {
97 if (!file
.readValue(num
))
99 if (!file
.readValue(den
))
102 dms
[i
] = num
/(double)den
;
105 return dms
[0] + dms
[1]/60 + dms
[2]/3600;
108 Coordinates
EXIFParser::coordinates(TIFFFile
&file
, const IFDEntry
&lon
,
109 const IFDEntry
&lonRef
, const IFDEntry
&lat
, const IFDEntry
&latRef
) const
111 if (!(latRef
.type
== TIFF_ASCII
&& latRef
.count
== 2
112 && lonRef
.type
== TIFF_ASCII
&& lonRef
.count
== 2))
113 return Coordinates();
115 Coordinates
c(coordinate(file
, lon
), coordinate(file
, lat
));
117 return Coordinates();
119 char ew
= file
.isBE() ? lonRef
.offset
>> 24 : lonRef
.offset
;
120 char ns
= file
.isBE() ? latRef
.offset
>> 24 : latRef
.offset
;
130 bool EXIFParser::readEntry(TIFFFile
&file
, const QSet
<quint16
> &tags
,
131 QMap
<quint16
, IFDEntry
> &entries
) const
136 if (!file
.readValue(tag
))
138 if (!file
.readValue(entry
.type
))
140 if (!file
.readValue(entry
.count
))
142 if (!file
.readValue(entry
.offset
))
145 if (tags
.contains(tag
))
146 entries
.insert(tag
, entry
);
151 bool EXIFParser::readIFD(TIFFFile
&file
, quint32 offset
,
152 const QSet
<quint16
> &tags
, QMap
<quint16
, IFDEntry
> &entries
) const
156 if (!file
.seek(offset
))
158 if (!file
.readValue(count
))
161 for (quint16 i
= 0; i
< count
; i
++)
162 if (!readEntry(file
, tags
, entries
))
168 bool EXIFParser::parseTIFF(QFile
*file
, QVector
<Waypoint
> &waypoints
)
171 if (!tiff
.isValid()) {
172 _errorString
= "Invalid EXIF data";
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";
185 if (!IFD0
.contains(GPSIFDTag
)) {
186 _errorString
= "GPS IFD not found";
190 QSet
<quint16
> GPSIFDTags
;
191 GPSIFDTags
<< GPSLatitude
<< GPSLongitude
<< GPSLatitudeRef
192 << GPSLongitudeRef
<< GPSAltitude
<< GPSAltitudeRef
<< GPSDateStamp
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";
202 Coordinates
c(coordinates(tiff
, GPSIFD
.value(GPSLongitude
),
203 GPSIFD
.value(GPSLongitudeRef
), GPSIFD
.value(GPSLatitude
),
204 GPSIFD
.value(GPSLatitudeRef
)));
206 _errorString
= "Invalid/missing GPS coordinates";
211 wp
.setName(Util::file2name(file
->fileName()));
212 wp
.addImage(file
->fileName());
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
)), QTimeZone::utc()));
218 wp
.setDescription(text(tiff
, IFD0
.value(ImageDescription
)).trimmed());
220 waypoints
.append(wp
);
225 bool EXIFParser::parse(QFile
*file
, QList
<TrackData
> &tracks
,
226 QList
<RouteData
> &routes
, QList
<Area
> &polygons
,
227 QVector
<Waypoint
> &waypoints
)
234 QDataStream
stream(file
);
235 stream
.setByteOrder(QDataStream::BigEndian
);
237 if (marker
!= SOI_MARKER
) {
238 _errorString
= "Not a JPEG file";
242 while (!stream
.atEnd()) {
244 if (marker
== APP1_MARKER
) {
248 if (stream
.readRawData(magic
, sizeof(magic
)) == sizeof(magic
) &&
249 !memcmp(magic
, "Exif\0\0", sizeof(magic
)))
250 return parseTIFF(file
, waypoints
);
256 _errorString
= "No EXIF data found";