diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 5cbc89a..e76556e 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -11,7 +11,7 @@ macro(kimageformats_read_tests) endif() if (NOT TARGET readtest) - add_executable(readtest readtest.cpp) + add_executable(readtest readtest.cpp templateimage.cpp) target_link_libraries(readtest Qt6::Gui) target_compile_definitions(readtest PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/read") diff --git a/autotests/read/psd/cmyk16_testcard.png b/autotests/read/psd/cmyk16_testcard.png new file mode 100644 index 0000000..e2e75bf Binary files /dev/null and b/autotests/read/psd/cmyk16_testcard.png differ diff --git a/autotests/read/psd/cmyk16_testcard.psd b/autotests/read/psd/cmyk16_testcard.psd new file mode 100644 index 0000000..dc7c9d3 Binary files /dev/null and b/autotests/read/psd/cmyk16_testcard.psd differ diff --git a/autotests/read/psd/cmyk16_testcard.psd.json b/autotests/read/psd/cmyk16_testcard.psd.json new file mode 100644 index 0000000..54b7623 --- /dev/null +++ b/autotests/read/psd/cmyk16_testcard.psd.json @@ -0,0 +1,11 @@ +[ + { + "minQtVersion" : "6.8.0", + "fileName" : "cmyk16_testcard_qt6_8.tif" + }, + { + "minQtVersion" : "6.0.0", + "maxQtVersion" : "6.7.99", + "fileName" : "cmyk16_testcard.png" + } +] diff --git a/autotests/read/psd/cmyk16_testcard_qt6_8.tif b/autotests/read/psd/cmyk16_testcard_qt6_8.tif new file mode 100644 index 0000000..3cb2394 Binary files /dev/null and b/autotests/read/psd/cmyk16_testcard_qt6_8.tif differ diff --git a/autotests/read/psd/cmyk8_testcard.png b/autotests/read/psd/cmyk8_testcard.png new file mode 100644 index 0000000..b4d39d6 Binary files /dev/null and b/autotests/read/psd/cmyk8_testcard.png differ diff --git a/autotests/read/psd/cmyk8_testcard.psd b/autotests/read/psd/cmyk8_testcard.psd new file mode 100644 index 0000000..1895f20 Binary files /dev/null and b/autotests/read/psd/cmyk8_testcard.psd differ diff --git a/autotests/read/psd/cmyk8_testcard.psd.json b/autotests/read/psd/cmyk8_testcard.psd.json new file mode 100644 index 0000000..0f11cd9 --- /dev/null +++ b/autotests/read/psd/cmyk8_testcard.psd.json @@ -0,0 +1,11 @@ +[ + { + "minQtVersion" : "6.8.0", + "fileName" : "cmyk8_testcard_qt6_8.tif" + }, + { + "minQtVersion" : "6.0.0", + "maxQtVersion" : "6.7.99", + "fileName" : "cmyk8_testcard.png" + } +] diff --git a/autotests/read/psd/cmyk8_testcard_qt6_8.tif b/autotests/read/psd/cmyk8_testcard_qt6_8.tif new file mode 100644 index 0000000..f5c1314 Binary files /dev/null and b/autotests/read/psd/cmyk8_testcard_qt6_8.tif differ diff --git a/autotests/read/psd/cmyka-16bits.psd.json b/autotests/read/psd/cmyka-16bits.psd.json new file mode 100644 index 0000000..a27e930 --- /dev/null +++ b/autotests/read/psd/cmyka-16bits.psd.json @@ -0,0 +1,11 @@ +[ + { + "minQtVersion" : "6.8.0", + "fileName" : "cmyka-16bits_qt6_8.png" + }, + { + "minQtVersion" : "6.0.0", + "maxQtVersion" : "6.7.99", + "fileName" : "cmyka-16bits.png" + } +] diff --git a/autotests/read/psd/cmyka-16bits_qt6_8.png b/autotests/read/psd/cmyka-16bits_qt6_8.png new file mode 100644 index 0000000..c62c2c6 Binary files /dev/null and b/autotests/read/psd/cmyka-16bits_qt6_8.png differ diff --git a/autotests/read/psd/cmyka-8bits.psd.json b/autotests/read/psd/cmyka-8bits.psd.json new file mode 100644 index 0000000..d21a8c4 --- /dev/null +++ b/autotests/read/psd/cmyka-8bits.psd.json @@ -0,0 +1,11 @@ +[ + { + "minQtVersion" : "6.8.0", + "fileName" : "cmyka-8bits_qt6_8.png" + }, + { + "minQtVersion" : "6.0.0", + "maxQtVersion" : "6.7.99", + "fileName" : "cmyka-8bits.png" + } +] diff --git a/autotests/read/psd/cmyka-8bits_qt6_8.png b/autotests/read/psd/cmyka-8bits_qt6_8.png new file mode 100644 index 0000000..50d9a69 Binary files /dev/null and b/autotests/read/psd/cmyka-8bits_qt6_8.png differ diff --git a/autotests/read/psd/mch-16bits.psd.json b/autotests/read/psd/mch-16bits.psd.json new file mode 100644 index 0000000..8d63adc --- /dev/null +++ b/autotests/read/psd/mch-16bits.psd.json @@ -0,0 +1,11 @@ +[ + { + "minQtVersion" : "6.8.0", + "fileName" : "mch-16bits_qt_6_8.tif" + }, + { + "minQtVersion" : "6.0.0", + "maxQtVersion" : "6.7.99", + "fileName" : "mch-16bits.png" + } +] diff --git a/autotests/read/psd/mch-16bits_qt_6_8.tif b/autotests/read/psd/mch-16bits_qt_6_8.tif new file mode 100644 index 0000000..f79edcd Binary files /dev/null and b/autotests/read/psd/mch-16bits_qt_6_8.tif differ diff --git a/autotests/read/psd/mch-8bits.psd.json b/autotests/read/psd/mch-8bits.psd.json new file mode 100644 index 0000000..3a253cb --- /dev/null +++ b/autotests/read/psd/mch-8bits.psd.json @@ -0,0 +1,11 @@ +[ + { + "minQtVersion" : "6.8.0", + "fileName" : "mch-8bits_qt_6.8.tif" + }, + { + "minQtVersion" : "6.0.0", + "maxQtVersion" : "6.7.99", + "fileName" : "mch-8bits.png" + } +] diff --git a/autotests/read/psd/mch-8bits_qt_6.8.tif b/autotests/read/psd/mch-8bits_qt_6.8.tif new file mode 100644 index 0000000..f13239e Binary files /dev/null and b/autotests/read/psd/mch-8bits_qt_6.8.tif differ diff --git a/autotests/read/xcf/birthday16.xcf.json b/autotests/read/xcf/birthday16.xcf.json new file mode 100644 index 0000000..6133982 --- /dev/null +++ b/autotests/read/xcf/birthday16.xcf.json @@ -0,0 +1,32 @@ +[ + { + "minQtVersion" : "6.7.0", + "fileName" : "birthday16.png", + "seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614" + }, + { + "minQtVersion" : "6.0.0", + "maxQtVersion" : "6.2.10", + "fileName" : "birthday16_alphabug.png" + }, + { + "minQtVersion" : "6.3.0", + "maxQtVersion" : "6.3.2", + "fileName" : "birthday32_alphabug.png" + }, + { + "minQtVersion" : "6.4.0", + "maxQtVersion" : "6.4.3", + "fileName" : "birthday32_alphabug.png" + }, + { + "minQtVersion" : "6.5.0", + "maxQtVersion" : "6.5.4", + "fileName" : "birthday16_alphabug.png" + }, + { + "minQtVersion" : "6.6.0", + "maxQtVersion" : "6.6.1", + "fileName" : "birthday16_alphabug.png" + } +] diff --git a/autotests/read/xcf/birthday16_alphabug.png b/autotests/read/xcf/birthday16_alphabug.png new file mode 100644 index 0000000..6bceb1e Binary files /dev/null and b/autotests/read/xcf/birthday16_alphabug.png differ diff --git a/autotests/read/xcf/birthday32.xcf.json b/autotests/read/xcf/birthday32.xcf.json new file mode 100644 index 0000000..a5715e8 --- /dev/null +++ b/autotests/read/xcf/birthday32.xcf.json @@ -0,0 +1,32 @@ +[ + { + "minQtVersion" : "6.7.0", + "fileName" : "birthday32.png", + "seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614" + }, + { + "minQtVersion" : "6.0.0", + "maxQtVersion" : "6.2.10", + "fileName" : "birthday32_alphabug.png" + }, + { + "minQtVersion" : "6.3.0", + "maxQtVersion" : "6.3.2", + "fileName" : "birthday32_alphabug.png" + }, + { + "minQtVersion" : "6.4.0", + "maxQtVersion" : "6.4.3", + "fileName" : "birthday32_alphabug.png" + }, + { + "minQtVersion" : "6.5.0", + "maxQtVersion" : "6.5.4", + "fileName" : "birthday32_alphabug.png" + }, + { + "minQtVersion" : "6.6.0", + "maxQtVersion" : "6.6.1", + "fileName" : "birthday32_alphabug.png" + } +] diff --git a/autotests/read/xcf/birthday32_alphabug.png b/autotests/read/xcf/birthday32_alphabug.png new file mode 100644 index 0000000..1626d95 Binary files /dev/null and b/autotests/read/xcf/birthday32_alphabug.png differ diff --git a/autotests/readtest.cpp b/autotests/readtest.cpp index e28e1b7..eed3852 100644 --- a/autotests/readtest.cpp +++ b/autotests/readtest.cpp @@ -16,6 +16,7 @@ #include #include "../tests/format-enum.h" +#include "templateimage.h" #include "fuzzyeq.cpp" @@ -95,7 +96,7 @@ int main(int argc, char **argv) QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR)); QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR)); QCoreApplication::setApplicationName(QStringLiteral("readtest")); - QCoreApplication::setApplicationVersion(QStringLiteral("1.1.0")); + QCoreApplication::setApplicationVersion(QStringLiteral("1.2.0")); QCommandLineParser parser; parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking.")); @@ -159,22 +160,24 @@ int main(int argc, char **argv) QTextStream(stdout) << "* Run on RANDOM ACCESS device\n"; } for (const QFileInfo &fi : lstImgDir) { - if (!fi.suffix().compare("png", Qt::CaseInsensitive) || !fi.suffix().compare("tif", Qt::CaseInsensitive)) { + TemplateImage timg(fi); + if (timg.isTemplate()) { continue; } - int suffixPos = fi.filePath().size() - suffix.size(); - QString inputfile = fi.filePath(); - QString fmt = QStringLiteral("png"); - QString expfile = fi.filePath().replace(suffixPos, suffix.size(), fmt); - if (!QFile::exists(expfile)) { // try with tiff - fmt = QStringLiteral("tif"); - expfile = fi.filePath().replace(suffixPos, suffix.size(), fmt); - } - QString expfilename = QFileInfo(expfile).fileName(); - std::unique_ptr inputDevice(seq ? new SequentialFile(inputfile) : new QFile(inputfile)); + QFileInfo expFileInfo = timg.compareImage(); + if (!formatStrings.contains(expFileInfo.suffix(), Qt::CaseInsensitive)) { + // Work Around for CCBUG: 468288 + QTextStream(stdout) << "SKIP : " << fi.fileName() << ": comparison image " << expFileInfo.fileName() << " cannot be loaded due to the lack of " + << expFileInfo.suffix().toUpper() << " plugin!\n"; + ++skipped; + continue; + } + QString expfilename = expFileInfo.fileName(); + + std::unique_ptr inputDevice(seq ? new SequentialFile(fi.filePath()) : new QFile(fi.filePath())); QImageReader inputReader(inputDevice.get(), format); - QImageReader expReader(expfile, fmt.toLatin1()); + QImageReader expReader(expFileInfo.filePath()); QImage inputImage; QImage expImage; diff --git a/autotests/templateimage.cpp b/autotests/templateimage.cpp new file mode 100644 index 0000000..7b8bed9 --- /dev/null +++ b/autotests/templateimage.cpp @@ -0,0 +1,98 @@ +/* + SPDX-FileCopyrightText: 2024 Mirco Miranda + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "templateimage.h" + +#include +#include +#include +#include +#include + +TemplateImage::TemplateImage(const QFileInfo &fi) : + m_fi(fi) +{ + +} + +bool TemplateImage::isTemplate() const +{ + auto list = suffixes(); + for (auto&& suffix : list) { + if (!m_fi.suffix().compare(suffix, Qt::CaseInsensitive)) + return true; + } + return false; +} + +QFileInfo TemplateImage::compareImage() const +{ + auto fi = jsonImage(); + if (fi.exists()) { + return fi; + } + return legacyImage(); +} + + +QStringList TemplateImage::suffixes() +{ + return QStringList({"png", "tif", "tiff", "json"}); +} + +QFileInfo TemplateImage::legacyImage() const +{ + auto list = suffixes(); + for (auto&& suffix : list) { + auto fi = QFileInfo(QStringLiteral("%1/%2.%3").arg(m_fi.path(), m_fi.completeBaseName(), suffix)); + if (fi.exists()) { + return fi; + } + } + return {}; +} + +QFileInfo TemplateImage::jsonImage() const +{ + auto fi = QFileInfo(QStringLiteral("%1.json").arg(m_fi.filePath())); + if (!fi.exists()) { + return {}; + } + + QFile f(fi.filePath()); + if (!f.open(QFile::ReadOnly)) { + return {}; + } + + QJsonParseError err; + auto doc = QJsonDocument::fromJson(f.readAll(), &err); + if (err.error != QJsonParseError::NoError || !doc.isArray()) { + return {}; + } + + auto currentQt = QVersionNumber::fromString(qVersion()); + auto arr = doc.array(); + for (auto val : arr) { + if (!val.isObject()) + continue; + auto obj = val.toObject(); + auto minQt = QVersionNumber::fromString(obj.value("minQtVersion").toString()); + auto maxQt = QVersionNumber::fromString(obj.value("maxQtVersion").toString()); + auto name = obj.value("fileName").toString(); + + // filter + if (name.isEmpty()) + continue; + if (!minQt.isNull() && currentQt < minQt) + continue; + if (!maxQt.isNull() && currentQt > maxQt) + continue; + return QFileInfo(QStringLiteral("%1/%2").arg(fi.path(), name)); + } + + return {}; +} + diff --git a/autotests/templateimage.h b/autotests/templateimage.h new file mode 100644 index 0000000..dd41645 --- /dev/null +++ b/autotests/templateimage.h @@ -0,0 +1,72 @@ +/* + SPDX-FileCopyrightText: 2024 Mirco Miranda + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef TEMPLATEIMAGE_H +#define TEMPLATEIMAGE_H + +#include + +/*! + * \brief The TemplateImage class + * Given an image name, it decides the template image to compare it with. + */ +class TemplateImage +{ +public: + /*! + * \brief TemplateImage + * \param fi The image to test. + */ + TemplateImage(const QFileInfo& fi); + + /*! + * \brief TemplateImage + * Default copy constructor. + */ + TemplateImage(const TemplateImage& other) = default; + /*! + * \brief operator = + * Default copy operator + */ + TemplateImage& operator=(const TemplateImage& other) = default; + + /*! + * \brief isTemplate + * \return True if the image is a template, false otherwise. + * \sa suffixes + */ + bool isTemplate() const; + + /*! + * \brief compareImage + * \return The template image to use for the comparison. + */ + QFileInfo compareImage() const; + + /*! + * \brief suffixes + * \return The list of suffixes considered templates. + */ + static QStringList suffixes(); + +private: + /*! + * \brief legacyImage + * \return The template image calculated from the source image name. + */ + QFileInfo legacyImage() const; + + /*! + * \brief jsonImage + * \return The template image read from the corresponding JSON. + */ + QFileInfo jsonImage() const; + +private: + QFileInfo m_fi; +}; + +#endif // TEMPLATEIMAGE_H diff --git a/src/imageformats/CMakeLists.txt b/src/imageformats/CMakeLists.txt index ce7df6c..e8b4262 100644 --- a/src/imageformats/CMakeLists.txt +++ b/src/imageformats/CMakeLists.txt @@ -83,7 +83,7 @@ kimageformats_add_plugin(kimg_pic SOURCES pic.cpp) ################################## -kimageformats_add_plugin(kimg_psd SOURCES psd.cpp) +kimageformats_add_plugin(kimg_psd SOURCES psd.cpp scanlineconverter.cpp) ################################## diff --git a/src/imageformats/psd.cpp b/src/imageformats/psd.cpp index badd95d..8c0405f 100644 --- a/src/imageformats/psd.cpp +++ b/src/imageformats/psd.cpp @@ -23,7 +23,7 @@ * - Color spaces other than RGB/Grayscale cannot be read due to lack of QImage * support. Where possible, a conversion to RGB is done: * - CMYK images are converted using an approximated way that ignores the color - * information (ICC profile). + * information (ICC profile) with Qt less than 6.8. * - LAB images are converted to sRGB using literature formulas. * - MULICHANNEL images more than 3 channels are converted as CMYK images. * - DUOTONE images are considered as Grayscale images. @@ -34,6 +34,7 @@ #include "fastmath_p.h" #include "psd_p.h" +#include "scanlineconverter_p.h" #include "util_p.h" #include @@ -60,9 +61,24 @@ typedef quint8 uchar; */ //#define PSD_FAST_LAB_CONVERSION +/* + * Since Qt version 6.8, the 8-bit CMYK format is natively supported. + * If you encounter problems with native CMYK support you can continue to force the plugin to convert + * to RGB as in previous versions by defining PSD_NATIVE_CMYK_SUPPORT_DISABLED. + */ +//#define PSD_NATIVE_CMYK_SUPPORT_DISABLED + namespace // Private. { +#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0) || defined(PSD_NATIVE_CMYK_SUPPORT_DISABLED) +# define CMYK_FORMAT QImage::Format_Invalid +#else +# define CMYK_FORMAT QImage::Format_CMYK8888 +#endif + +#define NATIVE_CMYK (CMYK_FORMAT != QImage::Format_Invalid) + enum Signature : quint32 { S_8BIM = 0x3842494D, // '8BIM' S_8B64 = 0x38423634, // '8B64' @@ -477,7 +493,7 @@ PSDColorModeDataSection readColorModeDataSection(QDataStream &s, bool *ok = null */ static bool setColorSpace(QImage& img, const PSDImageResourceSection& irs) { - if (!irs.contains(IRI_ICCPROFILE)) + if (!irs.contains(IRI_ICCPROFILE) || img.isNull()) return false; auto irb = irs.value(IRI_ICCPROFILE); auto cs = QColorSpace::fromIccProfile(irb.data); @@ -742,7 +758,9 @@ static QImage::Format imageFormat(const PSDHeader &header, bool alpha) break; case CM_MULTICHANNEL: // Treat MCH as CMYK (number of channel check is done in IsSupported()) case CM_CMYK: // Photoshop supports CMYK/MCH 8-bits and 16-bits only - if (header.depth == 16) + if (NATIVE_CMYK && header.channel_count == 4 && (header.depth == 16 || header.depth == 8)) + format = CMYK_FORMAT; + else if (header.depth == 16) format = header.channel_count < 5 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64; else if (header.depth == 8) format = header.channel_count < 5 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888; @@ -844,6 +862,18 @@ inline void planarToChunchy(uchar *target, const char *source, qint32 width, qin } } +template +inline void planarToChunchyCMYK(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn) +{ + auto s = reinterpret_cast(source); + auto t = reinterpret_cast(target); + const T d = std::numeric_limits::max() / std::numeric_limits::max(); + for (qint32 x = 0; x < width; ++x) { + t[x * cn + c] = quint8((std::numeric_limits::max() - xchg(s[x])) / d); + } +} + + template inline void planarToChunchyFloatToUInt16(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn) { @@ -903,6 +933,19 @@ inline void monoInvert(uchar *target, const char* source, qint32 bytes) } } +template +inline void rawChannelsCopyToCMYK(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width) +{ + auto s = reinterpret_cast(source); + auto t = reinterpret_cast(target); + const T d = std::numeric_limits::max() / std::numeric_limits::max(); + for (qint32 c = 0, cs = std::min(targetChannels, sourceChannels); c < cs; ++c) { + for (qint32 x = 0; x < width; ++x) { + t[x * targetChannels + c] = (std::numeric_limits::max() - s[x * sourceChannels + c]) / d; + } + } +} + template inline void rawChannelsCopy(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width) { @@ -915,6 +958,17 @@ inline void rawChannelsCopy(uchar *target, qint32 targetChannels, const char *so } } +template +inline void rawChannelCopy(uchar *target, qint32 targetChannels, qint32 targetChannel, const char *source, qint32 sourceChannels, qint32 sourceChannel, qint32 width) +{ + auto s = reinterpret_cast(source); + auto t = reinterpret_cast(target); + for (qint32 x = 0; x < width; ++x) { + t[x * targetChannels + targetChannel] = s[x * sourceChannels + sourceChannel]; + } +} + + template inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false) { @@ -1103,6 +1157,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img) auto imgChannels = imageChannels(img.format()); auto channel_num = std::min(qint32(header.channel_count), imgChannels); auto raw_count = qsizetype(header.width * header.depth + 7) / 8; + auto native_cmyk = img.format() == CMYK_FORMAT; if (header.height > kMaxQVectorSize / header.channel_count / sizeof(quint32)) { qWarning() << "LoadPSD() header height/channel_count too big" << header.height << header.channel_count; @@ -1138,13 +1193,25 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img) // clang-format off // checks the need of color conversion (that requires random access to the image) - auto randomAccess = (header.color_mode == CM_CMYK) || + auto randomAccess = (header.color_mode == CM_CMYK && !native_cmyk) || + (header.color_mode == CM_MULTICHANNEL && !native_cmyk) || (header.color_mode == CM_LABCOLOR) || - (header.color_mode == CM_MULTICHANNEL) || (header.color_mode != CM_INDEXED && img.hasAlphaChannel()); // clang-format on if (randomAccess) { + // CMYK with spots (e.g. CMYKA) ICC conversion to RGBA/RGBX + QImage tmpCmyk; + ScanLineConverter iccConv(img.format()); +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) && !defined(PSD_NATIVE_CMYK_SUPPORT_DISABLED) + if (header.color_mode == CM_CMYK && img.format() != QImage::Format_CMYK8888) { + auto tmpi = QImage(header.width, 1, QImage::Format_CMYK8888); + if (setColorSpace(tmpi, irs)) + tmpCmyk = tmpi; + iccConv.setTargetColorSpace(QColorSpace(QColorSpace::SRgb)); + } +#endif + // In order to make a colorspace transformation, we need all channels of a scanline QByteArray psdScanline; psdScanline.resize(qsizetype(header.width * header.depth * header.channel_count + 7) / 8); @@ -1200,10 +1267,26 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img) // Conversion to RGB if (header.color_mode == CM_CMYK || header.color_mode == CM_MULTICHANNEL) { - if (header.depth == 8) - cmykToRgb(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha); - else if (header.depth == 16) - cmykToRgb(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha); + if (tmpCmyk.isNull()) { + if (header.depth == 8) + cmykToRgb(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha); + else if (header.depth == 16) + cmykToRgb(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha); + } + else if (header.depth == 8) { + rawChannelsCopyToCMYK(tmpCmyk.bits(), 4, psdScanline.data(), header.channel_count, header.width); + if (auto rgbPtr = iccConv.convertedScanLine(tmpCmyk, 0)) + std::memcpy(img.scanLine(y), rgbPtr, img.bytesPerLine()); + if (imgChannels == 4 && header.channel_count >= 5) + rawChannelCopy(img.scanLine(y), imgChannels, 3, psdScanline.data(), header.channel_count, 4, header.width); + } + else if (header.depth == 16) { + rawChannelsCopyToCMYK(tmpCmyk.bits(), 4, psdScanline.data(), header.channel_count, header.width); + if (auto rgbPtr = iccConv.convertedScanLine(tmpCmyk, 0)) + std::memcpy(img.scanLine(y), rgbPtr, img.bytesPerLine()); + if (imgChannels == 4 && header.channel_count >= 5) + rawChannelCopy(img.scanLine(y), imgChannels, 3, psdScanline.data(), header.channel_count, 4, header.width); + } } if (header.color_mode == CM_LABCOLOR) { if (header.depth == 8) @@ -1235,11 +1318,17 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img) if (header.depth == 1) { // Bitmap monoInvert(scanLine, rawStride.data(), std::min(rawStride.size(), img.bytesPerLine())); } - else if (header.depth == 8) { // 8-bits images: Indexed, Grayscale, RGB/RGBA - planarToChunchy(scanLine, rawStride.data(), header.width, c, imgChannels); + else if (header.depth == 8) { // 8-bits images: Indexed, Grayscale, RGB/RGBA, CMYK, MCH4 + if (native_cmyk) + planarToChunchyCMYK(scanLine, rawStride.data(), header.width, c, imgChannels); + else + planarToChunchy(scanLine, rawStride.data(), header.width, c, imgChannels); } - else if (header.depth == 16) { // 16-bits integer images: Grayscale, RGB/RGBA - planarToChunchy(scanLine, rawStride.data(), header.width, c, imgChannels); + else if (header.depth == 16) { // 16-bits integer images: Grayscale, RGB/RGBA, CMYK, MCH4 + if (native_cmyk) + planarToChunchyCMYK(scanLine, rawStride.data(), header.width, c, imgChannels); + else + planarToChunchy(scanLine, rawStride.data(), header.width, c, imgChannels); } else if (header.depth == 32 && header.color_mode == CM_RGB) { // 32-bits float images: RGB/RGBA planarToChunchy(scanLine, rawStride.data(), header.width, c, imgChannels); @@ -1251,7 +1340,6 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img) } } - // Resolution info if (!setResolution(img, irs)) { // qDebug() << "No resolution info found!"; @@ -1384,7 +1472,11 @@ bool PSDHandler::canRead(QIODevice *device) } if (device->isSequential()) { - if (header.color_mode == CM_CMYK || header.color_mode == CM_LABCOLOR || header.color_mode == CM_MULTICHANNEL) { + if (header.color_mode == CM_CMYK || header.color_mode == CM_MULTICHANNEL) { + if (header.channel_count != 4 || !NATIVE_CMYK) + return false; + } + if (header.color_mode == CM_LABCOLOR) { return false; } if (header.color_mode == CM_RGB && header.channel_count > 3) {