mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2026-07-21 16:00:56 -04:00
EXIF: add support for Windows Explorer tags
This commit is contained in:
committed by
Mirco Miranda
parent
a0825fe5cb
commit
c21b50d699
@@ -146,6 +146,7 @@ About the image:
|
|||||||
scanned.
|
scanned.
|
||||||
- `HostComputer`: The computer and/or operating system in use at the time
|
- `HostComputer`: The computer and/or operating system in use at the time
|
||||||
of image creation.
|
of image creation.
|
||||||
|
- `Keywords`: Keywords, separated by semicolons, that represent the image.
|
||||||
- `Latitude`: Floating-point number indicating the latitude in degrees
|
- `Latitude`: Floating-point number indicating the latitude in degrees
|
||||||
north of the equator (e.g. 27.717).
|
north of the equator (e.g. 27.717).
|
||||||
- `Longitude`: Floating-point number indicating the longitude in degrees
|
- `Longitude`: Floating-point number indicating the longitude in degrees
|
||||||
@@ -154,6 +155,8 @@ About the image:
|
|||||||
without milliseconds (e.g. 2024-03-23T15:30:43). This value should be
|
without milliseconds (e.g. 2024-03-23T15:30:43). This value should be
|
||||||
updated every time the image is saved.
|
updated every time the image is saved.
|
||||||
- `Owner`: Name of the owner of the image.
|
- `Owner`: Name of the owner of the image.
|
||||||
|
- `Rating`: Integer number indicating the image rating (usually between 1
|
||||||
|
and 5).
|
||||||
- `Software`: Name and version number of the software package(s) used to
|
- `Software`: Name and version number of the software package(s) used to
|
||||||
create the image.
|
create the image.
|
||||||
- `Speed`: Floating-point number indicating the speed of GPS receiver
|
- `Speed`: Floating-point number indicating the speed of GPS receiver
|
||||||
|
|||||||
Binary file not shown.
@@ -12,6 +12,14 @@
|
|||||||
"key" : "ModificationDate",
|
"key" : "ModificationDate",
|
||||||
"value" : "2025-01-05T10:18:16"
|
"value" : "2025-01-05T10:18:16"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key" : "Keywords",
|
||||||
|
"value" : "kde;w11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key" : "Rating",
|
||||||
|
"value" : "4"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key" : "Software" ,
|
"key" : "Software" ,
|
||||||
"value" : "GIMP 3.0.0-RC2"
|
"value" : "GIMP 3.0.0-RC2"
|
||||||
|
|||||||
Binary file not shown.
@@ -59,6 +59,14 @@
|
|||||||
{
|
{
|
||||||
"key" : "Model",
|
"key" : "Model",
|
||||||
"value" : "KImageFormats"
|
"value" : "KImageFormats"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key" : "Keywords",
|
||||||
|
"value" : "broacast;test;kde"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key" : "Rating",
|
||||||
|
"value" : "5"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"resolution" : {
|
"resolution" : {
|
||||||
|
|||||||
@@ -37,6 +37,14 @@
|
|||||||
"key" : "Description",
|
"key" : "Description",
|
||||||
"value" : "テレビ放送テスト映像。(TV broadcast test image.)"
|
"value" : "テレビ放送テスト映像。(TV broadcast test image.)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key" : "Keywords",
|
||||||
|
"value" : "kde;test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key" : "Rating",
|
||||||
|
"value" : "5"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key" : "Latitude",
|
"key" : "Latitude",
|
||||||
"value" : "44.6478"
|
"value" : "44.6478"
|
||||||
|
|||||||
@@ -29,6 +29,10 @@
|
|||||||
"key" : "Description",
|
"key" : "Description",
|
||||||
"value" : "TV broadcast test image."
|
"value" : "TV broadcast test image."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key" : "Rating",
|
||||||
|
"value" : "4"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key" : "Latitude",
|
"key" : "Latitude",
|
||||||
"value" : "44.6478"
|
"value" : "44.6478"
|
||||||
|
|||||||
@@ -739,6 +739,13 @@ public:
|
|||||||
DESCRIPTIVEMETADATA meta;
|
DESCRIPTIVEMETADATA meta;
|
||||||
memset(&meta, 0, sizeof(meta));
|
memset(&meta, 0, sizeof(meta));
|
||||||
|
|
||||||
|
auto ok = false;
|
||||||
|
auto rating = image.text(QStringLiteral(META_KEY_RATING)).toUInt(&ok);
|
||||||
|
if (ok) {
|
||||||
|
meta.pvarRatingStars.vt = DPKVT_UI2;
|
||||||
|
meta.pvarRatingStars.VT.uiVal = quint16(rating);
|
||||||
|
}
|
||||||
|
|
||||||
#define META_CTEXT(name, field) \
|
#define META_CTEXT(name, field) \
|
||||||
auto field = image.text(QStringLiteral(name)).toUtf8(); \
|
auto field = image.text(QStringLiteral(name)).toUtf8(); \
|
||||||
if (!field.isEmpty()) { \
|
if (!field.isEmpty()) { \
|
||||||
@@ -1017,6 +1024,10 @@ private:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (meta.pvarRatingStars.vt == DPKVT_UI2) {
|
||||||
|
m_txtMeta.insert(QStringLiteral(META_KEY_RATING), QStringLiteral("%1").arg(meta.pvarRatingStars.VT.uiVal));
|
||||||
|
}
|
||||||
|
|
||||||
#define META_TEXT(name, field) \
|
#define META_TEXT(name, field) \
|
||||||
if (meta.field.vt == DPKVT_LPSTR) \
|
if (meta.field.vt == DPKVT_LPSTR) \
|
||||||
m_txtMeta.insert(QStringLiteral(name), QString::fromUtf8(meta.field.VT.pszVal)); \
|
m_txtMeta.insert(QStringLiteral(name), QString::fromUtf8(meta.field.VT.pszVal)); \
|
||||||
|
|||||||
@@ -30,6 +30,9 @@
|
|||||||
#define TIFF_ARTIST 0x013B
|
#define TIFF_ARTIST 0x013B
|
||||||
#define TIFF_DATETIME 0x0132
|
#define TIFF_DATETIME 0x0132
|
||||||
#define TIFF_COPYRIGHT 0x8298
|
#define TIFF_COPYRIGHT 0x8298
|
||||||
|
#define TIFF_XPRATING 0x4746 // added by Windows Explorer Image Properties
|
||||||
|
#define TIFF_XPTITLE 0x9C9B // added by Windows Explorer Image Properties
|
||||||
|
#define TIFF_XPKEYWORDS 0x9C9E // added by Windows Explorer Image Properties
|
||||||
|
|
||||||
#define TIFF_VAL_URES_NOABSOLUTE 1
|
#define TIFF_VAL_URES_NOABSOLUTE 1
|
||||||
#define TIFF_VAL_URES_INCH 2
|
#define TIFF_VAL_URES_INCH 2
|
||||||
@@ -134,6 +137,9 @@ static const KnownTags staticTagTypes = {
|
|||||||
TagInfo(TIFF_ARTIST, ExifTagType::Utf8),
|
TagInfo(TIFF_ARTIST, ExifTagType::Utf8),
|
||||||
TagInfo(TIFF_DATETIME, ExifTagType::Ascii),
|
TagInfo(TIFF_DATETIME, ExifTagType::Ascii),
|
||||||
TagInfo(TIFF_COPYRIGHT, ExifTagType::Utf8),
|
TagInfo(TIFF_COPYRIGHT, ExifTagType::Utf8),
|
||||||
|
TagInfo(TIFF_XPRATING, ExifTagType::Short),
|
||||||
|
TagInfo(TIFF_XPTITLE, ExifTagType::Byte),
|
||||||
|
TagInfo(TIFF_XPKEYWORDS, ExifTagType::Byte),
|
||||||
TagInfo(EXIF_EXPOSURETIME, ExifTagType::Rational),
|
TagInfo(EXIF_EXPOSURETIME, ExifTagType::Rational),
|
||||||
TagInfo(EXIF_FNUMBER, ExifTagType::Rational),
|
TagInfo(EXIF_FNUMBER, ExifTagType::Rational),
|
||||||
TagInfo(EXIF_EXIFIFD, ExifTagType::Long),
|
TagInfo(EXIF_EXIFIFD, ExifTagType::Long),
|
||||||
@@ -206,8 +212,8 @@ static const QList<std::pair<quint16, QString>> exifStrMap = {
|
|||||||
std::pair<quint16, QString>(EXIF_BODYSERIALNUMBER, QStringLiteral(META_KEY_SERIALNUMBER)),
|
std::pair<quint16, QString>(EXIF_BODYSERIALNUMBER, QStringLiteral(META_KEY_SERIALNUMBER)),
|
||||||
std::pair<quint16, QString>(EXIF_LENSMAKE, QStringLiteral(META_KEY_LENS_MANUFACTURER)),
|
std::pair<quint16, QString>(EXIF_LENSMAKE, QStringLiteral(META_KEY_LENS_MANUFACTURER)),
|
||||||
std::pair<quint16, QString>(EXIF_LENSMODEL, QStringLiteral(META_KEY_LENS_MODEL)),
|
std::pair<quint16, QString>(EXIF_LENSMODEL, QStringLiteral(META_KEY_LENS_MODEL)),
|
||||||
std::pair<quint16, QString>(EXIF_LENSSERIALNUMBER, QStringLiteral(META_KEY_LENS_SERIALNUMBER)),
|
std::pair<quint16, QString>(EXIF_LENSSERIALNUMBER, QStringLiteral(META_KEY_LENS_SERIALNUMBER))
|
||||||
std::pair<quint16, QString>(EXIF_IMAGETITLE, QStringLiteral(META_KEY_TITLE)),
|
// std::pair<quint16, QString>(EXIF_IMAGETITLE, QStringLiteral(META_KEY_TITLE)) // using functions due to XP title fallback
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
@@ -842,6 +848,32 @@ void MicroExif::setCopyright(const QString &s)
|
|||||||
setTiffString(TIFF_COPYRIGHT, s);
|
setTiffString(TIFF_COPYRIGHT, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList MicroExif::keywords() const
|
||||||
|
{
|
||||||
|
auto val = utf16String(m_tiffTags, TIFF_XPKEYWORDS);
|
||||||
|
if (val.isEmpty())
|
||||||
|
return {};
|
||||||
|
return val.split(QChar(u';'), Qt::SkipEmptyParts);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroExif::setKeywords(const QStringList &k)
|
||||||
|
{
|
||||||
|
setUtf16String(m_tiffTags, TIFF_XPKEYWORDS, k.join(QChar(u';')));
|
||||||
|
}
|
||||||
|
|
||||||
|
quint16 MicroExif::rating() const
|
||||||
|
{
|
||||||
|
return m_tiffTags.value(TIFF_XPRATING).toUInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroExif::setRating(quint16 rating)
|
||||||
|
{
|
||||||
|
if (rating == 0)
|
||||||
|
m_tiffTags.remove(TIFF_XPRATING);
|
||||||
|
else
|
||||||
|
m_tiffTags.insert(TIFF_XPRATING, rating);
|
||||||
|
}
|
||||||
|
|
||||||
QString MicroExif::make() const
|
QString MicroExif::make() const
|
||||||
{
|
{
|
||||||
return tiffString(TIFF_MAKE);
|
return tiffString(TIFF_MAKE);
|
||||||
@@ -964,12 +996,18 @@ void MicroExif::setDateTimeDigitized(const QDateTime &dt)
|
|||||||
|
|
||||||
QString MicroExif::title() const
|
QString MicroExif::title() const
|
||||||
{
|
{
|
||||||
return exifString(EXIF_IMAGETITLE);
|
auto s = exifString(EXIF_IMAGETITLE);
|
||||||
|
if (s.isEmpty()) { // fall back to XP title
|
||||||
|
s = utf16String(m_tiffTags, TIFF_XPTITLE);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MicroExif::setImageTitle(const QString &s)
|
void MicroExif::setTitle(const QString &s)
|
||||||
{
|
{
|
||||||
setExifString(EXIF_IMAGETITLE, s);
|
setExifString(EXIF_IMAGETITLE, s);
|
||||||
|
// Since there is an official title tag, I never write the non-standard Microsoft one.
|
||||||
|
setUtf16String(m_tiffTags, TIFF_XPTITLE, QString());
|
||||||
}
|
}
|
||||||
|
|
||||||
QUuid MicroExif::uniqueId() const
|
QUuid MicroExif::uniqueId() const
|
||||||
@@ -1337,6 +1375,11 @@ void MicroExif::updateImageMetadata(QImage &targetImage, bool replaceExisting) c
|
|||||||
if (!s.isEmpty())
|
if (!s.isEmpty())
|
||||||
targetImage.setText(p.second, s);
|
targetImage.setText(p.second, s);
|
||||||
}
|
}
|
||||||
|
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_TITLE)).isEmpty()) {
|
||||||
|
auto s = title();
|
||||||
|
if (!s.isEmpty())
|
||||||
|
targetImage.setText(QStringLiteral(META_KEY_TITLE), s);
|
||||||
|
}
|
||||||
|
|
||||||
// set date and time
|
// set date and time
|
||||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_MODIFICATIONDATE)).isEmpty()) {
|
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_MODIFICATIONDATE)).isEmpty()) {
|
||||||
@@ -1423,6 +1466,18 @@ void MicroExif::updateImageMetadata(QImage &targetImage, bool replaceExisting) c
|
|||||||
if (v != WhiteBalance::NotSet)
|
if (v != WhiteBalance::NotSet)
|
||||||
targetImage.setText(QStringLiteral(META_KEY_WHITEBALANCE), QStringLiteral("%1").arg(quint16(v)));
|
targetImage.setText(QStringLiteral(META_KEY_WHITEBALANCE), QStringLiteral("%1").arg(quint16(v)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set Microsoft tags
|
||||||
|
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_KEYWORDS)).isEmpty()) {
|
||||||
|
auto keywords = this->keywords();
|
||||||
|
if (!keywords.isEmpty())
|
||||||
|
targetImage.setText(QStringLiteral(META_KEY_KEYWORDS), keywords.join(QChar(u';')));
|
||||||
|
}
|
||||||
|
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_RATING)).isEmpty()) {
|
||||||
|
auto v = rating();
|
||||||
|
if (v != 0)
|
||||||
|
targetImage.setText(QStringLiteral(META_KEY_RATING), QStringLiteral("%1").arg(quint16(v)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MicroExif::updateImageResolution(QImage &targetImage)
|
bool MicroExif::updateImageResolution(QImage &targetImage)
|
||||||
@@ -1518,6 +1573,7 @@ MicroExif MicroExif::fromImage(const QImage &image)
|
|||||||
for (auto &&p : exifStrMap) {
|
for (auto &&p : exifStrMap) {
|
||||||
exif.setExifString(p.first, image.text(p.second));
|
exif.setExifString(p.first, image.text(p.second));
|
||||||
}
|
}
|
||||||
|
exif.setTitle(image.text(QStringLiteral(META_KEY_TITLE)));
|
||||||
|
|
||||||
// TIFF Software
|
// TIFF Software
|
||||||
if (exif.software().isEmpty()) {
|
if (exif.software().isEmpty()) {
|
||||||
@@ -1587,6 +1643,13 @@ MicroExif MicroExif::fromImage(const QImage &image)
|
|||||||
if (ok)
|
if (ok)
|
||||||
exif.setWhiteBalance(WhiteBalance(whtb));
|
exif.setWhiteBalance(WhiteBalance(whtb));
|
||||||
|
|
||||||
|
// Microsoft tags
|
||||||
|
exif.setKeywords(image.text(QStringLiteral(META_KEY_KEYWORDS)).split(QChar(u';'), Qt::SkipEmptyParts));
|
||||||
|
|
||||||
|
auto rating = image.text(QStringLiteral(META_KEY_RATING)).toUInt(&ok);
|
||||||
|
if (ok)
|
||||||
|
exif.setRating(rating);
|
||||||
|
|
||||||
return exif;
|
return exif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1676,3 +1739,29 @@ QString MicroExif::string(const Tags &tags, quint16 tagId)
|
|||||||
{
|
{
|
||||||
return tags.value(tagId).toString();
|
return tags.value(tagId).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MicroExif::setUtf16String(Tags &tags, quint16 tagId, const QString &s)
|
||||||
|
{
|
||||||
|
if (s.isEmpty()) {
|
||||||
|
tags.remove(tagId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto s16 = s.toStdU16String();
|
||||||
|
QList<quint8> uba;
|
||||||
|
auto ba = QByteArrayView(reinterpret_cast<char*>(s16.data()), s.size() * 2);
|
||||||
|
for(auto&& c : ba)
|
||||||
|
uba.append(quint8(c));
|
||||||
|
uba.append(quint8('\0'));
|
||||||
|
uba.append(quint8('\0'));
|
||||||
|
tags.insert(tagId, QVariant::fromValue(uba));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString MicroExif::utf16String(const Tags &tags, quint16 tagId)
|
||||||
|
{
|
||||||
|
auto ba = tags.value(tagId).value<QList<quint8>>();
|
||||||
|
if (ba.isEmpty())
|
||||||
|
return {};
|
||||||
|
auto p16 = reinterpret_cast<char16_t*>(ba.data());
|
||||||
|
auto sz = std::max(ba.size() / 2 - 1, qsizetype());
|
||||||
|
return QString::fromUtf16(p16, sz);
|
||||||
|
}
|
||||||
|
|||||||
@@ -251,6 +251,24 @@ public:
|
|||||||
QString copyright() const;
|
QString copyright() const;
|
||||||
void setCopyright(const QString& s);
|
void setCopyright(const QString& s);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief keywords
|
||||||
|
* Keywords tag used by Windows.
|
||||||
|
* \note Non-standard, added by Windows Explorer.
|
||||||
|
* \return The list of tags.
|
||||||
|
*/
|
||||||
|
QStringList keywords() const;
|
||||||
|
void setKeywords(const QStringList& k);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief rating
|
||||||
|
* Rating tag used by Windows.
|
||||||
|
* \note Non-standard, added by Windows Explorer.
|
||||||
|
* \return The rating. Usually between 0 (not set) and 5.
|
||||||
|
*/
|
||||||
|
quint16 rating() const;
|
||||||
|
void setRating(quint16 rating);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief make
|
* \brief make
|
||||||
* \return The manufacturer of the recording equipment.
|
* \return The manufacturer of the recording equipment.
|
||||||
@@ -319,7 +337,7 @@ public:
|
|||||||
* \return The title of the image.
|
* \return The title of the image.
|
||||||
*/
|
*/
|
||||||
QString title() const;
|
QString title() const;
|
||||||
void setImageTitle(const QString &s);
|
void setTitle(const QString &s);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief uniqueId
|
* \brief uniqueId
|
||||||
@@ -555,6 +573,9 @@ private:
|
|||||||
static void setString(Tags &tags, quint16 tagId, const QString &s);
|
static void setString(Tags &tags, quint16 tagId, const QString &s);
|
||||||
static QString string(const Tags &tags, quint16 tagId);
|
static QString string(const Tags &tags, quint16 tagId);
|
||||||
|
|
||||||
|
static void setUtf16String(Tags &tags, quint16 tagId, const QString &s);
|
||||||
|
static QString utf16String(const Tags &tags, quint16 tagId);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Tags m_tiffTags;
|
Tags m_tiffTags;
|
||||||
Tags m_exifTags;
|
Tags m_exifTags;
|
||||||
|
|||||||
@@ -44,10 +44,12 @@
|
|||||||
#define META_KEY_DIRECTION "Direction"
|
#define META_KEY_DIRECTION "Direction"
|
||||||
#define META_KEY_DOCUMENTNAME "DocumentName"
|
#define META_KEY_DOCUMENTNAME "DocumentName"
|
||||||
#define META_KEY_HOSTCOMPUTER "HostComputer"
|
#define META_KEY_HOSTCOMPUTER "HostComputer"
|
||||||
|
#define META_KEY_KEYWORDS "Keywords"
|
||||||
#define META_KEY_LATITUDE "Latitude"
|
#define META_KEY_LATITUDE "Latitude"
|
||||||
#define META_KEY_LONGITUDE "Longitude"
|
#define META_KEY_LONGITUDE "Longitude"
|
||||||
#define META_KEY_MODIFICATIONDATE "ModificationDate"
|
#define META_KEY_MODIFICATIONDATE "ModificationDate"
|
||||||
#define META_KEY_OWNER "Owner"
|
#define META_KEY_OWNER "Owner"
|
||||||
|
#define META_KEY_RATING "Rating"
|
||||||
#define META_KEY_SOFTWARE "Software"
|
#define META_KEY_SOFTWARE "Software"
|
||||||
#define META_KEY_SPEED "Speed"
|
#define META_KEY_SPEED "Speed"
|
||||||
#define META_KEY_TITLE "Title"
|
#define META_KEY_TITLE "Title"
|
||||||
|
|||||||
Reference in New Issue
Block a user