mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2026-09-22 23:17:00 -04:00
EXR: support for metadata saved by Photoshop
This commit is contained in:
committed by
Mirco Miranda
parent
43e6efb0e5
commit
6a3c7f2c86
@@ -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)
|
||||
|
||||
##################################
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user