File indexing completed on 2024-04-28 03:54:41
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2005 Christoph Hormann <chris_hormann@gmx.de> 0004 SPDX-FileCopyrightText: 2005 Ignacio CastaƱo <castanyo@yahoo.es> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "hdr_p.h" 0010 #include "util_p.h" 0011 0012 #include <QColorSpace> 0013 #include <QDataStream> 0014 #include <QFloat16> 0015 #include <QImage> 0016 #include <QLoggingCategory> 0017 #include <QRegularExpressionMatch> 0018 0019 #include <QDebug> 0020 0021 /* *** HDR_HALF_QUALITY *** 0022 * If defined, a 16-bits float image is created, otherwise a 32-bits float ones (default). 0023 */ 0024 //#define HDR_HALF_QUALITY // default commented -> you should define it in your cmake file 0025 0026 typedef unsigned char uchar; 0027 0028 Q_LOGGING_CATEGORY(HDRPLUGIN, "kf.imageformats.plugins.hdr", QtWarningMsg) 0029 0030 namespace // Private. 0031 { 0032 #define MAXLINE 1024 0033 #define MINELEN 8 // minimum scanline length for encoding 0034 #define MAXELEN 0x7fff // maximum scanline length for encoding 0035 0036 // read an old style line from the hdr image file 0037 // if 'first' is true the first byte is already read 0038 static bool Read_Old_Line(uchar *image, int width, QDataStream &s) 0039 { 0040 int rshift = 0; 0041 int i; 0042 0043 uchar *start = image; 0044 while (width > 0) { 0045 s >> image[0]; 0046 s >> image[1]; 0047 s >> image[2]; 0048 s >> image[3]; 0049 0050 if (s.atEnd()) { 0051 return false; 0052 } 0053 0054 if ((image[0] == 1) && (image[1] == 1) && (image[2] == 1)) { 0055 // NOTE: we don't have an image sample that cover this code 0056 if (rshift > 31) { 0057 return false; 0058 } 0059 for (i = image[3] << rshift; i > 0 && width > 0; i--) { 0060 if (image == start) { 0061 return false; // you cannot be here at the first run 0062 } 0063 // memcpy(image, image-4, 4); 0064 (uint &)image[0] = (uint &)image[0 - 4]; 0065 image += 4; 0066 width--; 0067 } 0068 rshift += 8; 0069 } else { 0070 image += 4; 0071 width--; 0072 rshift = 0; 0073 } 0074 } 0075 return true; 0076 } 0077 0078 template<class float_T> 0079 void RGBE_To_QRgbLine(uchar *image, float_T *scanline, int width) 0080 { 0081 for (int j = 0; j < width; j++) { 0082 // v = ldexp(1.0, int(image[3]) - 128); 0083 float v; 0084 int e = qBound(-31, int(image[3]) - 128, 31); 0085 if (e > 0) { 0086 v = float(1 << e); 0087 } else { 0088 v = 1.0f / float(1 << -e); 0089 } 0090 0091 auto j4 = j * 4; 0092 auto vn = v / 255.0f; 0093 scanline[j4] = float_T(std::min(float(image[0]) * vn, 1.0f)); 0094 scanline[j4 + 1] = float_T(std::min(float(image[1]) * vn, 1.0f)); 0095 scanline[j4 + 2] = float_T(std::min(float(image[2]) * vn, 1.0f)); 0096 scanline[j4 + 3] = float_T(1.0f); 0097 image += 4; 0098 } 0099 } 0100 0101 QImage::Format imageFormat() 0102 { 0103 #ifdef HDR_HALF_QUALITY 0104 return QImage::Format_RGBX16FPx4; 0105 #else 0106 return QImage::Format_RGBX32FPx4; 0107 #endif 0108 } 0109 0110 // Load the HDR image. 0111 static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &img) 0112 { 0113 uchar val; 0114 uchar code; 0115 0116 // Create dst image. 0117 img = imageAlloc(width, height, imageFormat()); 0118 if (img.isNull()) { 0119 qCDebug(HDRPLUGIN) << "Couldn't create image with size" << width << height << "and format RGB32"; 0120 return false; 0121 } 0122 0123 QByteArray lineArray; 0124 lineArray.resize(4 * width); 0125 uchar *image = reinterpret_cast<uchar *>(lineArray.data()); 0126 0127 for (int cline = 0; cline < height; cline++) { 0128 #ifdef HDR_HALF_QUALITY 0129 auto scanline = reinterpret_cast<qfloat16 *>(img.scanLine(cline)); 0130 #else 0131 auto scanline = reinterpret_cast<float *>(img.scanLine(cline)); 0132 #endif 0133 0134 // determine scanline type 0135 if ((width < MINELEN) || (MAXELEN < width)) { 0136 Read_Old_Line(image, width, s); 0137 RGBE_To_QRgbLine(image, scanline, width); 0138 continue; 0139 } 0140 0141 s >> val; 0142 0143 if (s.atEnd()) { 0144 return true; 0145 } 0146 0147 if (val != 2) { 0148 s.device()->ungetChar(val); 0149 Read_Old_Line(image, width, s); 0150 RGBE_To_QRgbLine(image, scanline, width); 0151 continue; 0152 } 0153 0154 s >> image[1]; 0155 s >> image[2]; 0156 s >> image[3]; 0157 0158 if (s.atEnd()) { 0159 return true; 0160 } 0161 0162 if ((image[1] != 2) || (image[2] & 128)) { 0163 image[0] = 2; 0164 Read_Old_Line(image + 4, width - 1, s); 0165 RGBE_To_QRgbLine(image, scanline, width); 0166 continue; 0167 } 0168 0169 if ((image[2] << 8 | image[3]) != width) { 0170 qCDebug(HDRPLUGIN) << "Line of pixels had width" << (image[2] << 8 | image[3]) << "instead of" << width; 0171 return false; 0172 } 0173 0174 // read each component 0175 for (int i = 0, len = int(lineArray.size()); i < 4; i++) { 0176 for (int j = 0; j < width;) { 0177 s >> code; 0178 if (s.atEnd()) { 0179 qCDebug(HDRPLUGIN) << "Truncated HDR file"; 0180 return false; 0181 } 0182 if (code > 128) { 0183 // run 0184 code &= 127; 0185 s >> val; 0186 while (code != 0) { 0187 auto idx = i + j * 4; 0188 if (idx < len) { 0189 image[idx] = val; 0190 } 0191 j++; 0192 code--; 0193 } 0194 } else { 0195 // non-run 0196 while (code != 0) { 0197 auto idx = i + j * 4; 0198 if (idx < len) { 0199 s >> image[idx]; 0200 } 0201 j++; 0202 code--; 0203 } 0204 } 0205 } 0206 } 0207 0208 RGBE_To_QRgbLine(image, scanline, width); 0209 } 0210 0211 return true; 0212 } 0213 0214 static QSize readHeaderSize(QIODevice *device) 0215 { 0216 int len; 0217 QByteArray line(MAXLINE + 1, Qt::Uninitialized); 0218 QByteArray format; 0219 0220 // Parse header 0221 do { 0222 len = device->readLine(line.data(), MAXLINE); 0223 0224 if (line.startsWith("FORMAT=")) { 0225 format = line.mid(7, len - 7 - 1 /*\n*/); 0226 } 0227 0228 } while ((len > 0) && (line[0] != '\n')); 0229 0230 if (format != "32-bit_rle_rgbe") { 0231 qCDebug(HDRPLUGIN) << "Unknown HDR format:" << format; 0232 return QSize(); 0233 } 0234 0235 len = device->readLine(line.data(), MAXLINE); 0236 line.resize(len); 0237 0238 /* 0239 TODO: handle flipping and rotation, as per the spec below 0240 The single resolution line consists of 4 values, a X and Y label each followed by a numerical 0241 integer value. The X and Y are immediately preceded by a sign which can be used to indicate 0242 flipping, the order of the X and Y indicate rotation. The standard coordinate system for 0243 Radiance images would have the following resolution string -Y N +X N. This indicates that the 0244 vertical axis runs down the file and the X axis is to the right (imagining the image as a 0245 rectangular block of data). A -X would indicate a horizontal flip of the image. A +Y would 0246 indicate a vertical flip. If the X value appears before the Y value then that indicates that 0247 the image is stored in column order rather than row order, that is, it is rotated by 90 degrees. 0248 The reader can convince themselves that the 8 combinations cover all the possible image orientations 0249 and rotations. 0250 */ 0251 QRegularExpression resolutionRegExp(QStringLiteral("([+\\-][XY]) ([0-9]+) ([+\\-][XY]) ([0-9]+)\n")); 0252 QRegularExpressionMatch match = resolutionRegExp.match(QString::fromLatin1(line)); 0253 if (!match.hasMatch()) { 0254 qCDebug(HDRPLUGIN) << "Invalid HDR file, the first line after the header didn't have the expected format:" << line; 0255 return QSize(); 0256 } 0257 0258 if ((match.captured(1).at(1) != u'Y') || (match.captured(3).at(1) != u'X')) { 0259 qCDebug(HDRPLUGIN) << "Unsupported image orientation in HDR file."; 0260 return QSize(); 0261 } 0262 0263 return QSize(match.captured(4).toInt(), match.captured(2).toInt()); 0264 } 0265 0266 } // namespace 0267 0268 bool HDRHandler::read(QImage *outImage) 0269 { 0270 QDataStream s(device()); 0271 0272 QSize size = readHeaderSize(s.device()); 0273 if (!size.isValid()) { 0274 return false; 0275 } 0276 0277 QImage img; 0278 if (!LoadHDR(s, size.width(), size.height(), img)) { 0279 // qDebug() << "Error loading HDR file."; 0280 return false; 0281 } 0282 // The images read by Gimp and Photoshop (including those of the tests) are interpreted with linear color space. 0283 // By setting the linear color space, programs that support profiles display HDR files as in GIMP and Photoshop. 0284 img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear)); 0285 0286 *outImage = img; 0287 return true; 0288 } 0289 0290 bool HDRHandler::supportsOption(ImageOption option) const 0291 { 0292 if (option == QImageIOHandler::Size) { 0293 return true; 0294 } 0295 if (option == QImageIOHandler::ImageFormat) { 0296 return true; 0297 } 0298 return false; 0299 } 0300 0301 QVariant HDRHandler::option(ImageOption option) const 0302 { 0303 QVariant v; 0304 0305 if (option == QImageIOHandler::Size) { 0306 if (auto d = device()) { 0307 // transactions works on both random and sequential devices 0308 d->startTransaction(); 0309 auto size = readHeaderSize(d); 0310 d->rollbackTransaction(); 0311 if (size.isValid()) { 0312 v = QVariant::fromValue(size); 0313 } 0314 } 0315 } 0316 0317 if (option == QImageIOHandler::ImageFormat) { 0318 v = QVariant::fromValue(imageFormat()); 0319 } 0320 0321 return v; 0322 } 0323 0324 HDRHandler::HDRHandler() 0325 { 0326 } 0327 0328 bool HDRHandler::canRead() const 0329 { 0330 if (canRead(device())) { 0331 setFormat("hdr"); 0332 return true; 0333 } 0334 return false; 0335 } 0336 0337 bool HDRHandler::canRead(QIODevice *device) 0338 { 0339 if (!device) { 0340 qWarning("HDRHandler::canRead() called with no device"); 0341 return false; 0342 } 0343 0344 // the .pic taken from official test cases does not start with this string but can be loaded. 0345 if(device->peek(11) == "#?RADIANCE\n" || device->peek(7) == "#?RGBE\n") { 0346 return true; 0347 } 0348 0349 // allow to load offical test cases: https://radsite.lbl.gov/radiance/framed.html 0350 device->startTransaction(); 0351 QSize size = readHeaderSize(device); 0352 device->rollbackTransaction(); 0353 if (size.isValid()) { 0354 return true; 0355 } 0356 0357 return false; 0358 } 0359 0360 QImageIOPlugin::Capabilities HDRPlugin::capabilities(QIODevice *device, const QByteArray &format) const 0361 { 0362 if (format == "hdr") { 0363 return Capabilities(CanRead); 0364 } 0365 if (!format.isEmpty()) { 0366 return {}; 0367 } 0368 if (!device->isOpen()) { 0369 return {}; 0370 } 0371 0372 Capabilities cap; 0373 if (device->isReadable() && HDRHandler::canRead(device)) { 0374 cap |= CanRead; 0375 } 0376 return cap; 0377 } 0378 0379 QImageIOHandler *HDRPlugin::create(QIODevice *device, const QByteArray &format) const 0380 { 0381 QImageIOHandler *handler = new HDRHandler; 0382 handler->setDevice(device); 0383 handler->setFormat(format); 0384 return handler; 0385 } 0386 0387 #include "moc_hdr_p.cpp"