Krita Source Code Documentation
Loading...
Searching...
No Matches
RGBEImport.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2023 Rasyuqa A. H. <qampidh@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 *
6 * Based on KImageFormats Radiance HDR loader
7 *
8 * SPDX-FileCopyrightText: 2005 Christoph Hormann <chris_hormann@gmx.de>
9 * SPDX-FileCopyrightText: 2005 Ignacio CastaƱo <castanyo@yahoo.es>
10 *
11 * SPDX-License-Identifier: LGPL-2.0-or-later
12 */
13
14#include <kpluginfactory.h>
15
16#include <QBuffer>
17#include <QByteArray>
18
19#include <KisDocument.h>
22#include <KoColorProfile.h>
24#include <KoDialog.h>
25#include <kis_group_layer.h>
26#include <kis_iterator_ng.h>
28#include <kis_paint_layer.h>
29#include <kis_painter.h>
32
33#include "RGBEImport.h"
34#include "RGBEImportUtils.h"
35
36K_PLUGIN_FACTORY_WITH_JSON(KisRGBEImportFactory, "krita_rgbe_import.json", registerPlugin<RGBEImport>();)
37
38#define MAXLINE 1024
39
40class Q_DECL_HIDDEN RGBEImportData
41{
42public:
43 KisPaintDeviceSP m_currentFrame{nullptr};
46 float m_gamma = 1.0;
47 float m_exposure = 1.0;
48 const KoColorSpace *cs = nullptr;
49};
50
51RGBEImport::RGBEImport(QObject *parent, const QVariantList &)
52 : KisImportExportFilter(parent)
53{
54}
55
57RGBEImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
58{
59 if (!io->isReadable()) {
60 errFile << "Cannot read image contents";
62 }
63
64 if (!(io->peek(11) == "#?RADIANCE\n" || io->peek(7) == "#?RGBE\n")) {
65 errFile << "Invalid RGBE header!";
67 }
68
70
71 int len;
72 QByteArray line(MAXLINE + 1, Qt::Uninitialized);
73 QByteArray rawFormat;
74 QByteArray rawGamma;
75 QByteArray rawExposure;
76 QByteArray rawHeaderInfo;
77
78 // Parse header
79 do {
80 len = io->readLine(line.data(), MAXLINE);
81 if (line.startsWith("# ")) {
82 rawHeaderInfo = line.mid(2, len - 2 - 1 /*\n*/);
83 } else if (line.startsWith("GAMMA=")) {
84 rawGamma = line.mid(6, len - 6 - 1 /*\n*/);
85 } else if (line.startsWith("EXPOSURE=")) {
86 rawExposure = line.mid(9, len - 9 - 1 /*\n*/);
87 } else if (line.startsWith("FORMAT=")) {
88 rawFormat = line.mid(7, len - 7 - 1 /*\n*/);
89 }
90 } while ((len > 0) && (line[0] != '\n'));
91
92 if (rawFormat != "32-bit_rle_rgbe") {
93 errFile << "Invalid RGBE format!";
95 }
96
97 const QString headerInfo = [&]() {
98 if (!rawHeaderInfo.isEmpty()) {
99 return QString(rawHeaderInfo).trimmed();
100 }
101 return QString();
102 }();
103
104 // Unused fields, I don't know what to do with gamma and exposure fields yet.
105 if (!rawGamma.isEmpty()) {
106 bool gammaOk = false;
107 const float gammaTemp = QString(rawGamma).toFloat(&gammaOk);
108 if (gammaOk) {
109 d.m_gamma = gammaTemp;
110 }
111 }
112 if (!rawExposure.isEmpty()) {
113 bool exposureOk = false;
114 const float expTemp = QString(rawExposure).toFloat(&exposureOk);
115 if (exposureOk) {
116 d.m_exposure = expTemp;
117 }
118 }
119
120 len = io->readLine(line.data(), MAXLINE);
121 line.resize(len);
122
123 /*
124 TODO: handle flipping and rotation, as per the spec below
125 The single resolution line consists of 4 values, a X and Y label each followed by a numerical
126 integer value. The X and Y are immediately preceded by a sign which can be used to indicate
127 flipping, the order of the X and Y indicate rotation. The standard coordinate system for
128 Radiance images would have the following resolution string -Y N +X N. This indicates that the
129 vertical axis runs down the file and the X axis is to the right (imagining the image as a
130 rectangular block of data). A -X would indicate a horizontal flip of the image. A +Y would
131 indicate a vertical flip. If the X value appears before the Y value then that indicates that
132 the image is stored in column order rather than row order, that is, it is rotated by 90 degrees.
133 The reader can convince themselves that the 8 combinations cover all the possible image orientations
134 and rotations.
135 */
136 QRegularExpression resolutionRegExp(QStringLiteral("([+\\-][XY]) ([0-9]+) ([+\\-][XY]) ([0-9]+)\n"));
137 QRegularExpressionMatch match = resolutionRegExp.match(QString::fromLatin1(line));
138 if (!match.hasMatch()) {
139 errFile << "Invalid HDR file, the first line after the header didn't have the expected format:" << line;
141 }
142
143 if ((match.captured(1).at(1) != u'Y') || (match.captured(3).at(1) != u'X')) {
144 errFile << "Unsupported image orientation in HDR file.";
146 }
147
148 const int width = match.captured(4).toInt();
149 const int height = match.captured(2).toInt();
150
151 dbgFile << "RGBE image information:";
152 dbgFile << "Program info:" << headerInfo;
153 if (!rawGamma.isEmpty()) {
154 dbgFile << "Gamma:" << d.m_gamma;
155 } else {
156 dbgFile << "No gamma metadata provided";
157 }
158 if (!rawExposure.isEmpty()) {
159 dbgFile << "Exposure:" << d.m_exposure;
160 } else {
161 dbgFile << "No exposure metadata provided";
162 }
163 dbgFile << "Dimension:" << width << "x" << height;
164
165 KisImageSP image;
166 KisLayerSP layer;
167
168 const KoColorProfile *profile = nullptr;
169
170 d.m_colorID = RGBAColorModelID;
171 d.m_depthID = Float32BitsColorDepthID;
172
174 d.cs = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID.id(), d.m_depthID.id(), profile);
175
176 image = new KisImage(document->createUndoStore(), width, height, d.cs, "RGBE image");
177 layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8);
178 d.m_currentFrame = new KisPaintDevice(image->colorSpace());
179
180 QDataStream stream(io);
181 KisSequentialIterator it(d.m_currentFrame, {0, 0, width, height});
182
183 if (!RGBEIMPORT::LoadHDR(stream, width, height, it)) {
184 errFile << "Error loading HDR file.";
186 }
187
188 layer->paintDevice()->makeCloneFrom(d.m_currentFrame, image->bounds());
189 image->addNode(layer, image->rootLayer().data());
190
191 document->setCurrentImage(image);
192
194}
195
196#include <RGBEImport.moc>
qreal u
const KoID Float32BitsColorDepthID("F32", ki18n("32-bit float/channel"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
const quint8 OPACITY_OPAQUE_U8
#define MAXLINE
KisGroupLayerSP rootLayer() const
const KoColorSpace * colorSpace() const
QString nextLayerName(const QString &baseName="") const
Definition kis_image.cc:715
QRect bounds() const override
The base class for import and export filters.
void makeCloneFrom(KisPaintDeviceSP src, const QRect &rect)
Definition KoID.h:30
RGBEImport(QObject *parent, const QVariantList &)
KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration=nullptr) override
K_PLUGIN_FACTORY_WITH_JSON(KritaASCCDLFactory, "kritaasccdl.json", registerPlugin< KritaASCCDL >();) KritaASCCDL
#define errFile
Definition kis_debug.h:115
#define dbgFile
Definition kis_debug.h:53
bool LoadHDR(QDataStream &s, const int width, const int height, KisSequentialIterator &it)
virtual KisPaintDeviceSP paintDevice() const =0
bool addNode(KisNodeSP node, KisNodeSP parent=KisNodeSP(), KisNodeAdditionFlags flags=KisNodeAdditionFlag::None)
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
const KoColorProfile * p709G10Profile() const