mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2026-05-25 13:08:28 -04:00
EXR: fix incorrect loading of EXR files saved by Photoshop 2026
This commit is contained in:
@@ -324,6 +324,10 @@ plugin:
|
||||
attribute named "xmp". Note that Gimp reads the "xmp" attribute and Darktable
|
||||
writes it as well.
|
||||
|
||||
The plugin can set the following additional metadata:
|
||||
- `EXRLayerName`: A string containing the name of the EXR layer used to decode
|
||||
the image.
|
||||
|
||||
### The EPS plugin
|
||||
|
||||
The plugin uses `Ghostscript` to convert the raster image. When reading it
|
||||
|
||||
BIN
autotests/read/exr/ps2026_testcard_rgb.exr
Normal file
BIN
autotests/read/exr/ps2026_testcard_rgb.exr
Normal file
Binary file not shown.
15
autotests/read/exr/ps2026_testcard_rgb.exr.json
Normal file
15
autotests/read/exr/ps2026_testcard_rgb.exr.json
Normal file
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "ps2026_testcard_rgb.png",
|
||||
"colorSpace" : {
|
||||
"description" : "sRGB build-in (Profilo RGB lineare)",
|
||||
"primaries" : "SRgb",
|
||||
"transferFunction" : "Linear",
|
||||
"gamma" : 1
|
||||
},
|
||||
"resolution" : {
|
||||
"dotsPerMeterX" : 3937,
|
||||
"dotsPerMeterY" : 3937
|
||||
}
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/exr/ps2026_testcard_rgb.png
Normal file
BIN
autotests/read/exr/ps2026_testcard_rgb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"fileName" : "rgb-gimp.png",
|
||||
"colorSpace" : {
|
||||
"description" : "",
|
||||
"description" : "Embedded RGB (linear)",
|
||||
"primaries" : "Custom",
|
||||
"transferFunction" : "Linear",
|
||||
"gamma" : 1
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
#include <ImathBox.h>
|
||||
#include <ImfArray.h>
|
||||
#include <ImfBoxAttribute.h>
|
||||
#include <ImfOpaqueAttribute.h>
|
||||
#include <ImfChannelListAttribute.h>
|
||||
#include <ImfCompressionAttribute.h>
|
||||
#include <ImfConvert.h>
|
||||
@@ -227,8 +228,9 @@ static QImage::Format imageFormat(const Imf::RgbaInputFile &file)
|
||||
|
||||
/*!
|
||||
* \brief viewList
|
||||
* \param header
|
||||
* \param header The image header.
|
||||
* \return The list of available views.
|
||||
* \note This plugin does not support compositing layers which are returned as single images.
|
||||
*/
|
||||
static QStringList viewList(const Imf::Header &h)
|
||||
{
|
||||
@@ -238,14 +240,44 @@ static QStringList viewList(const Imf::Header &h)
|
||||
l << QString::fromStdString(v);
|
||||
}
|
||||
}
|
||||
if (l.isEmpty()) {
|
||||
// Recent versions of Photoshop save images by setting the layer.
|
||||
// Channels are named Layer 1.A, Layer 1.B, etc., so I have to set
|
||||
// the layer or the images will appear black.
|
||||
auto channels = h.channels();
|
||||
for (auto i = channels.begin(); i != channels.end(); ++i) {
|
||||
auto name = QString::fromLatin1(i.name(), -1);
|
||||
auto idx = name.indexOf(QChar(u'.'));
|
||||
if (idx > -1)
|
||||
l << name.left(idx);
|
||||
}
|
||||
l.removeDuplicates();
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
static QString setLayerName(Imf::RgbaInputFile &file, qint32 imageNumber = -1)
|
||||
{
|
||||
// set the image to load
|
||||
QString layerName;
|
||||
auto &&header = file.header();
|
||||
if (imageNumber > -1) {
|
||||
auto views = viewList(header);
|
||||
if (imageNumber < views.count())
|
||||
layerName = views.at(imageNumber);
|
||||
}
|
||||
// set the layer name
|
||||
if (!layerName.isEmpty()) {
|
||||
file.setLayerName(layerName.toStdString());
|
||||
}
|
||||
return layerName;
|
||||
}
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
static void printAttributes(const Imf::Header &h)
|
||||
{
|
||||
for (auto i = h.begin(); i != h.end(); ++i) {
|
||||
qCDebug(LOG_EXRPLUGIN) << i.name();
|
||||
qCDebug(LOG_EXRPLUGIN) << i.name() << i.attribute().typeName();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -340,15 +372,29 @@ static void readColorSpace(const Imf::Header &header, QImage &image)
|
||||
{
|
||||
// final color operations
|
||||
QColorSpace cs;
|
||||
if (auto chroma = header.findTypedAttribute<Imf::ChromaticitiesAttribute>("chromaticities")) {
|
||||
auto &&v = chroma->value();
|
||||
cs = QColorSpace(QPointF(v.white.x, v.white.y),
|
||||
QPointF(v.red.x, v.red.y),
|
||||
QPointF(v.green.x, v.green.y),
|
||||
QPointF(v.blue.x, v.blue.y),
|
||||
QColorSpace::TransferFunction::Linear);
|
||||
|
||||
// Photoshop 2026 allow to save the ICC profile as "iccProfile" attribute
|
||||
if (auto iccProfile = header.findTypedAttribute<Imf::OpaqueAttribute>("iccProfile")) {
|
||||
auto &&v = iccProfile->data();
|
||||
cs = QColorSpace::fromIccProfile(QByteArray::fromRawData(v, v.size()));
|
||||
}
|
||||
|
||||
if (!cs.isValid()) {
|
||||
// Creating the ICC profile from Chromaticities
|
||||
if (auto chroma = header.findTypedAttribute<Imf::ChromaticitiesAttribute>("chromaticities")) {
|
||||
auto &&v = chroma->value();
|
||||
cs = QColorSpace(QPointF(v.white.x, v.white.y),
|
||||
QPointF(v.red.x, v.red.y),
|
||||
QPointF(v.green.x, v.green.y),
|
||||
QPointF(v.blue.x, v.blue.y),
|
||||
QColorSpace::TransferFunction::Linear);
|
||||
if (cs.isValid())
|
||||
cs.setDescription(QStringLiteral("Embedded RGB (linear)"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!cs.isValid()) {
|
||||
// Use a linear profile
|
||||
cs = QColorSpace(QColorSpace::SRgbLinear);
|
||||
}
|
||||
image.setColorSpace(cs);
|
||||
@@ -377,12 +423,7 @@ bool EXRHandler::read(QImage *outImage)
|
||||
auto &&header = file.header();
|
||||
|
||||
// set the image to load
|
||||
if (m_imageNumber > -1) {
|
||||
auto views = viewList(header);
|
||||
if (m_imageNumber < views.count()) {
|
||||
file.setLayerName(views.at(m_imageNumber).toStdString());
|
||||
}
|
||||
}
|
||||
auto layerName = setLayerName(file, m_imageNumber);
|
||||
|
||||
// get image info
|
||||
Imath::Box2i dw = file.dataWindow();
|
||||
@@ -401,6 +442,9 @@ bool EXRHandler::read(QImage *outImage)
|
||||
qCWarning(LOG_EXRPLUGIN) << "Failed to allocate image, invalid size?" << QSize(width, height);
|
||||
return false;
|
||||
}
|
||||
if (!layerName.isEmpty()) {
|
||||
image.setText(QStringLiteral("EXRLayerName"), layerName);
|
||||
}
|
||||
|
||||
Imf::Array2D<Imf::Rgba> pixels;
|
||||
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
|
||||
@@ -688,12 +732,7 @@ QVariant EXRHandler::option(ImageOption option) const
|
||||
try {
|
||||
K_IStream istr(d);
|
||||
Imf::RgbaInputFile file(istr);
|
||||
if (m_imageNumber > -1) { // set the image to read
|
||||
auto views = viewList(file.header());
|
||||
if (m_imageNumber < views.count()) {
|
||||
file.setLayerName(views.at(m_imageNumber).toStdString());
|
||||
}
|
||||
}
|
||||
setLayerName(file, m_imageNumber);
|
||||
Imath::Box2i dw = file.dataWindow();
|
||||
v = QVariant(QSize(dw.max.x - dw.min.x + 1, dw.max.y - dw.min.y + 1));
|
||||
} catch (const std::exception &) {
|
||||
@@ -713,6 +752,7 @@ QVariant EXRHandler::option(ImageOption option) const
|
||||
try {
|
||||
K_IStream istr(d);
|
||||
Imf::RgbaInputFile file(istr);
|
||||
setLayerName(file, m_imageNumber);
|
||||
v = QVariant::fromValue(imageFormat(file));
|
||||
} catch (const std::exception &) {
|
||||
// broken file or unsupported version
|
||||
@@ -787,12 +827,9 @@ bool EXRHandler::canRead(QIODevice *device)
|
||||
return false;
|
||||
}
|
||||
|
||||
#if OPENEXR_VERSION_MAJOR == 3 && OPENEXR_VERSION_MINOR > 2
|
||||
// openexpr >= 3.3 uses seek and tell extensively
|
||||
if (device->isSequential()) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
const QByteArray head = device->peek(4);
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@ private:
|
||||
* - 7: lossy 4-by-4 pixel block compression, fields are compressed more
|
||||
* - 8: lossy DCT based compression, in blocks of 32 scanlines. More efficient for partial buffer access.
|
||||
* - 9: lossy DCT based compression, in blocks of 256 scanlines. More efficient space wise and faster to decode full frames than DWAA_COMPRESSION.
|
||||
* - 10: High-Throughput JPEG2000 (HTJ2K), 256 scanlines (requires OpenEXR 3.4+).
|
||||
* - 11: High-Throughput JPEG2000 (HTJ2K), 32 scanlines (requires OpenEXR 3.4+).
|
||||
*/
|
||||
qint32 m_compressionRatio;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user