psd: duotone read

- New format added: Duotone
- Fix float to int conversion round issue
This commit is contained in:
Mirco Miranda 2022-04-11 21:07:23 +00:00 committed by Albert Astals Cid
parent c8a0806aab
commit 44fd6b7bc0
5 changed files with 69 additions and 21 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

View File

@ -23,7 +23,12 @@
* - 32-bit float image are converted to 16-bit integer image. * - 32-bit float image are converted to 16-bit integer image.
* NOTE: Qt 6.2 allow 32-bit float images (RGB only) * NOTE: Qt 6.2 allow 32-bit float images (RGB only)
* - Other color spaces cannot be read due to lack of QImage support for * - Other color spaces cannot be read due to lack of QImage support for
* color spaces other than RGB (and Grayscale). * color spaces other than RGB (and Grayscale): a conversion to
* RGB must be done.
* - The best way to convert between different color spaces is to use a
* color management engine (e.g. LittleCMS).
* - An approximate way is to ignore the color information and use
* literature formulas (possible but not recommended).
*/ */
#include "psd_p.h" #include "psd_p.h"
@ -76,6 +81,24 @@ struct PSDImageResourceBlock {
QByteArray data; QByteArray data;
}; };
/*!
* \brief The PSDDuotoneOptions struct
* \note You can decode the duotone data using the "Duotone Options"
* file format found in the "Photoshop File Format" specs.
*/
struct PSDDuotoneOptions {
QByteArray data;
};
/*!
* \brief The PSDColorModeDataSection struct
* Only indexed color and duotone have color mode data.
*/
struct PSDColorModeDataSection {
PSDDuotoneOptions duotone;
QVector<QRgb> palette;
};
using PSDImageResourceSection = QHash<quint16, PSDImageResourceBlock>; using PSDImageResourceSection = QHash<quint16, PSDImageResourceBlock>;
/*! /*!
@ -130,6 +153,7 @@ static QString readPascalString(QDataStream &s, qint32 alignBytes = 1, qint32 *s
* \brief readImageResourceSection * \brief readImageResourceSection
* Reads the image resource section. * Reads the image resource section.
* \param s The stream. * \param s The stream.
* \param ok Pointer to the operation result variable.
* \return The image resource section raw data. * \return The image resource section raw data.
*/ */
static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok = nullptr) static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok = nullptr)
@ -168,7 +192,7 @@ static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok
quint32 signature; quint32 signature;
s >> signature; s >> signature;
size -= sizeof(signature); size -= sizeof(signature);
// NOTE: MeSa signature is not documented but found in some old PSD found in Photoshop 7.0 CD. // NOTE: MeSa signature is not documented but found in some old PSD take from Photoshop 7.0 CD.
if (signature != 0x3842494D && signature != 0x4D655361) { // 8BIM and MeSa if (signature != 0x3842494D && signature != 0x4D655361) { // 8BIM and MeSa
qDebug() << "Invalid Image Resource Block Signature!"; qDebug() << "Invalid Image Resource Block Signature!";
*ok = false; *ok = false;
@ -223,31 +247,45 @@ static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok
return irs; return irs;
} }
QVector<QRgb> colorTable(QDataStream &s, bool *ok = nullptr) /*!
* \brief readColorModeDataSection
* Read the color mode section
* \param s The stream.
* \param ok Pointer to the operation result variable.
* \return The color mode section.
*/
PSDColorModeDataSection readColorModeDataSection(QDataStream &s, bool *ok = nullptr)
{ {
QVector<QRgb> palette; PSDColorModeDataSection cms;
bool tmp = false; bool tmp = false;
if (ok == nullptr) if (ok == nullptr)
ok = &tmp; ok = &tmp;
*ok = true; *ok = true;
qint32 size; qint32 size;
s >> size; s >> size;
if (size != 768) { if (size != 768) { // read the duotone data (524 bytes)
if (s.skipRawData(size) != size) // NOTE: A RGB/Gray float image has a 112 bytes ColorModeData that could be
// the "32-bit Toning Options" of Photoshop (starts with 'hdrt').
// Official Adobe specification tells "Only indexed color and duotone
// (see the mode field in the File header section) have color mode data.".
// See test case images 32bit_grayscale.psd and 32bit-rgb.psd
auto&& ba = cms.duotone.data;
ba.resize(size);
if (s.readRawData(ba.data(), ba.size()) != ba.size())
*ok = false; *ok = false;
return palette; }
else { // read the palette (768 bytes)
auto&& palette = cms.palette;
QVector<quint8> vect(size);
for (auto&& v : vect)
s >> v;
for (qsizetype i = 0, n = vect.size()/3; i < n; ++i)
palette.append(qRgb(vect.at(i), vect.at(n+i), vect.at(n+n+i)));
} }
QVector<quint8> vect(size); return cms;
for (auto&& v : vect)
s >> v;
for (qsizetype i = 0, n = vect.size()/3; i < n; ++i)
palette.append(qRgb(vect.at(i), vect.at(n+i), vect.at(n+n+i)));
return palette;
} }
/*! /*!
@ -312,7 +350,7 @@ static bool hasMergedData(const PSDImageResourceSection& irs)
* Set the image resolution. * Set the image resolution.
* \param img The image. * \param img The image.
* \param irs The image resource section. * \param irs The image resource section.
* \return True on success or if the block does not exists, otherwise false. * \return True on success, otherwise false.
*/ */
static bool setResolution(QImage& img, const PSDImageResourceSection& irs) static bool setResolution(QImage& img, const PSDImageResourceSection& irs)
{ {
@ -346,7 +384,7 @@ static bool setResolution(QImage& img, const PSDImageResourceSection& irs)
* Search for transparency index block and, if found, changes the alpha of the value at the given index. * Search for transparency index block and, if found, changes the alpha of the value at the given index.
* \param img The image. * \param img The image.
* \param irs The image resource section. * \param irs The image resource section.
* \return True on success or if the block does not exists, otherwise false. * \return True on success, otherwise false.
*/ */
static bool setTransparencyIndex(QImage& img, const PSDImageResourceSection& irs) static bool setTransparencyIndex(QImage& img, const PSDImageResourceSection& irs)
{ {
@ -408,6 +446,7 @@ static bool IsSupported(const PSDHeader &header)
if (header.color_mode != CM_RGB && if (header.color_mode != CM_RGB &&
header.color_mode != CM_GRAYSCALE && header.color_mode != CM_GRAYSCALE &&
header.color_mode != CM_INDEXED && header.color_mode != CM_INDEXED &&
header.color_mode != CM_DUOTONE &&
header.color_mode != CM_BITMAP) { header.color_mode != CM_BITMAP) {
return false; return false;
} }
@ -499,6 +538,7 @@ static QImage::Format imageFormat(const PSDHeader &header)
format = header.channel_count < 4 ? QImage::Format_RGB888 : QImage::Format_RGBA8888; format = header.channel_count < 4 ? QImage::Format_RGB888 : QImage::Format_RGBA8888;
break; break;
case CM_GRAYSCALE: case CM_GRAYSCALE:
case CM_DUOTONE:
format = header.depth == 8 ? QImage::Format_Grayscale8 : QImage::Format_Grayscale16; format = header.depth == 8 ? QImage::Format_Grayscale8 : QImage::Format_Grayscale16;
break; break;
case CM_INDEXED: case CM_INDEXED:
@ -571,7 +611,8 @@ inline void planarToChunchyFloat(uchar *target, const char* source, qint32 width
auto t = reinterpret_cast<quint16*>(target); auto t = reinterpret_cast<quint16*>(target);
for (qint32 x = 0; x < width; ++x) { for (qint32 x = 0; x < width; ++x) {
auto tmp = xchg(s[x]); auto tmp = xchg(s[x]);
t[x*cn+c] = quint16(*reinterpret_cast<float*>(&tmp) * 65535); t[x*cn+c] = std::min(quint16(*reinterpret_cast<float*>(&tmp) * std::numeric_limits<quint16>::max() + 0.5),
std::numeric_limits<quint16>::max());
} }
} }
@ -591,7 +632,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
bool ok = false; bool ok = false;
// Color Mode Data section // Color Mode Data section
auto palette = colorTable(stream, &ok); auto cmds = readColorModeDataSection(stream, &ok);
if (!ok) { if (!ok) {
qDebug() << "Error while skipping Color Mode Data section"; qDebug() << "Error while skipping Color Mode Data section";
return false; return false;
@ -638,8 +679,8 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
return false; return false;
} }
img.fill(qRgb(0, 0, 0)); img.fill(qRgb(0, 0, 0));
if (!palette.isEmpty()) { if (!cmds.palette.isEmpty()) {
img.setColorTable(palette); img.setColorTable(cmds.palette);
setTransparencyIndex(img, irs); setTransparencyIndex(img, irs);
} }
@ -722,6 +763,13 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
// qDebug() << "No XMP data found!"; // qDebug() << "No XMP data found!";
} }
// Duotone images: color data contains the duotone specification (not documented).
// Other applications that read Photoshop files can treat a duotone image as a gray image,
// and just preserve the contents of the duotone information when reading and writing the file.
if (!cmds.duotone.data.isEmpty()) {
img.setText(QStringLiteral("PSDDuotoneOptions"), QString::fromUtf8(cmds.duotone.data.toHex()));
}
return true; return true;
} }