mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-05-28 00:30:23 -04:00
AVIF: added support to XMP and EXIF metadata
Allow to load/save info about: - GPS info (latitude, longitude, altitude) - Various text info (title, description, author, copyright, etc...) - Image resolution The compatibility of the modifications has been tested with GIMP.
This commit is contained in:
parent
bb1c6aab9e
commit
90d4256f3d
BIN
autotests/read/avif/metadata.avif
Normal file
BIN
autotests/read/avif/metadata.avif
Normal file
Binary file not shown.
59
autotests/read/avif/metadata.avif.json
Normal file
59
autotests/read/avif/metadata.avif.json
Normal file
@ -0,0 +1,59 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "metadata.png",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "CreationDate",
|
||||
"value" : "2025-02-19T08:27:22+01:00"
|
||||
},
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "GIMP 3.0.0-RC3"
|
||||
},
|
||||
{
|
||||
"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" : 5905
|
||||
}
|
||||
}
|
||||
]
|
BIN
autotests/read/avif/metadata.png
Normal file
BIN
autotests/read/avif/metadata.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
57
autotests/write/basic/avif.json
Normal file
57
autotests/write/basic/avif.json
Normal file
@ -0,0 +1,57 @@
|
||||
{
|
||||
"format" : "avif",
|
||||
"metadata" : [
|
||||
{
|
||||
"key" : "CreationDate",
|
||||
"value" : "2025-01-14T13:53:32+01:00"
|
||||
},
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "Adobe Photoshop 26.2 (Windows)"
|
||||
},
|
||||
{
|
||||
"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" : 11812
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ kimageformats_add_plugin(kimg_ani SOURCES ani.cpp)
|
||||
##################################
|
||||
|
||||
if (TARGET avif)
|
||||
kimageformats_add_plugin(kimg_avif SOURCES "avif.cpp")
|
||||
kimageformats_add_plugin(kimg_avif SOURCES avif.cpp microexif.cpp)
|
||||
target_link_libraries(kimg_avif PRIVATE "avif")
|
||||
endif()
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <QColorSpace>
|
||||
|
||||
#include "avif_p.h"
|
||||
#include "microexif_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <cfloat>
|
||||
@ -151,9 +152,6 @@ bool QAVIFHandler::ensureDecoder()
|
||||
|
||||
m_decoder = avifDecoderCreate();
|
||||
|
||||
m_decoder->ignoreExif = AVIF_TRUE;
|
||||
m_decoder->ignoreXMP = AVIF_TRUE;
|
||||
|
||||
#if AVIF_VERSION >= 80400
|
||||
m_decoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
|
||||
#endif
|
||||
@ -534,12 +532,54 @@ bool QAVIFHandler::decode_one_frame()
|
||||
|
||||
m_current_image.setColorSpace(colorspace);
|
||||
|
||||
if (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
|
||||
if (exif.horizontalResolution() > 0)
|
||||
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) {
|
||||
auto ba = QByteArray::fromRawData(reinterpret_cast<const char *>(m_decoder->image->xmp.data), m_decoder->image->xmp.size);
|
||||
m_current_image.setText(QStringLiteral(META_KEY_XMP_ADOBE), QString::fromUtf8(ba));
|
||||
}
|
||||
|
||||
m_estimated_dimensions = m_current_image.size();
|
||||
|
||||
m_must_jump_to_next_image = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void setMetadata(avifImage *avif, const QImage& image)
|
||||
{
|
||||
auto xmp = image.text(QStringLiteral(META_KEY_XMP_ADOBE)).toUtf8();
|
||||
if (!xmp.isEmpty()) {
|
||||
#if AVIF_VERSION >= 1000000
|
||||
auto res = avifImageSetMetadataXMP(avif, reinterpret_cast<const uint8_t *>(xmp.constData()), xmp.size());
|
||||
if (res != AVIF_RESULT_OK) {
|
||||
qWarning("ERROR in avifImageSetMetadataXMP: %s", avifResultToString(res));
|
||||
}
|
||||
#else
|
||||
avifImageSetMetadataXMP(avif, reinterpret_cast<const uint8_t *>(xmp.constData()), xmp.size());
|
||||
#endif
|
||||
}
|
||||
auto exif = MicroExif::fromImage(image).toByteArray();
|
||||
if (!exif.isEmpty()) {
|
||||
#if AVIF_VERSION >= 1000000
|
||||
auto res = avifImageSetMetadataExif(avif, reinterpret_cast<const uint8_t *>(exif.constData()), exif.size());
|
||||
if (res != AVIF_RESULT_OK) {
|
||||
qWarning("ERROR in avifImageSetMetadataExif: %s", avifResultToString(res));
|
||||
}
|
||||
#else
|
||||
avifImageSetMetadataExif(avif, reinterpret_cast<const uint8_t *>(exif.constData()), exif.size());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool QAVIFHandler::read(QImage *image)
|
||||
{
|
||||
if (!ensureOpened()) {
|
||||
@ -689,6 +729,8 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
#else
|
||||
avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
|
||||
#endif
|
||||
// set EXIF and XMP metadata
|
||||
setMetadata(avif, tmpgrayimage);
|
||||
|
||||
if (tmpgrayimage.colorSpace().isValid()) {
|
||||
avif->colorPrimaries = (avifColorPrimaries)1;
|
||||
@ -915,6 +957,9 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
avif->colorPrimaries = primaries_to_save;
|
||||
avif->transferCharacteristics = transfer_to_save;
|
||||
|
||||
// set EXIF and XMP metadata
|
||||
setMetadata(avif, tmpcolorimage);
|
||||
|
||||
if (iccprofile.size() > 0) {
|
||||
#if AVIF_VERSION >= 1000000
|
||||
res = avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
|
||||
|
@ -1024,6 +1024,13 @@ MicroExif MicroExif::fromByteArray(const QByteArray &ba)
|
||||
return fromDevice(&buf);
|
||||
}
|
||||
|
||||
MicroExif MicroExif::fromRawData(const char *data, size_t size)
|
||||
{
|
||||
if (data == nullptr || size == 0)
|
||||
return {};
|
||||
return fromByteArray(QByteArray::fromRawData(data, size));
|
||||
}
|
||||
|
||||
MicroExif MicroExif::fromDevice(QIODevice *device)
|
||||
{
|
||||
if (device == nullptr || device->isSequential())
|
||||
|
@ -269,6 +269,14 @@ public:
|
||||
*/
|
||||
static MicroExif fromByteArray(const QByteArray &ba);
|
||||
|
||||
/*!
|
||||
* \brief fromRawData
|
||||
* Creates the class from RAW EXIF data.
|
||||
* \return The created class (empty on error).
|
||||
* \sa isEmpty
|
||||
*/
|
||||
static MicroExif fromRawData(const char *data, size_t size);
|
||||
|
||||
/*!
|
||||
* \brief fromDevice
|
||||
* Creates the class from a device.
|
||||
|
Loading…
Reference in New Issue
Block a user