From 75e1280073d7fa578aba59c03dbc6dae90a32f70 Mon Sep 17 00:00:00 2001 From: Mirco Miranda Date: Mon, 11 Sep 2023 09:08:18 +0000 Subject: [PATCH] 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 --- autotests/read/hdr/fake_earth.hdr | Bin 0 -> 1789 bytes autotests/read/hdr/fake_earth.png | Bin 0 -> 2761 bytes src/imageformats/hdr.cpp | 142 ++++++++++++++++++++++++------ src/imageformats/hdr_p.h | 3 + 4 files changed, 118 insertions(+), 27 deletions(-) create mode 100644 autotests/read/hdr/fake_earth.hdr create mode 100644 autotests/read/hdr/fake_earth.png diff --git a/autotests/read/hdr/fake_earth.hdr b/autotests/read/hdr/fake_earth.hdr new file mode 100644 index 0000000000000000000000000000000000000000..553cbdc26e51b8c9889c4d01c3c8f95e26a74925 GIT binary patch literal 1789 zcmZuye@Giw9Iw%wE~U2JO6^9sg|3^j&P}81oSLk*)~#jQk<7HBo2x8#S!GkxA0%GP zI~$#IGm|<=Cz4jOiiAQDX-hUP;cF)dsl^(6{NdGbu8mZJAX}i|$GPd1=dI-?vbTgvJg_B9Ij~=c( zzVhUT$2a2%M+8V^2M0xe*Q+m`oOfLve|+ii7tOUA?_`^)R6UJL|7 zaovjLgt7fQWVJJzs&U|KdiTuUnw9$MPjidCaV(by@6;f$+ho8t2y$%pEz&Q%GB|cZ zkw>(?mtQnIsCr}P2hAr3UQ;x0NO!WL$VAgI&%V6E169l3o`2k9t~Fmq+Id9r%-fTi zs#||Y-K1WlUHkKd?zwtDHiG&VzUenma%fW$nrku6U0g7qZfL1el})ihANl3X-Id8M z?}oY4F%hJMbn4~-`JVO%T;kD#AWbu|jJcs3;Mmma&8w5%@On4`8yTASIo(k>0MJ}J z$f17Rlmk{vf+hQ{=H8w_cd5>21^woDG9_B#Xms-7-)iR5LtuBH`f%}t1wN)kQ3QBy zc8P+;(r2|?=E9)}oEAb%rIURqMGb%>s=30wgT(7A;m=E^7Sz7z%pie1|_B__=RCjQd0aVZ^Ms(u3Z zKqNVP-Z>GJUS4M7u|xi&tq!;3 zn=s>W&Z7{U3;VlaZuHmQuS{`_r>ETaozm9WPrLW%83~Un*~If>Bzau|vF4@6;~d@^ z)vGn`W>%;G%nX~i zswK7C>#LA5V3t{LS2}V{iUPcEVg{V7I4fXZP@A_=0OVglc<0x&J#gLvsk>iGAA$G=I5~dp*>w9= zl2=<_(_4T>t_Zf^%WqTOoo-^aiJ=d;YSG{R{D^<@0c}w@b%2f~761SM32;bRa{vGi z!vFvd!vV){sAK>D00(qQO+^Ri2N(_|C#QdEd;kCe{ApBJbXZMHI%98bE@5PEVr4FP zZEyeq0001ZoQ+gXkDD+Mz3;EE*h_l|2C~&EC}E|NY#V85D-vm|-W(W$6^t$0SxA0; z4au@m1Zg>8&wFn^JTud$T99N18f{=p%b8^%n$E1(FOd_85OVtcC}DN{u}+S=WRhA> z!QHg>YbF?If@)Tj%CBb@N%;nR^WR)!3$+lvn^~)a(!fv%k}#Smv|3Rn3i!G~MQ(mIxOg4P4O=4%v={rZxMCYhfcAJHraE5CluxmTGkCY}IN9<+MhL$E63Sdxb zWq58cPvl&4mGc3Yoq5o)i8hLE$IDnRbG{6J4lr9&FNy5U!}d|2rdjLFAbOcfFWyYW zZw0E>2LU(+#rHCS#hakx0egzD_?nSS%KcMfkyj%DVe|2)Wo~nx+X*DCF}1g#rM^n#M56B*P#yH-`{FKCh}6 zJDrMxaa|ok2q8pK2<38iyJ0vSBpHvZY9?bCio)|0mC5kDqSV)CvxZ?9w{H1-*(^c~ zgRx<#s@u(R00^-xNh(SN<3&qhrO2_umHq(6lR8(&?6#4?k3t0|yvJmgnXwD*+}ZJf6#! z-R}Cs$Btd^y?K-4Zam+0+3k+U+uKi`?CLswy0S8n=<9p^_1RgclcEqxBt}PHdu`o1 zQJkJ`X{oNhbSWH;#bo)(CrR>y4?g*1!-lG=bQ+;oU){A!k~%v(J6V=t?hWI<43t)V zbaZeK;LxGB-&WPBsf`DgILtEow+F?Kp-86lc(Zss^mV;GLhWNbF{UzOtT3kCWQb8}yP6%0;K zM@qoW^vbnxKpEJB@~+pi^mx%TfKPbBj2!}awI4YKTT2tujg)}M2^XqsV2vaYVV zSrh@PsyMDtXlOtPq1sx40AQHK9^RJ$igGvrIy$<#08Eo07-oFj>Fn%O6vMFDR<8z# z#r%GR00IF~bi0R#b^Vb?u3ZDL+YO_-`t#4F6VE=|)HF49`Lfq5wIvdUfiXf)JW*ZU z-5m_}^t|u_Lff{rwyJ6@hB3g<5XZUQ$>jX}&!fSh*Bksb!yv@6_4NQgZ9wUscba=eR&%%^H_Wk`5juNxMA|xa+H`qnL{Q<6@c@cC99o&K4k)YhIl zCCeNa4p&wJER>3MSX@FZ%P@-4-QC*S-w)t$jE&K>Buz~@9H~@yH-Kpdg8&5a^fvz8^eEEdbfVhqEw0|Q>K z$CLi;qmL$&0|Tp9H#NoMKA$96+5Z=?Gy|q72nbC~?AYP;o;!zeEEbRFavo1U&+{z% z$}0c>b#-M)E{lZ$n5JP6L?UtL&dlB;M_hmV$C?Wl*4JY<~YA9iyW&Gb1CpoM{#cWl1iJ#a&2}C~AJbz2D_J)^g){qWR*F zu-92_3uZEz4EtkE)3o+>j-x1-t1Q`Xz~U~%^MZhJTVGq7^G|j={})-NY4*>u?DZN3 zLNtvr#$^dEi^UnpW<^mDMn?ARdFGj`R~e>h5&#PDqAcrM{Qey~SeEBGt}MxAu>9SK z5I|LxAOHjcQ&S@&C2LjH%9Vb<^|Pqu0dO3~>Gac2&&~Dsvurf_@y8|m#*GyfH8mV3 zid(jn&Vb5M{U$6QC3GDi6pBP7>C~F{9%?&TvR8!mM5MEuH#ar{EPPj6gT(=8T0Sp| zw{Ks%w0CdvuY7=OM-tO+TYv<>`{8Cj* z_TJv^ZiXpcEk|#8mN-^bQp|4;r6bQr8|u_xNN P00000NkvXXu0mjf1g$lC literal 0 HcmV?d00001 diff --git a/src/imageformats/hdr.cpp b/src/imageformats/hdr.cpp index 1303fc3..3d4e384 100644 --- a/src/imageformats/hdr.cpp +++ b/src/imageformats/hdr.cpp @@ -11,12 +11,18 @@ #include #include +#include #include #include #include #include +/* *** 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; 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 i; + uchar *start = image; while (width > 0) { s >> image[0]; 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)) { - 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); (uint &)image[0] = (uint &)image[0 - 4]; image += 4; @@ -61,12 +75,13 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s) return true; } -static void RGBE_To_QRgbLine(uchar *image, float *scanline, int width) +template +void RGBE_To_QRgbLine(uchar *image, float_T *scanline, int width) { for (int j = 0; j < width; j++) { // v = ldexp(1.0, int(image[3]) - 128); float v; - int e = int(image[3]) - 128; + int e = qBound(-31, int(image[3]) - 128, 31); if (e > 0) { v = float(1 << e); } else { @@ -75,14 +90,23 @@ static void RGBE_To_QRgbLine(uchar *image, float *scanline, int width) auto j4 = j * 4; auto vn = v / 255.0f; - scanline[j4] = std::min(float(image[0]) * vn, 1.0f); - scanline[j4 + 1] = std::min(float(image[1]) * vn, 1.0f); - scanline[j4 + 2] = std::min(float(image[2]) * vn, 1.0f); - scanline[j4 + 3] = 1.0f; + scanline[j4] = float_T(std::min(float(image[0]) * vn, 1.0f)); + scanline[j4 + 1] = float_T(std::min(float(image[1]) * vn, 1.0f)); + scanline[j4 + 2] = float_T(std::min(float(image[2]) * vn, 1.0f)); + scanline[j4 + 3] = float_T(1.0f); image += 4; } } +QImage::Format imageFormat() +{ +#ifdef HDR_HALF_QUALITY + return QImage::Format_RGBX16FPx4; +#else + return QImage::Format_RGBX32FPx4; +#endif +} + // Load the HDR image. 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; // Create dst image. - img = imageAlloc(width, height, QImage::Format_RGBX32FPx4); + img = imageAlloc(width, height, imageFormat()); if (img.isNull()) { qCDebug(HDRPLUGIN) << "Couldn't create image with size" << width << height << "and format RGB32"; return false; @@ -98,10 +122,14 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i QByteArray lineArray; lineArray.resize(4 * width); - uchar *image = (uchar *)lineArray.data(); + uchar *image = reinterpret_cast(lineArray.data()); for (int cline = 0; cline < height; cline++) { - auto scanline = (float *)img.scanLine(cline); +#ifdef HDR_HALF_QUALITY + auto scanline = reinterpret_cast(img.scanLine(cline)); +#else + auto scanline = reinterpret_cast(img.scanLine(cline)); +#endif // determine scanline type 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 - for (int i = 0; i < 4; i++) { + for (int i = 0, len = int(lineArray.size()); i < 4; i++) { for (int j = 0; j < width;) { s >> code; if (s.atEnd()) { @@ -156,14 +184,20 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i code &= 127; s >> val; while (code != 0) { - image[i + j * 4] = val; + auto idx = i + j * 4; + if (idx < len) { + image[idx] = val; + } j++; code--; } } else { // non-run while (code != 0) { - s >> image[i + j * 4]; + auto idx = i + j * 4; + if (idx < len) { + s >> image[idx]; + } j++; code--; } @@ -177,9 +211,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i return true; } -} // namespace - -bool HDRHandler::read(QImage *outImage) +static QSize readHeaderSize(QIODevice *device) { int len; QByteArray line(MAXLINE + 1, Qt::Uninitialized); @@ -187,7 +219,7 @@ bool HDRHandler::read(QImage *outImage) // Parse header do { - len = device()->readLine(line.data(), MAXLINE); + len = device->readLine(line.data(), MAXLINE); if (line.startsWith("FORMAT=")) { format = line.mid(7, len - 7 - 1 /*\n*/); @@ -197,10 +229,10 @@ bool HDRHandler::read(QImage *outImage) if (format != "32-bit_rle_rgbe") { 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); /* @@ -220,21 +252,30 @@ bool HDRHandler::read(QImage *outImage) QRegularExpressionMatch match = resolutionRegExp.match(QString::fromLatin1(line)); if (!match.hasMatch()) { 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')) { 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; } - const int width = match.captured(4).toInt(); - const int height = match.captured(2).toInt(); - - QDataStream s(device()); - QImage img; - if (!LoadHDR(s, width, height, img)) { + if (!LoadHDR(s, size.width(), size.height(), img)) { // qDebug() << "Error loading HDR file."; return false; } @@ -246,6 +287,40 @@ bool HDRHandler::read(QImage *outImage) 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() { } @@ -266,7 +341,20 @@ bool HDRHandler::canRead(QIODevice *device) 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 diff --git a/src/imageformats/hdr_p.h b/src/imageformats/hdr_p.h index afd7089..e1450c6 100644 --- a/src/imageformats/hdr_p.h +++ b/src/imageformats/hdr_p.h @@ -18,6 +18,9 @@ public: bool canRead() const 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); };