Compare commits

..

24 Commits

Author SHA1 Message Date
1b3d2afbe3 Update dependency version to 6.14.0 2025-05-02 16:02:43 +02:00
850068c1dc JXR: fix compilation error on 32-bit systems 2025-04-22 08:01:02 +02:00
6bf38ea638 Improved EXIF V3 compatibility (2)
This patch improve the string read/write in case of non ASCII encoding.
- When reading, checks for UTF-8 text even on ASCII data type. If it fails, Latin1 converter is used.
- When writing using V3 specs, a better check is done to identify 7-bit ASCII text.

Related to MR !358
2025-04-21 11:29:13 +00:00
2adca7c0ca jxr: Use qsizetype for image size variables
Fixes type mismatch on 32-bit architectures.

/builddir/build/BUILD/kf6-kimageformats-6.13.0-build/kimageformats-6.13.0/src/imageformats/jxr.cpp: In member function ‘virtual bool JXRHandler::read(QImage*)’:
/builddir/build/BUILD/kf6-kimageformats-6.13.0-build/kimageformats-6.13.0/src/imageformats/jxr.cpp:994:88: error: no matching function for call to ‘min(qint64&, qsizetype)’
  994 |                 std::memcpy(img.scanLine(y), ba.data() + convStrideSize * y, (std::min)(convStrideSize, img.bytesPerLine()));
      |                                                                              ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2025-04-21 10:07:00 +02:00
dd69fdaea9 lib prefix on Android
Qt's original qtimageformats plug-ins use lib prefix too,
without the lib prefix, the image plug-ins don't work on Android.
2025-04-19 14:16:18 +00:00
ae62ea3dfc Improved EXIF V3 compatibility
EXIF specs V3 added the UTF-8 data type. The MicroExif class now allows serializations to choose whether to support the V2 or V3 format. To maximize compatibility with old readers, even when V3 is set, the ASCII data type is used if possible.

The JXR plugin, based on TIFF V6 container, does not allow to use the V3 format (it does not recognize the UTF-8 data type) and therefore V2 has been forced. For all other plugins using MicroExif, it is now possible to save, e.g., descriptions in Japanese.

Please note that this patch is also a bugfix: when saving, version 3 was set but the strings were always saved as ASCII.
2025-04-18 13:53:12 +00:00
9c47845f15 Update version to 6.14.0 2025-04-10 19:53:58 +02:00
7c7fa73020 Remove conditions for no longer supported Qt versions 2025-04-08 18:46:13 +02:00
92e4271e84 GIT_SILENT Upgrade Qt6 version requirement to 6.7.0. 2025-04-07 10:13:41 +02:00
6f588c6fd3 Add missing include mocs 2025-04-03 07:34:17 +02:00
a182478e2c It compiles fine without qt6.9 deprecated methods 2025-03-23 22:52:05 +00:00
4026f41890 PSD: use linear profile on float images
On float images, if not color profile is present, a linear one should be chosen. Photoshop works on 32-bit images in a linear color space.
2025-03-23 22:31:23 +00:00
bef2b9168f It compiles fine without kf6.12 deprecated methods 2025-03-22 06:47:20 +01:00
473f5d9847 Write tests for hej2 format 2025-03-16 21:31:46 +01:00
9bee29cc01 heif: enable saving of hej2 format 2025-03-16 18:37:48 +01:00
cdf3be3af1 Update dependency version to 6.13.0 2025-03-14 21:31:42 +01:00
752b18a42c CI: Enable heif so we make sure it compiles 2025-03-12 16:17:29 +00:00
97a1ea181c writetest: special handling for HEIF format 2025-03-12 16:41:39 +01:00
64a43fb04f readtest: special handling for HEIF format 2025-03-12 16:17:59 +01:00
6821c29819 heif: disable AVCI decoder for libheif before 1.19.6 2025-03-12 13:26:42 +01:00
e4d95c03fa SKIP tests when libheif configuration is incomplete 2025-03-10 22:01:14 +01:00
afa8ed1a5d heif: enable reading images with native 16 bit depth 2025-03-10 19:42:34 +01:00
245c835d92 Use of heif_context_add_XMP_metadata instead
heif_context_add_XMP_metadata2
2025-03-09 10:44:18 +01:00
b2663d2651 Update dependency version to 6.13.0 2025-03-07 19:00:23 +01:00
55 changed files with 291 additions and 66 deletions

View File

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

View File

@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.16)
set(KF_VERSION "6.12.0") # handled by release scripts
set(KF_DEP_VERSION "6.12.0") # handled by release scripts
set(KF_VERSION "6.14.0") # handled by release scripts
set(KF_DEP_VERSION "6.14.0") # handled by release scripts
project(KImageFormats VERSION ${KF_VERSION})
include(FeatureSummary)
find_package(ECM 6.12.0 NO_MODULE)
find_package(ECM 6.14.0 NO_MODULE)
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)
@ -21,7 +21,7 @@ include(ECMDeprecationSettings)
include(CheckIncludeFiles)
include(FindPkgConfig)
set(REQUIRED_QT_VERSION 6.6.0)
set(REQUIRED_QT_VERSION 6.7.0)
find_package(Qt6Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
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")
ecm_set_disabled_deprecation_versions(
QT 6.8.0
KF 6.11.0
QT 6.9.0
KF 6.12.0
)
add_subdirectory(src)

View File

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

View File

@ -258,6 +258,15 @@ int main(int argc, char **argv)
});
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();
// 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) {
@ -323,7 +332,12 @@ int main(int argc, char **argv)
OptionTest optionTest;
if (!optionTest.store(&inputReader)) {
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": error while reading options\n";
++failed;
if (format == "heif") {
// libheif + ffmpeg decoder is unable to load all HEIF files.
++skipped;
} else {
++failed;
}
continue;
}

View File

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

View File

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

View File

@ -0,0 +1,65 @@
{
"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",
"value" : "TV broadcast test image."
"value" : "テレビ放送テスト映像。(TV broadcast test image.)"
},
{
"key" : "Latitude",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -370,7 +370,12 @@ int formatTest(const QString &suffix, bool createTemplates)
QBuffer buffer(&ba);
auto writtenImage = QImageReader(&buffer, suffix.toLatin1()).read();
if (writtenImage.isNull()) {
++failed;
if (suffix.toLatin1() == "heif") {
// libheif + ffmpeg decoder is unable to load all HEIF files.
++skipped;
} else {
++failed;
}
QTextStream(stdout) << "FAIL : error while reading the image " << formatName << "\n";
continue;
}
@ -622,8 +627,28 @@ int main(int argc, char **argv)
}
}
// run test
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
auto ret = basicTest(suffix, parser.isSet(lossless), parser.isSet(ignoreDataCheck), parser.isSet(skipOptTest), fuzzarg);
if (ret == 0) {
ret = formatTest(suffix, parser.isSet(createFormatTempates));

View File

@ -15,6 +15,16 @@ function(kimageformats_add_plugin plugin)
target_sources(${plugin} PRIVATE ${KIF_ADD_PLUGIN_SOURCES})
set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/imageformats)
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)
endfunction()

View File

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

View File

@ -16,8 +16,8 @@
#include <QDebug>
#include <QPointF>
#include <QSysInfo>
#include <cstring>
#include <limits>
#include <string.h>
#ifndef HEIF_MAX_METADATA_SIZE
/*!
@ -26,12 +26,12 @@
#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;
bool HEIFHandler::m_heif_encoder_available = false;
bool HEIFHandler::m_hej2_decoder_available = false;
bool HEIFHandler::m_hej2_encoder_available = false;
bool HEIFHandler::m_avci_decoder_available = false;
extern "C" {
@ -155,6 +155,14 @@ bool HEIFHandler::write_helper(const QImage &image)
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;
if (save_depth > 8) {
if (save_alpha) {
@ -287,7 +295,7 @@ bool HEIFHandler::write_helper(const QImage &image)
}
struct heif_encoder *encoder = nullptr;
err = heif_context_get_encoder_for_format(context, heif_compression_HEVC, &encoder);
err = heif_context_get_encoder_for_format(context, encoder_codec, &encoder);
if (err.code) {
qWarning() << "Unable to get an encoder instance:" << err.message;
heif_image_release(h_image);
@ -314,7 +322,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);
// exif metadata
@ -554,7 +562,7 @@ bool HEIFHandler::ensureDecoder()
QImage::Format target_image_format;
if (bit_depth == 10 || bit_depth == 12) {
if (bit_depth == 10 || bit_depth == 12 || bit_depth == 16) {
if (hasAlphaChannel) {
chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE;
target_image_format = QImage::Format_RGBA64;
@ -646,6 +654,35 @@ bool HEIFHandler::ensureDecoder()
}
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:
if (hasAlphaChannel) {
for (int y = 0; y < imageHeight; y++) {
@ -991,6 +1028,13 @@ bool HEIFHandler::isHej2DecoderAvailable()
return m_hej2_decoder_available;
}
bool HEIFHandler::isHej2EncoderAvailable()
{
HEIFHandler::queryHeifLib();
return m_hej2_encoder_available;
}
bool HEIFHandler::isAVCIDecoderAvailable()
{
HEIFHandler::queryHeifLib();
@ -1013,8 +1057,9 @@ void HEIFHandler::queryHeifLib()
m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
#if LIBHEIF_HAVE_VERSION(1, 13, 0)
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
#if LIBHEIF_HAVE_VERSION(1, 19, 0)
#if LIBHEIF_HAVE_VERSION(1, 19, 6)
m_avci_decoder_available = heif_have_decoder_for_format(heif_compression_AVC);
#endif
m_plugins_queried = true;
@ -1081,6 +1126,9 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
if (HEIFHandler::isHej2DecoderAvailable()) {
format_cap |= CanRead;
}
if (HEIFHandler::isHej2EncoderAvailable()) {
format_cap |= CanWrite;
}
return format_cap;
}
@ -1110,7 +1158,7 @@ QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const Q
}
}
if (device->isWritable() && HEIFHandler::isHeifEncoderAvailable()) {
if (device->isWritable() && (HEIFHandler::isHeifEncoderAvailable() || HEIFHandler::isHej2EncoderAvailable())) {
cap |= CanWrite;
}
return cap;

View File

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

View File

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

View File

@ -720,11 +720,11 @@ public:
auto exif = MicroExif::fromImage(image);
if (!exif.isEmpty()) {
auto exifIfd = exif.exifIfdByteArray(QDataStream::LittleEndian);
auto exifIfd = exif.exifIfdByteArray(QDataStream::LittleEndian, MicroExif::V2);
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;
}
auto gpsIfd = exif.gpsIfdByteArray(QDataStream::LittleEndian);
auto gpsIfd = exif.gpsIfdByteArray(QDataStream::LittleEndian, MicroExif::V2);
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;
}
@ -991,7 +991,7 @@ bool JXRHandler::read(QImage *outImage)
return false;
}
for (qint32 y = 0, h = img.height(); y < h; ++y) {
std::memcpy(img.scanLine(y), ba.data() + convStrideSize * y, (std::min)(convStrideSize, img.bytesPerLine()));
std::memcpy(img.scanLine(y), ba.data() + convStrideSize * y, (std::min)(convStrideSize, qint64(img.bytesPerLine())));
}
}
PKFormatConverter_Release(&pConverter);

View File

@ -12,6 +12,7 @@
#include <QCoreApplication>
#include <QDataStream>
#include <QHash>
#include <QStringDecoder>
#include <QTimeZone>
// TIFF 6 specs
@ -111,17 +112,17 @@ static const KnownTags staticTagTypes = {
TagInfo(TIFF_IMAGEWIDTH, ExifTagType::Long),
TagInfo(TIFF_IMAGEHEIGHT, ExifTagType::Long),
TagInfo(TIFF_BITSPERSAMPLE, ExifTagType::Short),
TagInfo(TIFF_IMAGEDESCRIPTION, ExifTagType::Ascii),
TagInfo(TIFF_MAKE, ExifTagType::Ascii),
TagInfo(TIFF_MODEL, ExifTagType::Ascii),
TagInfo(TIFF_IMAGEDESCRIPTION, ExifTagType::Utf8),
TagInfo(TIFF_MAKE, ExifTagType::Utf8),
TagInfo(TIFF_MODEL, ExifTagType::Utf8),
TagInfo(TIFF_ORIENT, ExifTagType::Short),
TagInfo(TIFF_XRES, ExifTagType::Rational),
TagInfo(TIFF_YRES, ExifTagType::Rational),
TagInfo(TIFF_URES, ExifTagType::Short),
TagInfo(TIFF_SOFTWARE, ExifTagType::Ascii),
TagInfo(TIFF_ARTIST, ExifTagType::Ascii),
TagInfo(TIFF_SOFTWARE, ExifTagType::Utf8),
TagInfo(TIFF_ARTIST, ExifTagType::Utf8),
TagInfo(TIFF_DATETIME, ExifTagType::Ascii),
TagInfo(TIFF_COPYRIGHT, ExifTagType::Ascii),
TagInfo(TIFF_COPYRIGHT, ExifTagType::Utf8),
TagInfo(EXIF_EXIFIFD, ExifTagType::Long),
TagInfo(EXIF_GPSIFD, ExifTagType::Long),
TagInfo(EXIF_DATETIMEORIGINAL, ExifTagType::Ascii),
@ -134,10 +135,10 @@ static const KnownTags staticTagTypes = {
TagInfo(EXIF_PIXELYDIM, ExifTagType::Long),
TagInfo(EXIF_IMAGEUNIQUEID, ExifTagType::Ascii),
TagInfo(EXIF_BODYSERIALNUMBER, ExifTagType::Ascii),
TagInfo(EXIF_LENSMAKE, ExifTagType::Ascii),
TagInfo(EXIF_LENSMODEL, ExifTagType::Ascii),
TagInfo(EXIF_LENSMAKE, ExifTagType::Utf8),
TagInfo(EXIF_LENSMODEL, ExifTagType::Utf8),
TagInfo(EXIF_LENSSERIALNUMBER, ExifTagType::Ascii),
TagInfo(EXIF_IMAGETITLE, ExifTagType::Ascii),
TagInfo(EXIF_IMAGETITLE, ExifTagType::Utf8),
TagInfo(EXIF_EXIFVERSION, ExifTagType::Undefined)
};
// clang-format on
@ -367,6 +368,28 @@ 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
* \param ds The stream.
@ -375,7 +398,12 @@ static void writeData(QDataStream &ds, const QVariant &value, const ExifTagType&
* \param knownTags List of known and supported tags.
* \return True on success, otherwise false.
*/
static bool writeIfd(QDataStream &ds, const MicroExif::Tags &tags, TagPos &positions, quint32 pos = 0, const KnownTags &knownTags = staticTagTypes)
static bool writeIfd(QDataStream &ds,
const MicroExif::Version &ver,
const MicroExif::Tags &tags,
TagPos &positions,
quint32 pos = 0,
const KnownTags &knownTags = staticTagTypes)
{
if (tags.isEmpty())
return true;
@ -390,7 +418,7 @@ static bool writeIfd(QDataStream &ds, const MicroExif::Tags &tags, TagPos &posit
continue;
}
auto value = tags.value(key);
auto dataType = knownTags.value(key);
auto dataType = updateDataType(knownTags.value(key), value, ver);
auto count = countBytes(dataType, value);
ds << quint16(key);
@ -412,9 +440,8 @@ static bool writeIfd(QDataStream &ds, const MicroExif::Tags &tags, TagPos &posit
if (!knownTags.contains(key)) {
continue;
}
auto value = tags.value(key);
auto dataType = knownTags.value(key);
auto dataType = updateDataType(knownTags.value(key), value, ver);
auto count = countBytes(dataType, value);
auto valueSize = count * EXIF_TAG_SIZEOF(dataType);
if (valueSize <= 4)
@ -534,8 +561,16 @@ 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)) {
auto l = readBytes(ds, count, true);
if (!l.isEmpty())
tags.insert(tagId, dataType == EXIF_TAG_DATATYPE(ExifTagType::Utf8) ? QString::fromUtf8(l) : QString::fromLatin1(l));
if (!l.isEmpty()) {
// It seems that converting to Latin 1 never detects errors so, using UTF-8.
// 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)) {
auto l = readBytes(ds, count, false);
if (!l.isEmpty())
@ -1032,7 +1067,7 @@ void MicroExif::setImageDirection(double degree, bool isMagnetic)
m_gpsTags.insert(GPS_IMGDIRECTION, degree);
}
QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder) const
QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder, const Version &version) const
{
QByteArray ba;
{
@ -1043,16 +1078,16 @@ QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder) const
return ba;
}
QByteArray MicroExif::exifIfdByteArray(const QDataStream::ByteOrder &byteOrder) const
QByteArray MicroExif::exifIfdByteArray(const QDataStream::ByteOrder &byteOrder, const Version &version) const
{
QByteArray ba;
{
QDataStream ds(&ba, QIODevice::WriteOnly);
ds.setByteOrder(byteOrder);
auto exifTags = m_exifTags;
exifTags.insert(EXIF_EXIFVERSION, QByteArray("0300"));
exifTags.insert(EXIF_EXIFVERSION, version == Version::V3 ? QByteArray("0300") : QByteArray("0232"));
TagPos positions;
if (!writeIfd(ds, exifTags, positions))
if (!writeIfd(ds, version, exifTags, positions))
return {};
}
return ba;
@ -1065,7 +1100,7 @@ bool MicroExif::setExifIfdByteArray(const QByteArray &ba, const QDataStream::Byt
return readIfd(ds, m_exifTags, 0, staticTagTypes);
}
QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder) const
QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder, const Version &version) const
{
QByteArray ba;
{
@ -1074,7 +1109,7 @@ QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder) c
auto gpsTags = m_gpsTags;
gpsTags.insert(GPS_GPSVERSION, QByteArray("2400"));
TagPos positions;
if (!writeIfd(ds, gpsTags, positions, 0, staticGpsTagTypes))
if (!writeIfd(ds, version, gpsTags, positions, 0, staticGpsTagTypes))
return {};
return ba;
}
@ -1087,7 +1122,7 @@ bool MicroExif::setGpsIfdByteArray(const QByteArray &ba, const QDataStream::Byte
return readIfd(ds, m_gpsTags, 0, staticGpsTagTypes);
}
bool MicroExif::write(QIODevice *device, const QDataStream::ByteOrder &byteOrder) const
bool MicroExif::write(QIODevice *device, const QDataStream::ByteOrder &byteOrder, const Version &version) const
{
if (device == nullptr || device->isSequential() || isEmpty())
return false;
@ -1096,7 +1131,7 @@ bool MicroExif::write(QIODevice *device, const QDataStream::ByteOrder &byteOrder
ds.setByteOrder(byteOrder);
if (!writeHeader(ds))
return false;
if (!writeIfds(ds))
if (!writeIfds(ds, version))
return false;
}
device->close();
@ -1327,30 +1362,30 @@ bool MicroExif::writeHeader(QDataStream &ds) const
return ds.status() == QDataStream::Ok;
}
bool MicroExif::writeIfds(QDataStream &ds) const
bool MicroExif::writeIfds(QDataStream &ds, const Version &version) const
{
auto tiffTags = m_tiffTags;
auto exifTags = m_exifTags;
auto gpsTags = m_gpsTags;
updateTags(tiffTags, exifTags, gpsTags);
updateTags(tiffTags, exifTags, gpsTags, version);
TagPos positions;
if (!writeIfd(ds, tiffTags, positions))
if (!writeIfd(ds, version, tiffTags, positions))
return false;
if (!writeIfd(ds, exifTags, positions, positions.value(EXIF_EXIFIFD)))
if (!writeIfd(ds, version, exifTags, positions, positions.value(EXIF_EXIFIFD)))
return false;
if (!writeIfd(ds, gpsTags, positions, positions.value(EXIF_GPSIFD), staticGpsTagTypes))
if (!writeIfd(ds, version, gpsTags, positions, positions.value(EXIF_GPSIFD), staticGpsTagTypes))
return false;
return true;
}
void MicroExif::updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags) const
void MicroExif::updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags, const Version &version) const
{
if (exifTags.isEmpty()) {
tiffTags.remove(EXIF_EXIFIFD);
} else {
tiffTags.insert(EXIF_EXIFIFD, quint32());
exifTags.insert(EXIF_EXIFVERSION, QByteArray("0300"));
exifTags.insert(EXIF_EXIFVERSION, version == Version::V3 ? QByteArray("0300") : QByteArray("0232"));
}
if (gpsTags.isEmpty()) {
tiffTags.remove(EXIF_GPSIFD);

View File

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

View File

@ -1504,7 +1504,18 @@ bool PSDHandler::read(QImage *image)
img.setColorSpace(QColorSpace(QColorSpace::SRgb));
#endif
} else if (!setColorSpace(img, irs)) {
// qDebug() << "No colorspace info set!";
// Float images are used by Photoshop as linear: if no color space
// 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

View File

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

View File

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