mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-05-28 00:30:23 -04:00
MicroExif: API improvements and minor bugfixes
This commit is contained in:
parent
d3386bbf50
commit
7742537f8c
@ -129,6 +129,8 @@ About the image:
|
|||||||
ISO 8601 format without milliseconds (e.g. 2024-03-23T15:30:43). This value
|
ISO 8601 format without milliseconds (e.g. 2024-03-23T15:30:43). This value
|
||||||
should be kept unchanged when present.
|
should be kept unchanged when present.
|
||||||
- `Description`: A string that describes the subject of the image.
|
- `Description`: A string that describes the subject of the image.
|
||||||
|
- `Direction`: Floating-point number indicating the direction of the image
|
||||||
|
when it was captured in degrees (e.g. 123.3).
|
||||||
- `DocumentName`: The name of the document from which this image was
|
- `DocumentName`: The name of the document from which this image was
|
||||||
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
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
"key" : "CreationDate",
|
"key" : "CreationDate",
|
||||||
"value" : "2025-01-14T13:53:32+01:00"
|
"value" : "2025-01-14T13:53:32+01:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key" : "Direction",
|
||||||
|
"value" : "123.7"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key" : "ModificationDate",
|
"key" : "ModificationDate",
|
||||||
"value" : "2025-02-14T15:58:44+01:00"
|
"value" : "2025-02-14T15:58:44+01:00"
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
"key" : "CreationDate",
|
"key" : "CreationDate",
|
||||||
"value" : "2025-01-14T13:53:32+01:00"
|
"value" : "2025-01-14T13:53:32+01:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key" : "Direction",
|
||||||
|
"value" : "123.7"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key" : "ModificationDate",
|
"key" : "ModificationDate",
|
||||||
"value" : "2025-02-14T15:58:44+01:00"
|
"value" : "2025-02-14T15:58:44+01:00"
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
"key" : "CreationDate",
|
"key" : "CreationDate",
|
||||||
"value" : "2025-01-14T13:53:32+01:00"
|
"value" : "2025-01-14T13:53:32+01:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key" : "Direction",
|
||||||
|
"value" : "123.7"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key" : "ModificationDate",
|
"key" : "ModificationDate",
|
||||||
"value" : "2025-02-14T15:58:44+01:00"
|
"value" : "2025-02-14T15:58:44+01:00"
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
"key" : "CreationDate",
|
"key" : "CreationDate",
|
||||||
"value" : "2025-01-14T13:53:32+01:00"
|
"value" : "2025-01-14T13:53:32+01:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key" : "Direction",
|
||||||
|
"value" : "123.7"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key" : "ModificationDate",
|
"key" : "ModificationDate",
|
||||||
"value" : "2025-02-14T15:58:44+01:00"
|
"value" : "2025-02-14T15:58:44+01:00"
|
||||||
|
@ -534,13 +534,8 @@ bool QAVIFHandler::decode_one_frame()
|
|||||||
|
|
||||||
if (m_decoder->image->exif.size) {
|
if (m_decoder->image->exif.size) {
|
||||||
auto exif = MicroExif::fromRawData(reinterpret_cast<const char *>(m_decoder->image->exif.data), m_decoder->image->exif.size);
|
auto exif = MicroExif::fromRawData(reinterpret_cast<const char *>(m_decoder->image->exif.data), m_decoder->image->exif.size);
|
||||||
// set image resolution
|
exif.updateImageResolution(m_current_image);
|
||||||
if (exif.horizontalResolution() > 0)
|
exif.updateImageMetadata(m_current_image);
|
||||||
m_current_image.setDotsPerMeterX(qRound(exif.horizontalResolution() / 25.4 * 1000));
|
|
||||||
if (exif.verticalResolution() > 0)
|
|
||||||
m_current_image.setDotsPerMeterY(qRound(exif.verticalResolution() / 25.4 * 1000));
|
|
||||||
// set image metadata
|
|
||||||
exif.toImageMetadata(m_current_image);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_decoder->image->xmp.size) {
|
if (m_decoder->image->xmp.size) {
|
||||||
|
@ -955,13 +955,8 @@ bool HEIFHandler::ensureDecoder()
|
|||||||
} else if (isExif) {
|
} else if (isExif) {
|
||||||
auto exif = MicroExif::fromByteArray(ba.mid(4));
|
auto exif = MicroExif::fromByteArray(ba.mid(4));
|
||||||
if (!exif.isEmpty()) {
|
if (!exif.isEmpty()) {
|
||||||
// set image resolution
|
exif.updateImageResolution(m_current_image);
|
||||||
if (exif.horizontalResolution() > 0)
|
exif.updateImageMetadata(m_current_image);
|
||||||
m_current_image.setDotsPerMeterX(qRound(exif.horizontalResolution() / 25.4 * 1000));
|
|
||||||
if (exif.verticalResolution() > 0)
|
|
||||||
m_current_image.setDotsPerMeterY(qRound(exif.verticalResolution() / 25.4 * 1000));
|
|
||||||
// set image metadata
|
|
||||||
exif.toImageMetadata(m_current_image);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -791,13 +791,8 @@ bool QJpegXLHandler::decode_one_frame()
|
|||||||
|
|
||||||
if (!m_exif.isEmpty()) {
|
if (!m_exif.isEmpty()) {
|
||||||
auto exif = MicroExif::fromByteArray(m_exif);
|
auto exif = MicroExif::fromByteArray(m_exif);
|
||||||
// set image resolution
|
exif.updateImageResolution(m_current_image);
|
||||||
if (exif.horizontalResolution() > 0)
|
exif.updateImageMetadata(m_current_image);
|
||||||
m_current_image.setDotsPerMeterX(qRound(exif.horizontalResolution() / 25.4 * 1000));
|
|
||||||
if (exif.verticalResolution() > 0)
|
|
||||||
m_current_image.setDotsPerMeterY(qRound(exif.verticalResolution() / 25.4 * 1000));
|
|
||||||
// set image metadata
|
|
||||||
exif.toImageMetadata(m_current_image);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_next_image_delay = m_framedelays[m_currentimage_index];
|
m_next_image_delay = m_framedelays[m_currentimage_index];
|
||||||
|
@ -473,7 +473,7 @@ public:
|
|||||||
}
|
}
|
||||||
auto exif = exifData();
|
auto exif = exifData();
|
||||||
if (!exif.isEmpty()) {
|
if (!exif.isEmpty()) {
|
||||||
exif.toImageMetadata(image);
|
exif.updateImageMetadata(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,8 @@
|
|||||||
#define GPS_LONGITUDE 4
|
#define GPS_LONGITUDE 4
|
||||||
#define GPS_ALTITUDEREF 5
|
#define GPS_ALTITUDEREF 5
|
||||||
#define GPS_ALTITUDE 6
|
#define GPS_ALTITUDE 6
|
||||||
|
#define GPS_IMGDIRECTIONREF 16
|
||||||
|
#define GPS_IMGDIRECTION 17
|
||||||
#define EXIF_TAG_VALUE(n, byteSize) (((n) << 6) | ((byteSize) & 0x3F))
|
#define EXIF_TAG_VALUE(n, byteSize) (((n) << 6) | ((byteSize) & 0x3F))
|
||||||
#define EXIF_TAG_SIZEOF(dataType) (quint16(dataType) & 0x3F)
|
#define EXIF_TAG_SIZEOF(dataType) (quint16(dataType) & 0x3F)
|
||||||
#define EXIF_TAG_DATATYPE(dataType) (quint16(dataType) >> 6)
|
#define EXIF_TAG_DATATYPE(dataType) (quint16(dataType) >> 6)
|
||||||
@ -152,7 +153,9 @@ static const KnownTags staticGpsTagTypes = {
|
|||||||
TagInfo(GPS_LONGITUDEREF, ExifTagType::Ascii),
|
TagInfo(GPS_LONGITUDEREF, ExifTagType::Ascii),
|
||||||
TagInfo(GPS_LONGITUDE, ExifTagType::Rational),
|
TagInfo(GPS_LONGITUDE, ExifTagType::Rational),
|
||||||
TagInfo(GPS_ALTITUDEREF, ExifTagType::Byte),
|
TagInfo(GPS_ALTITUDEREF, ExifTagType::Byte),
|
||||||
TagInfo(GPS_ALTITUDE, ExifTagType::Rational)
|
TagInfo(GPS_ALTITUDE, ExifTagType::Rational),
|
||||||
|
TagInfo(GPS_IMGDIRECTIONREF, ExifTagType::Ascii),
|
||||||
|
TagInfo(GPS_IMGDIRECTION, ExifTagType::Rational)
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
@ -944,6 +947,10 @@ double MicroExif::latitude() const
|
|||||||
|
|
||||||
void MicroExif::setLatitude(double degree)
|
void MicroExif::setLatitude(double degree)
|
||||||
{
|
{
|
||||||
|
if (qIsNaN(degree)) {
|
||||||
|
m_gpsTags.remove(GPS_LATITUDEREF);
|
||||||
|
m_gpsTags.remove(GPS_LATITUDE);
|
||||||
|
}
|
||||||
if (degree < -90.0 || degree > 90.0)
|
if (degree < -90.0 || degree > 90.0)
|
||||||
return; // invalid latitude
|
return; // invalid latitude
|
||||||
auto adeg = qAbs(degree);
|
auto adeg = qAbs(degree);
|
||||||
@ -969,6 +976,10 @@ double MicroExif::longitude() const
|
|||||||
|
|
||||||
void MicroExif::setLongitude(double degree)
|
void MicroExif::setLongitude(double degree)
|
||||||
{
|
{
|
||||||
|
if (qIsNaN(degree)) {
|
||||||
|
m_gpsTags.remove(GPS_LONGITUDEREF);
|
||||||
|
m_gpsTags.remove(GPS_LONGITUDE);
|
||||||
|
}
|
||||||
if (degree < -180.0 || degree > 180.0)
|
if (degree < -180.0 || degree > 180.0)
|
||||||
return; // invalid longitude
|
return; // invalid longitude
|
||||||
auto adeg = qAbs(degree);
|
auto adeg = qAbs(degree);
|
||||||
@ -983,16 +994,44 @@ double MicroExif::altitude() const
|
|||||||
auto ref = m_gpsTags.value(GPS_ALTITUDEREF);
|
auto ref = m_gpsTags.value(GPS_ALTITUDEREF);
|
||||||
if (ref.isNull())
|
if (ref.isNull())
|
||||||
return qQNaN();
|
return qQNaN();
|
||||||
|
if (!m_gpsTags.contains(GPS_ALTITUDE))
|
||||||
|
return qQNaN();
|
||||||
auto alt = m_gpsTags.value(GPS_ALTITUDE).toDouble();
|
auto alt = m_gpsTags.value(GPS_ALTITUDE).toDouble();
|
||||||
return (ref.toInt() == 0 || ref.toInt() == 2) ? alt : -alt;
|
return (ref.toInt() == 0 || ref.toInt() == 2) ? alt : -alt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MicroExif::setAltitude(double meters)
|
void MicroExif::setAltitude(double meters)
|
||||||
{
|
{
|
||||||
|
if (qIsNaN(meters)) {
|
||||||
|
m_gpsTags.remove(GPS_ALTITUDEREF);
|
||||||
|
m_gpsTags.remove(GPS_ALTITUDE);
|
||||||
|
}
|
||||||
m_gpsTags.insert(GPS_ALTITUDEREF, quint8(meters < 0 ? 1 : 0));
|
m_gpsTags.insert(GPS_ALTITUDEREF, quint8(meters < 0 ? 1 : 0));
|
||||||
m_gpsTags.insert(GPS_ALTITUDE, meters);
|
m_gpsTags.insert(GPS_ALTITUDE, meters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double MicroExif::imageDirection(bool *isMagnetic) const
|
||||||
|
{
|
||||||
|
auto tmp = false;
|
||||||
|
if (isMagnetic == nullptr)
|
||||||
|
isMagnetic = &tmp;
|
||||||
|
if (!m_gpsTags.contains(GPS_IMGDIRECTION))
|
||||||
|
return qQNaN();
|
||||||
|
auto ref = gpsString(GPS_IMGDIRECTIONREF).toUpper();
|
||||||
|
*isMagnetic = (ref == QStringLiteral("M"));
|
||||||
|
return m_gpsTags.value(GPS_IMGDIRECTION).toDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroExif::setImageDirection(double degree, bool isMagnetic)
|
||||||
|
{
|
||||||
|
if (qIsNaN(degree)) {
|
||||||
|
m_gpsTags.remove(GPS_IMGDIRECTIONREF);
|
||||||
|
m_gpsTags.remove(GPS_IMGDIRECTION);
|
||||||
|
}
|
||||||
|
m_gpsTags.insert(GPS_IMGDIRECTIONREF, isMagnetic ? QStringLiteral("M") : QStringLiteral("T"));
|
||||||
|
m_gpsTags.insert(GPS_IMGDIRECTION, degree);
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder) const
|
QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder) const
|
||||||
{
|
{
|
||||||
QByteArray ba;
|
QByteArray ba;
|
||||||
@ -1064,7 +1103,7 @@ bool MicroExif::write(QIODevice *device, const QDataStream::ByteOrder &byteOrder
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MicroExif::toImageMetadata(QImage &targetImage, bool replaceExisting) const
|
void MicroExif::updateImageMetadata(QImage &targetImage, bool replaceExisting) const
|
||||||
{
|
{
|
||||||
// set TIFF strings
|
// set TIFF strings
|
||||||
for (auto &&p : tiffStrMap) {
|
for (auto &&p : tiffStrMap) {
|
||||||
@ -1112,6 +1151,19 @@ void MicroExif::toImageMetadata(QImage &targetImage, bool replaceExisting) const
|
|||||||
if (!qIsNaN(v))
|
if (!qIsNaN(v))
|
||||||
targetImage.setText(QStringLiteral(META_KEY_LONGITUDE), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
targetImage.setText(QStringLiteral(META_KEY_LONGITUDE), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
||||||
}
|
}
|
||||||
|
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_DIRECTION)).isEmpty()) {
|
||||||
|
auto v = imageDirection();
|
||||||
|
if (!qIsNaN(v))
|
||||||
|
targetImage.setText(QStringLiteral(META_KEY_DIRECTION), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MicroExif::updateImageResolution(QImage &targetImage)
|
||||||
|
{
|
||||||
|
if (horizontalResolution() > 0)
|
||||||
|
targetImage.setDotsPerMeterX(qRound(horizontalResolution() / 25.4 * 1000));
|
||||||
|
if (verticalResolution() > 0)
|
||||||
|
targetImage.setDotsPerMeterY(qRound(verticalResolution() / 25.4 * 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
MicroExif MicroExif::fromByteArray(const QByteArray &ba)
|
MicroExif MicroExif::fromByteArray(const QByteArray &ba)
|
||||||
@ -1215,6 +1267,9 @@ MicroExif MicroExif::fromImage(const QImage &image)
|
|||||||
auto lon = image.text(QStringLiteral(META_KEY_LONGITUDE)).toDouble(&ok);
|
auto lon = image.text(QStringLiteral(META_KEY_LONGITUDE)).toDouble(&ok);
|
||||||
if (ok)
|
if (ok)
|
||||||
exif.setLongitude(lon);
|
exif.setLongitude(lon);
|
||||||
|
auto dir = image.text(QStringLiteral(META_KEY_DIRECTION)).toDouble(&ok);
|
||||||
|
if (ok)
|
||||||
|
exif.setImageDirection(dir);
|
||||||
|
|
||||||
return exif;
|
return exif;
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,6 @@
|
|||||||
*
|
*
|
||||||
* This class is a partial (or rather minimal) implementation and is only used
|
* This class is a partial (or rather minimal) implementation and is only used
|
||||||
* to avoid including external libraries when only a few tags are needed.
|
* to avoid including external libraries when only a few tags are needed.
|
||||||
*
|
|
||||||
* It reads/writes the main IFD only.
|
|
||||||
*/
|
*/
|
||||||
class MicroExif
|
class MicroExif
|
||||||
{
|
{
|
||||||
@ -251,6 +249,14 @@ public:
|
|||||||
double altitude() const;
|
double altitude() const;
|
||||||
void setAltitude(double meters);
|
void setAltitude(double meters);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief imageDirection
|
||||||
|
* \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.
|
||||||
|
* \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.
|
||||||
|
*/
|
||||||
|
double imageDirection(bool *isMagnetic = nullptr) const;
|
||||||
|
void setImageDirection(double degree, bool isMagnetic = false);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief toByteArray
|
* \brief toByteArray
|
||||||
* Converts the class to RAW data. The raw data contains:
|
* Converts the class to RAW data. The raw data contains:
|
||||||
@ -309,12 +315,19 @@ public:
|
|||||||
bool write(QIODevice *device, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
|
bool write(QIODevice *device, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief toImageMetadata
|
* \brief updateImageMetadata
|
||||||
* Helper to set metadata in an image.
|
* Helper to set EXIF metadata to the image.
|
||||||
* \param targetImage The image to set metadata on.
|
* \param targetImage The image to set metadata on.
|
||||||
* \param replaceExisting Replaces any existing metadata.
|
* \param replaceExisting Replaces any existing metadata.
|
||||||
*/
|
*/
|
||||||
void toImageMetadata(QImage& targetImage, bool replaceExisting = false) const;
|
void updateImageMetadata(QImage &targetImage, bool replaceExisting = false) const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief updateImageResolution
|
||||||
|
* Helper to set the EXIF resolution to the image. Resolution is set only if valid.
|
||||||
|
* \param targetImage The image to set resolution on.
|
||||||
|
*/
|
||||||
|
void updateImageResolution(QImage &targetImage);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief fromByteArray
|
* \brief fromByteArray
|
||||||
|
@ -545,7 +545,7 @@ static bool setExifData(QImage &img, const MicroExif &exif)
|
|||||||
{
|
{
|
||||||
if (exif.isEmpty())
|
if (exif.isEmpty())
|
||||||
return false;
|
return false;
|
||||||
exif.toImageMetadata(img);
|
exif.updateImageMetadata(img);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#define META_KEY_COPYRIGHT "Copyright"
|
#define META_KEY_COPYRIGHT "Copyright"
|
||||||
#define META_KEY_CREATIONDATE "CreationDate"
|
#define META_KEY_CREATIONDATE "CreationDate"
|
||||||
#define META_KEY_DESCRIPTION "Description"
|
#define META_KEY_DESCRIPTION "Description"
|
||||||
|
#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_LATITUDE "Latitude"
|
#define META_KEY_LATITUDE "Latitude"
|
||||||
|
Loading…
Reference in New Issue
Block a user