diff --git a/autotests/read/heif/metadata.heif b/autotests/read/heif/metadata.heif new file mode 100644 index 0000000..df7bfb4 Binary files /dev/null and b/autotests/read/heif/metadata.heif differ diff --git a/autotests/read/heif/metadata.heif.json b/autotests/read/heif/metadata.heif.json new file mode 100644 index 0000000..8669721 --- /dev/null +++ b/autotests/read/heif/metadata.heif.json @@ -0,0 +1,59 @@ +[ + { + "fileName" : "metadata.png", + "metadata" : [ + { + "key" : "ModificationDate", + "value" : "2025-02-26T16:52:06Z" + }, + { + "key" : "Software" , + "value" : "LIFE Pro 2.18.10 (Linux)" + }, + { + "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 + } + } +] diff --git a/autotests/read/heif/metadata.png b/autotests/read/heif/metadata.png new file mode 100644 index 0000000..faa4d49 Binary files /dev/null and b/autotests/read/heif/metadata.png differ diff --git a/autotests/write/basic/heif.json b/autotests/write/basic/heif.json new file mode 100644 index 0000000..39de6e7 --- /dev/null +++ b/autotests/write/basic/heif.json @@ -0,0 +1,61 @@ +{ + "format" : "heif", + "metadata" : [ + { + "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)" + }, + { + "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 + } +} diff --git a/src/imageformats/CMakeLists.txt b/src/imageformats/CMakeLists.txt index 58b4a6b..ded7d5a 100644 --- a/src/imageformats/CMakeLists.txt +++ b/src/imageformats/CMakeLists.txt @@ -68,7 +68,7 @@ kimageformats_add_plugin(kimg_hdr SOURCES hdr.cpp) ################################## if (LibHeif_FOUND) - kimageformats_add_plugin(kimg_heif SOURCES heif.cpp) + kimageformats_add_plugin(kimg_heif SOURCES heif.cpp microexif.cpp) target_link_libraries(kimg_heif PRIVATE PkgConfig::LibHeif) endif() diff --git a/src/imageformats/heif.cpp b/src/imageformats/heif.cpp index 029ae13..c735e01 100644 --- a/src/imageformats/heif.cpp +++ b/src/imageformats/heif.cpp @@ -8,6 +8,7 @@ */ #include "heif_p.h" +#include "microexif_p.h" #include "util_p.h" #include @@ -18,6 +19,14 @@ #include #include +#ifndef HEIF_MAX_METADATA_SIZE +/*! + * XMP and EXIF maximum size. + */ +#define HEIF_MAX_METADATA_SIZE (4 * 1024 * 1024) +#endif + + size_t HEIFHandler::m_initialized_count = 0; bool HEIFHandler::m_plugins_queried = false; bool HEIFHandler::m_heif_decoder_available = false; @@ -305,7 +314,25 @@ bool HEIFHandler::write_helper(const QImage &image) } } - err = heif_context_encode_image(context, h_image, encoder, encoder_options, nullptr); + struct heif_image_handle* handle; + err = heif_context_encode_image(context, h_image, encoder, encoder_options, &handle); + + // exif metadata + if (err.code == heif_error_Ok) { + auto exif = MicroExif::fromImage(tmpimage); + if (!exif.isEmpty()) { + auto ba = exif.toByteArray(); + err = heif_context_add_exif_metadata(context, handle, ba.constData(), ba.size()); + } + } + // xmp metadata + if (err.code == heif_error_Ok) { + auto xmp = image.text(META_KEY_XMP_ADOBE); + if (!xmp.isEmpty()) { + auto ba = xmp.toUtf8(); + err = heif_context_add_XMP_metadata2(context, handle, ba.constData(), ba.size(), heif_metadata_compression_off); + } + } if (encoder_options) { heif_encoding_options_free(encoder_options); @@ -904,6 +931,43 @@ bool HEIFHandler::ensureDecoder() m_current_image.setColorSpace(QColorSpace(QColorSpace::SRgb)); } + // read metadata + if (auto numMetadata = heif_image_handle_get_number_of_metadata_blocks(handle, nullptr)) { + QVector ids(numMetadata); + heif_image_handle_get_list_of_metadata_block_IDs(handle, nullptr, ids.data(), numMetadata); + for (int n = 0; n < numMetadata; ++n) { + auto itemtype = heif_image_handle_get_metadata_type(handle, ids[n]); + auto contenttype = heif_image_handle_get_metadata_content_type(handle, ids[n]); + auto isExif = !std::strcmp(itemtype, "Exif"); + auto isXmp = !std::strcmp(contenttype, "application/rdf+xml"); + if (isExif || isXmp) { + auto sz = heif_image_handle_get_metadata_size(handle, ids[n]); + if (sz == 0 || sz >= HEIF_MAX_METADATA_SIZE) + continue; + QByteArray ba(sz, char()); + auto err = heif_image_handle_get_metadata(handle, ids[n], ba.data()); + if (err.code != heif_error_Ok) { + qWarning() << "Error while reading metadata" << err.message; + continue; + } + if (isXmp) { + m_current_image.setText(META_KEY_XMP_ADOBE, QString::fromUtf8(ba)); + } else if (isExif) { + auto exif = MicroExif::fromByteArray(ba.mid(4)); + if (!exif.isEmpty()) { + // 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); + } + } + } + } + } + heif_image_release(img); heif_image_handle_release(handle); heif_context_free(ctx);