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"