mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-06-03 17:08:08 -04:00
PSD: support native CMYK introduced by Qt 6.8
Qt 6.8 will introduce native support for the CMYK (8-bit) format. With this patch you will finally be able to correctly see the colors of CMYK images with ICC profile. The testing part has been updated with the addition of an (optional) json file for each image to test. Inside you enter which image to use depending on the Qt version. In short: - Added native CMYK suport to PSD reader - CMYK with alpha is converted using QColorSpace in a RGBA image - Read tests changed to use the correct comparison image based on the Qt version - Fixed also XCF tests: now works with all Qt version (see also [QTBUG-120614](https://bugreports.qt.io/browse/QTBUG-120614)) - Work around for CCBUG: 468288
This commit is contained in:
parent
a54c5e876c
commit
4995c9cd15
@ -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")
|
||||
|
BIN
autotests/read/psd/cmyk16_testcard.png
Normal file
BIN
autotests/read/psd/cmyk16_testcard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
autotests/read/psd/cmyk16_testcard.psd
Normal file
BIN
autotests/read/psd/cmyk16_testcard.psd
Normal file
Binary file not shown.
11
autotests/read/psd/cmyk16_testcard.psd.json
Normal file
11
autotests/read/psd/cmyk16_testcard.psd.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
BIN
autotests/read/psd/cmyk16_testcard_qt6_8.tif
Normal file
BIN
autotests/read/psd/cmyk16_testcard_qt6_8.tif
Normal file
Binary file not shown.
BIN
autotests/read/psd/cmyk8_testcard.png
Normal file
BIN
autotests/read/psd/cmyk8_testcard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
autotests/read/psd/cmyk8_testcard.psd
Normal file
BIN
autotests/read/psd/cmyk8_testcard.psd
Normal file
Binary file not shown.
11
autotests/read/psd/cmyk8_testcard.psd.json
Normal file
11
autotests/read/psd/cmyk8_testcard.psd.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
BIN
autotests/read/psd/cmyk8_testcard_qt6_8.tif
Normal file
BIN
autotests/read/psd/cmyk8_testcard_qt6_8.tif
Normal file
Binary file not shown.
11
autotests/read/psd/cmyka-16bits.psd.json
Normal file
11
autotests/read/psd/cmyka-16bits.psd.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
BIN
autotests/read/psd/cmyka-16bits_qt6_8.png
Normal file
BIN
autotests/read/psd/cmyka-16bits_qt6_8.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 111 KiB |
11
autotests/read/psd/cmyka-8bits.psd.json
Normal file
11
autotests/read/psd/cmyka-8bits.psd.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
BIN
autotests/read/psd/cmyka-8bits_qt6_8.png
Normal file
BIN
autotests/read/psd/cmyka-8bits_qt6_8.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 77 KiB |
11
autotests/read/psd/mch-16bits.psd.json
Normal file
11
autotests/read/psd/mch-16bits.psd.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
BIN
autotests/read/psd/mch-16bits_qt_6_8.tif
Normal file
BIN
autotests/read/psd/mch-16bits_qt_6_8.tif
Normal file
Binary file not shown.
11
autotests/read/psd/mch-8bits.psd.json
Normal file
11
autotests/read/psd/mch-8bits.psd.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
BIN
autotests/read/psd/mch-8bits_qt_6.8.tif
Normal file
BIN
autotests/read/psd/mch-8bits_qt_6.8.tif
Normal file
Binary file not shown.
32
autotests/read/xcf/birthday16.xcf.json
Normal file
32
autotests/read/xcf/birthday16.xcf.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
BIN
autotests/read/xcf/birthday16_alphabug.png
Normal file
BIN
autotests/read/xcf/birthday16_alphabug.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
32
autotests/read/xcf/birthday32.xcf.json
Normal file
32
autotests/read/xcf/birthday32.xcf.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
BIN
autotests/read/xcf/birthday32_alphabug.png
Normal file
BIN
autotests/read/xcf/birthday32_alphabug.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
@ -16,6 +16,7 @@
|
||||
#include <QTextStream>
|
||||
|
||||
#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<QIODevice> 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<QIODevice> 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;
|
||||
|
98
autotests/templateimage.cpp
Normal file
98
autotests/templateimage.cpp
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#include "templateimage.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QVersionNumber>
|
||||
|
||||
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 {};
|
||||
}
|
||||
|
72
autotests/templateimage.h
Normal file
72
autotests/templateimage.h
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#ifndef TEMPLATEIMAGE_H
|
||||
#define TEMPLATEIMAGE_H
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
/*!
|
||||
* \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
|
@ -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)
|
||||
|
||||
##################################
|
||||
|
||||
|
@ -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 <QDataStream>
|
||||
@ -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<class T>
|
||||
inline void planarToChunchyCMYK(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn)
|
||||
{
|
||||
auto s = reinterpret_cast<const T*>(source);
|
||||
auto t = reinterpret_cast<quint8*>(target);
|
||||
const T d = std::numeric_limits<T>::max() / std::numeric_limits<quint8>::max();
|
||||
for (qint32 x = 0; x < width; ++x) {
|
||||
t[x * cn + c] = quint8((std::numeric_limits<T>::max() - xchg(s[x])) / d);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<class T>
|
||||
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<class T>
|
||||
inline void rawChannelsCopyToCMYK(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width)
|
||||
{
|
||||
auto s = reinterpret_cast<const T*>(source);
|
||||
auto t = reinterpret_cast<quint8*>(target);
|
||||
const T d = std::numeric_limits<T>::max() / std::numeric_limits<quint8>::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<T>::max() - s[x * sourceChannels + c]) / d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
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<class T>
|
||||
inline void rawChannelCopy(uchar *target, qint32 targetChannels, qint32 targetChannel, const char *source, qint32 sourceChannels, qint32 sourceChannel, qint32 width)
|
||||
{
|
||||
auto s = reinterpret_cast<const T*>(source);
|
||||
auto t = reinterpret_cast<T*>(target);
|
||||
for (qint32 x = 0; x < width; ++x) {
|
||||
t[x * targetChannels + targetChannel] = s[x * sourceChannels + sourceChannel];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<class T>
|
||||
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<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||
else if (header.depth == 16)
|
||||
cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||
if (tmpCmyk.isNull()) {
|
||||
if (header.depth == 8)
|
||||
cmykToRgb<quint8>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||
else if (header.depth == 16)
|
||||
cmykToRgb<quint16>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width, alpha);
|
||||
}
|
||||
else if (header.depth == 8) {
|
||||
rawChannelsCopyToCMYK<quint8>(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<quint8>(img.scanLine(y), imgChannels, 3, psdScanline.data(), header.channel_count, 4, header.width);
|
||||
}
|
||||
else if (header.depth == 16) {
|
||||
rawChannelsCopyToCMYK<quint16>(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<quint16>(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<quint8>(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<quint8>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||
else
|
||||
planarToChunchy<quint8>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||
}
|
||||
else if (header.depth == 16) { // 16-bits integer images: Grayscale, RGB/RGBA
|
||||
planarToChunchy<quint16>(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<quint16>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||
else
|
||||
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||
}
|
||||
else if (header.depth == 32 && header.color_mode == CM_RGB) { // 32-bits float images: RGB/RGBA
|
||||
planarToChunchy<float>(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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user