mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-11-22 10:02:43 -05:00
PSD: add support for GrayA (8/16/32 bit) and Gray 32 bit
This commit is contained in:
@ -414,6 +414,8 @@ PSD support has the following limitations:
|
|||||||
- Multichannel images are treated as CMYK if they have 2 or more channels.
|
- Multichannel images are treated as CMYK if they have 2 or more channels.
|
||||||
- Multichannel images are treated as Grayscale if they have 1 channel.
|
- Multichannel images are treated as Grayscale if they have 1 channel.
|
||||||
- Duotone images are treated as grayscale images.
|
- Duotone images are treated as grayscale images.
|
||||||
|
- Grayscale images with alpha channel or at 32 bit depth are converted to
|
||||||
|
RGBA due to the lack of the appropriate Qt grayscale container.
|
||||||
- Extra channels other than alpha are discarded.
|
- Extra channels other than alpha are discarded.
|
||||||
|
|
||||||
The following defines can be defined in cmake to modify the behavior of the
|
The following defines can be defined in cmake to modify the behavior of the
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
{
|
{
|
||||||
"fileName" : "32bit_grayscale.png",
|
"fileName" : "32bit_grayscale.png",
|
||||||
"colorSpace" : {
|
"colorSpace" : {
|
||||||
"description" : "Linear Grayscale Profile",
|
"description" : "RGB emulation of \"Linear Grayscale Profile\"",
|
||||||
"colorModel" : "Gray",
|
"colorModel" : "Rgb",
|
||||||
"primaries" : "Custom",
|
"primaries" : "Custom",
|
||||||
"transferFunction" : "Linear",
|
"transferFunction" : "Linear",
|
||||||
"gamma" : 1
|
"gamma" : 1
|
||||||
|
|||||||
BIN
autotests/read/psd/testcard_graya16.png
Normal file
BIN
autotests/read/psd/testcard_graya16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
autotests/read/psd/testcard_graya16.psd
Normal file
BIN
autotests/read/psd/testcard_graya16.psd
Normal file
Binary file not shown.
26
autotests/read/psd/testcard_graya16.psd.json
Normal file
26
autotests/read/psd/testcard_graya16.psd.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"fileName" : "testcard_graya16.png",
|
||||||
|
"colorSpace" : {
|
||||||
|
"description" : "RGB emulation of \"Gray Gamma 2.2\"",
|
||||||
|
"colorModel" : "Rgb",
|
||||||
|
"primaries" : "SRgb",
|
||||||
|
"transferFunction" : "Gamma",
|
||||||
|
"gamma" : 2.19922
|
||||||
|
},
|
||||||
|
"metadata" : [
|
||||||
|
{
|
||||||
|
"key" : "ModificationDate",
|
||||||
|
"value" : "2025-11-17T07:27:47"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key" : "Software" ,
|
||||||
|
"value" : "Adobe Photoshop 26.11 (Windows)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"resolution" : {
|
||||||
|
"dotsPerMeterX" : 11811,
|
||||||
|
"dotsPerMeterY" : 11811
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
BIN
autotests/read/psd/testcard_graya32.png
Normal file
BIN
autotests/read/psd/testcard_graya32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
autotests/read/psd/testcard_graya32.psd
Normal file
BIN
autotests/read/psd/testcard_graya32.psd
Normal file
Binary file not shown.
26
autotests/read/psd/testcard_graya32.psd.json
Normal file
26
autotests/read/psd/testcard_graya32.psd.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"fileName" : "testcard_graya32.png",
|
||||||
|
"colorSpace" : {
|
||||||
|
"description" : "RGB emulation of \"Profilo scala di grigio lineare\"",
|
||||||
|
"colorModel" : "Rgb",
|
||||||
|
"primaries" : "Custom",
|
||||||
|
"transferFunction" : "Linear",
|
||||||
|
"gamma" : 1
|
||||||
|
},
|
||||||
|
"metadata" : [
|
||||||
|
{
|
||||||
|
"key" : "ModificationDate",
|
||||||
|
"value" : "2025-11-17T07:29:19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key" : "Software" ,
|
||||||
|
"value" : "Adobe Photoshop 26.11 (Windows)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"resolution" : {
|
||||||
|
"dotsPerMeterX" : 11811,
|
||||||
|
"dotsPerMeterY" : 11811
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
BIN
autotests/read/psd/testcard_graya8.png
Normal file
BIN
autotests/read/psd/testcard_graya8.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
autotests/read/psd/testcard_graya8.psd
Normal file
BIN
autotests/read/psd/testcard_graya8.psd
Normal file
Binary file not shown.
28
autotests/read/psd/testcard_graya8.psd.json
Normal file
28
autotests/read/psd/testcard_graya8.psd.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"fileName" : "testcard_graya8.png",
|
||||||
|
"fuzziness" : 1,
|
||||||
|
"perceptiveFuzziness" : true,
|
||||||
|
"colorSpace" : {
|
||||||
|
"description" : "RGB emulation of \"Gray Gamma 2.2\"",
|
||||||
|
"colorModel" : "Rgb",
|
||||||
|
"primaries" : "SRgb",
|
||||||
|
"transferFunction" : "Gamma",
|
||||||
|
"gamma" : 2.19922
|
||||||
|
},
|
||||||
|
"metadata" : [
|
||||||
|
{
|
||||||
|
"key" : "ModificationDate",
|
||||||
|
"value" : "2025-11-17T07:28:50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key" : "Software" ,
|
||||||
|
"value" : "Adobe Photoshop 26.11 (Windows)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"resolution" : {
|
||||||
|
"dotsPerMeterX" : 11811,
|
||||||
|
"dotsPerMeterY" : 11811
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -546,8 +546,21 @@ static bool setColorSpace(QImage &img, const PSDImageResourceSection &irs)
|
|||||||
auto cs = QColorSpace::fromIccProfile(irb.data);
|
auto cs = QColorSpace::fromIccProfile(irb.data);
|
||||||
if (!cs.isValid())
|
if (!cs.isValid())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (cs.colorModel() == QColorSpace::ColorModel::Gray && img.pixelFormat().colorModel() != QPixelFormat::Grayscale) {
|
||||||
|
// I created an RGB from a grayscale without using color profile conversion (fast).
|
||||||
|
// I'll try to create an RGB profile that looks the same.
|
||||||
|
if (cs.transferFunction() != QColorSpace::TransferFunction::Custom) {
|
||||||
|
auto tmp = QColorSpace(QColorSpace::Primaries::SRgb, cs.transferFunction(), cs.gamma());
|
||||||
|
tmp.setWhitePoint(cs.whitePoint());
|
||||||
|
tmp.setDescription(QStringLiteral("RGB emulation of \"%1\"").arg(cs.description()));
|
||||||
|
if (tmp.isValid())
|
||||||
|
cs = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
img.setColorSpace(cs);
|
img.setColorSpace(cs);
|
||||||
return true;
|
return img.colorSpace().isValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -800,6 +813,14 @@ static QImage::Format imageFormat(const PSDHeader &header, bool alpha)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CM_GRAYSCALE:
|
case CM_GRAYSCALE:
|
||||||
|
if (header.depth == 32) {
|
||||||
|
format = !alpha ? QImage::Format_RGBX32FPx4 : QImage::Format_RGBA32FPx4_Premultiplied;
|
||||||
|
} else if (header.depth == 16) {
|
||||||
|
format = !alpha ? QImage::Format_Grayscale16 : QImage::Format_RGBA64_Premultiplied;
|
||||||
|
} else {
|
||||||
|
format = !alpha ? QImage::Format_Grayscale8 : QImage::Format_RGBA8888_Premultiplied;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case CM_DUOTONE:
|
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;
|
||||||
@ -1309,7 +1330,6 @@ bool PSDHandler::read(QImage *image)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto imgChannels = imageChannels(img.format());
|
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 raw_count = qsizetype(header.width * header.depth + 7) / 8;
|
||||||
auto native_cmyk = img.format() == CMYK_FORMAT;
|
auto native_cmyk = img.format() == CMYK_FORMAT;
|
||||||
|
|
||||||
@ -1417,6 +1437,14 @@ bool PSDHandler::read(QImage *image)
|
|||||||
else if (header.depth == 32)
|
else if (header.depth == 32)
|
||||||
premulConversion<float>(scanLine, header.width, 3, header.channel_count, PremulConversion::PS2P);
|
premulConversion<float>(scanLine, header.width, 3, header.channel_count, PremulConversion::PS2P);
|
||||||
}
|
}
|
||||||
|
if (header.color_mode == CM_GRAYSCALE) {
|
||||||
|
if (header.depth == 8)
|
||||||
|
premulConversion<quint8>(scanLine, header.width, 1, header.channel_count, PremulConversion::PS2P);
|
||||||
|
else if (header.depth == 16)
|
||||||
|
premulConversion<quint16>(scanLine, header.width, 1, header.channel_count, PremulConversion::PS2P);
|
||||||
|
else if (header.depth == 32)
|
||||||
|
premulConversion<float>(scanLine, header.width, 1, header.channel_count, PremulConversion::PS2P);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conversion to RGB
|
// Conversion to RGB
|
||||||
@ -1454,9 +1482,21 @@ bool PSDHandler::read(QImage *image)
|
|||||||
else if (header.depth == 32)
|
else if (header.depth == 32)
|
||||||
rawChannelsCopy<float>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width);
|
rawChannelsCopy<float>(img.scanLine(y), imgChannels, psdScanline.data(), header.channel_count, header.width);
|
||||||
}
|
}
|
||||||
|
if (header.color_mode == CM_GRAYSCALE) {
|
||||||
|
for (auto c = 0; c < imgChannels; ++c) { // GRAYA to RGBA
|
||||||
|
auto sc = qBound(0, c - 2, int(header.channel_count));
|
||||||
|
if (header.depth == 8)
|
||||||
|
rawChannelCopy<quint8>(img.scanLine(y), imgChannels, c, psdScanline.data(), header.channel_count, sc, header.width);
|
||||||
|
else if (header.depth == 16)
|
||||||
|
rawChannelCopy<quint16>(img.scanLine(y), imgChannels, c, psdScanline.data(), header.channel_count, sc, header.width);
|
||||||
|
else if (header.depth == 32)
|
||||||
|
rawChannelCopy<float>(img.scanLine(y), imgChannels, c, psdScanline.data(), header.channel_count, sc, header.width);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Linear read (no position jumps): optimized code usable only for the colorspaces supported by QImage
|
// Linear read (no position jumps): optimized code usable only for the colorspaces supported by QImage
|
||||||
|
auto channel_num = std::min(qint32(header.channel_count), header.color_mode == CM_GRAYSCALE ? 1 : imgChannels);
|
||||||
for (qint32 c = 0; c < channel_num; ++c) {
|
for (qint32 c = 0; c < channel_num; ++c) {
|
||||||
for (qint32 y = 0, h = header.height; y < h; ++y) {
|
for (qint32 y = 0, h = header.height; y < h; ++y) {
|
||||||
auto&& strideSize = strides.at(c * qsizetype(h) + y);
|
auto&& strideSize = strides.at(c * qsizetype(h) + y);
|
||||||
@ -1485,8 +1525,13 @@ bool PSDHandler::read(QImage *image)
|
|||||||
// 32-bits float images: RGB/RGBA
|
// 32-bits float images: RGB/RGBA
|
||||||
planarToChunchy<float>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
planarToChunchy<float>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||||
} else if (header.depth == 32 && header.color_mode == CM_GRAYSCALE) {
|
} else if (header.depth == 32 && header.color_mode == CM_GRAYSCALE) {
|
||||||
// 32-bits float images: Grayscale (coverted to equivalent integer 16-bits)
|
if (imgChannels >= 3) { // GRAY to RGB
|
||||||
planarToChunchyFloatToUInt16<float>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
planarToChunchy<float>(scanLine, rawStride.data(), header.width, 0, imgChannels);
|
||||||
|
planarToChunchy<float>(scanLine, rawStride.data(), header.width, 1, imgChannels);
|
||||||
|
planarToChunchy<float>(scanLine, rawStride.data(), header.width, 2, imgChannels);
|
||||||
|
} else { // 32-bits float images: Grayscale (coverted to equivalent integer 16-bits)
|
||||||
|
planarToChunchyFloatToUInt16<float>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1622,6 +1667,9 @@ bool PSDHandler::canRead(QIODevice *device)
|
|||||||
if (header.color_mode == CM_RGB && header.channel_count > 3) {
|
if (header.color_mode == CM_RGB && header.channel_count > 3) {
|
||||||
return false; // supposing extra channel as alpha
|
return false; // supposing extra channel as alpha
|
||||||
}
|
}
|
||||||
|
if (header.color_mode == CM_GRAYSCALE && (header.channel_count > 1 || header.depth == 32)) {
|
||||||
|
return false; // supposing extra channel as alpha
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return IsSupported(header);
|
return IsSupported(header);
|
||||||
|
|||||||
Reference in New Issue
Block a user