JXR: added support to EXIF metadata

Improved metadata support via EXIF ​​metadata. Since JXR is based on a TIFF container, EXIF ​​data is read directly from the file so it always works (even with versions of libjxr that don't have the metadata reading API).

It also solves the following issues:
- Incorrect date format on saved JXR files (was saved in ISO format instead of `yyyy:MM:dd HH:mm:ss`).
- Incorrect date type setting in EXIF ​​data: the `DateTime` tag should be updated on every save (verified by GIMP and Photoshop). Our `CreationDate` metadata is the equivalent of the EXIF ​​`DateTimeOriginal` tag.

Closes #22
This commit is contained in:
Mirco Miranda 2025-02-23 00:38:27 +00:00 committed by Albert Astals Cid
parent 90d4256f3d
commit e5cf9caac5
17 changed files with 327 additions and 42 deletions

View File

@ -125,8 +125,9 @@ About the image:
example a verbal description of the image.
- `Copyright`: Copyright notice of the person or organization that claims
the copyright to the image.
- `CreationDate`: Creation date and time in ISO 8601 format without
milliseconds (e.g. 2024-03-23T15:30:43).
- `CreationDate`: When the image was created or captured. Date and time in
ISO 8601 format without milliseconds (e.g. 2024-03-23T15:30:43). This value
should be kept unchanged when present.
- `Description`: A string that describes the subject of the image.
- `DocumentName`: The name of the document from which this image was
scanned.
@ -136,6 +137,9 @@ About the image:
north of the equator (e.g. 27.717).
- `Longitude`: Floating-point number indicating the longitude in degrees
east of Greenwich (e.g. 85.317).
- `ModificationDate`: Last modification date and time in ISO 8601 format
without milliseconds (e.g. 2024-03-23T15:30:43). This value should be
updated every time the image is saved.
- `Owner`: Name of the owner of the image.
- `Software`: Name and version number of the software package(s) used to
create the image.
@ -189,6 +193,7 @@ are created automatically:
- `Software`: Created using `applicationName` and `applicationVersion` methods
of [`QCoreApplication`](https://doc.qt.io/qt-6/qcoreapplication.html).
- `CreationDate`: Set to current time and date.
- `ModificationDate`: Set to current time and date.
### ICC profile support

View File

@ -3,7 +3,7 @@
"fileName" : "metadata.png",
"metadata" : [
{
"key" : "CreationDate",
"key" : "ModificationDate",
"value" : "2025-02-19T08:27:22+01:00"
},
{

View File

@ -3,7 +3,7 @@
"fileName" : "gimp_exif.png",
"metadata" : [
{
"key" : "CreationDate",
"key" : "ModificationDate",
"value" : "2025-01-05T10:18:16"
},
{

Binary file not shown.

View File

@ -0,0 +1,59 @@
[
{
"fileName" : "metadata.png",
"metadata" : [
{
"key" : "CreationDate",
"value" : "2025-01-14T10:34:51Z"
},
{
"key" : "Software" ,
"value" : "LIFE Pro 2.18.10"
},
{
"key" : "Altitude",
"value" : "34"
},
{
"key" : "Author",
"value" : "KDE Project"
},
{
"key" : "Copyright",
"value" : "@2025 KDE Project"
},
{
"key" : "Description",
"value" : "TV broadcast test image."
},
{
"key" : "Latitude",
"value" : "44.6478"
},
{
"key" : "LensManufacturer",
"value" : "KDE Glasses"
},
{
"key" : "LensModel",
"value" : "A1234"
},
{
"key" : "Longitude",
"value" : "10.9254"
},
{
"key" : "Manufacturer",
"value" : "KFramework"
},
{
"key" : "Model",
"value" : "KImageFormats"
}
],
"resolution" : {
"dotsPerMeterX" : 11811,
"dotsPerMeterY" : 5906
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -8,7 +8,7 @@
},
"metadata" : [
{
"key" : "CreationDate",
"key" : "ModificationDate",
"value" : "2022-11-11T14:27:52"
},
{

View File

@ -8,7 +8,7 @@
},
"metadata" : [
{
"key" : "CreationDate",
"key" : "ModificationDate",
"value" : "2022-11-11T14:27:39"
},
{

View File

@ -3,7 +3,7 @@
"fileName" : "metadata.png",
"metadata" : [
{
"key" : "CreationDate",
"key" : "ModificationDate",
"value" : "2025-01-14T13:53:32+01:00"
},
{

View File

@ -5,6 +5,10 @@
"key" : "CreationDate",
"value" : "2025-01-14T13:53:32+01:00"
},
{
"key" : "ModificationDate",
"value" : "2025-02-14T15:58:44+01:00"
},
{
"key" : "Software" ,
"value" : "Adobe Photoshop 26.2 (Windows)"

View File

@ -5,6 +5,10 @@
"key" : "CreationDate",
"value" : "2025-01-14T13:53:32+01:00"
},
{
"key" : "ModificationDate",
"value" : "2025-02-14T15:58:44+01:00"
},
{
"key" : "Software" ,
"value" : "Adobe Photoshop 26.2 (Windows)"

View File

@ -5,6 +5,14 @@
"key" : "CreationDate",
"value" : "2025-01-14T13:53:32+01:00"
},
{
"key" : "ModificationDate",
"value" : "2025-02-14T15:58:44+01:00"
},
{
"key" : "Altitude",
"value" : "34"
},
{
"key" : "Author",
"value" : "KDE Project"
@ -17,6 +25,22 @@
"key" : "Description",
"value" : "TV broadcast test image."
},
{
"key" : "Latitude",
"value" : "44.6478"
},
{
"key" : "LensManufacturer",
"value" : "KDE Glasses"
},
{
"key" : "LensModel",
"value" : "A1234"
},
{
"key" : "Longitude",
"value" : "10.9254"
},
{
"key" : "Manufacturer",
"value" : "KFramework"

View File

@ -140,7 +140,7 @@ endif()
##################################
if (LibJXR_FOUND)
kimageformats_add_plugin(kimg_jxr SOURCES jxr.cpp)
kimageformats_add_plugin(kimg_jxr SOURCES jxr.cpp microexif.cpp)
kde_enable_exceptions()
target_include_directories(kimg_jxr PRIVATE ${LIBJXR_INCLUDE_DIRS})
target_link_libraries(kimg_jxr PRIVATE ${LIBJXR_LIBRARIES})

View File

@ -15,6 +15,7 @@
*/
#include "jxr_p.h"
#include "microexif_p.h"
#include "util_p.h"
#include <QColorSpace>
@ -83,9 +84,10 @@ Q_LOGGING_CATEGORY(LOG_JXRPLUGIN, "kf.imageformats.plugins.jxr", QtWarningMsg)
class JXRHandlerPrivate : public QSharedData
{
private:
QSharedPointer<QTemporaryDir> tempDir;
mutable QSharedPointer<QFile> jxrFile;
mutable QHash<QString, QString> txtMeta;
QSharedPointer<QTemporaryDir> m_tempDir;
QSharedPointer<QFile> m_jxrFile;
MicroExif m_exif;
mutable QHash<QString, QString> m_txtMeta;
public:
PKFactory *pFactory = nullptr;
@ -95,7 +97,7 @@ public:
JXRHandlerPrivate()
{
tempDir = QSharedPointer<QTemporaryDir>(new QTemporaryDir);
m_tempDir = QSharedPointer<QTemporaryDir>(new QTemporaryDir);
if (PKCreateFactory(&pFactory, PK_SDK_VERSION) == WMP_errSuccess) {
PKCreateCodecFactory(&pCodecFactory, WMP_SDK_VERSION);
}
@ -123,7 +125,7 @@ public:
QString fileName() const
{
return jxrFile->fileName();
return m_jxrFile->fileName();
}
/* *** READ *** */
@ -318,11 +320,20 @@ public:
}
/*!
* \brief setTextMetadata
* Set the text metadata into \a image
* \brief exifData
* \return The EXIF data.
*/
MicroExif exifData() const
{
return m_exif;
}
/*!
* \brief setMetadata
* Set the metadata into \a image
* \param image Image on which to write metadata
*/
void setTextMetadata(QImage& image)
void setMetadata(QImage& image)
{
auto xmp = xmpData();
if (!xmp.isEmpty()) {
@ -344,10 +355,6 @@ public:
if (!model.isEmpty()) {
image.setText(QStringLiteral(META_KEY_MODEL), model);
}
auto cDate = dateTime();
if (!cDate.isEmpty()) {
image.setText(QStringLiteral(META_KEY_CREATIONDATE), cDate);
}
auto author = artist();
if (!author.isEmpty()) {
image.setText(QStringLiteral(META_KEY_AUTHOR), author);
@ -368,20 +375,23 @@ public:
if (!docn.isEmpty()) {
image.setText(QStringLiteral(META_KEY_DOCUMENTNAME), docn);
}
auto exif = exifData();
if (!exif.isEmpty()) {
exif.toImageMetadata(image);
}
}
#define META_TEXT(name, key) \
QString name() const \
{ \
readTextMeta(); \
return txtMeta.value(QStringLiteral(key)); \
return m_txtMeta.value(QStringLiteral(key)); \
}
META_TEXT(description, META_KEY_DESCRIPTION)
META_TEXT(cameraMake, META_KEY_MANUFACTURER)
META_TEXT(cameraModel, META_KEY_MODEL)
META_TEXT(software, META_KEY_SOFTWARE)
META_TEXT(dateTime, META_KEY_CREATIONDATE)
META_TEXT(artist, META_KEY_AUTHOR)
META_TEXT(copyright, META_KEY_COPYRIGHT)
META_TEXT(caption, META_KEY_TITLE)
@ -400,9 +410,9 @@ public:
bool initForWriting()
{
// I have to use QFile because, on Windows, the QTemporary file is locked (even if I close it)
auto fileName = QStringLiteral("%1.jxr").arg(tempDir->filePath(QUuid::createUuid().toString(QUuid::WithoutBraces).left(8)));
auto fileName = QStringLiteral("%1.jxr").arg(m_tempDir->filePath(QUuid::createUuid().toString(QUuid::WithoutBraces).left(8)));
QSharedPointer<QFile> file(new QFile(fileName));
jxrFile = file;
m_jxrFile = file;
return initEncoder();
}
@ -422,7 +432,7 @@ public:
return false;
}
if (!deviceCopy(device, jxrFile.data())) {
if (!deviceCopy(device, m_jxrFile.data())) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::finalizeWriting() error while writing in the target device";
return false;
}
@ -580,7 +590,6 @@ public:
META_CTEXT(META_KEY_MODEL, pvarCameraModel)
META_CTEXT(META_KEY_AUTHOR, pvarArtist)
META_CTEXT(META_KEY_COPYRIGHT, pvarCopyright)
META_CTEXT(META_KEY_CREATIONDATE, pvarDateTime)
META_CTEXT(META_KEY_DOCUMENTNAME, pvarDocumentName)
META_CTEXT(META_KEY_HOSTCOMPUTER, pvarHostComputer)
META_WTEXT(META_KEY_TITLE, pvarCaption)
@ -595,12 +604,33 @@ public:
meta.pvarSoftware.VT.pszVal = software.data();
}
// Date and Time (TIFF format)
auto cDate = QDateTime::fromString(image.text(QStringLiteral(META_KEY_MODIFICATIONDATE)), Qt::ISODate);
auto sDate = cDate.isValid() ? cDate.toString(QStringLiteral("yyyy:MM:dd HH:mm:ss")).toLatin1() : QByteArray();
if (!sDate.isEmpty()) {
meta.pvarDateTime.vt = DPKVT_LPSTR;
meta.pvarDateTime.VT.pszVal = sDate.data();
}
auto xmp = image.text(QStringLiteral(META_KEY_XMP_ADOBE)).toUtf8();
if (!xmp.isNull()) {
if (auto err = PKImageEncode_SetXMPMetadata_WMP(pEncoder, reinterpret_cast<quint8 *>(xmp.data()), xmp.size())) {
if (auto err = PKImageEncode_SetXMPMetadata_WMP(pEncoder, reinterpret_cast<const quint8 *>(xmp.constData()), xmp.size())) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting XMP data:" << err;
}
}
auto exif = MicroExif::fromImage(image);
if (!exif.isEmpty()) {
auto exifIfd = exif.exifIfdByteArray(QDataStream::LittleEndian);
if (auto err = PKImageEncode_SetEXIFMetadata_WMP(pEncoder, reinterpret_cast<const quint8 *>(exifIfd.constData()), exifIfd.size())) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting EXIF data:" << err;
}
auto gpsIfd = exif.gpsIfdByteArray(QDataStream::LittleEndian);
if (auto err = PKImageEncode_SetGPSInfoMetadata_WMP(pEncoder, reinterpret_cast<const quint8 *>(gpsIfd.constData()), gpsIfd.size())) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting GPS data:" << err;
}
}
if (auto err = pEncoder->SetDescriptiveMetadata(pEncoder, &meta)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting descriptive data:" << err;
}
@ -710,11 +740,11 @@ private:
if (device == nullptr) {
return false;
}
if (!jxrFile.isNull()) {
if (!m_jxrFile.isNull()) {
return true;
}
// I have to use QFile because, on Windows, the QTemporary file is locked (even if I close it)
auto fileName = QStringLiteral("%1.jxr").arg(tempDir->filePath(QUuid::createUuid().toString(QUuid::WithoutBraces).left(8)));
auto fileName = QStringLiteral("%1.jxr").arg(m_tempDir->filePath(QUuid::createUuid().toString(QUuid::WithoutBraces).left(8)));
QSharedPointer<QFile> file(new QFile(fileName));
if (!file->open(QFile::WriteOnly)) {
return false;
@ -724,7 +754,8 @@ private:
return false;
}
file->close();
jxrFile = file;
m_exif = MicroExif::fromDevice(file.data());
m_jxrFile = file;
return true;
}
@ -762,7 +793,7 @@ private:
if (pDecoder == nullptr) {
return false;
}
if (!txtMeta.isEmpty()) {
if (!m_txtMeta.isEmpty()) {
return true;
}
@ -773,15 +804,14 @@ private:
#define META_TEXT(name, field) \
if (meta.field.vt == DPKVT_LPSTR) \
txtMeta.insert(QStringLiteral(name), QString::fromUtf8(meta.field.VT.pszVal)); \
m_txtMeta.insert(QStringLiteral(name), QString::fromUtf8(meta.field.VT.pszVal)); \
else if (meta.field.vt == DPKVT_LPWSTR) \
txtMeta.insert(QStringLiteral(name), QString::fromUtf16(reinterpret_cast<char16_t *>(meta.field.VT.pwszVal)));
m_txtMeta.insert(QStringLiteral(name), QString::fromUtf16(reinterpret_cast<char16_t *>(meta.field.VT.pwszVal)));
META_TEXT(META_KEY_DESCRIPTION, pvarImageDescription)
META_TEXT(META_KEY_MANUFACTURER, pvarCameraMake)
META_TEXT(META_KEY_MODEL, pvarCameraModel)
META_TEXT(META_KEY_SOFTWARE, pvarSoftware)
META_TEXT(META_KEY_CREATIONDATE, pvarDateTime)
META_TEXT(META_KEY_AUTHOR, pvarArtist)
META_TEXT(META_KEY_COPYRIGHT, pvarCopyright)
META_TEXT(META_KEY_TITLE, pvarCaption)
@ -867,7 +897,7 @@ bool JXRHandler::read(QImage *outImage)
// Metadata (e.g.: icc profile, description, etc...)
img.setColorSpace(d->colorSpace());
d->setTextMetadata(img);
d->setMetadata(img);
#ifndef JXR_DENY_FLOAT_IMAGE
// JXR float are stored in scRGB.

View File

@ -37,7 +37,12 @@
// EXIF 3 specs
#define EXIF_EXIFIFD 0x8769
#define EXIF_GPSIFD 0x8825
#define EXIF_EXIFVERSION 0x9000
#define EXIF_DATETIMEORIGINAL 0x9003
#define EXIF_DATETIMEDIGITIZED 0x9004
#define EXIF_OFFSETTIME 0x9010
#define EXIF_OFFSETTIMEORIGINAL 0x9011
#define EXIF_OFFSETTIMEDIGITIZED 0x9012
#define EXIF_COLORSPACE 0xA001
#define EXIF_PIXELXDIM 0xA002
#define EXIF_PIXELYDIM 0xA003
@ -47,7 +52,6 @@
#define EXIF_LENSMODEL 0xA434
#define EXIF_LENSSERIALNUMBER 0xA435
#define EXIF_IMAGETITLE 0xA436
#define EXIF_EXIFVERSION 0x9000
#define EXIF_VAL_COLORSPACE_SRGB 1
#define EXIF_VAL_COLORSPACE_UNCAL 0xFFFF
@ -119,7 +123,11 @@ static const KnownTags staticTagTypes = {
TagInfo(TIFF_COPYRIGHT, ExifTagType::Ascii),
TagInfo(EXIF_EXIFIFD, ExifTagType::Long),
TagInfo(EXIF_GPSIFD, ExifTagType::Long),
TagInfo(EXIF_DATETIMEORIGINAL, ExifTagType::Ascii),
TagInfo(EXIF_OFFSETTIMEDIGITIZED, ExifTagType::Ascii),
TagInfo(EXIF_OFFSETTIME, ExifTagType::Ascii),
TagInfo(EXIF_OFFSETTIMEORIGINAL, ExifTagType::Ascii),
TagInfo(EXIF_OFFSETTIMEDIGITIZED, ExifTagType::Ascii),
TagInfo(EXIF_COLORSPACE, ExifTagType::Short),
TagInfo(EXIF_PIXELXDIM, ExifTagType::Long),
TagInfo(EXIF_PIXELYDIM, ExifTagType::Long),
@ -230,8 +238,8 @@ static bool checkHeader(QDataStream &ds)
quint16 version;
ds >> version;
if (version != 0x2A)
return false;
if (version != 0x002A && version != 0x01BC)
return false; // not TIFF or JXR
quint32 offset;
ds >> offset;
@ -852,6 +860,46 @@ void MicroExif::setDateTime(const QDateTime &dt)
setExifString(EXIF_OFFSETTIME, timeOffset(dt.offsetFromUtc() / 60));
}
QDateTime MicroExif::dateTimeOriginal() const
{
auto dt = QDateTime::fromString(exifString(EXIF_DATETIMEORIGINAL), QStringLiteral("yyyy:MM:dd HH:mm:ss"));
auto ofTag = exifString(EXIF_OFFSETTIMEORIGINAL);
if (dt.isValid() && !ofTag.isEmpty())
dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(timeOffset(ofTag) * 60));
return(dt);
}
void MicroExif::setDateTimeOriginal(const QDateTime &dt)
{
if (!dt.isValid()) {
m_exifTags.remove(EXIF_DATETIMEORIGINAL);
m_exifTags.remove(EXIF_OFFSETTIMEORIGINAL);
return;
}
setExifString(EXIF_DATETIMEORIGINAL, dt.toString(QStringLiteral("yyyy:MM:dd HH:mm:ss")));
setExifString(EXIF_OFFSETTIMEORIGINAL, timeOffset(dt.offsetFromUtc() / 60));
}
QDateTime MicroExif::dateTimeDigitized() const
{
auto dt = QDateTime::fromString(exifString(EXIF_DATETIMEDIGITIZED), QStringLiteral("yyyy:MM:dd HH:mm:ss"));
auto ofTag = exifString(EXIF_OFFSETTIMEDIGITIZED);
if (dt.isValid() && !ofTag.isEmpty())
dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(timeOffset(ofTag) * 60));
return(dt);
}
void MicroExif::setDateTimeDigitized(const QDateTime &dt)
{
if (!dt.isValid()) {
m_exifTags.remove(EXIF_DATETIMEDIGITIZED);
m_exifTags.remove(EXIF_OFFSETTIMEDIGITIZED);
return;
}
setExifString(EXIF_DATETIMEDIGITIZED, dt.toString(QStringLiteral("yyyy:MM:dd HH:mm:ss")));
setExifString(EXIF_OFFSETTIMEDIGITIZED, timeOffset(dt.offsetFromUtc() / 60));
}
QString MicroExif::title() const
{
return exifString(EXIF_IMAGETITLE);
@ -956,6 +1004,50 @@ QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder) const
return ba;
}
QByteArray MicroExif::exifIfdByteArray(const QDataStream::ByteOrder &byteOrder) const
{
QByteArray ba;
{
QDataStream ds(&ba, QIODevice::WriteOnly);
ds.setByteOrder(byteOrder);
auto exifTags = m_exifTags;
exifTags.insert(EXIF_EXIFVERSION, QByteArray("0300"));
TagPos positions;
if (!writeIfd(ds, exifTags, positions))
return {};
}
return ba;
}
bool MicroExif::setExifIfdByteArray(const QByteArray &ba, const QDataStream::ByteOrder &byteOrder)
{
QDataStream ds(ba);
ds.setByteOrder(byteOrder);
return readIfd(ds, m_exifTags, 0, staticTagTypes);
}
QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder) const
{
QByteArray ba;
{
QDataStream ds(&ba, QIODevice::WriteOnly);
ds.setByteOrder(byteOrder);
auto gpsTags = m_gpsTags;
gpsTags.insert(GPS_GPSVERSION, QByteArray("2400"));
TagPos positions;
if (!writeIfd(ds, gpsTags, positions, 0, staticGpsTagTypes))
return {};
return ba;
}
}
bool MicroExif::setGpsIfdByteArray(const QByteArray &ba, const QDataStream::ByteOrder &byteOrder)
{
QDataStream ds(ba);
ds.setByteOrder(byteOrder);
return readIfd(ds, m_gpsTags, 0, staticGpsTagTypes);
}
bool MicroExif::write(QIODevice *device, const QDataStream::ByteOrder &byteOrder) const
{
if (device == nullptr || device->isSequential() || isEmpty())
@ -993,8 +1085,13 @@ void MicroExif::toImageMetadata(QImage &targetImage, bool replaceExisting) const
}
// set date and time
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_CREATIONDATE)).isEmpty()) {
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_MODIFICATIONDATE)).isEmpty()) {
auto dt = dateTime();
if (dt.isValid())
targetImage.setText(QStringLiteral(META_KEY_MODIFICATIONDATE), dt.toString(Qt::ISODate));
}
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_CREATIONDATE)).isEmpty()) {
auto dt = dateTimeOriginal();
if (dt.isValid())
targetImage.setText(QStringLiteral(META_KEY_CREATIONDATE), dt.toString(Qt::ISODate));
}
@ -1095,12 +1192,18 @@ MicroExif MicroExif::fromImage(const QImage &image)
exif.setSoftware(sw.trimmed());
}
// TIFF Creation date and time
auto dt = QDateTime::fromString(image.text(QStringLiteral(META_KEY_CREATIONDATE)), Qt::ISODate);
// TIFF date and time
auto dt = QDateTime::fromString(image.text(QStringLiteral(META_KEY_MODIFICATIONDATE)), Qt::ISODate);
if (!dt.isValid())
dt = QDateTime::currentDateTime();
exif.setDateTime(dt);
// EXIF original date and time
dt = QDateTime::fromString(image.text(QStringLiteral(META_KEY_CREATIONDATE)), Qt::ISODate);
if (!dt.isValid())
dt = QDateTime::currentDateTime();
exif.setDateTimeOriginal(dt);
// GPS Info
auto ok = false;
auto alt = image.text(QStringLiteral(META_KEY_ALTITUDE)).toDouble(&ok);

View File

@ -201,6 +201,20 @@ public:
QDateTime dateTime() const;
void setDateTime(const QDateTime& dt);
/*!
* \brief dateTimeOriginal
* \return The date and time when the original image data was generated.
*/
QDateTime dateTimeOriginal() const;
void setDateTimeOriginal(const QDateTime& dt);
/*!
* \brief dateTimeDigitized
* \return The date and time when the image was stored as digital data.
*/
QDateTime dateTimeDigitized() const;
void setDateTimeDigitized(const QDateTime& dt);
/*!
* \brief title
* \return The title of the image.
@ -239,17 +253,58 @@ public:
/*!
* \brief toByteArray
* Converts the class to RAW data. The raw data contains:
* - TIFF header
* - MAIN IFD
* - EXIF IFD
* - GPS IFD
* \param byteOrder Sets the serialization byte order for EXIF data.
* \return A byte array containing the serialized data.
* \sa write
*/
QByteArray toByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
/*!
* \brief exifIfdByteArray
* Convert the EXIF IFD only to RAW data. Useful when you want to add EXIF data to an existing TIFF container.
* \param byteOrder Sets the serialization byte order for the data.
* \return A byte array containing the serialized data.
*/
QByteArray exifIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
/*!
* \brief setExifIfdByteArray
* \param ba The RAW data of EXIF IFD.
* \param byteOrder Sets the serialization byte order of the data.
* \return True on success, otherwise false.
*/
bool setExifIfdByteArray(const QByteArray& ba, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER);
/*!
* \brief gpsIfdByteArray
* Convert the GPS IFD only to RAW data. Useful when you want to add GPS data to an existing TIFF container.
* \param byteOrder Sets the serialization byte order for the data.
* \return A byte array containing the serialized data.
*/
QByteArray gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
/*!
* \brief setGpsIfdByteArray
* \param ba The RAW data of GPS IFD.
* \param byteOrder Sets the serialization byte order of the data.
* \return True on success, otherwise false.
*/
bool setGpsIfdByteArray(const QByteArray& ba, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER);
/*!
* \brief write
* Serialize the class on a device.
* Serialize the class on a device. The serialized data contains:
* - TIFF header
* - MAIN IFD
* - EXIF IFD
* - GPS IFD
* \param device A random access device.
* \param byteOrder Sets the serialization byte order for EXIF data.
* \return True on success, otherwise false.
* \sa toByteArray
*/
bool write(QIODevice *device, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;

View File

@ -24,6 +24,7 @@
#define META_KEY_HOSTCOMPUTER "HostComputer"
#define META_KEY_LATITUDE "Latitude"
#define META_KEY_LONGITUDE "Longitude"
#define META_KEY_MODIFICATIONDATE "ModificationDate"
#define META_KEY_OWNER "Owner"
#define META_KEY_SOFTWARE "Software"
#define META_KEY_TITLE "Title"