8#include "microexif_p.h"
12#include <QCoreApplication>
15#include <QStringDecoder>
19#define TIFF_IMAGEWIDTH 0x100
20#define TIFF_IMAGEHEIGHT 0x101
21#define TIFF_BITSPERSAMPLE 0x102
22#define TIFF_IMAGEDESCRIPTION 0x10E
23#define TIFF_MAKE 0x10F
24#define TIFF_MODEL 0x110
25#define TIFF_ORIENT 0x0112
26#define TIFF_XRES 0x011A
27#define TIFF_YRES 0x011B
28#define TIFF_URES 0x0128
29#define TIFF_SOFTWARE 0x0131
30#define TIFF_ARTIST 0x013B
31#define TIFF_DATETIME 0x0132
32#define TIFF_COPYRIGHT 0x8298
34#define TIFF_VAL_URES_NOABSOLUTE 1
35#define TIFF_VAL_URES_INCH 2
36#define TIFF_VAL_URES_CENTIMETER 3
39#define EXIF_EXIFIFD 0x8769
40#define EXIF_GPSIFD 0x8825
41#define EXIF_EXIFVERSION 0x9000
42#define EXIF_DATETIMEORIGINAL 0x9003
43#define EXIF_DATETIMEDIGITIZED 0x9004
44#define EXIF_OFFSETTIME 0x9010
45#define EXIF_OFFSETTIMEORIGINAL 0x9011
46#define EXIF_OFFSETTIMEDIGITIZED 0x9012
47#define EXIF_COLORSPACE 0xA001
48#define EXIF_PIXELXDIM 0xA002
49#define EXIF_PIXELYDIM 0xA003
50#define EXIF_IMAGEUNIQUEID 0xA420
51#define EXIF_BODYSERIALNUMBER 0xA431
52#define EXIF_LENSMAKE 0xA433
53#define EXIF_LENSMODEL 0xA434
54#define EXIF_LENSSERIALNUMBER 0xA435
55#define EXIF_IMAGETITLE 0xA436
57#define EXIF_VAL_COLORSPACE_SRGB 1
58#define EXIF_VAL_COLORSPACE_UNCAL 0xFFFF
60#define GPS_GPSVERSION 0
61#define GPS_LATITUDEREF 1
63#define GPS_LONGITUDEREF 3
64#define GPS_LONGITUDE 4
65#define GPS_ALTITUDEREF 5
67#define GPS_IMGDIRECTIONREF 16
68#define GPS_IMGDIRECTION 17
69#define EXIF_TAG_VALUE(n, byteSize) (((n) << 6) | ((byteSize) & 0x3F))
70#define EXIF_TAG_SIZEOF(dataType) (quint16(dataType) & 0x3F)
71#define EXIF_TAG_DATATYPE(dataType) (quint16(dataType) >> 6)
73enum class ExifTagType : quint16 {
75 Byte = EXIF_TAG_VALUE(1, 1),
76 Ascii = EXIF_TAG_VALUE(2, 1),
77 Short = EXIF_TAG_VALUE(3, 2),
78 Long = EXIF_TAG_VALUE(4, 4),
79 Rational = EXIF_TAG_VALUE(5, 8),
82 SByte = EXIF_TAG_VALUE(6, 1),
83 Undefined = EXIF_TAG_VALUE(7, 1),
84 SShort = EXIF_TAG_VALUE(8, 2),
85 SLong = EXIF_TAG_VALUE(9, 4),
86 SRational = EXIF_TAG_VALUE(10, 8),
88 Float = EXIF_TAG_VALUE(11, 4),
89 Double = EXIF_TAG_VALUE(12, 8),
90 Ifd = EXIF_TAG_VALUE(13, 4),
93 Long8 = EXIF_TAG_VALUE(16, 8),
94 SLong8 = EXIF_TAG_VALUE(17, 8),
95 Ifd8 = EXIF_TAG_VALUE(18, 8),
98 Utf8 = EXIF_TAG_VALUE(129, 1)
103using TagInfo = std::pair<quint16, ExifTagType>;
111static const KnownTags staticTagTypes = {
112 TagInfo(TIFF_IMAGEWIDTH, ExifTagType::Long),
113 TagInfo(TIFF_IMAGEHEIGHT, ExifTagType::Long),
114 TagInfo(TIFF_BITSPERSAMPLE, ExifTagType::Short),
115 TagInfo(TIFF_IMAGEDESCRIPTION, ExifTagType::Utf8),
116 TagInfo(TIFF_MAKE, ExifTagType::Utf8),
117 TagInfo(TIFF_MODEL, ExifTagType::Utf8),
118 TagInfo(TIFF_ORIENT, ExifTagType::Short),
119 TagInfo(TIFF_XRES, ExifTagType::Rational),
120 TagInfo(TIFF_YRES, ExifTagType::Rational),
121 TagInfo(TIFF_URES, ExifTagType::Short),
122 TagInfo(TIFF_SOFTWARE, ExifTagType::Utf8),
123 TagInfo(TIFF_ARTIST, ExifTagType::Utf8),
124 TagInfo(TIFF_DATETIME, ExifTagType::Ascii),
125 TagInfo(TIFF_COPYRIGHT, ExifTagType::Utf8),
126 TagInfo(EXIF_EXIFIFD, ExifTagType::Long),
127 TagInfo(EXIF_GPSIFD, ExifTagType::Long),
128 TagInfo(EXIF_DATETIMEORIGINAL, ExifTagType::Ascii),
129 TagInfo(EXIF_OFFSETTIMEDIGITIZED, ExifTagType::Ascii),
130 TagInfo(EXIF_OFFSETTIME, ExifTagType::Ascii),
131 TagInfo(EXIF_OFFSETTIMEORIGINAL, ExifTagType::Ascii),
132 TagInfo(EXIF_OFFSETTIMEDIGITIZED, ExifTagType::Ascii),
133 TagInfo(EXIF_COLORSPACE, ExifTagType::Short),
134 TagInfo(EXIF_PIXELXDIM, ExifTagType::Long),
135 TagInfo(EXIF_PIXELYDIM, ExifTagType::Long),
136 TagInfo(EXIF_IMAGEUNIQUEID, ExifTagType::Ascii),
137 TagInfo(EXIF_BODYSERIALNUMBER, ExifTagType::Ascii),
138 TagInfo(EXIF_LENSMAKE, ExifTagType::Utf8),
139 TagInfo(EXIF_LENSMODEL, ExifTagType::Utf8),
140 TagInfo(EXIF_LENSSERIALNUMBER, ExifTagType::Ascii),
141 TagInfo(EXIF_IMAGETITLE, ExifTagType::Utf8),
142 TagInfo(EXIF_EXIFVERSION, ExifTagType::Undefined)
150static const KnownTags staticGpsTagTypes = {
151 TagInfo(GPS_GPSVERSION, ExifTagType::Byte),
152 TagInfo(GPS_LATITUDEREF, ExifTagType::Ascii),
153 TagInfo(GPS_LATITUDE, ExifTagType::Rational),
154 TagInfo(GPS_LONGITUDEREF, ExifTagType::Ascii),
155 TagInfo(GPS_LONGITUDE, ExifTagType::Rational),
156 TagInfo(GPS_ALTITUDEREF, ExifTagType::Byte),
157 TagInfo(GPS_ALTITUDE, ExifTagType::Rational),
158 TagInfo(GPS_IMGDIRECTIONREF, ExifTagType::Ascii),
159 TagInfo(GPS_IMGDIRECTION, ExifTagType::Rational)
169 std::pair<quint16, QString>(TIFF_IMAGEDESCRIPTION, QStringLiteral(META_KEY_DESCRIPTION)),
170 std::pair<quint16, QString>(TIFF_ARTIST, QStringLiteral(META_KEY_AUTHOR)),
171 std::pair<quint16, QString>(TIFF_SOFTWARE, QStringLiteral(META_KEY_SOFTWARE)),
172 std::pair<quint16, QString>(TIFF_COPYRIGHT, QStringLiteral(META_KEY_COPYRIGHT)),
173 std::pair<quint16, QString>(TIFF_MAKE, QStringLiteral(META_KEY_MANUFACTURER)),
174 std::pair<quint16, QString>(TIFF_MODEL, QStringLiteral(META_KEY_MODEL))
184 std::pair<quint16, QString>(EXIF_BODYSERIALNUMBER, QStringLiteral(META_KEY_SERIALNUMBER)),
185 std::pair<quint16, QString>(EXIF_LENSMAKE, QStringLiteral(META_KEY_LENS_MANUFACTURER)),
186 std::pair<quint16, QString>(EXIF_LENSMODEL, QStringLiteral(META_KEY_LENS_MODEL)),
187 std::pair<quint16, QString>(EXIF_LENSSERIALNUMBER, QStringLiteral(META_KEY_LENS_SERIALNUMBER)),
188 std::pair<quint16, QString>(EXIF_IMAGETITLE, QStringLiteral(META_KEY_TITLE)),
197static qint16 timeOffset(
const QString& offset)
199 if (offset.
size() != 6 || offset.
at(3) != u
':')
205 auto mm = offset.
mid(4, 2).
toInt(&ok) * (hh < 0 ? -1 : 1);
208 return qint16(hh * 60 + mm);
216static QString timeOffset(qint16 offset)
218 auto absOff = quint16(std::abs(offset));
219 return QStringLiteral(
"%1%2:%3")
220 .arg(offset < 0 ? QStringLiteral(
"-") : QStringLiteral(
"+"))
221 .arg(absOff / 60, 2, 10,
QChar(u
'0'))
222 .arg(absOff % 60, 2, 10,
QChar(u
'0'));
235 if (order == 0x4949) {
237 }
else if (order == 0x4d4d) {
245 if (version != 0x002A && version != 0x01BC)
276static qint32 countBytes(
const ExifTagType &dataType,
const QVariant &value)
279 if (dataType == ExifTagType::Ascii) {
281 }
else if (dataType == ExifTagType::Utf8) {
283 }
else if (dataType == ExifTagType::Undefined) {
285 }
else if (dataType == ExifTagType::Byte) {
287 }
else if (dataType == ExifTagType::Short) {
289 }
else if (dataType == ExifTagType::Long || dataType == ExifTagType::Ifd) {
291 }
else if (dataType == ExifTagType::SByte) {
293 }
else if (dataType == ExifTagType::SShort) {
295 }
else if (dataType == ExifTagType::SLong) {
297 }
else if (dataType == ExifTagType::Rational || dataType == ExifTagType::SRational || dataType == ExifTagType::Double) {
299 }
else if (dataType == ExifTagType::Float) {
302 return std::max(1, count);
311 for (;l.size() < qsizetype(4 /
sizeof(T));)
317inline qint32 rationalPrecision(
double v)
320 return 8 - qBound(0, v < 1 ? 8 :
int(std::log10(v)), 8);
330 auto den = std::pow(10, rationalPrecision(v));
331 ds << T(qRound(v * den));
340 for (
auto n = ba.size(); n < 4; ++n)
346 if (dataType == ExifTagType::Ascii) {
348 }
else if (dataType == ExifTagType::Utf8) {
350 }
else if (dataType == ExifTagType::Undefined) {
352 }
else if (dataType == ExifTagType::Byte) {
353 writeList<quint8>(ds, value);
354 }
else if (dataType == ExifTagType::SByte) {
355 writeList<qint8>(ds, value);
356 }
else if (dataType == ExifTagType::Short) {
357 writeList<quint16>(ds, value);
358 }
else if (dataType == ExifTagType::SShort) {
359 writeList<qint16>(ds, value);
360 }
else if (dataType == ExifTagType::Long || dataType == ExifTagType::Ifd) {
361 writeList<quint32>(ds, value);
362 }
else if (dataType == ExifTagType::SLong) {
363 writeList<qint32>(ds, value);
364 }
else if (dataType == ExifTagType::Rational) {
365 writeRationalList<quint32>(ds, value);
366 }
else if (dataType == ExifTagType::SRational) {
367 writeRationalList<qint32>(ds, value);
371static ExifTagType updateDataType(
const ExifTagType &dataType,
const QVariant &value,
const MicroExif::Version &ver)
373 if (dataType != ExifTagType::Utf8)
376 if (ver == MicroExif::V2)
377 return ExifTagType::Ascii;
385 for (
auto &&c : u8) {
390 return ExifTagType::Ascii;
402 const MicroExif::Version &ver,
403 const MicroExif::Tags &tags,
406 const KnownTags &knownTags = staticTagTypes)
410 if (!updatePos(ds, pos))
413 auto keys = tags.keys();
414 auto entries = quint16(keys.size());
416 for (
auto &&key : keys) {
417 if (!knownTags.contains(key)) {
420 auto value = tags.
value(key);
421 auto dataType = updateDataType(knownTags.value(key), value, ver);
422 auto count = countBytes(dataType, value);
425 ds << quint16(EXIF_TAG_DATATYPE(dataType));
426 ds << quint32(count);
428 auto valueSize = count * EXIF_TAG_SIZEOF(dataType);
432 writeData(ds, value, dataType);
439 for (
auto &&key : keys) {
440 if (!knownTags.contains(key)) {
443 auto value = tags.
value(key);
444 auto dataType = updateDataType(knownTags.value(key), value, ver);
445 auto count = countBytes(dataType, value);
446 auto valueSize = count * EXIF_TAG_SIZEOF(dataType);
449 if (!updatePos(ds, positions.
value(key)))
451 writeData(ds, value, dataType);
462 for (quint32 i = 0; i < count; ++i) {
466 for (
auto n = count; n < quint32(4 /
sizeof(T)); ++n) {
476 for (quint32 i = 0; i < count; ++i) {
481 l.
append(den == 0 ? 0 :
double(num) /
double(den));
493 for (quint32 i = 0; i < count; ++i) {
497 if (asciiz && l.
at(l.
size() - 1) == 0) {
500 for (
auto n = count; n < 4; ++n) {
515static bool readIfd(
QDataStream &ds, MicroExif::Tags &tags, quint32 pos = 0,
const KnownTags &knownTags = staticTagTypes, quint32 *nextIfd =
nullptr)
517 auto localNextIfd = quint32();
518 if (nextIfd ==
nullptr)
519 nextIfd = &localNextIfd;
522 auto device = ds.
device();
523 if (pos && !device->seek(pos))
531 for (quint16 i = 0; i < entries; ++i) {
542 if (!knownTags.contains(tagId)) {
549 auto toRead = qint64(count) * EXIF_TAG_SIZEOF(knownTags.value(tagId));
550 if (toRead > qint64(device->size()))
553 auto curPos = qint64();
557 curPos = device->pos();
558 if (!device->seek(value))
562 if (dataType == EXIF_TAG_DATATYPE(ExifTagType::Ascii) || dataType == EXIF_TAG_DATATYPE(ExifTagType::Utf8)) {
563 auto l = readBytes(ds, count,
true);
574 }
else if (dataType == EXIF_TAG_DATATYPE(ExifTagType::Undefined)) {
575 auto l = readBytes(ds, count,
false);
577 tags.insert(tagId, l);
578 }
else if (dataType == EXIF_TAG_DATATYPE(ExifTagType::Byte)) {
579 auto l = readList<quint8>(ds, count);
581 }
else if (dataType == EXIF_TAG_DATATYPE(ExifTagType::SByte)) {
582 auto l = readList<qint8>(ds, count);
584 }
else if (dataType == EXIF_TAG_DATATYPE(ExifTagType::Short)) {
585 auto l = readList<quint16>(ds, count);
587 }
else if (dataType == EXIF_TAG_DATATYPE(ExifTagType::SShort)) {
588 auto l = readList<qint16>(ds, count);
590 }
else if (dataType == EXIF_TAG_DATATYPE(ExifTagType::Long) || dataType == EXIF_TAG_DATATYPE(ExifTagType::Ifd)) {
591 auto l = readList<quint32>(ds, count);
593 }
else if (dataType == EXIF_TAG_DATATYPE(ExifTagType::SLong)) {
594 auto l = readList<qint32>(ds, count);
596 }
else if (dataType == EXIF_TAG_DATATYPE(ExifTagType::Rational)) {
597 auto l = readRationalList<quint32>(ds, count);
599 }
else if (dataType == EXIF_TAG_DATATYPE(ExifTagType::SRational)) {
600 auto l = readRationalList<qint32>(ds, count);
604 if (curPos > 0 && !device->seek(curPos))
612MicroExif::MicroExif()
617void MicroExif::clear()
624bool MicroExif::isEmpty()
const
626 return m_tiffTags.isEmpty() && m_exifTags.isEmpty() && m_gpsTags.isEmpty();
629double MicroExif::horizontalResolution()
const
631 auto u = m_tiffTags.value(TIFF_URES).toUInt();
632 auto v = m_tiffTags.value(TIFF_XRES).toDouble();
633 if (u == TIFF_VAL_URES_CENTIMETER)
638void MicroExif::setHorizontalResolution(
double hres)
640 auto u = m_tiffTags.value(TIFF_URES).toUInt();
641 if (u == TIFF_VAL_URES_CENTIMETER) {
643 }
else if (u < TIFF_VAL_URES_INCH) {
644 m_tiffTags.insert(TIFF_URES, TIFF_VAL_URES_INCH);
646 m_tiffTags.insert(TIFF_XRES, hres);
649double MicroExif::verticalResolution()
const
651 auto u = m_tiffTags.value(TIFF_URES).toUInt();
652 auto v = m_tiffTags.value(TIFF_YRES).toDouble();
653 if (u == TIFF_VAL_URES_CENTIMETER)
658void MicroExif::setVerticalResolution(
double vres)
660 auto u = m_tiffTags.value(TIFF_URES).toUInt();
661 if (u == TIFF_VAL_URES_CENTIMETER) {
663 }
else if (u < TIFF_VAL_URES_INCH) {
664 m_tiffTags.insert(TIFF_URES, TIFF_VAL_URES_INCH);
666 m_tiffTags.insert(TIFF_YRES, vres);
671 if (m_exifTags.value(EXIF_COLORSPACE).toUInt() == EXIF_VAL_COLORSPACE_SRGB)
673 return QColorSpace();
676void MicroExif::setColorSpace(
const QColorSpace &cs)
678 auto srgb = cs.
transferFunction() == QColorSpace::TransferFunction::SRgb && cs.
primaries() == QColorSpace::Primaries::SRgb;
679 m_exifTags.insert(EXIF_COLORSPACE, srgb ? EXIF_VAL_COLORSPACE_SRGB : EXIF_VAL_COLORSPACE_UNCAL);
685 m_exifTags.insert(EXIF_COLORSPACE, srgb ? EXIF_VAL_COLORSPACE_SRGB : EXIF_VAL_COLORSPACE_UNCAL);
688qint32 MicroExif::width()
const
690 return m_tiffTags.value(TIFF_IMAGEWIDTH).toUInt();
693void MicroExif::setWidth(qint32 w)
695 m_tiffTags.insert(TIFF_IMAGEWIDTH, w);
696 m_exifTags.insert(EXIF_PIXELXDIM, w);
699qint32 MicroExif::height()
const
701 return m_tiffTags.value(TIFF_IMAGEHEIGHT).toUInt();
704void MicroExif::setHeight(qint32 h)
706 m_tiffTags.insert(TIFF_IMAGEHEIGHT, h);
707 m_exifTags.insert(EXIF_PIXELYDIM, h);
710quint16 MicroExif::orientation()
const
712 return m_tiffTags.value(TIFF_ORIENT).toUInt();
715void MicroExif::setOrientation(quint16 orient)
717 if (orient < 1 || orient > 8)
718 m_tiffTags.remove(TIFF_ORIENT);
720 m_tiffTags.insert(TIFF_ORIENT, orient);
725 switch (orientation()) {
781QString MicroExif::software()
const
783 return tiffString(TIFF_SOFTWARE);
786void MicroExif::setSoftware(
const QString &s)
788 setTiffString(TIFF_SOFTWARE, s);
791QString MicroExif::description()
const
793 return tiffString(TIFF_IMAGEDESCRIPTION);
796void MicroExif::setDescription(
const QString &s)
798 setTiffString(TIFF_IMAGEDESCRIPTION, s);
801QString MicroExif::artist()
const
803 return tiffString(TIFF_ARTIST);
806void MicroExif::setArtist(
const QString &s)
808 setTiffString(TIFF_ARTIST, s);
811QString MicroExif::copyright()
const
813 return tiffString(TIFF_COPYRIGHT);
816void MicroExif::setCopyright(
const QString &s)
818 setTiffString(TIFF_COPYRIGHT, s);
823 return tiffString(TIFF_MAKE);
826void MicroExif::setMake(
const QString &s)
828 setTiffString(TIFF_MAKE, s);
831QString MicroExif::model()
const
833 return tiffString(TIFF_MODEL);
836void MicroExif::setModel(
const QString &s)
838 setTiffString(TIFF_MODEL, s);
841QString MicroExif::serialNumber()
const
843 return tiffString(EXIF_BODYSERIALNUMBER);
846void MicroExif::setSerialNumber(
const QString &s)
848 setTiffString(EXIF_BODYSERIALNUMBER, s);
851QString MicroExif::lensMake()
const
853 return tiffString(EXIF_LENSMAKE);
856void MicroExif::setLensMake(
const QString &s)
858 setTiffString(EXIF_LENSMAKE, s);
861QString MicroExif::lensModel()
const
863 return tiffString(EXIF_LENSMODEL);
866void MicroExif::setLensModel(
const QString &s)
868 setTiffString(EXIF_LENSMODEL, s);
871QString MicroExif::lensSerialNumber()
const
873 return tiffString(EXIF_LENSSERIALNUMBER);
876void MicroExif::setLensSerialNumber(
const QString &s)
878 setTiffString(EXIF_LENSSERIALNUMBER, s);
884 auto ofTag = exifString(EXIF_OFFSETTIME);
885 if (dt.isValid() && !ofTag.isEmpty())
890void MicroExif::setDateTime(
const QDateTime &dt)
893 m_tiffTags.remove(TIFF_DATETIME);
894 m_exifTags.remove(EXIF_OFFSETTIME);
897 setTiffString(TIFF_DATETIME, dt.
toString(QStringLiteral(
"yyyy:MM:dd HH:mm:ss")));
898 setExifString(EXIF_OFFSETTIME, timeOffset(dt.
offsetFromUtc() / 60));
901QDateTime MicroExif::dateTimeOriginal()
const
903 auto dt =
QDateTime::fromString(exifString(EXIF_DATETIMEORIGINAL), QStringLiteral(
"yyyy:MM:dd HH:mm:ss"));
904 auto ofTag = exifString(EXIF_OFFSETTIMEORIGINAL);
905 if (dt.
isValid() && !ofTag.isEmpty())
910void MicroExif::setDateTimeOriginal(
const QDateTime &dt)
913 m_exifTags.remove(EXIF_DATETIMEORIGINAL);
914 m_exifTags.remove(EXIF_OFFSETTIMEORIGINAL);
917 setExifString(EXIF_DATETIMEORIGINAL, dt.
toString(QStringLiteral(
"yyyy:MM:dd HH:mm:ss")));
918 setExifString(EXIF_OFFSETTIMEORIGINAL, timeOffset(dt.
offsetFromUtc() / 60));
921QDateTime MicroExif::dateTimeDigitized()
const
923 auto dt =
QDateTime::fromString(exifString(EXIF_DATETIMEDIGITIZED), QStringLiteral(
"yyyy:MM:dd HH:mm:ss"));
924 auto ofTag = exifString(EXIF_OFFSETTIMEDIGITIZED);
925 if (dt.
isValid() && !ofTag.isEmpty())
930void MicroExif::setDateTimeDigitized(
const QDateTime &dt)
933 m_exifTags.remove(EXIF_DATETIMEDIGITIZED);
934 m_exifTags.remove(EXIF_OFFSETTIMEDIGITIZED);
937 setExifString(EXIF_DATETIMEDIGITIZED, dt.
toString(QStringLiteral(
"yyyy:MM:dd HH:mm:ss")));
938 setExifString(EXIF_OFFSETTIMEDIGITIZED, timeOffset(dt.
offsetFromUtc() / 60));
941QString MicroExif::title()
const
943 return exifString(EXIF_IMAGETITLE);
946void MicroExif::setImageTitle(
const QString &s)
948 setExifString(EXIF_IMAGETITLE, s);
951QUuid MicroExif::uniqueId()
const
953 auto s = exifString(EXIF_IMAGEUNIQUEID);
954 if (s.length() == 32) {
955 auto tmp = QStringLiteral(
"%1-%2-%3-%4-%5").arg(s.left(8), s.mid(8, 4), s.mid(12, 4), s.mid(16, 4), s.mid(20));
961void MicroExif::setUniqueId(
const QUuid &uuid)
964 setExifString(EXIF_IMAGEUNIQUEID, QString());
969double MicroExif::latitude()
const
971 auto ref = gpsString(GPS_LATITUDEREF).toUpper();
972 if (ref != QStringLiteral(
"N") && ref != QStringLiteral(
"S"))
974 auto lat = m_gpsTags.value(GPS_LATITUDE).value<QList<double>>();
977 auto degree = lat.at(0) + lat.at(1) / 60 + lat.at(2) / 3600;
978 if (degree < -90.0 || degree > 90.0)
980 return ref == QStringLiteral(
"N") ? degree : -degree;
983void MicroExif::setLatitude(
double degree)
985 if (qIsNaN(degree)) {
986 m_gpsTags.remove(GPS_LATITUDEREF);
987 m_gpsTags.remove(GPS_LATITUDE);
989 if (degree < -90.0 || degree > 90.0)
991 auto adeg = qAbs(degree);
992 auto min = (adeg - int(adeg)) * 60;
993 auto sec = (min - int(min)) * 60;
994 m_gpsTags.insert(GPS_LATITUDEREF, degree < 0 ? QStringLiteral(
"S") : QStringLiteral(
"N"));
995 m_gpsTags.insert(GPS_LATITUDE,
QVariant::fromValue(QList<double>() <<
int(adeg) <<
int(min) << sec));
998double MicroExif::longitude()
const
1000 auto ref = gpsString(GPS_LONGITUDEREF).toUpper();
1001 if (ref != QStringLiteral(
"E") && ref != QStringLiteral(
"W"))
1003 auto lon = m_gpsTags.value(GPS_LONGITUDE).value<QList<double>>();
1004 if (lon.size() != 3)
1006 auto degree = lon.at(0) + lon.at(1) / 60 + lon.at(2) / 3600;
1007 if (degree < -180.0 || degree > 180.0)
1009 return ref == QStringLiteral(
"E") ? degree : -degree;
1012void MicroExif::setLongitude(
double degree)
1014 if (qIsNaN(degree)) {
1015 m_gpsTags.remove(GPS_LONGITUDEREF);
1016 m_gpsTags.remove(GPS_LONGITUDE);
1018 if (degree < -180.0 || degree > 180.0)
1020 auto adeg = qAbs(degree);
1021 auto min = (adeg - int(adeg)) * 60;
1022 auto sec = (min - int(min)) * 60;
1023 m_gpsTags.insert(GPS_LONGITUDEREF, degree < 0 ? QStringLiteral(
"W") : QStringLiteral(
"E"));
1024 m_gpsTags.insert(GPS_LONGITUDE,
QVariant::fromValue(QList<double>() <<
int(adeg) <<
int(min) << sec));
1027double MicroExif::altitude()
const
1029 auto ref = m_gpsTags.value(GPS_ALTITUDEREF);
1032 if (!m_gpsTags.contains(GPS_ALTITUDE))
1034 auto alt = m_gpsTags.value(GPS_ALTITUDE).toDouble();
1035 return (ref.toInt() == 0 || ref.toInt() == 2) ? alt : -alt;
1038void MicroExif::setAltitude(
double meters)
1040 if (qIsNaN(meters)) {
1041 m_gpsTags.remove(GPS_ALTITUDEREF);
1042 m_gpsTags.remove(GPS_ALTITUDE);
1044 m_gpsTags.insert(GPS_ALTITUDEREF, quint8(meters < 0 ? 1 : 0));
1045 m_gpsTags.insert(GPS_ALTITUDE, meters);
1048double MicroExif::imageDirection(
bool *isMagnetic)
const
1051 if (isMagnetic ==
nullptr)
1053 if (!m_gpsTags.contains(GPS_IMGDIRECTION))
1055 auto ref = gpsString(GPS_IMGDIRECTIONREF).toUpper();
1056 *isMagnetic = (ref == QStringLiteral(
"M"));
1057 return m_gpsTags.value(GPS_IMGDIRECTION).toDouble();
1060void MicroExif::setImageDirection(
double degree,
bool isMagnetic)
1062 if (qIsNaN(degree)) {
1063 m_gpsTags.remove(GPS_IMGDIRECTIONREF);
1064 m_gpsTags.remove(GPS_IMGDIRECTION);
1066 m_gpsTags.insert(GPS_IMGDIRECTIONREF, isMagnetic ? QStringLiteral(
"M") : QStringLiteral(
"T"));
1067 m_gpsTags.insert(GPS_IMGDIRECTION, degree);
1075 if (!write(&buf, byteOrder))
1087 auto exifTags = m_exifTags;
1088 exifTags.insert(EXIF_EXIFVERSION, version == Version::V3 ? QByteArray(
"0300") : QByteArray(
"0232"));
1090 if (!writeIfd(ds, version, exifTags, positions))
1100 return readIfd(ds, m_exifTags, 0, staticTagTypes);
1109 auto gpsTags = m_gpsTags;
1110 gpsTags.insert(GPS_GPSVERSION, QByteArray(
"2400"));
1112 if (!writeIfd(ds, version, gpsTags, positions, 0, staticGpsTagTypes))
1122 return readIfd(ds, m_gpsTags, 0, staticGpsTagTypes);
1127 if (device ==
nullptr || device->
isSequential() || isEmpty())
1130 QDataStream ds(device);
1132 if (!writeHeader(ds))
1134 if (!writeIfds(ds, version))
1141void MicroExif::updateImageMetadata(
QImage &targetImage,
bool replaceExisting)
const
1144 for (
auto &&p : tiffStrMap) {
1145 if (!replaceExisting && !targetImage.
text(p.second).
isEmpty())
1147 auto s = tiffString(p.first);
1149 targetImage.
setText(p.second, s);
1153 for (
auto &&p : exifStrMap) {
1154 if (!replaceExisting && !targetImage.
text(p.second).
isEmpty())
1156 auto s = exifString(p.first);
1158 targetImage.
setText(p.second, s);
1162 if (replaceExisting || targetImage.
text(QStringLiteral(META_KEY_MODIFICATIONDATE)).isEmpty()) {
1163 auto dt = dateTime();
1167 if (replaceExisting || targetImage.
text(QStringLiteral(META_KEY_CREATIONDATE)).isEmpty()) {
1168 auto dt = dateTimeOriginal();
1174 if (replaceExisting || targetImage.
text(QStringLiteral(META_KEY_ALTITUDE)).isEmpty()) {
1175 auto v = altitude();
1177 targetImage.
setText(QStringLiteral(META_KEY_ALTITUDE), QStringLiteral(
"%1").arg(v, 0,
'g', 9));
1179 if (replaceExisting || targetImage.
text(QStringLiteral(META_KEY_LATITUDE)).isEmpty()) {
1180 auto v = latitude();
1182 targetImage.
setText(QStringLiteral(META_KEY_LATITUDE), QStringLiteral(
"%1").arg(v, 0,
'g', 9));
1184 if (replaceExisting || targetImage.
text(QStringLiteral(META_KEY_LONGITUDE)).isEmpty()) {
1185 auto v = longitude();
1187 targetImage.
setText(QStringLiteral(META_KEY_LONGITUDE), QStringLiteral(
"%1").arg(v, 0,
'g', 9));
1189 if (replaceExisting || targetImage.
text(QStringLiteral(META_KEY_DIRECTION)).isEmpty()) {
1190 auto v = imageDirection();
1192 targetImage.
setText(QStringLiteral(META_KEY_DIRECTION), QStringLiteral(
"%1").arg(v, 0,
'g', 9));
1196void MicroExif::updateImageResolution(
QImage &targetImage)
1198 if (horizontalResolution() > 0)
1199 targetImage.
setDotsPerMeterX(qRound(horizontalResolution() / 25.4 * 1000));
1200 if (verticalResolution() > 0)
1204MicroExif MicroExif::fromByteArray(
const QByteArray &ba,
bool searchHeader)
1208 auto idxLE = ba0.indexOf(QByteArray(
"II"));
1209 auto idxBE = ba0.indexOf(QByteArray(
"MM"));
1211 if (idxLE > -1 && idxBE > -1)
1212 idx = std::min(idxLE, idxBE);
1214 idx = idxLE > -1 ? idxLE : idxBE;
1220 return fromDevice(&buf);
1223MicroExif MicroExif::fromRawData(
const char *data,
size_t size,
bool searchHeader)
1225 if (data ==
nullptr || size == 0)
1230MicroExif MicroExif::fromDevice(
QIODevice *device)
1237 QDataStream ds(device);
1238 if (!checkHeader(ds))
1244 if (!readIfd(ds, exif.m_tiffTags))
1248 if (
auto pos = exif.m_tiffTags.value(EXIF_EXIFIFD).toUInt()) {
1249 if (!readIfd(ds, exif.m_exifTags, pos))
1254 if (
auto pos = exif.m_tiffTags.value(EXIF_GPSIFD).toUInt()) {
1255 if (!readIfd(ds, exif.m_gpsTags, pos, staticGpsTagTypes))
1262MicroExif MicroExif::fromImage(
const QImage &image)
1269 exif.setWidth(image.
width());
1270 exif.setHeight(image.
height());
1271 exif.setHorizontalResolution(image.
dotsPerMeterX() * 25.4 / 1000);
1272 exif.setVerticalResolution(image.
dotsPerMeterY() * 25.4 / 1000);
1276 for (
auto &&p : tiffStrMap) {
1277 exif.setTiffString(p.first, image.
text(p.second));
1281 for (
auto &&p : exifStrMap) {
1282 exif.setExifString(p.first, image.
text(p.second));
1286 if (exif.software().isEmpty()) {
1289 if (!sw.isEmpty() && !ver.isEmpty())
1290 sw.append(QStringLiteral(
" %1").arg(ver));
1291 exif.setSoftware(sw.trimmed());
1298 exif.setDateTime(dt);
1304 exif.setDateTimeOriginal(dt);
1308 auto alt = image.
text(QStringLiteral(META_KEY_ALTITUDE)).
toDouble(&ok);
1310 exif.setAltitude(alt);
1311 auto lat = image.
text(QStringLiteral(META_KEY_LATITUDE)).
toDouble(&ok);
1313 exif.setLatitude(lat);
1314 auto lon = image.
text(QStringLiteral(META_KEY_LONGITUDE)).
toDouble(&ok);
1316 exif.setLongitude(lon);
1317 auto dir = image.
text(QStringLiteral(META_KEY_DIRECTION)).
toDouble(&ok);
1319 exif.setImageDirection(dir);
1324void MicroExif::setTiffString(quint16 tagId,
const QString &s)
1326 MicroExif::setString(m_tiffTags, tagId, s);
1329QString MicroExif::tiffString(quint16 tagId)
const
1331 return MicroExif::string(m_tiffTags, tagId);
1334void MicroExif::setExifString(quint16 tagId,
const QString &s)
1336 MicroExif::setString(m_exifTags, tagId, s);
1339QString MicroExif::exifString(quint16 tagId)
const
1341 return MicroExif::string(m_exifTags, tagId);
1344void MicroExif::setGpsString(quint16 tagId,
const QString &s)
1346 MicroExif::setString(m_gpsTags, tagId, s);
1349QString MicroExif::gpsString(quint16 tagId)
const
1351 return MicroExif::string(m_gpsTags, tagId);
1357 ds << quint16(0x4949);
1359 ds << quint16(0x4d4d);
1360 ds << quint16(0x002a);
1365bool MicroExif::writeIfds(
QDataStream &ds,
const Version &version)
const
1367 auto tiffTags = m_tiffTags;
1368 auto exifTags = m_exifTags;
1369 auto gpsTags = m_gpsTags;
1370 updateTags(tiffTags, exifTags, gpsTags, version);
1373 if (!writeIfd(ds, version, tiffTags, positions))
1375 if (!writeIfd(ds, version, exifTags, positions, positions.
value(EXIF_EXIFIFD)))
1377 if (!writeIfd(ds, version, gpsTags, positions, positions.
value(EXIF_GPSIFD), staticGpsTagTypes))
1382void MicroExif::updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags,
const Version &version)
const
1384 if (exifTags.isEmpty()) {
1385 tiffTags.remove(EXIF_EXIFIFD);
1387 tiffTags.insert(EXIF_EXIFIFD, quint32());
1388 exifTags.insert(EXIF_EXIFVERSION, version == Version::V3 ? QByteArray(
"0300") : QByteArray(
"0232"));
1390 if (gpsTags.isEmpty()) {
1391 tiffTags.remove(EXIF_GPSIFD);
1393 tiffTags.insert(EXIF_GPSIFD, quint32());
1394 gpsTags.insert(GPS_GPSVERSION, QByteArray(
"2400"));
1398void MicroExif::setString(Tags &tags, quint16 tagId,
const QString &s)
1403 tags.insert(tagId, s);
1406QString MicroExif::string(
const Tags &tags, quint16 tagId)
1408 return tags.value(tagId).toString();
KDB_EXPORT KDbVersionInfo version()
KIOCORE_EXPORT QString dir(const QString &fileClass)
void setData(const QByteArray &data)
QByteArray & append(QByteArrayView data)
char at(qsizetype i) const const
QByteArray first(qsizetype n) const const
QByteArray fromRawData(const char *data, qsizetype size)
bool isEmpty() const const
QByteArray & removeLast()
qsizetype size() const const
Primaries primaries() const const
TransferFunction transferFunction() const const
ByteOrder byteOrder() const const
QIODevice * device() const const
void setByteOrder(ByteOrder bo)
Status status() const const
QDateTime currentDateTime()
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
bool isValid() const const
int offsetFromUtc() const const
void setTimeZone(const QTimeZone &toZone)
QString toString(QStringView format, QCalendar cal) const const
iterator insert(const Key &key, const T &value)
bool remove(const Key &key)
T value(const Key &key) const const
QColorSpace colorSpace() const const
int dotsPerMeterX() const const
int dotsPerMeterY() const const
bool isNull() const const
void setDotsPerMeterX(int x)
void setDotsPerMeterY(int y)
void setText(const QString &key, const QString &text)
QString text(const QString &key) const const
virtual bool isSequential() const const
virtual bool open(QIODeviceBase::OpenMode mode)
virtual qint64 pos() const const
void append(QList< T > &&value)
const QChar at(qsizetype position) const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString left(qsizetype n) const const
QString mid(qsizetype position, qsizetype n) const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
qsizetype size() const const
double toDouble(bool *ok) const const
int toInt(bool *ok, int base) const const
QByteArray toLatin1() const const
QByteArray toUtf8() const const
QTextStream & dec(QTextStream &stream)
QTimeZone fromSecondsAheadOfUtc(int offset)
bool isNull() const const
QString toString(StringFormat mode) const const
QVariant fromValue(T &&value)
QByteArray toByteArray() const const
double toDouble(bool *ok) const const
int toInt(bool *ok) const const
QString toString() const const