1 | /* |
2 | This file is part of the KDE project |
3 | SPDX-FileCopyrightText: 2025 Mirco Miranda <mircomir@outlook.com> |
4 | |
5 | SPDX-License-Identifier: LGPL-2.1-or-later |
6 | */ |
7 | |
8 | #ifndef MICROEXIF_P_H |
9 | #define MICROEXIF_P_H |
10 | |
11 | #include <QByteArray> |
12 | #include <QColorSpace> |
13 | #include <QDataStream> |
14 | #include <QDateTime> |
15 | #include <QImage> |
16 | #include <QImageIOHandler> |
17 | #include <QMap> |
18 | #include <QUuid> |
19 | #include <QVariant> |
20 | |
21 | #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN |
22 | #define EXIF_DEFAULT_BYTEORDER QDataStream::LittleEndian |
23 | #else |
24 | #define EXIF_DEFAULT_BYTEORDER QDataStream::BigEndian |
25 | #endif |
26 | |
27 | /*! |
28 | * \brief The MicroExif class |
29 | * Class to extract / write minimal EXIF data (e.g. resolution, rotation, |
30 | * some strings). |
31 | * |
32 | * This class is a partial (or rather minimal) implementation and is only used |
33 | * to avoid including external libraries when only a few tags are needed. |
34 | */ |
35 | class MicroExif |
36 | { |
37 | public: |
38 | using Tags = QMap<quint16, QVariant>; |
39 | |
40 | /*! |
41 | * \brief The Version enum |
42 | * Exif specs version used when writing. |
43 | */ |
44 | enum Version { |
45 | V2, // V2.xx |
46 | V3 // V3.xx, use of UTF-8 data type (default) |
47 | }; |
48 | |
49 | /*! |
50 | * \brief MicroExif |
51 | * Constructs an empty class. |
52 | * \sa isEmpty |
53 | */ |
54 | MicroExif(); |
55 | |
56 | MicroExif(const MicroExif &other) = default; |
57 | MicroExif &operator=(const MicroExif &other) = default; |
58 | |
59 | /*! |
60 | * \brief clear |
61 | * Removes all items. |
62 | */ |
63 | void clear(); |
64 | |
65 | /*! |
66 | * \brief isEmpty |
67 | * \return True if it contains no items, otherwise false. |
68 | */ |
69 | bool isEmpty() const; |
70 | |
71 | /*! |
72 | * \brief horizontalResolution |
73 | * \return The horizontal resolution in DPI. |
74 | */ |
75 | double horizontalResolution() const; |
76 | void setHorizontalResolution(double hres); |
77 | |
78 | /*! |
79 | * \brief verticalResolution |
80 | * \return The vertical resolution in DPI. |
81 | */ |
82 | double verticalResolution() const; |
83 | void setVerticalResolution(double vres); |
84 | |
85 | /*! |
86 | * \brief colosSpace |
87 | * \return sRGB color space or an invalid one. |
88 | */ |
89 | QColorSpace colosSpace() const; |
90 | void setColorSpace(const QColorSpace& cs); |
91 | void setColorSpace(const QColorSpace::NamedColorSpace& csName); |
92 | |
93 | /*! |
94 | * \brief width |
95 | * \return The image width. |
96 | */ |
97 | qint32 width() const; |
98 | void setWidth(qint32 w); |
99 | |
100 | /*! |
101 | * \brief height |
102 | * \return The image height. |
103 | */ |
104 | qint32 height() const; |
105 | void setHeight(qint32 h); |
106 | |
107 | /*! |
108 | * \brief orientation |
109 | * The orientation of the image with respect to the rows and columns. |
110 | * |
111 | * Valid orientation values: |
112 | * - 1 = The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side. |
113 | * - 2 = The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side. |
114 | * - 3 = The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side. |
115 | * - 4 = The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side. |
116 | * - 5 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual top. |
117 | * - 6 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual top. |
118 | * - 7 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom. |
119 | * - 8 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom. |
120 | * \return The orientation value or 0 if none. |
121 | * \sa transformation |
122 | */ |
123 | quint16 orientation() const; |
124 | void setOrientation(quint16 orient); |
125 | |
126 | /*! |
127 | * \brief transformation |
128 | * \return The orientation converted in the equvalent Qt transformation. |
129 | * \sa orientation |
130 | */ |
131 | QImageIOHandler::Transformation transformation() const; |
132 | void setTransformation(const QImageIOHandler::Transformation& t); |
133 | |
134 | /*! |
135 | * \brief software |
136 | * \return Name and version number of the software package(s) used to create the image. |
137 | */ |
138 | QString software() const; |
139 | void setSoftware(const QString& s); |
140 | |
141 | /*! |
142 | * \brief description |
143 | * \return A string that describes the subject of the image. |
144 | */ |
145 | QString description() const; |
146 | void setDescription(const QString& s); |
147 | |
148 | /*! |
149 | * \brief artist |
150 | * \return Person who created the image. |
151 | */ |
152 | QString artist() const; |
153 | void setArtist(const QString& s); |
154 | |
155 | /*! |
156 | * \brief copyright |
157 | * \return Copyright notice of the person or organization that claims the copyright to the image. |
158 | */ |
159 | QString copyright() const; |
160 | void setCopyright(const QString& s); |
161 | |
162 | /*! |
163 | * \brief make |
164 | * \return The manufacturer of the recording equipment. |
165 | */ |
166 | QString make() const; |
167 | void setMake(const QString& s); |
168 | |
169 | /*! |
170 | * \brief model |
171 | * \return The model name or model number of the equipment. |
172 | */ |
173 | QString model() const; |
174 | void setModel(const QString& s); |
175 | |
176 | /*! |
177 | * \brief serialNumber |
178 | * \return The serial number of the recording equipment. |
179 | */ |
180 | QString serialNumber() const; |
181 | void setSerialNumber(const QString &s); |
182 | |
183 | /*! |
184 | * \brief lensMake |
185 | * \return The manufacturer of the interchangeable lens that was used. |
186 | */ |
187 | QString lensMake() const; |
188 | void setLensMake(const QString &s); |
189 | |
190 | /*! |
191 | * \brief lensModel |
192 | * \return The model name or model number of the lens that was used. |
193 | */ |
194 | QString lensModel() const; |
195 | void setLensModel(const QString &s); |
196 | |
197 | /*! |
198 | * \brief lensSerialNumber |
199 | * \return The serial number of the interchangeable lens that was used. |
200 | */ |
201 | QString lensSerialNumber() const; |
202 | void setLensSerialNumber(const QString &s); |
203 | |
204 | /*! |
205 | * \brief dateTime |
206 | * \return Creation date and time. |
207 | */ |
208 | QDateTime dateTime() const; |
209 | void setDateTime(const QDateTime& dt); |
210 | |
211 | /*! |
212 | * \brief dateTimeOriginal |
213 | * \return The date and time when the original image data was generated. |
214 | */ |
215 | QDateTime dateTimeOriginal() const; |
216 | void setDateTimeOriginal(const QDateTime& dt); |
217 | |
218 | /*! |
219 | * \brief dateTimeDigitized |
220 | * \return The date and time when the image was stored as digital data. |
221 | */ |
222 | QDateTime dateTimeDigitized() const; |
223 | void setDateTimeDigitized(const QDateTime& dt); |
224 | |
225 | /*! |
226 | * \brief title |
227 | * \return The title of the image. |
228 | */ |
229 | QString title() const; |
230 | void setImageTitle(const QString &s); |
231 | |
232 | /*! |
233 | * \brief uniqueId |
234 | * \return An identifier assigned uniquely to each image or null one if none. |
235 | */ |
236 | QUuid uniqueId() const; |
237 | void setUniqueId(const QUuid &uuid); |
238 | |
239 | /*! |
240 | * \brief latitude |
241 | * \return Floating-point number indicating the latitude in degrees north of the equator (e.g. 27.717) or NaN if not set. |
242 | */ |
243 | double latitude() const; |
244 | void setLatitude(double degree); |
245 | |
246 | /*! |
247 | * \brief longitude |
248 | * \return Floating-point number indicating the longitude in degrees east of Greenwich (e.g. 85.317) or NaN if not set. |
249 | */ |
250 | double longitude() const; |
251 | void setLongitude(double degree); |
252 | |
253 | /*! |
254 | * \brief altitude |
255 | * \return Floating-point number indicating the GPS altitude in meters above sea level or ellipsoidal surface (e.g. 35.4) or NaN if not set. |
256 | * \note It makes no distinction between an 'ellipsoidal surface' and 'sea level'. |
257 | */ |
258 | double altitude() const; |
259 | void setAltitude(double meters); |
260 | |
261 | /*! |
262 | * \brief imageDirection |
263 | * \param isMagnetic Set to true if the direction is relative to magnetic north, false if it is relative to true north. Leave nullptr if is not of interest. |
264 | * \return Floating-point number indicating the direction of the image when it was captured. The range of values is from 0.00 to 359.99 or NaN if not set. |
265 | */ |
266 | double imageDirection(bool *isMagnetic = nullptr) const; |
267 | void setImageDirection(double degree, bool isMagnetic = false); |
268 | |
269 | /*! |
270 | * \brief toByteArray |
271 | * Converts the class to RAW data. The raw data contains: |
272 | * - TIFF header |
273 | * - MAIN IFD |
274 | * - EXIF IFD |
275 | * - GPS IFD |
276 | * \param byteOrder Sets the serialization byte order for EXIF data. |
277 | * \param version The EXIF specs version to use. |
278 | * \return A byte array containing the serialized data. |
279 | * \sa write |
280 | */ |
281 | QByteArray toByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const; |
282 | |
283 | /*! |
284 | * \brief exifIfdByteArray |
285 | * Convert the EXIF IFD only to RAW data. Useful when you want to add EXIF data to an existing TIFF container. |
286 | * \param byteOrder Sets the serialization byte order for the data. |
287 | * \param version The EXIF specs version to use. |
288 | * \return A byte array containing the serialized data. |
289 | */ |
290 | QByteArray exifIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const; |
291 | /*! |
292 | * \brief setExifIfdByteArray |
293 | * \param ba The RAW data of EXIF IFD. |
294 | * \param byteOrder Sets the serialization byte order of the data. |
295 | * \return True on success, otherwise false. |
296 | */ |
297 | bool setExifIfdByteArray(const QByteArray& ba, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER); |
298 | |
299 | /*! |
300 | * \brief gpsIfdByteArray |
301 | * Convert the GPS IFD only to RAW data. Useful when you want to add GPS data to an existing TIFF container. |
302 | * \param byteOrder Sets the serialization byte order for the data. |
303 | * \param version The EXIF specs version to use. |
304 | * \return A byte array containing the serialized data. |
305 | */ |
306 | QByteArray gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const; |
307 | /*! |
308 | * \brief setGpsIfdByteArray |
309 | * \param ba The RAW data of GPS IFD. |
310 | * \param byteOrder Sets the serialization byte order of the data. |
311 | * \return True on success, otherwise false. |
312 | */ |
313 | bool setGpsIfdByteArray(const QByteArray& ba, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER); |
314 | |
315 | /*! |
316 | * \brief write |
317 | * Serialize the class on a device. The serialized data contains: |
318 | * - TIFF header |
319 | * - MAIN IFD |
320 | * - EXIF IFD |
321 | * - GPS IFD |
322 | * \param device A random access device. |
323 | * \param byteOrder Sets the serialization byte order for EXIF data. |
324 | * \param version The EXIF specs version to use. |
325 | * \return True on success, otherwise false. |
326 | * \sa toByteArray |
327 | */ |
328 | bool write(QIODevice *device, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const; |
329 | |
330 | /*! |
331 | * \brief updateImageMetadata |
332 | * Helper to set EXIF metadata to the image. |
333 | * \param targetImage The image to set metadata on. |
334 | * \param replaceExisting Replaces any existing metadata. |
335 | */ |
336 | void updateImageMetadata(QImage &targetImage, bool replaceExisting = false) const; |
337 | |
338 | /*! |
339 | * \brief updateImageResolution |
340 | * Helper to set the EXIF resolution to the image. Resolution is set only if valid. |
341 | * \param targetImage The image to set resolution on. |
342 | * \return True if either the x-resolution or the y-resolution has been changed, otherwise false. |
343 | */ |
344 | bool updateImageResolution(QImage &targetImage); |
345 | |
346 | /*! |
347 | * \brief fromByteArray |
348 | * Creates the class from RAW EXIF data. |
349 | * \param ba Raw data containing EXIF data. |
350 | * \param searchHeader If true, the EXIF header is searched within the data. If false, the data must begin with the EXIF header. |
351 | * \return The created class (empty on error). |
352 | * \sa isEmpty |
353 | */ |
354 | static MicroExif fromByteArray(const QByteArray &ba, bool = false); |
355 | |
356 | /*! |
357 | * \brief fromRawData |
358 | * Creates the class from RAW EXIF data. |
359 | * \param data Raw data containing EXIF data. |
360 | * \param size The size of \a data. |
361 | * \param searchHeader If true, the EXIF header is searched within the data. If false, the data must begin with the EXIF header. |
362 | * \return The created class (empty on error). |
363 | * \sa isEmpty, fromByteArray |
364 | */ |
365 | static MicroExif fromRawData(const char *data, size_t size, bool = false); |
366 | |
367 | /*! |
368 | * \brief fromDevice |
369 | * Creates the class from a device. |
370 | * \param device A random access device. |
371 | * \return The created class (empty on error). |
372 | * \sa isEmpty |
373 | */ |
374 | static MicroExif fromDevice(QIODevice *device); |
375 | |
376 | /*! |
377 | * \brief fromImage |
378 | * Creates the class and fill it with image info (e.g. resolution). |
379 | */ |
380 | static MicroExif fromImage(const QImage &image); |
381 | |
382 | private: |
383 | void setTiffString(quint16 tagId, const QString &s); |
384 | QString tiffString(quint16 tagId) const; |
385 | void setExifString(quint16 tagId, const QString& s); |
386 | QString exifString(quint16 tagId) const; |
387 | void setGpsString(quint16 tagId, const QString& s); |
388 | QString gpsString(quint16 tagId) const; |
389 | bool (QDataStream &ds) const; |
390 | bool writeIfds(QDataStream &ds, const Version &version) const; |
391 | void updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags, const Version &version) const; |
392 | |
393 | static void setString(Tags &tags, quint16 tagId, const QString &s); |
394 | static QString string(const Tags &tags, quint16 tagId); |
395 | |
396 | private: |
397 | Tags m_tiffTags; |
398 | Tags m_exifTags; |
399 | Tags m_gpsTags; |
400 | }; |
401 | |
402 | #endif // MICROEXIF_P_H |
403 | |