File indexing completed on 2024-04-28 15:25:40
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 <QImage> 0015 #include <QLoggingCategory> 0016 #include <QRegularExpressionMatch> 0017 0018 #include <QDebug> 0019 0020 typedef unsigned char uchar; 0021 0022 Q_LOGGING_CATEGORY(HDRPLUGIN, "kf.imageformats.plugins.hdr", QtWarningMsg) 0023 0024 namespace // Private. 0025 { 0026 #define MAXLINE 1024 0027 #define MINELEN 8 // minimum scanline length for encoding 0028 #define MAXELEN 0x7fff // maximum scanline length for encoding 0029 0030 static inline uchar ClipToByte(float value) 0031 { 0032 // we know value is positive. 0033 return uchar(std::min(value + 0.5f, 255.0f)); 0034 } 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 static void RGBE_To_QRgbLine(uchar *image, QRgb *scanline, int width) 0079 { 0080 for (int j = 0; j < width; j++) { 0081 // v = ldexp(1.0, int(image[3]) - 128); 0082 float v; 0083 int e = qBound(-31, int(image[3]) - 128, 31); 0084 if (e > 0) { 0085 v = float(1 << e); 0086 } else { 0087 v = 1.0f / float(1 << -e); 0088 } 0089 0090 scanline[j] = qRgb(ClipToByte(float(image[0]) * v), ClipToByte(float(image[1]) * v), ClipToByte(float(image[2]) * v)); 0091 0092 image += 4; 0093 } 0094 } 0095 0096 // Load the HDR image. 0097 static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &img) 0098 { 0099 uchar val; 0100 uchar code; 0101 0102 // Create dst image. 0103 img = imageAlloc(width, height, QImage::Format_RGB32); 0104 if (img.isNull()) { 0105 qCDebug(HDRPLUGIN) << "Couldn't create image with size" << width << height << "and format RGB32"; 0106 return false; 0107 } 0108 0109 QByteArray lineArray; 0110 lineArray.resize(4 * width); 0111 uchar *image = (uchar *)lineArray.data(); 0112 0113 for (int cline = 0; cline < height; cline++) { 0114 QRgb *scanline = (QRgb *)img.scanLine(cline); 0115 0116 // determine scanline type 0117 if ((width < MINELEN) || (MAXELEN < width)) { 0118 Read_Old_Line(image, width, s); 0119 RGBE_To_QRgbLine(image, scanline, width); 0120 continue; 0121 } 0122 0123 s >> val; 0124 0125 if (s.atEnd()) { 0126 return true; 0127 } 0128 0129 if (val != 2) { 0130 s.device()->ungetChar(val); 0131 Read_Old_Line(image, width, s); 0132 RGBE_To_QRgbLine(image, scanline, width); 0133 continue; 0134 } 0135 0136 s >> image[1]; 0137 s >> image[2]; 0138 s >> image[3]; 0139 0140 if (s.atEnd()) { 0141 return true; 0142 } 0143 0144 if ((image[1] != 2) || (image[2] & 128)) { 0145 image[0] = 2; 0146 Read_Old_Line(image + 4, width - 1, s); 0147 RGBE_To_QRgbLine(image, scanline, width); 0148 continue; 0149 } 0150 0151 if ((image[2] << 8 | image[3]) != width) { 0152 qCDebug(HDRPLUGIN) << "Line of pixels had width" << (image[2] << 8 | image[3]) << "instead of" << width; 0153 return false; 0154 } 0155 0156 // read each component 0157 for (int i = 0, len = int(lineArray.size()); i < 4; i++) { 0158 for (int j = 0; j < width;) { 0159 s >> code; 0160 if (s.atEnd()) { 0161 qCDebug(HDRPLUGIN) << "Truncated HDR file"; 0162 return false; 0163 } 0164 if (code > 128) { 0165 // run 0166 code &= 127; 0167 s >> val; 0168 while (code != 0) { 0169 auto idx = i + j * 4; 0170 if (idx < len) { 0171 image[idx] = val; 0172 } 0173 j++; 0174 code--; 0175 } 0176 } else { 0177 // non-run 0178 while (code != 0) { 0179 auto idx = i + j * 4; 0180 if (idx < len) { 0181 s >> image[idx]; 0182 } 0183 j++; 0184 code--; 0185 } 0186 } 0187 } 0188 } 0189 0190 RGBE_To_QRgbLine(image, scanline, width); 0191 } 0192 0193 return true; 0194 } 0195 0196 } // namespace 0197 0198 bool HDRHandler::read(QImage *outImage) 0199 { 0200 int len; 0201 QByteArray line(MAXLINE + 1, Qt::Uninitialized); 0202 QByteArray format; 0203 0204 // Parse header 0205 do { 0206 len = device()->readLine(line.data(), MAXLINE); 0207 0208 if (line.startsWith("FORMAT=")) { 0209 format = line.mid(7, len - 7 - 1 /*\n*/); 0210 } 0211 0212 } while ((len > 0) && (line[0] != '\n')); 0213 0214 if (format != "32-bit_rle_rgbe") { 0215 qCDebug(HDRPLUGIN) << "Unknown HDR format:" << format; 0216 return false; 0217 } 0218 0219 len = device()->readLine(line.data(), MAXLINE); 0220 line.resize(len); 0221 0222 /* 0223 TODO: handle flipping and rotation, as per the spec below 0224 The single resolution line consists of 4 values, a X and Y label each followed by a numerical 0225 integer value. The X and Y are immediately preceded by a sign which can be used to indicate 0226 flipping, the order of the X and Y indicate rotation. The standard coordinate system for 0227 Radiance images would have the following resolution string -Y N +X N. This indicates that the 0228 vertical axis runs down the file and the X axis is to the right (imagining the image as a 0229 rectangular block of data). A -X would indicate a horizontal flip of the image. A +Y would 0230 indicate a vertical flip. If the X value appears before the Y value then that indicates that 0231 the image is stored in column order rather than row order, that is, it is rotated by 90 degrees. 0232 The reader can convince themselves that the 8 combinations cover all the possible image orientations 0233 and rotations. 0234 */ 0235 QRegularExpression resolutionRegExp(QStringLiteral("([+\\-][XY]) ([0-9]+) ([+\\-][XY]) ([0-9]+)\n")); 0236 QRegularExpressionMatch match = resolutionRegExp.match(QString::fromLatin1(line)); 0237 if (!match.hasMatch()) { 0238 qCDebug(HDRPLUGIN) << "Invalid HDR file, the first line after the header didn't have the expected format:" << line; 0239 return false; 0240 } 0241 0242 if ((match.captured(1).at(1) != u'Y') || (match.captured(3).at(1) != u'X')) { 0243 qCDebug(HDRPLUGIN) << "Unsupported image orientation in HDR file."; 0244 return false; 0245 } 0246 0247 const int width = match.captured(4).toInt(); 0248 const int height = match.captured(2).toInt(); 0249 0250 QDataStream s(device()); 0251 0252 QImage img; 0253 if (!LoadHDR(s, width, height, img)) { 0254 // qDebug() << "Error loading HDR file."; 0255 return false; 0256 } 0257 // The images read by Gimp and Photoshop (including those of the tests) are interpreted with linear color space. 0258 // By setting the linear color space, programs that support profiles display HDR files as in GIMP and Photoshop. 0259 img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear)); 0260 0261 *outImage = img; 0262 return true; 0263 } 0264 0265 HDRHandler::HDRHandler() 0266 { 0267 } 0268 0269 bool HDRHandler::canRead() const 0270 { 0271 if (canRead(device())) { 0272 setFormat("hdr"); 0273 return true; 0274 } 0275 return false; 0276 } 0277 0278 bool HDRHandler::canRead(QIODevice *device) 0279 { 0280 if (!device) { 0281 qWarning("HDRHandler::canRead() called with no device"); 0282 return false; 0283 } 0284 0285 return device->peek(11) == "#?RADIANCE\n" || device->peek(7) == "#?RGBE\n"; 0286 } 0287 0288 QImageIOPlugin::Capabilities HDRPlugin::capabilities(QIODevice *device, const QByteArray &format) const 0289 { 0290 if (format == "hdr") { 0291 return Capabilities(CanRead); 0292 } 0293 if (!format.isEmpty()) { 0294 return {}; 0295 } 0296 if (!device->isOpen()) { 0297 return {}; 0298 } 0299 0300 Capabilities cap; 0301 if (device->isReadable() && HDRHandler::canRead(device)) { 0302 cap |= CanRead; 0303 } 0304 return cap; 0305 } 0306 0307 QImageIOHandler *HDRPlugin::create(QIODevice *device, const QByteArray &format) const 0308 { 0309 QImageIOHandler *handler = new HDRHandler; 0310 handler->setDevice(device); 0311 handler->setFormat(format); 0312 return handler; 0313 } 0314 0315 #include "moc_hdr_p.cpp"