diff --git a/autotests/read/psd/orientation1.psd b/autotests/read/psd/orientation1.psd new file mode 100644 index 0000000..5cf13b4 Binary files /dev/null and b/autotests/read/psd/orientation1.psd differ diff --git a/autotests/read/psd/orientation1.psd.json b/autotests/read/psd/orientation1.psd.json new file mode 100644 index 0000000..e09eb63 --- /dev/null +++ b/autotests/read/psd/orientation1.psd.json @@ -0,0 +1,11 @@ +[ + { + "fileName" : "orientation_all.png", + "metadata" : [ + { + "key" : "Software" , + "value" : "LIFE Pro 2.18.5 (Windows)" + } + ] + } +] diff --git a/autotests/read/psd/orientation2.psd b/autotests/read/psd/orientation2.psd new file mode 100644 index 0000000..ee2dc04 Binary files /dev/null and b/autotests/read/psd/orientation2.psd differ diff --git a/autotests/read/psd/orientation2.psd.json b/autotests/read/psd/orientation2.psd.json new file mode 100644 index 0000000..e09eb63 --- /dev/null +++ b/autotests/read/psd/orientation2.psd.json @@ -0,0 +1,11 @@ +[ + { + "fileName" : "orientation_all.png", + "metadata" : [ + { + "key" : "Software" , + "value" : "LIFE Pro 2.18.5 (Windows)" + } + ] + } +] diff --git a/autotests/read/psd/orientation3.psd b/autotests/read/psd/orientation3.psd new file mode 100644 index 0000000..aaa1cd6 Binary files /dev/null and b/autotests/read/psd/orientation3.psd differ diff --git a/autotests/read/psd/orientation3.psd.json b/autotests/read/psd/orientation3.psd.json new file mode 100644 index 0000000..e09eb63 --- /dev/null +++ b/autotests/read/psd/orientation3.psd.json @@ -0,0 +1,11 @@ +[ + { + "fileName" : "orientation_all.png", + "metadata" : [ + { + "key" : "Software" , + "value" : "LIFE Pro 2.18.5 (Windows)" + } + ] + } +] diff --git a/autotests/read/psd/orientation4.psd b/autotests/read/psd/orientation4.psd new file mode 100644 index 0000000..1a33634 Binary files /dev/null and b/autotests/read/psd/orientation4.psd differ diff --git a/autotests/read/psd/orientation4.psd.json b/autotests/read/psd/orientation4.psd.json new file mode 100644 index 0000000..e09eb63 --- /dev/null +++ b/autotests/read/psd/orientation4.psd.json @@ -0,0 +1,11 @@ +[ + { + "fileName" : "orientation_all.png", + "metadata" : [ + { + "key" : "Software" , + "value" : "LIFE Pro 2.18.5 (Windows)" + } + ] + } +] diff --git a/autotests/read/psd/orientation5.psd b/autotests/read/psd/orientation5.psd new file mode 100644 index 0000000..93f0a0e Binary files /dev/null and b/autotests/read/psd/orientation5.psd differ diff --git a/autotests/read/psd/orientation5.psd.json b/autotests/read/psd/orientation5.psd.json new file mode 100644 index 0000000..e09eb63 --- /dev/null +++ b/autotests/read/psd/orientation5.psd.json @@ -0,0 +1,11 @@ +[ + { + "fileName" : "orientation_all.png", + "metadata" : [ + { + "key" : "Software" , + "value" : "LIFE Pro 2.18.5 (Windows)" + } + ] + } +] diff --git a/autotests/read/psd/orientation6.psd b/autotests/read/psd/orientation6.psd new file mode 100644 index 0000000..f3331ad Binary files /dev/null and b/autotests/read/psd/orientation6.psd differ diff --git a/autotests/read/psd/orientation6.psd.json b/autotests/read/psd/orientation6.psd.json new file mode 100644 index 0000000..e09eb63 --- /dev/null +++ b/autotests/read/psd/orientation6.psd.json @@ -0,0 +1,11 @@ +[ + { + "fileName" : "orientation_all.png", + "metadata" : [ + { + "key" : "Software" , + "value" : "LIFE Pro 2.18.5 (Windows)" + } + ] + } +] diff --git a/autotests/read/psd/orientation7.psd b/autotests/read/psd/orientation7.psd new file mode 100644 index 0000000..c033979 Binary files /dev/null and b/autotests/read/psd/orientation7.psd differ diff --git a/autotests/read/psd/orientation7.psd.json b/autotests/read/psd/orientation7.psd.json new file mode 100644 index 0000000..e09eb63 --- /dev/null +++ b/autotests/read/psd/orientation7.psd.json @@ -0,0 +1,11 @@ +[ + { + "fileName" : "orientation_all.png", + "metadata" : [ + { + "key" : "Software" , + "value" : "LIFE Pro 2.18.5 (Windows)" + } + ] + } +] diff --git a/autotests/read/psd/orientation8.psd b/autotests/read/psd/orientation8.psd new file mode 100644 index 0000000..45e8c6a Binary files /dev/null and b/autotests/read/psd/orientation8.psd differ diff --git a/autotests/read/psd/orientation8.psd.json b/autotests/read/psd/orientation8.psd.json new file mode 100644 index 0000000..e09eb63 --- /dev/null +++ b/autotests/read/psd/orientation8.psd.json @@ -0,0 +1,11 @@ +[ + { + "fileName" : "orientation_all.png", + "metadata" : [ + { + "key" : "Software" , + "value" : "LIFE Pro 2.18.5 (Windows)" + } + ] + } +] diff --git a/autotests/read/psd/orientation_all.png b/autotests/read/psd/orientation_all.png new file mode 100644 index 0000000..3515c95 Binary files /dev/null and b/autotests/read/psd/orientation_all.png differ diff --git a/src/imageformats/psd.cpp b/src/imageformats/psd.cpp index 23b5013..c30a090 100644 --- a/src/imageformats/psd.cpp +++ b/src/imageformats/psd.cpp @@ -473,9 +473,9 @@ PSDColorModeDataSection readColorModeDataSection(QDataStream &s, bool *ok = null if (cms.duotone.data.size() != size) *ok = false; } else { // read the palette (768 bytes) - auto&& palette = cms.palette; + auto &&palette = cms.palette; QList vect(size); - for (auto&& v : vect) + 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))); @@ -491,7 +491,7 @@ PSDColorModeDataSection readColorModeDataSection(QDataStream &s, bool *ok = null * \param irs The image resource section. * \return True on success, otherwise false. */ -static bool setColorSpace(QImage& img, const PSDImageResourceSection& irs) +static bool setColorSpace(QImage &img, const PSDImageResourceSection &irs) { if (!irs.contains(IRI_ICCPROFILE) || img.isNull()) return false; @@ -510,7 +510,7 @@ static bool setColorSpace(QImage& img, const PSDImageResourceSection& irs) * \param irs The image resource section. * \return True on success, otherwise false. */ -static bool setXmpData(QImage& img, const PSDImageResourceSection& irs) +static bool setXmpData(QImage &img, const PSDImageResourceSection &irs) { if (!irs.contains(IRI_XMPMETADATA)) return false; @@ -529,15 +529,11 @@ static bool setXmpData(QImage& img, const PSDImageResourceSection& irs) * \brief setExifData * Adds EXIF metadata to QImage. * \param img The image. - * \param irs The image resource section. + * \param exif The decoded EXIF data. * \return True on success, otherwise false. */ -static bool setExifData(QImage& img, const PSDImageResourceSection& irs) +static bool setExifData(QImage &img, const MicroExif &exif) { - if (!irs.contains(IRI_EXIFDATA1)) - return false; - auto irb = irs.value(IRI_EXIFDATA1); - auto exif = MicroExif::fromByteArray(irb.data); if (exif.isEmpty()) return false; exif.toImageMetadata(img); @@ -545,12 +541,12 @@ static bool setExifData(QImage& img, const PSDImageResourceSection& irs) } /*! - * \brief hasMergedData + * \brief HasMergedData * Checks if merged image data are available. * \param irs The image resource section. * \return True on success or if the block does not exist, otherwise false. */ -static bool hasMergedData(const PSDImageResourceSection& irs) +static bool HasMergedData(const PSDImageResourceSection &irs) { if (!irs.contains(IRI_VERSIONINFO)) return true; @@ -567,7 +563,7 @@ static bool hasMergedData(const PSDImageResourceSection& irs) * \param irs The image resource section. * \return True on success, otherwise false. */ -static bool setResolution(QImage& img, const PSDImageResourceSection& irs) +static bool setResolution(QImage &img, const PSDImageResourceSection &irs) { if (!irs.contains(IRI_RESOLUTIONINFO)) return false; @@ -601,7 +597,7 @@ static bool setResolution(QImage& img, const PSDImageResourceSection& irs) * \param irs The image resource section. * \return True on success, otherwise false. */ -static bool setTransparencyIndex(QImage& img, const PSDImageResourceSection& irs) +static bool setTransparencyIndex(QImage &img, const PSDImageResourceSection &irs) { if (!irs.contains(IRI_TRANSPARENCYINDEX)) return false; @@ -613,7 +609,7 @@ static bool setTransparencyIndex(QImage& img, const PSDImageResourceSection& irs auto palette = img.colorTable(); if (idx < palette.size()) { - auto&& v = palette[idx]; + auto &&v = palette[idx]; v = QRgb(v & ~0xFF000000); img.setColorTable(palette); return true; @@ -813,7 +809,7 @@ static QImage::Format imageFormat(const PSDHeader &header, bool alpha) * \param format The Qt image format. * \return The number of channels of the image format. */ -static qint32 imageChannels(const QImage::Format& format) +static qint32 imageChannels(const QImage::Format &format) { qint32 c = 4; switch(format) { @@ -1086,7 +1082,7 @@ inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, q } } -bool readChannel(QByteArray& target, QDataStream &stream, quint32 compressedSize, quint16 compression) +bool readChannel(QByteArray &target, QDataStream &stream, quint32 compressedSize, quint16 compression) { if (compression) { if (compressedSize > kMaxQVectorSize) { @@ -1107,39 +1103,161 @@ bool readChannel(QByteArray& target, QDataStream &stream, quint32 compressedSize return stream.status() == QDataStream::Ok; } -// Load the PSD image. -static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img) +} // Private + +class PSDHandlerPrivate { - // Checking for PSB - auto isPsb = header.version == 2; - bool ok = false; - - // Color Mode Data section - auto cmds = readColorModeDataSection(stream, &ok); - if (!ok) { - qDebug() << "Error while skipping Color Mode Data section"; - return false; +public: + PSDHandlerPrivate() + { + } + ~PSDHandlerPrivate() + { } - // Image Resources Section - auto irs = readImageResourceSection(stream, &ok); - if (!ok) { - qDebug() << "Error while reading Image Resources Section"; - return false; - } - // Checking for merged image (Photoshop compatibility data) - if (!hasMergedData(irs)) { - qDebug() << "No merged data found"; - return false; + bool isPsb() const + { + return m_header.version == 2; } - // Layer and Mask section - auto lms = readLayerAndMaskSection(stream, isPsb, &ok); - if (!ok) { - qDebug() << "Error while skipping Layer and Mask section"; - return false; + bool isValid() const + { + return IsValid(m_header); } + bool isSupported() const + { + return IsSupported(m_header); + } + + bool hasAlpha() const + { + // Try to identify the nature of spots: note that this is just one of many ways to identify the presence + // of alpha channels: should work in most cases where colorspaces != RGB/Gray + auto alpha = m_header.color_mode == CM_RGB; + if (!m_lms.isNull()) + alpha = m_lms.hasAlpha(); + return alpha; + } + + bool hasMergedData() const + { + return HasMergedData(m_irs); + } + + QSize size() const + { + if (isValid()) + return QSize(m_header.width, m_header.height); + return {}; + } + + QImage::Format format() const + { + return imageFormat(m_header, hasAlpha()); + } + + QImageIOHandler::Transformations transformation() const + { + return m_exif.transformation(); + } + + bool readInfo(QDataStream &stream) + { + // Checking for PSB + auto ok = false; + + // Header + stream >> m_header; + + // Check image file format. + if (stream.atEnd() || !IsValid(m_header)) { + // qDebug() << "This PSD file is not valid."; + return false; + } + + // Check if it's a supported format. + if (!IsSupported(m_header)) { + // qDebug() << "This PSD file is not supported."; + return false; + } + + // Color Mode Data section + m_cmds = readColorModeDataSection(stream, &ok); + if (!ok) { + qDebug() << "Error while skipping Color Mode Data section"; + return false; + } + + // Image Resources Section + m_irs = readImageResourceSection(stream, &ok); + if (!ok) { + qDebug() << "Error while reading Image Resources Section"; + return false; + } + // Checking for merged image (Photoshop compatibility data) + if (!hasMergedData()) { + qDebug() << "No merged data found"; + return false; + } + + // Layer and Mask section + m_lms = readLayerAndMaskSection(stream, isPsb(), &ok); + if (!ok) { + qDebug() << "Error while skipping Layer and Mask section"; + return false; + } + + // storing decoded EXIF + if (m_irs.contains(IRI_EXIFDATA1)) { + m_exif = MicroExif::fromByteArray(m_irs.value(IRI_EXIFDATA1).data); + } + + return ok; + } + + PSDHeader m_header; + PSDColorModeDataSection m_cmds; + PSDImageResourceSection m_irs; + PSDLayerAndMaskSection m_lms; + + // cache to avoid decoding exif multiple times + MicroExif m_exif; +}; + +PSDHandler::PSDHandler() + : QImageIOHandler() + , d(new PSDHandlerPrivate) +{ +} + +bool PSDHandler::canRead() const +{ + if (canRead(device())) { + setFormat("psd"); + return true; + } + return false; +} + +bool PSDHandler::read(QImage *image) +{ + QDataStream stream(device()); + stream.setByteOrder(QDataStream::BigEndian); + + if (!d->isValid()) { + if (!d->readInfo(stream)) + return false; + } + + auto &&header = d->m_header; + auto &&cmds = d->m_cmds; + auto &&irs = d->m_irs; + // auto &&lms = d->m_lms; + auto isPsb = d->isPsb(); + auto alpha = d->hasAlpha(); + + QImage img; // Find out if the data is compressed. // Known values: // 0: no compression @@ -1151,19 +1269,13 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img) return false; } - // Try to identify the nature of spots: note that this is just one of many ways to identify the presence - // of alpha channels: should work in most cases where colorspaces != RGB/Gray - auto alpha = header.color_mode == CM_RGB; - if (!lms.isNull()) - alpha = lms.hasAlpha(); - - const QImage::Format format = imageFormat(header, alpha); + const QImage::Format format = d->format(); if (format == QImage::Format_Invalid) { qWarning() << "Unsupported image format. color_mode:" << header.color_mode << "depth:" << header.depth << "channel_count:" << header.channel_count; return false; } - img = imageAlloc(header.width, header.height, format); + img = imageAlloc(d->size(), format); if (img.isNull()) { qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width, header.height); return false; @@ -1187,7 +1299,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img) QList strides(header.height * header.channel_count, raw_count); // Read the compressed stride sizes if (compression) { - for (auto&& v : strides) { + for (auto &&v : strides) { if (isPsb) { stream >> v; continue; @@ -1242,7 +1354,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img) qDebug() << "Error while seeking the stream of channel" << c << "line" << y; return false; } - auto&& strideSize = strides.at(strideNumber); + auto &&strideSize = strides.at(strideNumber); if (!readChannel(rawStride, stream, strideSize, compression)) { qDebug() << "Error while reading the stream of channel" << c << "line" << y; return false; @@ -1379,7 +1491,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img) } // EXIF data - if (!setExifData(img, irs)) { + if (!setExifData(img, d->m_exif)) { // qDebug() << "No EXIF data found!"; } @@ -1390,60 +1502,6 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img) img.setText(QStringLiteral("PSDDuotoneOptions"), QString::fromUtf8(cmds.duotone.data.toHex())); } - return true; -} - -} // Private - -class PSDHandlerPrivate -{ -public: - PSDHandlerPrivate() {} - ~PSDHandlerPrivate() {} - PSDHeader m_header; -}; - -PSDHandler::PSDHandler() - : QImageIOHandler() - , d(new PSDHandlerPrivate) -{ -} - -bool PSDHandler::canRead() const -{ - if (canRead(device())) { - setFormat("psd"); - return true; - } - return false; -} - -bool PSDHandler::read(QImage *image) -{ - QDataStream s(device()); - s.setByteOrder(QDataStream::BigEndian); - - auto&& header = d->m_header; - s >> header; - - // Check image file format. - if (s.atEnd() || !IsValid(header)) { - // qDebug() << "This PSD file is not valid."; - return false; - } - - // Check if it's a supported format. - if (!IsSupported(header)) { - // qDebug() << "This PSD file is not supported."; - return false; - } - - QImage img; - if (!LoadPSD(s, header, img)) { - // qDebug() << "Error loading PSD file."; - return false; - } - *image = img; return true; } @@ -1452,6 +1510,12 @@ bool PSDHandler::supportsOption(ImageOption option) const { if (option == QImageIOHandler::Size) return true; + if (option == QImageIOHandler::ImageFormat) + return true; + if (option == QImageIOHandler::ImageTransformation) + return true; + if (option == QImageIOHandler::Description) + return true; return false; } @@ -1459,18 +1523,37 @@ QVariant PSDHandler::option(ImageOption option) const { QVariant v; - if (option == QImageIOHandler::Size) { - auto&& header = d->m_header; - if (IsValid(header)) { - v = QVariant::fromValue(QSize(header.width, header.height)); - } else if (auto dev = device()) { - auto ba = dev->peek(sizeof(PSDHeader)); - QDataStream s(ba); + if (auto dev = device()) { + if (!d->isValid()) { + QDataStream s(dev); s.setByteOrder(QDataStream::BigEndian); + d->readInfo(s); + } + } - s >> header; - if (s.status() == QDataStream::Ok && IsValid(header)) - v = QVariant::fromValue(QSize(header.width, header.height)); + if (option == QImageIOHandler::Size) { + if (d->isValid()) { + v = QVariant::fromValue(d->size()); + } + } + + if (option == QImageIOHandler::ImageFormat) { + if (d->isValid()) { + v = QVariant::fromValue(d->format()); + } + } + + if (option == QImageIOHandler::ImageTransformation) { + if (d->isValid()) { + v = QVariant::fromValue(int(d->transformation())); + } + } + + if (option == QImageIOHandler::Description) { + if (d->isValid()) { + auto descr = d->m_exif.description(); + if (!descr.isEmpty()) + v = QVariant::fromValue(descr); } }