mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-06-03 17:08:08 -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.
After Width: | Height: | Size: 18 KiB |
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)
|
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")
|
target_link_libraries(kimg_avif PRIVATE "avif")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include <QColorSpace>
|
#include <QColorSpace>
|
||||||
|
|
||||||
#include "avif_p.h"
|
#include "avif_p.h"
|
||||||
|
#include "microexif_p.h"
|
||||||
#include "util_p.h"
|
#include "util_p.h"
|
||||||
|
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
@ -151,9 +152,6 @@ bool QAVIFHandler::ensureDecoder()
|
|||||||
|
|
||||||
m_decoder = avifDecoderCreate();
|
m_decoder = avifDecoderCreate();
|
||||||
|
|
||||||
m_decoder->ignoreExif = AVIF_TRUE;
|
|
||||||
m_decoder->ignoreXMP = AVIF_TRUE;
|
|
||||||
|
|
||||||
#if AVIF_VERSION >= 80400
|
#if AVIF_VERSION >= 80400
|
||||||
m_decoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
|
m_decoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
|
||||||
#endif
|
#endif
|
||||||
@ -534,12 +532,54 @@ bool QAVIFHandler::decode_one_frame()
|
|||||||
|
|
||||||
m_current_image.setColorSpace(colorspace);
|
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_estimated_dimensions = m_current_image.size();
|
||||||
|
|
||||||
m_must_jump_to_next_image = false;
|
m_must_jump_to_next_image = false;
|
||||||
return true;
|
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)
|
bool QAVIFHandler::read(QImage *image)
|
||||||
{
|
{
|
||||||
if (!ensureOpened()) {
|
if (!ensureOpened()) {
|
||||||
@ -689,6 +729,8 @@ bool QAVIFHandler::write(const QImage &image)
|
|||||||
#else
|
#else
|
||||||
avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
|
avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
|
||||||
#endif
|
#endif
|
||||||
|
// set EXIF and XMP metadata
|
||||||
|
setMetadata(avif, tmpgrayimage);
|
||||||
|
|
||||||
if (tmpgrayimage.colorSpace().isValid()) {
|
if (tmpgrayimage.colorSpace().isValid()) {
|
||||||
avif->colorPrimaries = (avifColorPrimaries)1;
|
avif->colorPrimaries = (avifColorPrimaries)1;
|
||||||
@ -915,6 +957,9 @@ bool QAVIFHandler::write(const QImage &image)
|
|||||||
avif->colorPrimaries = primaries_to_save;
|
avif->colorPrimaries = primaries_to_save;
|
||||||
avif->transferCharacteristics = transfer_to_save;
|
avif->transferCharacteristics = transfer_to_save;
|
||||||
|
|
||||||
|
// set EXIF and XMP metadata
|
||||||
|
setMetadata(avif, tmpcolorimage);
|
||||||
|
|
||||||
if (iccprofile.size() > 0) {
|
if (iccprofile.size() > 0) {
|
||||||
#if AVIF_VERSION >= 1000000
|
#if AVIF_VERSION >= 1000000
|
||||||
res = avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
|
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);
|
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)
|
MicroExif MicroExif::fromDevice(QIODevice *device)
|
||||||
{
|
{
|
||||||
if (device == nullptr || device->isSequential())
|
if (device == nullptr || device->isSequential())
|
||||||
|
@ -269,6 +269,14 @@ public:
|
|||||||
*/
|
*/
|
||||||
static MicroExif fromByteArray(const QByteArray &ba);
|
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
|
* \brief fromDevice
|
||||||
* Creates the class from a device.
|
* Creates the class from a device.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user