mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2026-09-20 06:37:00 -04:00
EXR: store of "adobe_rsrc" attribute with XMP/EXIF/Resolution data
This commit is contained in:
@@ -384,9 +384,12 @@ The following defines can be defined in cmake to modify the behavior of the
|
|||||||
plugin:
|
plugin:
|
||||||
- `EXR_CONVERT_TO_SRGB`: the linear data is converted to sRGB on read to
|
- `EXR_CONVERT_TO_SRGB`: the linear data is converted to sRGB on read to
|
||||||
accommodate programs that do not support color profiles.
|
accommodate programs that do not support color profiles.
|
||||||
- `EXR_DISABLE_XMP_ATTRIBUTE`: disables the stores XMP values in a non-standard
|
- `EXR_DISABLE_XMP_ATTRIBUTE`: disables the storage of XMP values in a
|
||||||
attribute named "xmp". Note that Gimp reads the "xmp" attribute and Darktable
|
non-standard attribute named "xmp". Note that Gimp reads the "xmp" attribute
|
||||||
writes it as well.
|
and Darktable writes it as well.
|
||||||
|
- `EXR_DISABLE_ADOBE_ATTRIBUTE`: disables the storage of non-standard
|
||||||
|
attribute "adobe_rsrc". Note that Photoshop uses "adobe_rsrc" attribute to
|
||||||
|
read and write XMP/EXIF/IPTC data.
|
||||||
|
|
||||||
The plugin can set the following additional metadata:
|
The plugin can set the following additional metadata:
|
||||||
- `EXRLayerName`: A string containing the name of the EXR layer used to decode
|
- `EXRLayerName`: A string containing the name of the EXR layer used to decode
|
||||||
|
|||||||
+121
-24
@@ -21,8 +21,8 @@
|
|||||||
*/
|
*/
|
||||||
//#define EXR_CONVERT_TO_SRGB // default: commented -> you should define it in your cmake file
|
//#define EXR_CONVERT_TO_SRGB // default: commented -> you should define it in your cmake file
|
||||||
|
|
||||||
/* *** EXR_STORE_XMP_ATTRIBUTE ***
|
/* *** EXR_DISABLE_XMP_ATTRIBUTE ***
|
||||||
* If defined, disables the stores XMP values in a non-standard attribute named "xmp".
|
* If defined, disables the storage of XMP values in the non-standard attribute named "xmp"
|
||||||
* The QImage metadata used is "XML:com.adobe.xmp".
|
* The QImage metadata used is "XML:com.adobe.xmp".
|
||||||
* NOTE: The use of non-standard attributes is possible but discouraged by the specification. However,
|
* NOTE: The use of non-standard attributes is possible but discouraged by the specification. However,
|
||||||
* metadata is essential for good image management and programs like darktable also set this
|
* metadata is essential for good image management and programs like darktable also set this
|
||||||
@@ -30,6 +30,15 @@
|
|||||||
*/
|
*/
|
||||||
//#define EXR_DISABLE_XMP_ATTRIBUTE // default: commented -> you should define it in your cmake file
|
//#define EXR_DISABLE_XMP_ATTRIBUTE // default: commented -> you should define it in your cmake file
|
||||||
|
|
||||||
|
/* *** EXR_DISABLE_ADOBE_ATTRIBUTE ***
|
||||||
|
* If defined, disables the storage of non-standard attribute "adobe_rsrc".
|
||||||
|
* NOTE: The use of non-standard attributes is possible but discouraged by the specification. However,
|
||||||
|
* metadata is essential for good image management and programs like Photoshop also set this
|
||||||
|
* attribute. Photoshop uses "adobe_rsrc" attribute to read/write XMP/EXIF/IPTC data.
|
||||||
|
*/
|
||||||
|
//#define EXR_DISABLE_ADOBE_ATTRIBUTE // default: commented -> you should define it in your cmake file
|
||||||
|
|
||||||
|
|
||||||
/* *** EXR_MAX_IMAGE_WIDTH and EXR_MAX_IMAGE_HEIGHT ***
|
/* *** EXR_MAX_IMAGE_WIDTH and EXR_MAX_IMAGE_HEIGHT ***
|
||||||
* The maximum size in pixel allowed by the plugin.
|
* The maximum size in pixel allowed by the plugin.
|
||||||
*/
|
*/
|
||||||
@@ -92,6 +101,12 @@ Q_LOGGING_CATEGORY(LOG_EXRPLUGIN, "kf.imageformats.plugins.exr", QtDebugMsg)
|
|||||||
Q_LOGGING_CATEGORY(LOG_EXRPLUGIN, "kf.imageformats.plugins.exr", QtWarningMsg)
|
Q_LOGGING_CATEGORY(LOG_EXRPLUGIN, "kf.imageformats.plugins.exr", QtWarningMsg)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef EXR_DISABLE_ADOBE_ATTRIBUTE
|
||||||
|
#if QT_VERSION_CHECK(OPENEXR_VERSION_MAJOR, OPENEXR_VERSION_MINOR, OPENEXR_VERSION_PATCH) < QT_VERSION_CHECK(3, 3, 0)
|
||||||
|
#define EXR_DISABLE_ADOBE_ATTRIBUTE
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
class K_IStream : public Imf::IStream
|
class K_IStream : public Imf::IStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -213,6 +228,7 @@ EXRHandler::EXRHandler()
|
|||||||
, m_imageCount(0)
|
, m_imageCount(0)
|
||||||
, m_startPos(-1)
|
, m_startPos(-1)
|
||||||
, m_subType(EXR_SUBFORMAT_RGB)
|
, m_subType(EXR_SUBFORMAT_RGB)
|
||||||
|
, m_transformation(QImageIOHandler::TransformationNone)
|
||||||
{
|
{
|
||||||
// Set the number of threads to use (0 is allowed)
|
// Set the number of threads to use (0 is allowed)
|
||||||
Imf::setGlobalThreadCount(QThread::idealThreadCount() / 2);
|
Imf::setGlobalThreadCount(QThread::idealThreadCount() / 2);
|
||||||
@@ -292,6 +308,27 @@ static void printAttributes(const Imf::Header &h)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static PSDImageResourceSection readPSDImageResourceSection(const Imf::Header &header)
|
||||||
|
{
|
||||||
|
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) {
|
||||||
|
return irs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return{};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief readMetadata
|
* \brief readMetadata
|
||||||
* Reads EXR attributes from the \a header and set its as metadata in the \a image.
|
* Reads EXR attributes from the \a header and set its as metadata in the \a image.
|
||||||
@@ -389,24 +426,12 @@ static void readMetadata(const Imf::Header &header, QImage &image)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Photoshop image resource section
|
// Photoshop image resource section
|
||||||
if (auto adobe = header.findTypedAttribute<Imf::OpaqueAttribute>("adobe_rsrc")) {
|
auto irs = readPSDImageResourceSection(header);
|
||||||
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)) {
|
if (irs.contains(IRI_EXIFDATA1)) {
|
||||||
auto exif = MicroExif::fromByteArray(irs.value(IRI_EXIFDATA1).data);
|
auto exif = MicroExif::fromByteArray(irs.value(IRI_EXIFDATA1).data);
|
||||||
exif.updateImageMetadata(image);
|
exif.updateImageMetadata(image);
|
||||||
exif.updateImageResolution(image);
|
exif.updateImageResolution(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (irs.contains(IRI_XMPMETADATA)) {
|
if (irs.contains(IRI_XMPMETADATA)) {
|
||||||
auto irb = irs.value(IRI_XMPMETADATA);
|
auto irb = irs.value(IRI_XMPMETADATA);
|
||||||
auto xmp = QString::fromUtf8(irb.data);
|
auto xmp = QString::fromUtf8(irb.data);
|
||||||
@@ -414,8 +439,6 @@ static void readMetadata(const Imf::Header &header, QImage &image)
|
|||||||
image.setText(QStringLiteral(META_KEY_XMP_ADOBE), xmp);
|
image.setText(QStringLiteral(META_KEY_XMP_ADOBE), xmp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief readColorSpace
|
* \brief readColorSpace
|
||||||
@@ -580,8 +603,11 @@ bool makePreview(const QImage &image, Imf::Array2D<Imf::PreviewRgba> &pixels)
|
|||||||
* \brief setMetadata
|
* \brief setMetadata
|
||||||
* Reades the metadata from \a image and set its as attributes in the \a header.
|
* Reades the metadata from \a image and set its as attributes in the \a header.
|
||||||
*/
|
*/
|
||||||
static void setMetadata(const QImage &image, Imf::Header &header)
|
static void setMetadata(const QImage &image, Imf::Header &header, const QImageIOHandler::Transformation &transformation)
|
||||||
{
|
{
|
||||||
|
PSDImageResourceSection irs;
|
||||||
|
QString xmpData;
|
||||||
|
|
||||||
auto dateTime = QDateTime::currentDateTime();
|
auto dateTime = QDateTime::currentDateTime();
|
||||||
for (auto &&key : image.textKeys()) {
|
for (auto &&key : image.textKeys()) {
|
||||||
auto text = image.text(key);
|
auto text = image.text(key);
|
||||||
@@ -612,11 +638,9 @@ static void setMetadata(const QImage &image, Imf::Header &header)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef EXR_DISABLE_XMP_ATTRIBUTE // warning: Non-standard attribute!
|
|
||||||
if (!key.compare(QStringLiteral(META_KEY_XMP_ADOBE), Qt::CaseInsensitive)) {
|
if (!key.compare(QStringLiteral(META_KEY_XMP_ADOBE), Qt::CaseInsensitive)) {
|
||||||
header.insert("xmp", Imf::StringAttribute(text.toStdString()));
|
xmpData = text;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!key.compare(QStringLiteral(META_KEY_MANUFACTURER), Qt::CaseInsensitive)) {
|
if (!key.compare(QStringLiteral(META_KEY_MANUFACTURER), Qt::CaseInsensitive)) {
|
||||||
header.insert("cameraMake", Imf::StringAttribute(text.toStdString()));
|
header.insert("cameraMake", Imf::StringAttribute(text.toStdString()));
|
||||||
@@ -676,6 +700,41 @@ static void setMetadata(const QImage &image, Imf::Header &header)
|
|||||||
header.insert("pixelAspectRatio", Imf::FloatAttribute(float(image.dotsPerMeterY()) / float(image.dotsPerMeterX())));
|
header.insert("pixelAspectRatio", Imf::FloatAttribute(float(image.dotsPerMeterY()) / float(image.dotsPerMeterX())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef EXR_DISABLE_XMP_ATTRIBUTE
|
||||||
|
if (!xmpData.isEmpty()) {
|
||||||
|
header.insert("xmp", Imf::StringAttribute(xmpData.toStdString()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef EXR_DISABLE_ADOBE_ATTRIBUTE
|
||||||
|
auto resInfo = PSDResolutionInfoBlock::fromImage(image);
|
||||||
|
if (resInfo.isValid()) {
|
||||||
|
PSDImageResourceBlock irb;
|
||||||
|
irb.data = resInfo.toByteArray();
|
||||||
|
if (!irb.data.isEmpty())
|
||||||
|
irs.insert(IRI_RESOLUTIONINFO, irb);
|
||||||
|
}
|
||||||
|
auto exif = MicroExif::fromImage(image);
|
||||||
|
if (!exif.isEmpty()) {
|
||||||
|
exif.setTransformation(transformation);
|
||||||
|
PSDImageResourceBlock irb;
|
||||||
|
irb.data = exif.toByteArray(QDataStream::BigEndian);
|
||||||
|
irs.insert(IRI_EXIFDATA1, irb);
|
||||||
|
}
|
||||||
|
if (!xmpData.isEmpty()) {
|
||||||
|
PSDImageResourceBlock irb;
|
||||||
|
irb.data = xmpData.toUtf8();
|
||||||
|
irs.insert(IRI_XMPMETADATA, irb);
|
||||||
|
}
|
||||||
|
if (!irs.isEmpty()) {
|
||||||
|
bool ok = false;
|
||||||
|
auto ba = irs.toByteArray(&ok);
|
||||||
|
if (ok) {
|
||||||
|
header.insert("adobe_rsrc", Imf::OpaqueAttribute("adobe_rsrc", ba.size(), ba.data()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// set default chroma (default constructor ITU-R BT.709-3 -> sRGB)
|
// set default chroma (default constructor ITU-R BT.709-3 -> sRGB)
|
||||||
// The image is converted to Linear sRGB so, the chroma is the default EXR value.
|
// The image is converted to Linear sRGB so, the chroma is the default EXR value.
|
||||||
// If a file doesn’t have a chromaticities attribute, display software should assume that the
|
// If a file doesn’t have a chromaticities attribute, display software should assume that the
|
||||||
@@ -718,7 +777,7 @@ bool EXRHandler::write(const QImage &image)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set metadata (EXR attributes)
|
// set metadata (EXR attributes)
|
||||||
setMetadata(image, header);
|
setMetadata(image, header, m_transformation);
|
||||||
|
|
||||||
// write the EXR
|
// write the EXR
|
||||||
K_OStream ostr(device());
|
K_OStream ostr(device());
|
||||||
@@ -791,6 +850,15 @@ void EXRHandler::setOption(ImageOption option, const QVariant &value)
|
|||||||
m_subType = EXR_SUBFORMAT_RGB;
|
m_subType = EXR_SUBFORMAT_RGB;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifndef EXR_DISABLE_ADOBE_ATTRIBUTE
|
||||||
|
if (option == QImageIOHandler::ImageTransformation) {
|
||||||
|
auto ok = false;
|
||||||
|
auto t = value.toInt(&ok);
|
||||||
|
if (ok) {
|
||||||
|
m_transformation = QImageIOHandler::Transformation(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EXRHandler::supportsOption(ImageOption option) const
|
bool EXRHandler::supportsOption(ImageOption option) const
|
||||||
@@ -815,6 +883,11 @@ bool EXRHandler::supportsOption(ImageOption option) const
|
|||||||
if (option == QImageIOHandler::SupportedSubTypes) {
|
if (option == QImageIOHandler::SupportedSubTypes) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
#ifndef EXR_DISABLE_ADOBE_ATTRIBUTE
|
||||||
|
if (option == QImageIOHandler::ImageTransformation) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -870,13 +943,37 @@ QVariant EXRHandler::option(ImageOption option) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (option == QImageIOHandler::SupportedSubTypes) {
|
if (option == QImageIOHandler::SupportedSubTypes) {
|
||||||
return QVariant::fromValue(QList<QByteArray>() << EXR_SUBFORMAT_RGB << EXR_SUBFORMAT_YC);
|
v = QVariant::fromValue(QList<QByteArray>() << EXR_SUBFORMAT_RGB << EXR_SUBFORMAT_YC);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (option == QImageIOHandler::SubType) {
|
if (option == QImageIOHandler::SubType) {
|
||||||
return QVariant::fromValue(m_subType);
|
v = QVariant::fromValue(m_subType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef EXR_DISABLE_ADOBE_ATTRIBUTE
|
||||||
|
if (option == QImageIOHandler::ImageTransformation) {
|
||||||
|
if (auto d = device()) {
|
||||||
|
// transactions works on both random and sequential devices
|
||||||
|
d->startTransaction();
|
||||||
|
if (m_startPos > -1) {
|
||||||
|
d->seek(m_startPos);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
K_IStream istr(d);
|
||||||
|
Imf::RgbaInputFile file(istr);
|
||||||
|
auto irs = readPSDImageResourceSection(file.header());
|
||||||
|
if (irs.contains(IRI_EXIFDATA1)) {
|
||||||
|
auto exif = MicroExif::fromByteArray(irs.value(IRI_EXIFDATA1).data);
|
||||||
|
v = int(exif.transformation());
|
||||||
|
}
|
||||||
|
} catch (const std::exception &) {
|
||||||
|
// broken file or unsupported version
|
||||||
|
}
|
||||||
|
d->rollbackTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,12 @@ private:
|
|||||||
* - YC: converts RGB data into luminance (Y) and 2x2 subsampled chroma (RY, BY), cutting raw color data by ~50% for significantly smaller file sizes.
|
* - YC: converts RGB data into luminance (Y) and 2x2 subsampled chroma (RY, BY), cutting raw color data by ~50% for significantly smaller file sizes.
|
||||||
*/
|
*/
|
||||||
QByteArray m_subType;
|
QByteArray m_subType;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief m_transformation
|
||||||
|
* The Qt image transformation.
|
||||||
|
*/
|
||||||
|
QImageIOHandler::Transformation m_transformation;
|
||||||
};
|
};
|
||||||
|
|
||||||
class EXRPlugin : public QImageIOPlugin
|
class EXRPlugin : public QImageIOPlugin
|
||||||
|
|||||||
@@ -84,6 +84,8 @@
|
|||||||
#define EXIF_TAG_SIZEOF(dataType) (quint16(dataType) & 0x3F)
|
#define EXIF_TAG_SIZEOF(dataType) (quint16(dataType) & 0x3F)
|
||||||
#define EXIF_TAG_DATATYPE(dataType) (quint16(dataType) >> 6)
|
#define EXIF_TAG_DATATYPE(dataType) (quint16(dataType) >> 6)
|
||||||
|
|
||||||
|
#define GPS_GPSVERSION_VALUE QList<quint8>{0x02, 0x04, 0x00, 0x00}
|
||||||
|
|
||||||
enum class ExifTagType : quint16 {
|
enum class ExifTagType : quint16 {
|
||||||
// Base data types
|
// Base data types
|
||||||
Byte = EXIF_TAG_VALUE(1, 1),
|
Byte = EXIF_TAG_VALUE(1, 1),
|
||||||
@@ -1325,7 +1327,7 @@ QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder, c
|
|||||||
QDataStream ds(&ba, QIODevice::WriteOnly);
|
QDataStream ds(&ba, QIODevice::WriteOnly);
|
||||||
ds.setByteOrder(byteOrder);
|
ds.setByteOrder(byteOrder);
|
||||||
auto gpsTags = m_gpsTags;
|
auto gpsTags = m_gpsTags;
|
||||||
gpsTags.insert(GPS_GPSVERSION, QByteArray("2400"));
|
gpsTags.insert(GPS_GPSVERSION, QVariant::fromValue(GPS_GPSVERSION_VALUE));
|
||||||
TagPos positions;
|
TagPos positions;
|
||||||
if (!writeIfd(ds, version, gpsTags, positions, 0, staticGpsTagTypes))
|
if (!writeIfd(ds, version, gpsTags, positions, 0, staticGpsTagTypes))
|
||||||
return {};
|
return {};
|
||||||
@@ -1723,7 +1725,7 @@ void MicroExif::updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags, const
|
|||||||
tiffTags.remove(EXIF_GPSIFD);
|
tiffTags.remove(EXIF_GPSIFD);
|
||||||
} else {
|
} else {
|
||||||
tiffTags.insert(EXIF_GPSIFD, quint32());
|
tiffTags.insert(EXIF_GPSIFD, quint32());
|
||||||
gpsTags.insert(GPS_GPSVERSION, QByteArray("2400"));
|
gpsTags.insert(GPS_GPSVERSION, QVariant::fromValue(GPS_GPSVERSION_VALUE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,23 @@ QString readPascalString(QDataStream &s, qint32 alignBytes, qint32 *size)
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool writePascalString(const QString &str, QDataStream &s, qint32 alignBytes)
|
||||||
|
{
|
||||||
|
auto data = str.toLatin1();
|
||||||
|
if(data.size() > 250) {
|
||||||
|
data = data.left(250);
|
||||||
|
}
|
||||||
|
auto sz = data.size();
|
||||||
|
s << quint8(sz);
|
||||||
|
if (sz && s.writeRawData(data.data(), sz) != sz) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for(alignBytes = std::max(1, alignBytes), sz += 1; sz % alignBytes; ++sz) {
|
||||||
|
s << char();
|
||||||
|
}
|
||||||
|
return s.status() == QDataStream::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok)
|
PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok)
|
||||||
{
|
{
|
||||||
PSDImageResourceSection irs;
|
PSDImageResourceSection irs;
|
||||||
@@ -138,3 +155,111 @@ PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok)
|
|||||||
|
|
||||||
return irs;
|
return irs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool writeImageResourceSection(const PSDImageResourceSection &irs, QDataStream &s)
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
auto ba = irs.toByteArray(&ok);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
s << quint32(ba.size());
|
||||||
|
if (s.writeRawData(ba.data(), ba.size()) != ba.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (s.status() == QDataStream::Ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QByteArray PSDImageResourceSection::toByteArray(bool *ok) const
|
||||||
|
{
|
||||||
|
QByteArray ba;
|
||||||
|
|
||||||
|
bool tmp = true;
|
||||||
|
if (ok == nullptr)
|
||||||
|
ok = &tmp;
|
||||||
|
*ok = true;
|
||||||
|
|
||||||
|
if (!isEmpty()) {
|
||||||
|
QDataStream s(&ba, QDataStream::WriteOnly);
|
||||||
|
s.setByteOrder(QDataStream::BigEndian);
|
||||||
|
|
||||||
|
auto ids = keys();
|
||||||
|
for(auto &&id : ids) {
|
||||||
|
auto irb = value(id);
|
||||||
|
if (irb.data.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// signature
|
||||||
|
s << quint32(S_8BIM);
|
||||||
|
|
||||||
|
// resource id
|
||||||
|
s << quint16(id);
|
||||||
|
|
||||||
|
// resource name (2 bytes aligned)
|
||||||
|
if (!writePascalString(irb.name, s, 2)) {
|
||||||
|
*ok = false;
|
||||||
|
return{};
|
||||||
|
}
|
||||||
|
|
||||||
|
// data (2 bytes aligned)
|
||||||
|
auto sz = irb.data.size();
|
||||||
|
s << quint32(sz);
|
||||||
|
if (s.writeRawData(irb.data.data(), sz) != sz) {
|
||||||
|
*ok = false;
|
||||||
|
return{};
|
||||||
|
}
|
||||||
|
if (sz % 2) {
|
||||||
|
s << char();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.status() != QDataStream::Ok) {
|
||||||
|
*ok = false;
|
||||||
|
return{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_ASSERT(ba.size() % 2 == 0);
|
||||||
|
return ba;
|
||||||
|
}
|
||||||
|
|
||||||
|
PSDResolutionInfoBlock::PSDResolutionInfoBlock(qint32 ppmX, qint32 ppmY)
|
||||||
|
: m_ppmX(ppmX)
|
||||||
|
, m_ppmY(ppmY)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PSDResolutionInfoBlock::isValid() const
|
||||||
|
{
|
||||||
|
return m_ppmX > 0 && m_ppmY > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PSDResolutionInfoBlock PSDResolutionInfoBlock::fromImage(const QImage &image)
|
||||||
|
{
|
||||||
|
return PSDResolutionInfoBlock(image.dotsPerMeterX(), image.dotsPerMeterY());
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray PSDResolutionInfoBlock::toByteArray() const
|
||||||
|
{
|
||||||
|
QByteArray ba;
|
||||||
|
QDataStream ds(&ba, QIODevice::WriteOnly);
|
||||||
|
ds.setByteOrder(QDataStream::BigEndian);
|
||||||
|
|
||||||
|
auto hres = qRoundOrZero(dppm2dpi(m_ppmX) * 65536);
|
||||||
|
ds << hres;
|
||||||
|
ds << quint16(1); // dpi
|
||||||
|
ds << quint16(2); // cm (display)
|
||||||
|
auto vres = qRoundOrZero(dppm2dpi(m_ppmY) * 65536);
|
||||||
|
ds << vres;
|
||||||
|
ds << quint16(1);
|
||||||
|
ds << quint16(2);
|
||||||
|
|
||||||
|
if (hres == 0 || vres == 0) {
|
||||||
|
return{};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ba;
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QImage>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
enum Signature : quint32 {
|
enum Signature : quint32 {
|
||||||
@@ -55,7 +56,50 @@ struct PSDImageResourceBlock {
|
|||||||
QByteArray data;
|
QByteArray data;
|
||||||
};
|
};
|
||||||
|
|
||||||
using PSDImageResourceSection = QHash<ImageResourceId, PSDImageResourceBlock>;
|
class PSDImageResourceSection : public QHash<ImageResourceId, PSDImageResourceBlock>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PSDImageResourceSection() : QHash<ImageResourceId, PSDImageResourceBlock>() {}
|
||||||
|
PSDImageResourceSection(const PSDImageResourceSection& other) = default;
|
||||||
|
PSDImageResourceSection& operator =(const PSDImageResourceSection& other) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief toByteArray
|
||||||
|
* \param ok Pointer to the operation result variable.
|
||||||
|
* \return The binary IRB to be written into the PSD file.
|
||||||
|
*/
|
||||||
|
QByteArray toByteArray(bool *ok = nullptr) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PSDResolutionInfoBlock
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PSDResolutionInfoBlock(qint32 ppmX, qint32 ppmY);
|
||||||
|
PSDResolutionInfoBlock(const PSDResolutionInfoBlock& other) = default;
|
||||||
|
PSDResolutionInfoBlock& operator =(const PSDResolutionInfoBlock& other) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief isValid
|
||||||
|
* \return true if both m_ppmX and m_ppmY are grater than 0. Otherwise false.
|
||||||
|
*/
|
||||||
|
bool isValid() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief fromImage
|
||||||
|
* Initialize the class using the image resolution.
|
||||||
|
*/
|
||||||
|
static PSDResolutionInfoBlock fromImage(const QImage& image);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief toByteArray
|
||||||
|
* \return The binary data ready for the IMage Resource Section.
|
||||||
|
*/
|
||||||
|
QByteArray toByteArray() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
qint32 m_ppmX;
|
||||||
|
qint32 m_ppmY;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -68,6 +112,16 @@ using PSDImageResourceSection = QHash<ImageResourceId, PSDImageResourceBlock>;
|
|||||||
*/
|
*/
|
||||||
QString readPascalString(QDataStream &s, qint32 alignBytes = 1, qint32 *size = nullptr);
|
QString readPascalString(QDataStream &s, qint32 alignBytes = 1, qint32 *size = nullptr);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief writePascalString
|
||||||
|
* Writes the Pascal string as defined in the PSD specification.
|
||||||
|
* \param str The string to be written.
|
||||||
|
* \param s The stream.
|
||||||
|
* \param alignBytes Alignment of the string.
|
||||||
|
* \return True on success, otherwise false.
|
||||||
|
*/
|
||||||
|
bool writePascalString(const QString& str, QDataStream &s, qint32 alignBytes = 1);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief readImageResourceSection
|
* \brief readImageResourceSection
|
||||||
* Reads the image resource section.
|
* Reads the image resource section.
|
||||||
@@ -77,4 +131,13 @@ QString readPascalString(QDataStream &s, qint32 alignBytes = 1, qint32 *size = n
|
|||||||
*/
|
*/
|
||||||
PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok = nullptr);
|
PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok = nullptr);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief writeImageResourceSection
|
||||||
|
* Writes the image resource section.
|
||||||
|
* \param irs The image resource section raw data.
|
||||||
|
* \param s The stream.
|
||||||
|
* \return True on success, otherwise false.
|
||||||
|
*/
|
||||||
|
bool writeImageResourceSection(const PSDImageResourceSection& irs, QDataStream &s);
|
||||||
|
|
||||||
#endif // PHOTOSHOP_P_H
|
#endif // PHOTOSHOP_P_H
|
||||||
|
|||||||
Reference in New Issue
Block a user