hdr: options support and bugfixes

- Support for Size and Format options
- Added compile option to generate 16-bit instead of 32-bit images
- Includes MR !189 and MR !192 fixes
This commit is contained in:
Mirco Miranda 2023-09-11 09:08:18 +00:00 committed by Albert Astals Cid
parent 0a6fbd88e9
commit 75e1280073
4 changed files with 118 additions and 27 deletions

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -11,12 +11,18 @@
#include <QColorSpace> #include <QColorSpace>
#include <QDataStream> #include <QDataStream>
#include <QFloat16>
#include <QImage> #include <QImage>
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QRegularExpressionMatch> #include <QRegularExpressionMatch>
#include <QDebug> #include <QDebug>
/* *** HDR_HALF_QUALITY ***
* If defined, a 16-bits float image is created, otherwise a 32-bits float ones (default).
*/
//#define HDR_HALF_QUALITY // default commented -> you should define it in your cmake file
typedef unsigned char uchar; typedef unsigned char uchar;
Q_LOGGING_CATEGORY(HDRPLUGIN, "kf.imageformats.plugins.hdr", QtWarningMsg) Q_LOGGING_CATEGORY(HDRPLUGIN, "kf.imageformats.plugins.hdr", QtWarningMsg)
@ -34,6 +40,7 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
int rshift = 0; int rshift = 0;
int i; int i;
uchar *start = image;
while (width > 0) { while (width > 0) {
s >> image[0]; s >> image[0];
s >> image[1]; s >> image[1];
@ -45,7 +52,14 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
} }
if ((image[0] == 1) && (image[1] == 1) && (image[2] == 1)) { if ((image[0] == 1) && (image[1] == 1) && (image[2] == 1)) {
for (i = image[3] << rshift; i > 0; i--) { // NOTE: we don't have an image sample that cover this code
if (rshift > 31) {
return false;
}
for (i = image[3] << rshift; i > 0 && width > 0; i--) {
if (image == start) {
return false; // you cannot be here at the first run
}
// memcpy(image, image-4, 4); // memcpy(image, image-4, 4);
(uint &)image[0] = (uint &)image[0 - 4]; (uint &)image[0] = (uint &)image[0 - 4];
image += 4; image += 4;
@ -61,12 +75,13 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
return true; return true;
} }
static void RGBE_To_QRgbLine(uchar *image, float *scanline, int width) template<class float_T>
void RGBE_To_QRgbLine(uchar *image, float_T *scanline, int width)
{ {
for (int j = 0; j < width; j++) { for (int j = 0; j < width; j++) {
// v = ldexp(1.0, int(image[3]) - 128); // v = ldexp(1.0, int(image[3]) - 128);
float v; float v;
int e = int(image[3]) - 128; int e = qBound(-31, int(image[3]) - 128, 31);
if (e > 0) { if (e > 0) {
v = float(1 << e); v = float(1 << e);
} else { } else {
@ -75,14 +90,23 @@ static void RGBE_To_QRgbLine(uchar *image, float *scanline, int width)
auto j4 = j * 4; auto j4 = j * 4;
auto vn = v / 255.0f; auto vn = v / 255.0f;
scanline[j4] = std::min(float(image[0]) * vn, 1.0f); scanline[j4] = float_T(std::min(float(image[0]) * vn, 1.0f));
scanline[j4 + 1] = std::min(float(image[1]) * vn, 1.0f); scanline[j4 + 1] = float_T(std::min(float(image[1]) * vn, 1.0f));
scanline[j4 + 2] = std::min(float(image[2]) * vn, 1.0f); scanline[j4 + 2] = float_T(std::min(float(image[2]) * vn, 1.0f));
scanline[j4 + 3] = 1.0f; scanline[j4 + 3] = float_T(1.0f);
image += 4; image += 4;
} }
} }
QImage::Format imageFormat()
{
#ifdef HDR_HALF_QUALITY
return QImage::Format_RGBX16FPx4;
#else
return QImage::Format_RGBX32FPx4;
#endif
}
// Load the HDR image. // Load the HDR image.
static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &img) static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &img)
{ {
@ -90,7 +114,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
uchar code; uchar code;
// Create dst image. // Create dst image.
img = imageAlloc(width, height, QImage::Format_RGBX32FPx4); img = imageAlloc(width, height, imageFormat());
if (img.isNull()) { if (img.isNull()) {
qCDebug(HDRPLUGIN) << "Couldn't create image with size" << width << height << "and format RGB32"; qCDebug(HDRPLUGIN) << "Couldn't create image with size" << width << height << "and format RGB32";
return false; return false;
@ -98,10 +122,14 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
QByteArray lineArray; QByteArray lineArray;
lineArray.resize(4 * width); lineArray.resize(4 * width);
uchar *image = (uchar *)lineArray.data(); uchar *image = reinterpret_cast<uchar *>(lineArray.data());
for (int cline = 0; cline < height; cline++) { for (int cline = 0; cline < height; cline++) {
auto scanline = (float *)img.scanLine(cline); #ifdef HDR_HALF_QUALITY
auto scanline = reinterpret_cast<qfloat16 *>(img.scanLine(cline));
#else
auto scanline = reinterpret_cast<float *>(img.scanLine(cline));
#endif
// determine scanline type // determine scanline type
if ((width < MINELEN) || (MAXELEN < width)) { if ((width < MINELEN) || (MAXELEN < width)) {
@ -144,7 +172,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
} }
// read each component // read each component
for (int i = 0; i < 4; i++) { for (int i = 0, len = int(lineArray.size()); i < 4; i++) {
for (int j = 0; j < width;) { for (int j = 0; j < width;) {
s >> code; s >> code;
if (s.atEnd()) { if (s.atEnd()) {
@ -156,14 +184,20 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
code &= 127; code &= 127;
s >> val; s >> val;
while (code != 0) { while (code != 0) {
image[i + j * 4] = val; auto idx = i + j * 4;
if (idx < len) {
image[idx] = val;
}
j++; j++;
code--; code--;
} }
} else { } else {
// non-run // non-run
while (code != 0) { while (code != 0) {
s >> image[i + j * 4]; auto idx = i + j * 4;
if (idx < len) {
s >> image[idx];
}
j++; j++;
code--; code--;
} }
@ -177,9 +211,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
return true; return true;
} }
} // namespace static QSize readHeaderSize(QIODevice *device)
bool HDRHandler::read(QImage *outImage)
{ {
int len; int len;
QByteArray line(MAXLINE + 1, Qt::Uninitialized); QByteArray line(MAXLINE + 1, Qt::Uninitialized);
@ -187,7 +219,7 @@ bool HDRHandler::read(QImage *outImage)
// Parse header // Parse header
do { do {
len = device()->readLine(line.data(), MAXLINE); len = device->readLine(line.data(), MAXLINE);
if (line.startsWith("FORMAT=")) { if (line.startsWith("FORMAT=")) {
format = line.mid(7, len - 7 - 1 /*\n*/); format = line.mid(7, len - 7 - 1 /*\n*/);
@ -197,10 +229,10 @@ bool HDRHandler::read(QImage *outImage)
if (format != "32-bit_rle_rgbe") { if (format != "32-bit_rle_rgbe") {
qCDebug(HDRPLUGIN) << "Unknown HDR format:" << format; qCDebug(HDRPLUGIN) << "Unknown HDR format:" << format;
return false; return QSize();
} }
len = device()->readLine(line.data(), MAXLINE); len = device->readLine(line.data(), MAXLINE);
line.resize(len); line.resize(len);
/* /*
@ -220,21 +252,30 @@ bool HDRHandler::read(QImage *outImage)
QRegularExpressionMatch match = resolutionRegExp.match(QString::fromLatin1(line)); QRegularExpressionMatch match = resolutionRegExp.match(QString::fromLatin1(line));
if (!match.hasMatch()) { if (!match.hasMatch()) {
qCDebug(HDRPLUGIN) << "Invalid HDR file, the first line after the header didn't have the expected format:" << line; qCDebug(HDRPLUGIN) << "Invalid HDR file, the first line after the header didn't have the expected format:" << line;
return false; return QSize();
} }
if ((match.captured(1).at(1) != u'Y') || (match.captured(3).at(1) != u'X')) { if ((match.captured(1).at(1) != u'Y') || (match.captured(3).at(1) != u'X')) {
qCDebug(HDRPLUGIN) << "Unsupported image orientation in HDR file."; qCDebug(HDRPLUGIN) << "Unsupported image orientation in HDR file.";
return QSize();
}
return QSize(match.captured(4).toInt(), match.captured(2).toInt());
}
} // namespace
bool HDRHandler::read(QImage *outImage)
{
QDataStream s(device());
QSize size = readHeaderSize(s.device());
if (!size.isValid()) {
return false; return false;
} }
const int width = match.captured(4).toInt();
const int height = match.captured(2).toInt();
QDataStream s(device());
QImage img; QImage img;
if (!LoadHDR(s, width, height, img)) { if (!LoadHDR(s, size.width(), size.height(), img)) {
// qDebug() << "Error loading HDR file."; // qDebug() << "Error loading HDR file.";
return false; return false;
} }
@ -246,6 +287,40 @@ bool HDRHandler::read(QImage *outImage)
return true; return true;
} }
bool HDRHandler::supportsOption(ImageOption option) const
{
if (option == QImageIOHandler::Size) {
return true;
}
if (option == QImageIOHandler::ImageFormat) {
return true;
}
return false;
}
QVariant HDRHandler::option(ImageOption option) const
{
QVariant v;
if (option == QImageIOHandler::Size) {
if (auto d = device()) {
// transactions works on both random and sequential devices
d->startTransaction();
auto size = readHeaderSize(d);
d->rollbackTransaction();
if (size.isValid()) {
v = QVariant::fromValue(size);
}
}
}
if (option == QImageIOHandler::ImageFormat) {
v = QVariant::fromValue(imageFormat());
}
return v;
}
HDRHandler::HDRHandler() HDRHandler::HDRHandler()
{ {
} }
@ -266,7 +341,20 @@ bool HDRHandler::canRead(QIODevice *device)
return false; return false;
} }
return device->peek(11) == "#?RADIANCE\n" || device->peek(7) == "#?RGBE\n"; // the .pic taken from official test cases does not start with this string but can be loaded.
if(device->peek(11) == "#?RADIANCE\n" || device->peek(7) == "#?RGBE\n") {
return true;
}
// allow to load offical test cases: https://radsite.lbl.gov/radiance/framed.html
device->startTransaction();
QSize size = readHeaderSize(device);
device->rollbackTransaction();
if (size.isValid()) {
return true;
}
return false;
} }
QImageIOPlugin::Capabilities HDRPlugin::capabilities(QIODevice *device, const QByteArray &format) const QImageIOPlugin::Capabilities HDRPlugin::capabilities(QIODevice *device, const QByteArray &format) const

View File

@ -18,6 +18,9 @@ public:
bool canRead() const override; bool canRead() const override;
bool read(QImage *outImage) override; bool read(QImage *outImage) override;
bool supportsOption(QImageIOHandler::ImageOption option) const override;
QVariant option(QImageIOHandler::ImageOption option) const override;
static bool canRead(QIODevice *device); static bool canRead(QIODevice *device);
}; };