EXR: support for metadata saved by Photoshop

This commit is contained in:
Mirco Miranda
2026-09-10 07:36:10 +02:00
committed by Mirco Miranda
parent 43e6efb0e5
commit 6a3c7f2c86
8 changed files with 299 additions and 186 deletions
+2 -2
View File
@@ -57,7 +57,7 @@ endif()
##################################
if(OpenEXR_FOUND)
kimageformats_add_plugin(kimg_exr SOURCES exr.cpp scanlineconverter.cpp)
kimageformats_add_plugin(kimg_exr SOURCES exr.cpp microexif.cpp photoshop.cpp scanlineconverter.cpp)
if(TARGET OpenEXR::OpenEXR)
target_link_libraries(kimg_exr PRIVATE OpenEXR::OpenEXR)
else()
@@ -117,7 +117,7 @@ kimageformats_add_plugin(kimg_pfm SOURCES pfm.cpp)
##################################
kimageformats_add_plugin(kimg_psd SOURCES psd.cpp microexif.cpp scanlineconverter.cpp)
kimageformats_add_plugin(kimg_psd SOURCES psd.cpp microexif.cpp photoshop.cpp scanlineconverter.cpp)
##################################
+30
View File
@@ -8,6 +8,8 @@
*/
#include "exr_p.h"
#include "microexif_p.h"
#include "photoshop_p.h"
#include "scanlineconverter_p.h"
#include "util_p.h"
@@ -385,6 +387,34 @@ static void readMetadata(const Imf::Header &header, QImage &image)
if (auto focalLen = header.findTypedAttribute<Imf::FloatAttribute>("effectiveFocalLength")) {
image.setText(QStringLiteral(META_KEY_FOCALLENGTH), QLocale::c().toString(focalLen->value()));
}
// Photoshop image resource section
if (auto adobe = header.findTypedAttribute<Imf::OpaqueAttribute>("adobe_rsrc")) {
auto &&data = adobe->data();
auto ba = QByteArray(data, data.size());
ba.prepend((data.size()) & 0xFF);
ba.prepend((data.size() >> 8) & 0xFF);
ba.prepend((data.size() >> 16) & 0xFF);
ba.prepend((data.size() >> 24) & 0xFF);
QDataStream ds(ba);
ds.setByteOrder(QDataStream::BigEndian);
auto ok = false;
auto irs = readImageResourceSection(ds, &ok);
if (ok) {
if (irs.contains(IRI_EXIFDATA1)) {
auto exif = MicroExif::fromByteArray(irs.value(IRI_EXIFDATA1).data);
exif.updateImageMetadata(image);
exif.updateImageResolution(image);
}
if (irs.contains(IRI_XMPMETADATA)) {
auto irb = irs.value(IRI_XMPMETADATA);
auto xmp = QString::fromUtf8(irb.data);
if (!xmp.isEmpty())
image.setText(QStringLiteral(META_KEY_XMP_ADOBE), xmp);
}
}
}
}
/*!
+140
View File
@@ -0,0 +1,140 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2026 Mirco Miranda <[email protected]>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "photoshop_p.h"
#include "util_p.h"
#include <QLoggingCategory>
#ifdef QT_DEBUG
Q_LOGGING_CATEGORY(LOG_PSDSHARED, "kf.imageformats.plugins.psdshared", QtDebugMsg)
#else
Q_LOGGING_CATEGORY(LOG_PSDSHARED, "kf.imageformats.plugins.psdshared", QtWarningMsg)
#endif
QString readPascalString(QDataStream &s, qint32 alignBytes, qint32 *size)
{
qint32 tmp = 0;
if (size == nullptr)
size = &tmp;
quint8 stringSize;
s >> stringSize;
*size = sizeof(stringSize);
QString str;
if (stringSize > 0) {
QByteArray ba;
ba.resize(stringSize);
auto read = s.readRawData(ba.data(), ba.size());
if (read > 0) {
*size += read;
str = QString::fromLatin1(ba);
}
}
// align
if (alignBytes > 1)
if (auto pad = *size % alignBytes)
*size += s.skipRawData(alignBytes - pad);
return str;
}
PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok)
{
PSDImageResourceSection irs;
bool tmp = true;
if (ok == nullptr)
ok = &tmp;
*ok = true;
// Section size
quint32 tmpSize;
s >> tmpSize;
qint64 sectioSize = tmpSize;
// Reading Image resource block
for (auto size = sectioSize; size > 0;) {
#define DEC_SIZE(value) \
if ((size -= qint64(value)) < 0) { \
*ok = false; \
break; }
// Length Description
// -------------------------------------------------------------------
// 4 Signature: '8BIM'
// 2 Unique identifier for the resource. Image resource IDs
// contains a list of resource IDs used by Photoshop.
// Variable Name: Pascal string, padded to make the size even
// (a null name consists of two bytes of 0)
// 4 Actual size of resource data that follows
// Variable The resource data, described in the sections on the
// individual resource types. It is padded to make the size
// even.
quint32 signature;
s >> signature;
DEC_SIZE(sizeof(signature))
// NOTE: MeSa signature is not documented but found in some old PSD take from Photoshop 7.0 CD.
if (signature != S_8BIM && signature != S_MeSa) { // 8BIM and MeSa
qCDebug(LOG_PSDSHARED) << "Invalid Image Resource Block Signature!";
*ok = false;
break;
}
// id
quint16 id;
s >> id;
DEC_SIZE(sizeof(id))
// getting data
PSDImageResourceBlock irb;
// name
qint32 bytes = 0;
irb.name = readPascalString(s, 2, &bytes);
DEC_SIZE(bytes)
// data read
quint32 dataSize;
s >> dataSize;
DEC_SIZE(sizeof(dataSize))
if (auto dev = s.device()) {
if (dataSize > size) {
qCDebug(LOG_PSDSHARED) << "Invalid Image Resource Block Data Size!";
*ok = false;
break;
}
irb.data = deviceRead(dev, dataSize);
}
auto read = irb.data.size();
if (read > 0) {
DEC_SIZE(read)
}
if (read != qint64(dataSize)) {
qCDebug(LOG_PSDSHARED) << "Image Resource Block Read Error!";
*ok = false;
break;
}
if (auto pad = dataSize % 2) {
auto skipped = s.skipRawData(pad);
if (skipped > 0) {
DEC_SIZE(skipped);
}
}
// insert IRB
irs.insert(ImageResourceId(id), irb);
#undef DEC_SIZE
}
return irs;
}
+80
View File
@@ -0,0 +1,80 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2026 Mirco Miranda <[email protected]>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef PHOTOSHOP_P_H
#define PHOTOSHOP_P_H
#include <QDataStream>
#include <QHash>
#include <QString>
enum Signature : quint32 {
S_8BIM = 0x3842494D, // '8BIM'
S_8B64 = 0x38423634, // '8B64'
S_MeSa = 0x4D655361 // 'MeSa'
};
enum ColorMode : quint16 {
CM_BITMAP = 0,
CM_GRAYSCALE = 1,
CM_INDEXED = 2,
CM_RGB = 3,
CM_CMYK = 4,
CM_MULTICHANNEL = 7,
CM_DUOTONE = 8,
CM_LABCOLOR = 9,
};
enum ImageResourceId : quint16 {
IRI_RESOLUTIONINFO = 0x03ED,
IRI_ICCPROFILE = 0x040F,
IRI_TRANSPARENCYINDEX = 0x0417,
IRI_ALPHAIDENTIFIERS = 0x041D,
IRI_VERSIONINFO = 0x0421,
IRI_EXIFDATA1 = 0x0422,
IRI_EXIFDATA3 = 0x0423, // never seen
IRI_XMPMETADATA = 0x0424
};
enum LayerId : quint32 {
LI_MT16 = 0x4D743136, // 'Mt16',
LI_MT32 = 0x4D743332, // 'Mt32',
LI_MTRN = 0x4D74726E // 'Mtrn'
};
/*!
* \brief The PSDImageResourceBlock class
* Raw data of a PSD image resource block
*/
struct PSDImageResourceBlock {
QString name;
QByteArray data;
};
using PSDImageResourceSection = QHash<ImageResourceId, PSDImageResourceBlock>;
/*!
* \brief readPascalString
* Reads the Pascal string as defined in the PSD specification.
* \param s The stream.
* \param alignBytes Alignment of the string.
* \param size Number of stream bytes used.
* \return The string read.
*/
QString readPascalString(QDataStream &s, qint32 alignBytes = 1, qint32 *size = nullptr);
/*!
* \brief readImageResourceSection
* Reads the image resource section.
* \param s The stream.
* \param ok Pointer to the operation result variable.
* \return The image resource section raw data.
*/
PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok = nullptr);
#endif // PHOTOSHOP_P_H
+1 -181
View File
@@ -29,12 +29,12 @@
#include "fastmath_p.h"
#include "microexif_p.h"
#include "packbits_p.h"
#include "photoshop_p.h"
#include "psd_p.h"
#include "scanlineconverter_p.h"
#include "util_p.h"
#include <QColorSpace>
#include <QDataStream>
#include <QImage>
#include <QLoggingCategory>
@@ -99,41 +99,6 @@ namespace // Private.
#define NATIVE_CMYK (CMYK_FORMAT != QImage::Format_Invalid)
enum Signature : quint32 {
S_8BIM = 0x3842494D, // '8BIM'
S_8B64 = 0x38423634, // '8B64'
S_MeSa = 0x4D655361 // 'MeSa'
};
enum ColorMode : quint16 {
CM_BITMAP = 0,
CM_GRAYSCALE = 1,
CM_INDEXED = 2,
CM_RGB = 3,
CM_CMYK = 4,
CM_MULTICHANNEL = 7,
CM_DUOTONE = 8,
CM_LABCOLOR = 9,
};
enum ImageResourceId : quint16 {
IRI_RESOLUTIONINFO = 0x03ED,
IRI_ICCPROFILE = 0x040F,
IRI_TRANSPARENCYINDEX = 0x0417,
IRI_ALPHAIDENTIFIERS = 0x041D,
IRI_VERSIONINFO = 0x0421,
IRI_EXIFDATA1 = 0x0422,
IRI_EXIFDATA3 = 0x0423, // never seen
IRI_XMPMETADATA = 0x0424
};
enum LayerId : quint32 {
LI_MT16 = 0x4D743136, // 'Mt16',
LI_MT32 = 0x4D743332, // 'Mt32',
LI_MTRN = 0x4D74726E // 'Mtrn'
};
struct PSDHeader {
PSDHeader() {
memset(this, 0, sizeof(PSDHeader));
@@ -149,11 +114,6 @@ struct PSDHeader {
ushort color_mode;
};
struct PSDImageResourceBlock {
QString name;
QByteArray data;
};
/*!
* \brief The PSDDuotoneOptions struct
* \note You can decode the duotone data using the "Duotone Options"
@@ -172,7 +132,6 @@ struct PSDColorModeDataSection {
QList<QRgb> palette;
};
using PSDImageResourceSection = QHash<quint16, PSDImageResourceBlock>;
struct PSDLayerInfo {
qint64 size = -1;
@@ -273,145 +232,6 @@ static bool skip_section(QDataStream &s, bool psb = false)
return skip_data(s, section_length);
}
/*!
* \brief readPascalString
* Reads the Pascal string as defined in the PSD specification.
* \param s The stream.
* \param alignBytes Alignment of the string.
* \param size Number of stream bytes used.
* \return The string read.
*/
static QString readPascalString(QDataStream &s, qint32 alignBytes = 1, qint32 *size = nullptr)
{
qint32 tmp = 0;
if (size == nullptr)
size = &tmp;
quint8 stringSize;
s >> stringSize;
*size = sizeof(stringSize);
QString str;
if (stringSize > 0) {
QByteArray ba;
ba.resize(stringSize);
auto read = s.readRawData(ba.data(), ba.size());
if (read > 0) {
*size += read;
str = QString::fromLatin1(ba);
}
}
// align
if (alignBytes > 1)
if (auto pad = *size % alignBytes)
*size += s.skipRawData(alignBytes - pad);
return str;
}
/*!
* \brief readImageResourceSection
* Reads the image resource section.
* \param s The stream.
* \param ok Pointer to the operation result variable.
* \return The image resource section raw data.
*/
static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok = nullptr)
{
PSDImageResourceSection irs;
bool tmp = true;
if (ok == nullptr)
ok = &tmp;
*ok = true;
// Section size
quint32 tmpSize;
s >> tmpSize;
qint64 sectioSize = tmpSize;
// Reading Image resource block
for (auto size = sectioSize; size > 0;) {
#define DEC_SIZE(value) \
if ((size -= qint64(value)) < 0) { \
*ok = false; \
break; }
// Length Description
// -------------------------------------------------------------------
// 4 Signature: '8BIM'
// 2 Unique identifier for the resource. Image resource IDs
// contains a list of resource IDs used by Photoshop.
// Variable Name: Pascal string, padded to make the size even
// (a null name consists of two bytes of 0)
// 4 Actual size of resource data that follows
// Variable The resource data, described in the sections on the
// individual resource types. It is padded to make the size
// even.
quint32 signature;
s >> signature;
DEC_SIZE(sizeof(signature))
// NOTE: MeSa signature is not documented but found in some old PSD take from Photoshop 7.0 CD.
if (signature != S_8BIM && signature != S_MeSa) { // 8BIM and MeSa
qCDebug(LOG_PSDPLUGIN) << "Invalid Image Resource Block Signature!";
*ok = false;
break;
}
// id
quint16 id;
s >> id;
DEC_SIZE(sizeof(id))
// getting data
PSDImageResourceBlock irb;
// name
qint32 bytes = 0;
irb.name = readPascalString(s, 2, &bytes);
DEC_SIZE(bytes)
// data read
quint32 dataSize;
s >> dataSize;
DEC_SIZE(sizeof(dataSize))
if (auto dev = s.device()) {
if (dataSize > size) {
qCDebug(LOG_PSDPLUGIN) << "Invalid Image Resource Block Data Size!";
*ok = false;
break;
}
irb.data = deviceRead(dev, dataSize);
}
auto read = irb.data.size();
if (read > 0) {
DEC_SIZE(read)
}
if (read != qint64(dataSize)) {
qCDebug(LOG_PSDPLUGIN) << "Image Resource Block Read Error!";
*ok = false;
break;
}
if (auto pad = dataSize % 2) {
auto skipped = s.skipRawData(pad);
if (skipped > 0) {
DEC_SIZE(skipped);
}
}
// insert IRB
irs.insert(id, irb);
#undef DEC_SIZE
}
return irs;
}
PSDAdditionalLayerInfo readAdditionalLayer(QDataStream &s, bool *ok = nullptr)
{
PSDAdditionalLayerInfo li;