File indexing completed on 2024-12-22 04:15:54
0001 /* 0002 * SPDX-FileCopyrightText: 2023 Rasyuqa A. H. <qampidh@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 * 0006 * Based on KImageFormats Radiance HDR loader 0007 * 0008 * SPDX-FileCopyrightText: 2005 Christoph Hormann <chris_hormann@gmx.de> 0009 * SPDX-FileCopyrightText: 2005 Ignacio CastaƱo <castanyo@yahoo.es> 0010 * 0011 * SPDX-License-Identifier: LGPL-2.0-or-later 0012 */ 0013 0014 #include <kpluginfactory.h> 0015 0016 #include <QBuffer> 0017 #include <QByteArray> 0018 0019 #include <cmath> 0020 #include <cstdint> 0021 #include <memory> 0022 0023 #include <KisDocument.h> 0024 #include <KisImportExportErrorCode.h> 0025 #include <KoColorModelStandardIds.h> 0026 #include <KoColorProfile.h> 0027 #include <KoCompositeOpRegistry.h> 0028 #include <KoDialog.h> 0029 #include <kis_group_layer.h> 0030 #include <kis_iterator_ng.h> 0031 #include <kis_meta_data_backend_registry.h> 0032 #include <kis_paint_layer.h> 0033 #include <kis_painter.h> 0034 #include <kis_properties_configuration.h> 0035 #include <kis_sequential_iterator.h> 0036 0037 #include "RGBEImport.h" 0038 0039 K_PLUGIN_FACTORY_WITH_JSON(KisRGBEImportFactory, "krita_rgbe_import.json", registerPlugin<RGBEImport>();) 0040 0041 class Q_DECL_HIDDEN RGBEImportData 0042 { 0043 public: 0044 KisPaintDeviceSP m_currentFrame{nullptr}; 0045 KoID m_colorID; 0046 KoID m_depthID; 0047 float m_gamma = 1.0; 0048 float m_exposure = 1.0; 0049 const KoColorSpace *cs = nullptr; 0050 }; 0051 0052 namespace RGBEIMPORT 0053 { 0054 #define MAXLINE 1024 0055 #define MINELEN 8 // minimum scanline length for encoding 0056 #define MAXELEN 0x7fff // maximum scanline length for encoding 0057 0058 // read an old style line from the hdr image file 0059 // if 'first' is true the first byte is already read 0060 static bool ReadOldLine(quint8 *image, int width, QDataStream &s) 0061 { 0062 int rshift = 0; 0063 int i; 0064 0065 while (width > 0) { 0066 s >> image[0]; 0067 s >> image[1]; 0068 s >> image[2]; 0069 s >> image[3]; 0070 0071 if (s.atEnd()) { 0072 return false; 0073 } 0074 0075 if ((image[0] == 1) && (image[1] == 1) && (image[2] == 1)) { 0076 for (i = image[3] << rshift; i > 0; i--) { 0077 memcpy(image, image-4, 4); 0078 image += 4; 0079 width--; 0080 } 0081 rshift += 8; 0082 } else { 0083 image += 4; 0084 width--; 0085 rshift = 0; 0086 } 0087 } 0088 return true; 0089 } 0090 0091 static void RGBEToPaintDevice(quint8 *image, int width, KisSequentialIterator &it) 0092 { 0093 for (int j = 0; j < width; j++) { 0094 it.nextPixel(); 0095 auto *dst = reinterpret_cast<float *>(it.rawData()); 0096 0097 if (image[3]) { 0098 const float v = std::ldexp(1.0f, int(image[3]) - (128 + 8)); 0099 const float pixelData[4] = {float(image[0]) * v, 0100 float(image[1]) * v, 0101 float(image[2]) * v, 0102 1.0f}; 0103 memcpy(dst, pixelData, 4 * sizeof(float)); 0104 } else { 0105 // Zero exponent handle 0106 const float pixelData[4] = {0.0f, 0.0f, 0.0f, 1.0f}; 0107 memcpy(dst, pixelData, 4 * sizeof(float)); 0108 } 0109 0110 image += 4; 0111 } 0112 } 0113 0114 // Load the HDR image. 0115 static bool LoadHDR(QDataStream &s, const int width, const int height, KisSequentialIterator &it) 0116 { 0117 quint8 val; 0118 quint8 code; 0119 0120 QByteArray lineArray; 0121 lineArray.resize(4 * width); 0122 quint8 *image = (quint8 *)lineArray.data(); 0123 0124 for (int cline = 0; cline < height; cline++) { 0125 // determine scanline type 0126 if ((width < MINELEN) || (MAXELEN < width)) { 0127 ReadOldLine(image, width, s); 0128 RGBEToPaintDevice(image, width, it); 0129 continue; 0130 } 0131 0132 s >> val; 0133 0134 if (s.atEnd()) { 0135 return true; 0136 } 0137 0138 if (val != 2) { 0139 s.device()->ungetChar(val); 0140 ReadOldLine(image, width, s); 0141 RGBEToPaintDevice(image, width, it); 0142 continue; 0143 } 0144 0145 s >> image[1]; 0146 s >> image[2]; 0147 s >> image[3]; 0148 0149 if (s.atEnd()) { 0150 return true; 0151 } 0152 0153 if ((image[1] != 2) || (image[2] & 128)) { 0154 image[0] = 2; 0155 ReadOldLine(image + 4, width - 1, s); 0156 RGBEToPaintDevice(image, width, it); 0157 continue; 0158 } 0159 0160 if ((image[2] << 8 | image[3]) != width) { 0161 dbgFile << "Line of pixels had width" << (image[2] << 8 | image[3]) << "instead of" << width; 0162 return false; 0163 } 0164 0165 // read each component 0166 for (int i = 0; i < 4; i++) { 0167 for (int j = 0; j < width;) { 0168 s >> code; 0169 if (s.atEnd()) { 0170 dbgFile << "Truncated HDR file"; 0171 return false; 0172 } 0173 if (code > 128) { 0174 // run 0175 code &= 127; 0176 s >> val; 0177 while (code != 0) { 0178 image[i + j * 4] = val; 0179 j++; 0180 code--; 0181 } 0182 } else { 0183 // non-run 0184 while (code != 0) { 0185 s >> image[i + j * 4]; 0186 j++; 0187 code--; 0188 } 0189 } 0190 } 0191 } 0192 0193 RGBEToPaintDevice(image, width, it); 0194 } 0195 0196 return true; 0197 } 0198 0199 } // namespace RGBEIMPORT 0200 0201 RGBEImport::RGBEImport(QObject *parent, const QVariantList &) 0202 : KisImportExportFilter(parent) 0203 { 0204 } 0205 0206 KisImportExportErrorCode 0207 RGBEImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) 0208 { 0209 if (!io->isReadable()) { 0210 errFile << "Cannot read image contents"; 0211 return ImportExportCodes::NoAccessToRead; 0212 } 0213 0214 if (!(io->peek(11) == "#?RADIANCE\n" || io->peek(7) == "#?RGBE\n")) { 0215 errFile << "Invalid RGBE header!"; 0216 return ImportExportCodes::ErrorWhileReading; 0217 } 0218 0219 RGBEImportData d{}; 0220 0221 int len; 0222 QByteArray line(MAXLINE + 1, Qt::Uninitialized); 0223 QByteArray rawFormat; 0224 QByteArray rawGamma; 0225 QByteArray rawExposure; 0226 QByteArray rawHeaderInfo; 0227 0228 // Parse header 0229 do { 0230 len = io->readLine(line.data(), MAXLINE); 0231 if (line.startsWith("# ")) { 0232 rawHeaderInfo = line.mid(2, len - 2 - 1 /*\n*/); 0233 } else if (line.startsWith("GAMMA=")) { 0234 rawGamma = line.mid(6, len - 6 - 1 /*\n*/); 0235 } else if (line.startsWith("EXPOSURE=")) { 0236 rawExposure = line.mid(9, len - 9 - 1 /*\n*/); 0237 } else if (line.startsWith("FORMAT=")) { 0238 rawFormat = line.mid(7, len - 7 - 1 /*\n*/); 0239 } 0240 } while ((len > 0) && (line[0] != '\n')); 0241 0242 if (rawFormat != "32-bit_rle_rgbe") { 0243 errFile << "Invalid RGBE format!"; 0244 return ImportExportCodes::ErrorWhileReading; 0245 } 0246 0247 const QString headerInfo = [&]() { 0248 if (!rawHeaderInfo.isEmpty()) { 0249 return QString(rawHeaderInfo).trimmed(); 0250 } 0251 return QString(); 0252 }(); 0253 0254 // Unused fields, I don't know what to do with gamma and exposure fields yet. 0255 if (!rawGamma.isEmpty()) { 0256 bool gammaOk = false; 0257 const float gammaTemp = QString(rawGamma).toFloat(&gammaOk); 0258 if (gammaOk) { 0259 d.m_gamma = gammaTemp; 0260 } 0261 } 0262 if (!rawExposure.isEmpty()) { 0263 bool exposureOk = false; 0264 const float expTemp = QString(rawExposure).toFloat(&exposureOk); 0265 if (exposureOk) { 0266 d.m_exposure = expTemp; 0267 } 0268 } 0269 0270 len = io->readLine(line.data(), MAXLINE); 0271 line.resize(len); 0272 0273 /* 0274 TODO: handle flipping and rotation, as per the spec below 0275 The single resolution line consists of 4 values, a X and Y label each followed by a numerical 0276 integer value. The X and Y are immediately preceded by a sign which can be used to indicate 0277 flipping, the order of the X and Y indicate rotation. The standard coordinate system for 0278 Radiance images would have the following resolution string -Y N +X N. This indicates that the 0279 vertical axis runs down the file and the X axis is to the right (imagining the image as a 0280 rectangular block of data). A -X would indicate a horizontal flip of the image. A +Y would 0281 indicate a vertical flip. If the X value appears before the Y value then that indicates that 0282 the image is stored in column order rather than row order, that is, it is rotated by 90 degrees. 0283 The reader can convince themselves that the 8 combinations cover all the possible image orientations 0284 and rotations. 0285 */ 0286 QRegularExpression resolutionRegExp(QStringLiteral("([+\\-][XY]) ([0-9]+) ([+\\-][XY]) ([0-9]+)\n")); 0287 QRegularExpressionMatch match = resolutionRegExp.match(QString::fromLatin1(line)); 0288 if (!match.hasMatch()) { 0289 errFile << "Invalid HDR file, the first line after the header didn't have the expected format:" << line; 0290 return ImportExportCodes::InternalError; 0291 } 0292 0293 if ((match.captured(1).at(1) != u'Y') || (match.captured(3).at(1) != u'X')) { 0294 errFile << "Unsupported image orientation in HDR file."; 0295 return ImportExportCodes::InternalError; 0296 } 0297 0298 const int width = match.captured(4).toInt(); 0299 const int height = match.captured(2).toInt(); 0300 0301 dbgFile << "RGBE image information:"; 0302 dbgFile << "Program info:" << headerInfo; 0303 if (!rawGamma.isEmpty()) { 0304 dbgFile << "Gamma:" << d.m_gamma; 0305 } else { 0306 dbgFile << "No gamma metadata provided"; 0307 } 0308 if (!rawExposure.isEmpty()) { 0309 dbgFile << "Exposure:" << d.m_exposure; 0310 } else { 0311 dbgFile << "No exposure metadata provided"; 0312 } 0313 dbgFile << "Dimension:" << width << "x" << height; 0314 0315 KisImageSP image; 0316 KisLayerSP layer; 0317 0318 const KoColorProfile *profile = nullptr; 0319 0320 d.m_colorID = RGBAColorModelID; 0321 d.m_depthID = Float32BitsColorDepthID; 0322 0323 profile = KoColorSpaceRegistry::instance()->p709G10Profile(); 0324 d.cs = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID.id(), d.m_depthID.id(), profile); 0325 0326 image = new KisImage(document->createUndoStore(), width, height, d.cs, "RGBE image"); 0327 layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8); 0328 d.m_currentFrame = new KisPaintDevice(image->colorSpace()); 0329 0330 QDataStream stream(io); 0331 KisSequentialIterator it(d.m_currentFrame, {0, 0, width, height}); 0332 0333 if (!RGBEIMPORT::LoadHDR(stream, width, height, it)) { 0334 errFile << "Error loading HDR file."; 0335 return ImportExportCodes::InternalError; 0336 } 0337 0338 layer->paintDevice()->makeCloneFrom(d.m_currentFrame, image->bounds()); 0339 image->addNode(layer, image->rootLayer().data()); 0340 0341 document->setCurrentImage(image); 0342 0343 return ImportExportCodes::OK; 0344 } 0345 0346 #include <RGBEImport.moc>