Compare commits

..

1 Commits

Author SHA1 Message Date
a33446f86a Use of heif_context_add_XMP_metadata instead
heif_context_add_XMP_metadata2

(cherry picked from commit 245c835d92)
2025-03-11 11:32:12 +01:00
55 changed files with 66 additions and 291 deletions

View File

@ -7,5 +7,5 @@ Dependencies:
Options: Options:
test-before-installing: True test-before-installing: True
require-passing-tests-on: ['Linux', 'FreeBSD', 'Windows'] require-passing-tests-on: ['Linux', 'FreeBSD', 'Windows']
cmake-options: "-DKIMAGEFORMATS_DDS=ON -DKIMAGEFORMATS_JXR=ON -DKIMAGEFORMATS_HEIF=ON" cmake-options: "-DKIMAGEFORMATS_DDS=ON -DKIMAGEFORMATS_JXR=ON"
per-test-timeout: 90 per-test-timeout: 90

View File

@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
set(KF_VERSION "6.14.0") # handled by release scripts set(KF_VERSION "6.12.0") # handled by release scripts
set(KF_DEP_VERSION "6.14.0") # handled by release scripts set(KF_DEP_VERSION "6.12.0") # handled by release scripts
project(KImageFormats VERSION ${KF_VERSION}) project(KImageFormats VERSION ${KF_VERSION})
include(FeatureSummary) include(FeatureSummary)
find_package(ECM 6.14.0 NO_MODULE) find_package(ECM 6.12.0 NO_MODULE)
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules") set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
@ -21,7 +21,7 @@ include(ECMDeprecationSettings)
include(CheckIncludeFiles) include(CheckIncludeFiles)
include(FindPkgConfig) include(FindPkgConfig)
set(REQUIRED_QT_VERSION 6.7.0) set(REQUIRED_QT_VERSION 6.6.0)
find_package(Qt6Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) find_package(Qt6Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
find_package(KF6Archive ${KF_DEP_VERSION}) find_package(KF6Archive ${KF_DEP_VERSION})
@ -99,8 +99,8 @@ endif()
add_feature_info(LibJXR LibJXR_FOUND "required for the QImage plugin for JPEG XR images") add_feature_info(LibJXR LibJXR_FOUND "required for the QImage plugin for JPEG XR images")
ecm_set_disabled_deprecation_versions( ecm_set_disabled_deprecation_versions(
QT 6.9.0 QT 6.8.0
KF 6.12.0 KF 6.11.0
) )
add_subdirectory(src) add_subdirectory(src)

View File

@ -122,12 +122,9 @@ if (LibHeif_FOUND)
kimageformats_read_tests(FUZZ 1 kimageformats_read_tests(FUZZ 1
hej2 hej2
) )
kimageformats_write_tests(FUZZ 1
hej2-nodatacheck-lossless
)
endif() endif()
if (LibHeif_VERSION VERSION_GREATER_EQUAL "1.19.6") if (LibHeif_VERSION VERSION_GREATER_EQUAL "1.19.0")
kimageformats_read_tests(FUZZ 4 kimageformats_read_tests(FUZZ 4
avci avci
) )

View File

@ -258,15 +258,6 @@ int main(int argc, char **argv)
}); });
QTextStream(stdout) << "QImageReader::supportedImageFormats: " << formatStrings.join(", ") << "\n"; QTextStream(stdout) << "QImageReader::supportedImageFormats: " << formatStrings.join(", ") << "\n";
if (!formats.contains(format)) {
if (format == "avci" || format == "heif" || format == "hej2") {
QTextStream(stdout) << "WARNING : " << suffix << " is not supported with current libheif configuration!\n"
<< "********* "
<< "Finished basic read tests for " << suffix << " images *********\n";
return 0;
}
}
const QFileInfoList lstImgDir = imgdir.entryInfoList(); const QFileInfoList lstImgDir = imgdir.entryInfoList();
// Launch 2 runs for each test: first run on a random access device, second run on a sequential access device // Launch 2 runs for each test: first run on a random access device, second run on a sequential access device
for (int seq = 0; seq < 2; ++seq) { for (int seq = 0; seq < 2; ++seq) {
@ -332,12 +323,7 @@ int main(int argc, char **argv)
OptionTest optionTest; OptionTest optionTest;
if (!optionTest.store(&inputReader)) { if (!optionTest.store(&inputReader)) {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": error while reading options\n"; QTextStream(stdout) << "FAIL : " << fi.fileName() << ": error while reading options\n";
if (format == "heif") { ++failed;
// libheif + ffmpeg decoder is unable to load all HEIF files.
++skipped;
} else {
++failed;
}
continue; continue;
} }

View File

@ -31,7 +31,7 @@
}, },
{ {
"key" : "Description", "key" : "Description",
"value" : "テレビ放送テスト映像。(TV broadcast test image.)" "value" : "TV broadcast test image."
}, },
{ {
"key" : "Latitude", "key" : "Latitude",

View File

@ -31,7 +31,7 @@
}, },
{ {
"key" : "Description", "key" : "Description",
"value" : "テレビ放送テスト映像。(TV broadcast test image.)" "value" : "TV broadcast test image."
}, },
{ {
"key" : "Latitude", "key" : "Latitude",

View File

@ -1,65 +0,0 @@
{
"format" : "hej2",
"metadata" : [
{
"key" : "CreationDate",
"value" : "2025-01-14T13:53:32+01:00"
},
{
"key" : "Direction",
"value" : "123.7"
},
{
"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
}
}

View File

@ -31,7 +31,7 @@
}, },
{ {
"key" : "Description", "key" : "Description",
"value" : "テレビ放送テスト映像。(TV broadcast test image.)" "value" : "TV broadcast test image."
}, },
{ {
"key" : "Latitude", "key" : "Latitude",

View File

@ -370,12 +370,7 @@ int formatTest(const QString &suffix, bool createTemplates)
QBuffer buffer(&ba); QBuffer buffer(&ba);
auto writtenImage = QImageReader(&buffer, suffix.toLatin1()).read(); auto writtenImage = QImageReader(&buffer, suffix.toLatin1()).read();
if (writtenImage.isNull()) { if (writtenImage.isNull()) {
if (suffix.toLatin1() == "heif") { ++failed;
// libheif + ffmpeg decoder is unable to load all HEIF files.
++skipped;
} else {
++failed;
}
QTextStream(stdout) << "FAIL : error while reading the image " << formatName << "\n"; QTextStream(stdout) << "FAIL : error while reading the image " << formatName << "\n";
continue; continue;
} }
@ -627,28 +622,8 @@ int main(int argc, char **argv)
} }
} }
auto suffix = args.at(0);
// skip test if libheif configuration is obviously incomplete
QByteArray format = suffix.toLatin1();
const QList<QByteArray> read_formats = QImageReader::supportedImageFormats();
const QList<QByteArray> write_formats = QImageWriter::supportedImageFormats();
if (!read_formats.contains(format)) {
if (format == "heif" || format == "hej2") {
QTextStream(stdout) << "WARNING : libheif configuration is missing necessary decoder(s)!\n";
return 0;
}
}
if (!write_formats.contains(format)) {
if (format == "heif" || format == "hej2") {
QTextStream(stdout) << "WARNING : libheif configuration is missing necessary encoder(s)!\n";
return 0;
}
}
// run test // run test
auto suffix = args.at(0);
auto ret = basicTest(suffix, parser.isSet(lossless), parser.isSet(ignoreDataCheck), parser.isSet(skipOptTest), fuzzarg); auto ret = basicTest(suffix, parser.isSet(lossless), parser.isSet(ignoreDataCheck), parser.isSet(skipOptTest), fuzzarg);
if (ret == 0) { if (ret == 0) {
ret = formatTest(suffix, parser.isSet(createFormatTempates)); ret = formatTest(suffix, parser.isSet(createFormatTempates));

View File

@ -15,16 +15,6 @@ function(kimageformats_add_plugin plugin)
target_sources(${plugin} PRIVATE ${KIF_ADD_PLUGIN_SOURCES}) target_sources(${plugin} PRIVATE ${KIF_ADD_PLUGIN_SOURCES})
set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/imageformats) set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/imageformats)
target_link_libraries(${plugin} PRIVATE Qt6::Gui) target_link_libraries(${plugin} PRIVATE Qt6::Gui)
if(ANDROID)
# Plugins should be named with lib prefix on Android
# Working name: libplugins_imageformats_kimg_avif_armeabi-v7a.so
# Doesn't work: plugins_imageformats_kimg_avif_armeabi-v7a.so
if(NOT ${CMAKE_SHARED_LIBRARY_PREFIX} STREQUAL "")
set_target_properties(${plugin} PROPERTIES PREFIX ${CMAKE_SHARED_LIBRARY_PREFIX})
endif()
endif()
install(TARGETS ${plugin} DESTINATION ${KDE_INSTALL_QTPLUGINDIR}/imageformats) install(TARGETS ${plugin} DESTINATION ${KDE_INSTALL_QTPLUGINDIR}/imageformats)
endfunction() endfunction()

View File

@ -2557,5 +2557,3 @@ QImageIOHandler *QDDSPlugin::create(QIODevice *device, const QByteArray &format)
handler->setFormat(format); handler->setFormat(format);
return handler; return handler;
} }
#include "moc_dds_p.cpp"

View File

@ -16,8 +16,8 @@
#include <QDebug> #include <QDebug>
#include <QPointF> #include <QPointF>
#include <QSysInfo> #include <QSysInfo>
#include <cstring>
#include <limits> #include <limits>
#include <string.h>
#ifndef HEIF_MAX_METADATA_SIZE #ifndef HEIF_MAX_METADATA_SIZE
/*! /*!
@ -26,12 +26,12 @@
#define HEIF_MAX_METADATA_SIZE (4 * 1024 * 1024) #define HEIF_MAX_METADATA_SIZE (4 * 1024 * 1024)
#endif #endif
size_t HEIFHandler::m_initialized_count = 0; size_t HEIFHandler::m_initialized_count = 0;
bool HEIFHandler::m_plugins_queried = false; bool HEIFHandler::m_plugins_queried = false;
bool HEIFHandler::m_heif_decoder_available = false; bool HEIFHandler::m_heif_decoder_available = false;
bool HEIFHandler::m_heif_encoder_available = false; bool HEIFHandler::m_heif_encoder_available = false;
bool HEIFHandler::m_hej2_decoder_available = false; bool HEIFHandler::m_hej2_decoder_available = false;
bool HEIFHandler::m_hej2_encoder_available = false;
bool HEIFHandler::m_avci_decoder_available = false; bool HEIFHandler::m_avci_decoder_available = false;
extern "C" { extern "C" {
@ -155,14 +155,6 @@ bool HEIFHandler::write_helper(const QImage &image)
break; break;
} }
heif_compression_format encoder_codec = heif_compression_HEVC;
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
if (format() == "hej2") {
encoder_codec = heif_compression_JPEG2000;
save_depth = 8; // for compatibility reasons
}
#endif
heif_chroma chroma; heif_chroma chroma;
if (save_depth > 8) { if (save_depth > 8) {
if (save_alpha) { if (save_alpha) {
@ -295,7 +287,7 @@ bool HEIFHandler::write_helper(const QImage &image)
} }
struct heif_encoder *encoder = nullptr; struct heif_encoder *encoder = nullptr;
err = heif_context_get_encoder_for_format(context, encoder_codec, &encoder); err = heif_context_get_encoder_for_format(context, heif_compression_HEVC, &encoder);
if (err.code) { if (err.code) {
qWarning() << "Unable to get an encoder instance:" << err.message; qWarning() << "Unable to get an encoder instance:" << err.message;
heif_image_release(h_image); heif_image_release(h_image);
@ -322,7 +314,7 @@ bool HEIFHandler::write_helper(const QImage &image)
} }
} }
struct heif_image_handle *handle; struct heif_image_handle* handle;
err = heif_context_encode_image(context, h_image, encoder, encoder_options, &handle); err = heif_context_encode_image(context, h_image, encoder, encoder_options, &handle);
// exif metadata // exif metadata
@ -562,7 +554,7 @@ bool HEIFHandler::ensureDecoder()
QImage::Format target_image_format; QImage::Format target_image_format;
if (bit_depth == 10 || bit_depth == 12 || bit_depth == 16) { if (bit_depth == 10 || bit_depth == 12) {
if (hasAlphaChannel) { if (hasAlphaChannel) {
chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE; chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE;
target_image_format = QImage::Format_RGBA64; target_image_format = QImage::Format_RGBA64;
@ -654,35 +646,6 @@ bool HEIFHandler::ensureDecoder()
} }
switch (bit_depth) { switch (bit_depth) {
case 16:
if (hasAlphaChannel) {
for (int y = 0; y < imageHeight; y++) {
memcpy(m_current_image.scanLine(y), src + (y * stride), 8 * size_t(imageWidth));
}
} else { // no alpha channel
for (int y = 0; y < imageHeight; y++) {
const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride));
uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y));
for (int x = 0; x < imageWidth; x++) {
// R
*dest_data = *src_word;
src_word++;
dest_data++;
// G
*dest_data = *src_word;
src_word++;
dest_data++;
// B
*dest_data = *src_word;
src_word++;
dest_data++;
// X = 0xffff
*dest_data = 0xffff;
dest_data++;
}
}
}
break;
case 12: case 12:
if (hasAlphaChannel) { if (hasAlphaChannel) {
for (int y = 0; y < imageHeight; y++) { for (int y = 0; y < imageHeight; y++) {
@ -1028,13 +991,6 @@ bool HEIFHandler::isHej2DecoderAvailable()
return m_hej2_decoder_available; return m_hej2_decoder_available;
} }
bool HEIFHandler::isHej2EncoderAvailable()
{
HEIFHandler::queryHeifLib();
return m_hej2_encoder_available;
}
bool HEIFHandler::isAVCIDecoderAvailable() bool HEIFHandler::isAVCIDecoderAvailable()
{ {
HEIFHandler::queryHeifLib(); HEIFHandler::queryHeifLib();
@ -1057,9 +1013,8 @@ void HEIFHandler::queryHeifLib()
m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC); m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
#if LIBHEIF_HAVE_VERSION(1, 13, 0) #if LIBHEIF_HAVE_VERSION(1, 13, 0)
m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000); m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000);
m_hej2_encoder_available = heif_have_encoder_for_format(heif_compression_JPEG2000);
#endif #endif
#if LIBHEIF_HAVE_VERSION(1, 19, 6) #if LIBHEIF_HAVE_VERSION(1, 19, 0)
m_avci_decoder_available = heif_have_decoder_for_format(heif_compression_AVC); m_avci_decoder_available = heif_have_decoder_for_format(heif_compression_AVC);
#endif #endif
m_plugins_queried = true; m_plugins_queried = true;
@ -1126,9 +1081,6 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
if (HEIFHandler::isHej2DecoderAvailable()) { if (HEIFHandler::isHej2DecoderAvailable()) {
format_cap |= CanRead; format_cap |= CanRead;
} }
if (HEIFHandler::isHej2EncoderAvailable()) {
format_cap |= CanWrite;
}
return format_cap; return format_cap;
} }
@ -1158,7 +1110,7 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
} }
} }
if (device->isWritable() && (HEIFHandler::isHeifEncoderAvailable() || HEIFHandler::isHej2EncoderAvailable())) { if (device->isWritable() && HEIFHandler::isHeifEncoderAvailable()) {
cap |= CanWrite; cap |= CanWrite;
} }
return cap; return cap;

View File

@ -31,7 +31,6 @@ public:
static bool isHeifDecoderAvailable(); static bool isHeifDecoderAvailable();
static bool isHeifEncoderAvailable(); static bool isHeifEncoderAvailable();
static bool isHej2DecoderAvailable(); static bool isHej2DecoderAvailable();
static bool isHej2EncoderAvailable();
static bool isAVCIDecoderAvailable(); static bool isAVCIDecoderAvailable();
static bool isSupportedBMFFType(const QByteArray &header); static bool isSupportedBMFFType(const QByteArray &header);
@ -63,7 +62,6 @@ private:
static bool m_heif_decoder_available; static bool m_heif_decoder_available;
static bool m_heif_encoder_available; static bool m_heif_encoder_available;
static bool m_hej2_decoder_available; static bool m_hej2_decoder_available;
static bool m_hej2_encoder_available;
static bool m_avci_decoder_available; static bool m_avci_decoder_available;
static QMutex &getHEIFHandlerMutex(); static QMutex &getHEIFHandlerMutex();

View File

@ -20,7 +20,7 @@
#include <string.h> #include <string.h>
// Avoid rotation on buggy Qts (see also https://bugreports.qt.io/browse/QTBUG-126575) // Avoid rotation on buggy Qts (see also https://bugreports.qt.io/browse/QTBUG-126575)
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 3) #if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 7) && QT_VERSION < QT_VERSION_CHECK(6, 6, 0)) || (QT_VERSION >= QT_VERSION_CHECK(6, 7, 3))
#ifndef JXL_QT_AUTOTRANSFORM #ifndef JXL_QT_AUTOTRANSFORM
#define JXL_QT_AUTOTRANSFORM #define JXL_QT_AUTOTRANSFORM
#endif #endif

View File

@ -720,11 +720,11 @@ public:
auto exif = MicroExif::fromImage(image); auto exif = MicroExif::fromImage(image);
if (!exif.isEmpty()) { if (!exif.isEmpty()) {
auto exifIfd = exif.exifIfdByteArray(QDataStream::LittleEndian, MicroExif::V2); auto exifIfd = exif.exifIfdByteArray(QDataStream::LittleEndian);
if (auto err = PKImageEncode_SetEXIFMetadata_WMP(pEncoder, reinterpret_cast<const quint8 *>(exifIfd.constData()), exifIfd.size())) { 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; qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting EXIF data:" << err;
} }
auto gpsIfd = exif.gpsIfdByteArray(QDataStream::LittleEndian, MicroExif::V2); auto gpsIfd = exif.gpsIfdByteArray(QDataStream::LittleEndian);
if (auto err = PKImageEncode_SetGPSInfoMetadata_WMP(pEncoder, reinterpret_cast<const quint8 *>(gpsIfd.constData()), gpsIfd.size())) { 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; qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting GPS data:" << err;
} }
@ -991,7 +991,7 @@ bool JXRHandler::read(QImage *outImage)
return false; return false;
} }
for (qint32 y = 0, h = img.height(); y < h; ++y) { for (qint32 y = 0, h = img.height(); y < h; ++y) {
std::memcpy(img.scanLine(y), ba.data() + convStrideSize * y, (std::min)(convStrideSize, qint64(img.bytesPerLine()))); std::memcpy(img.scanLine(y), ba.data() + convStrideSize * y, (std::min)(convStrideSize, img.bytesPerLine()));
} }
} }
PKFormatConverter_Release(&pConverter); PKFormatConverter_Release(&pConverter);

View File

@ -12,7 +12,6 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QDataStream> #include <QDataStream>
#include <QHash> #include <QHash>
#include <QStringDecoder>
#include <QTimeZone> #include <QTimeZone>
// TIFF 6 specs // TIFF 6 specs
@ -112,17 +111,17 @@ static const KnownTags staticTagTypes = {
TagInfo(TIFF_IMAGEWIDTH, ExifTagType::Long), TagInfo(TIFF_IMAGEWIDTH, ExifTagType::Long),
TagInfo(TIFF_IMAGEHEIGHT, ExifTagType::Long), TagInfo(TIFF_IMAGEHEIGHT, ExifTagType::Long),
TagInfo(TIFF_BITSPERSAMPLE, ExifTagType::Short), TagInfo(TIFF_BITSPERSAMPLE, ExifTagType::Short),
TagInfo(TIFF_IMAGEDESCRIPTION, ExifTagType::Utf8), TagInfo(TIFF_IMAGEDESCRIPTION, ExifTagType::Ascii),
TagInfo(TIFF_MAKE, ExifTagType::Utf8), TagInfo(TIFF_MAKE, ExifTagType::Ascii),
TagInfo(TIFF_MODEL, ExifTagType::Utf8), TagInfo(TIFF_MODEL, ExifTagType::Ascii),
TagInfo(TIFF_ORIENT, ExifTagType::Short), TagInfo(TIFF_ORIENT, ExifTagType::Short),
TagInfo(TIFF_XRES, ExifTagType::Rational), TagInfo(TIFF_XRES, ExifTagType::Rational),
TagInfo(TIFF_YRES, ExifTagType::Rational), TagInfo(TIFF_YRES, ExifTagType::Rational),
TagInfo(TIFF_URES, ExifTagType::Short), TagInfo(TIFF_URES, ExifTagType::Short),
TagInfo(TIFF_SOFTWARE, ExifTagType::Utf8), TagInfo(TIFF_SOFTWARE, ExifTagType::Ascii),
TagInfo(TIFF_ARTIST, ExifTagType::Utf8), TagInfo(TIFF_ARTIST, ExifTagType::Ascii),
TagInfo(TIFF_DATETIME, ExifTagType::Ascii), TagInfo(TIFF_DATETIME, ExifTagType::Ascii),
TagInfo(TIFF_COPYRIGHT, ExifTagType::Utf8), TagInfo(TIFF_COPYRIGHT, ExifTagType::Ascii),
TagInfo(EXIF_EXIFIFD, ExifTagType::Long), TagInfo(EXIF_EXIFIFD, ExifTagType::Long),
TagInfo(EXIF_GPSIFD, ExifTagType::Long), TagInfo(EXIF_GPSIFD, ExifTagType::Long),
TagInfo(EXIF_DATETIMEORIGINAL, ExifTagType::Ascii), TagInfo(EXIF_DATETIMEORIGINAL, ExifTagType::Ascii),
@ -135,10 +134,10 @@ static const KnownTags staticTagTypes = {
TagInfo(EXIF_PIXELYDIM, ExifTagType::Long), TagInfo(EXIF_PIXELYDIM, ExifTagType::Long),
TagInfo(EXIF_IMAGEUNIQUEID, ExifTagType::Ascii), TagInfo(EXIF_IMAGEUNIQUEID, ExifTagType::Ascii),
TagInfo(EXIF_BODYSERIALNUMBER, ExifTagType::Ascii), TagInfo(EXIF_BODYSERIALNUMBER, ExifTagType::Ascii),
TagInfo(EXIF_LENSMAKE, ExifTagType::Utf8), TagInfo(EXIF_LENSMAKE, ExifTagType::Ascii),
TagInfo(EXIF_LENSMODEL, ExifTagType::Utf8), TagInfo(EXIF_LENSMODEL, ExifTagType::Ascii),
TagInfo(EXIF_LENSSERIALNUMBER, ExifTagType::Ascii), TagInfo(EXIF_LENSSERIALNUMBER, ExifTagType::Ascii),
TagInfo(EXIF_IMAGETITLE, ExifTagType::Utf8), TagInfo(EXIF_IMAGETITLE, ExifTagType::Ascii),
TagInfo(EXIF_EXIFVERSION, ExifTagType::Undefined) TagInfo(EXIF_EXIFVERSION, ExifTagType::Undefined)
}; };
// clang-format on // clang-format on
@ -368,28 +367,6 @@ static void writeData(QDataStream &ds, const QVariant &value, const ExifTagType&
} }
} }
static ExifTagType updateDataType(const ExifTagType &dataType, const QVariant &value, const MicroExif::Version &ver)
{
if (dataType != ExifTagType::Utf8)
return dataType;
if (ver == MicroExif::V2)
return ExifTagType::Ascii;
// Note that in EXIF specs, UTF-8 is backward compatible with ASCII: all UTF-8 tags can also be ASCII.
// To maximize compatibility, I check if the string can be encoded in ASCII.
auto txt = value.toString();
// Exif ASCII data type allow only values up to 127 (7-bit ASCII).
auto u8 = txt.toUtf8();
for (auto &&c : u8) {
if (uchar(c) > 127)
return dataType;
}
return ExifTagType::Ascii;
}
/*! /*!
* \brief writeIfd * \brief writeIfd
* \param ds The stream. * \param ds The stream.
@ -398,12 +375,7 @@ static ExifTagType updateDataType(const ExifTagType &dataType, const QVariant &v
* \param knownTags List of known and supported tags. * \param knownTags List of known and supported tags.
* \return True on success, otherwise false. * \return True on success, otherwise false.
*/ */
static bool writeIfd(QDataStream &ds, static bool writeIfd(QDataStream &ds, const MicroExif::Tags &tags, TagPos &positions, quint32 pos = 0, const KnownTags &knownTags = staticTagTypes)
const MicroExif::Version &ver,
const MicroExif::Tags &tags,
TagPos &positions,
quint32 pos = 0,
const KnownTags &knownTags = staticTagTypes)
{ {
if (tags.isEmpty()) if (tags.isEmpty())
return true; return true;
@ -418,7 +390,7 @@ static bool writeIfd(QDataStream &ds,
continue; continue;
} }
auto value = tags.value(key); auto value = tags.value(key);
auto dataType = updateDataType(knownTags.value(key), value, ver); auto dataType = knownTags.value(key);
auto count = countBytes(dataType, value); auto count = countBytes(dataType, value);
ds << quint16(key); ds << quint16(key);
@ -440,8 +412,9 @@ static bool writeIfd(QDataStream &ds,
if (!knownTags.contains(key)) { if (!knownTags.contains(key)) {
continue; continue;
} }
auto value = tags.value(key); auto value = tags.value(key);
auto dataType = updateDataType(knownTags.value(key), value, ver); auto dataType = knownTags.value(key);
auto count = countBytes(dataType, value); auto count = countBytes(dataType, value);
auto valueSize = count * EXIF_TAG_SIZEOF(dataType); auto valueSize = count * EXIF_TAG_SIZEOF(dataType);
if (valueSize <= 4) if (valueSize <= 4)
@ -561,16 +534,8 @@ static bool readIfd(QDataStream &ds, MicroExif::Tags &tags, quint32 pos = 0, con
if (dataType == EXIF_TAG_DATATYPE(ExifTagType::Ascii) || dataType == EXIF_TAG_DATATYPE(ExifTagType::Utf8)) { if (dataType == EXIF_TAG_DATATYPE(ExifTagType::Ascii) || dataType == EXIF_TAG_DATATYPE(ExifTagType::Utf8)) {
auto l = readBytes(ds, count, true); auto l = readBytes(ds, count, true);
if (!l.isEmpty()) { if (!l.isEmpty())
// It seems that converting to Latin 1 never detects errors so, using UTF-8. tags.insert(tagId, dataType == EXIF_TAG_DATATYPE(ExifTagType::Utf8) ? QString::fromUtf8(l) : QString::fromLatin1(l));
// Note that if the dataType is ASCII, by EXIF specification, it must use only the
// first 128 values so the UTF-8 conversion is correct.
auto dec = QStringDecoder(QStringDecoder::Utf8);
// QStringDecoder raise an error only after converting to QString
auto ut8 = QString(dec(l));
// If there are errors in the conversion to UTF-8, then I try with latin1 (extended ASCII)
tags.insert(tagId, dec.hasError() ? QString::fromLatin1(l) : ut8);
}
} else if (dataType == EXIF_TAG_DATATYPE(ExifTagType::Undefined)) { } else if (dataType == EXIF_TAG_DATATYPE(ExifTagType::Undefined)) {
auto l = readBytes(ds, count, false); auto l = readBytes(ds, count, false);
if (!l.isEmpty()) if (!l.isEmpty())
@ -1067,7 +1032,7 @@ void MicroExif::setImageDirection(double degree, bool isMagnetic)
m_gpsTags.insert(GPS_IMGDIRECTION, degree); m_gpsTags.insert(GPS_IMGDIRECTION, degree);
} }
QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder, const Version &version) const QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder) const
{ {
QByteArray ba; QByteArray ba;
{ {
@ -1078,16 +1043,16 @@ QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder, const
return ba; return ba;
} }
QByteArray MicroExif::exifIfdByteArray(const QDataStream::ByteOrder &byteOrder, const Version &version) const QByteArray MicroExif::exifIfdByteArray(const QDataStream::ByteOrder &byteOrder) const
{ {
QByteArray ba; QByteArray ba;
{ {
QDataStream ds(&ba, QIODevice::WriteOnly); QDataStream ds(&ba, QIODevice::WriteOnly);
ds.setByteOrder(byteOrder); ds.setByteOrder(byteOrder);
auto exifTags = m_exifTags; auto exifTags = m_exifTags;
exifTags.insert(EXIF_EXIFVERSION, version == Version::V3 ? QByteArray("0300") : QByteArray("0232")); exifTags.insert(EXIF_EXIFVERSION, QByteArray("0300"));
TagPos positions; TagPos positions;
if (!writeIfd(ds, version, exifTags, positions)) if (!writeIfd(ds, exifTags, positions))
return {}; return {};
} }
return ba; return ba;
@ -1100,7 +1065,7 @@ bool MicroExif::setExifIfdByteArray(const QByteArray &ba, const QDataStream::Byt
return readIfd(ds, m_exifTags, 0, staticTagTypes); return readIfd(ds, m_exifTags, 0, staticTagTypes);
} }
QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder, const Version &version) const QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder) const
{ {
QByteArray ba; QByteArray ba;
{ {
@ -1109,7 +1074,7 @@ QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder, c
auto gpsTags = m_gpsTags; auto gpsTags = m_gpsTags;
gpsTags.insert(GPS_GPSVERSION, QByteArray("2400")); gpsTags.insert(GPS_GPSVERSION, QByteArray("2400"));
TagPos positions; TagPos positions;
if (!writeIfd(ds, version, gpsTags, positions, 0, staticGpsTagTypes)) if (!writeIfd(ds, gpsTags, positions, 0, staticGpsTagTypes))
return {}; return {};
return ba; return ba;
} }
@ -1122,7 +1087,7 @@ bool MicroExif::setGpsIfdByteArray(const QByteArray &ba, const QDataStream::Byte
return readIfd(ds, m_gpsTags, 0, staticGpsTagTypes); return readIfd(ds, m_gpsTags, 0, staticGpsTagTypes);
} }
bool MicroExif::write(QIODevice *device, const QDataStream::ByteOrder &byteOrder, const Version &version) const bool MicroExif::write(QIODevice *device, const QDataStream::ByteOrder &byteOrder) const
{ {
if (device == nullptr || device->isSequential() || isEmpty()) if (device == nullptr || device->isSequential() || isEmpty())
return false; return false;
@ -1131,7 +1096,7 @@ bool MicroExif::write(QIODevice *device, const QDataStream::ByteOrder &byteOrder
ds.setByteOrder(byteOrder); ds.setByteOrder(byteOrder);
if (!writeHeader(ds)) if (!writeHeader(ds))
return false; return false;
if (!writeIfds(ds, version)) if (!writeIfds(ds))
return false; return false;
} }
device->close(); device->close();
@ -1362,30 +1327,30 @@ bool MicroExif::writeHeader(QDataStream &ds) const
return ds.status() == QDataStream::Ok; return ds.status() == QDataStream::Ok;
} }
bool MicroExif::writeIfds(QDataStream &ds, const Version &version) const bool MicroExif::writeIfds(QDataStream &ds) const
{ {
auto tiffTags = m_tiffTags; auto tiffTags = m_tiffTags;
auto exifTags = m_exifTags; auto exifTags = m_exifTags;
auto gpsTags = m_gpsTags; auto gpsTags = m_gpsTags;
updateTags(tiffTags, exifTags, gpsTags, version); updateTags(tiffTags, exifTags, gpsTags);
TagPos positions; TagPos positions;
if (!writeIfd(ds, version, tiffTags, positions)) if (!writeIfd(ds, tiffTags, positions))
return false; return false;
if (!writeIfd(ds, version, exifTags, positions, positions.value(EXIF_EXIFIFD))) if (!writeIfd(ds, exifTags, positions, positions.value(EXIF_EXIFIFD)))
return false; return false;
if (!writeIfd(ds, version, gpsTags, positions, positions.value(EXIF_GPSIFD), staticGpsTagTypes)) if (!writeIfd(ds, gpsTags, positions, positions.value(EXIF_GPSIFD), staticGpsTagTypes))
return false; return false;
return true; return true;
} }
void MicroExif::updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags, const Version &version) const void MicroExif::updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags) const
{ {
if (exifTags.isEmpty()) { if (exifTags.isEmpty()) {
tiffTags.remove(EXIF_EXIFIFD); tiffTags.remove(EXIF_EXIFIFD);
} else { } else {
tiffTags.insert(EXIF_EXIFIFD, quint32()); tiffTags.insert(EXIF_EXIFIFD, quint32());
exifTags.insert(EXIF_EXIFVERSION, version == Version::V3 ? QByteArray("0300") : QByteArray("0232")); exifTags.insert(EXIF_EXIFVERSION, QByteArray("0300"));
} }
if (gpsTags.isEmpty()) { if (gpsTags.isEmpty()) {
tiffTags.remove(EXIF_GPSIFD); tiffTags.remove(EXIF_GPSIFD);

View File

@ -37,15 +37,6 @@ class MicroExif
public: public:
using Tags = QMap<quint16, QVariant>; using Tags = QMap<quint16, QVariant>;
/*!
* \brief The Version enum
* Exif specs version used when writing.
*/
enum Version {
V2, // V2.xx
V3 // V3.xx, use of UTF-8 data type (default)
};
/*! /*!
* \brief MicroExif * \brief MicroExif
* Constructs an empty class. * Constructs an empty class.
@ -274,20 +265,18 @@ public:
* - EXIF IFD * - EXIF IFD
* - GPS IFD * - GPS IFD
* \param byteOrder Sets the serialization byte order for EXIF data. * \param byteOrder Sets the serialization byte order for EXIF data.
* \param version The EXIF specs version to use.
* \return A byte array containing the serialized data. * \return A byte array containing the serialized data.
* \sa write * \sa write
*/ */
QByteArray toByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const; QByteArray toByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
/*! /*!
* \brief exifIfdByteArray * \brief exifIfdByteArray
* Convert the EXIF IFD only to RAW data. Useful when you want to add EXIF data to an existing TIFF container. * 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. * \param byteOrder Sets the serialization byte order for the data.
* \param version The EXIF specs version to use.
* \return A byte array containing the serialized data. * \return A byte array containing the serialized data.
*/ */
QByteArray exifIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const; QByteArray exifIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
/*! /*!
* \brief setExifIfdByteArray * \brief setExifIfdByteArray
* \param ba The RAW data of EXIF IFD. * \param ba The RAW data of EXIF IFD.
@ -300,10 +289,9 @@ public:
* \brief gpsIfdByteArray * \brief gpsIfdByteArray
* Convert the GPS IFD only to RAW data. Useful when you want to add GPS data to an existing TIFF container. * 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. * \param byteOrder Sets the serialization byte order for the data.
* \param version The EXIF specs version to use.
* \return A byte array containing the serialized data. * \return A byte array containing the serialized data.
*/ */
QByteArray gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const; QByteArray gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
/*! /*!
* \brief setGpsIfdByteArray * \brief setGpsIfdByteArray
* \param ba The RAW data of GPS IFD. * \param ba The RAW data of GPS IFD.
@ -321,11 +309,10 @@ public:
* - GPS IFD * - GPS IFD
* \param device A random access device. * \param device A random access device.
* \param byteOrder Sets the serialization byte order for EXIF data. * \param byteOrder Sets the serialization byte order for EXIF data.
* \param version The EXIF specs version to use.
* \return True on success, otherwise false. * \return True on success, otherwise false.
* \sa toByteArray * \sa toByteArray
*/ */
bool write(QIODevice *device, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const; bool write(QIODevice *device, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
/*! /*!
* \brief updateImageMetadata * \brief updateImageMetadata
@ -386,8 +373,8 @@ private:
void setGpsString(quint16 tagId, const QString& s); void setGpsString(quint16 tagId, const QString& s);
QString gpsString(quint16 tagId) const; QString gpsString(quint16 tagId) const;
bool writeHeader(QDataStream &ds) const; bool writeHeader(QDataStream &ds) const;
bool writeIfds(QDataStream &ds, const Version &version) const; bool writeIfds(QDataStream &ds) const;
void updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags, const Version &version) const; void updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags) const;
static void setString(Tags &tags, quint16 tagId, const QString &s); static void setString(Tags &tags, quint16 tagId, const QString &s);
static QString string(const Tags &tags, quint16 tagId); static QString string(const Tags &tags, quint16 tagId);

View File

@ -1504,18 +1504,7 @@ bool PSDHandler::read(QImage *image)
img.setColorSpace(QColorSpace(QColorSpace::SRgb)); img.setColorSpace(QColorSpace(QColorSpace::SRgb));
#endif #endif
} else if (!setColorSpace(img, irs)) { } else if (!setColorSpace(img, irs)) {
// Float images are used by Photoshop as linear: if no color space // qDebug() << "No colorspace info set!";
// is present, a linear one should be chosen.
if (header.color_mode == CM_RGB && header.depth == 32) {
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
if (header.color_mode == CM_GRAYSCALE && header.depth == 32) {
auto qs = QColorSpace(QPointF(0.3127, 0.3291), QColorSpace::TransferFunction::Linear);
qs.setDescription(QStringLiteral("Linear grayscale"));
img.setColorSpace(qs);
}
#endif
} }
// XMP data // XMP data

View File

@ -457,4 +457,3 @@ QImageIOHandler *ScitexPlugin::create(QIODevice *device, const QByteArray &forma
return handler; return handler;
} }
#include "moc_sct_p.cpp"

View File

@ -962,7 +962,11 @@ bool XCFImageFormat::loadImageProperties(QDataStream &xcf_io, XCFImage &xcf_imag
case PROP_PARASITES: case PROP_PARASITES:
while (!property.atEnd()) { while (!property.atEnd()) {
char *tag; char *tag;
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
quint32 size;
#else
qint64 size; qint64 size;
#endif
property.readBytes(tag, size); property.readBytes(tag, size);