Krita Source Code Documentation
Loading...
Searching...
No Matches
RGBEExport.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
7#include "RGBEExport.h"
8
10
11#include <kpluginfactory.h>
12
13#include <QBuffer>
14#include <array>
15#include <cmath>
16#include <cstdint>
17#include <cstring>
18
19#include <KisDocument.h>
22#include <KoAlwaysInline.h>
24#include <KoColorProfile.h>
25#include <KoColorSpace.h>
27#include <KoConfig.h>
28#include <KoDocumentInfo.h>
29#include <kis_assert.h>
30#include <kis_debug.h>
31#include <kis_iterator_ng.h>
32#include <kis_layer.h>
33#include <kis_layer_utils.h>
34#include <kis_painter.h>
37
39
40K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_rgbe_export.json", registerPlugin<RGBEExport>();)
41
42namespace RGBE
43{
44inline QByteArray floatToRGBE(const int width, const int height, KisPaintDeviceSP &dev)
45{
46 KisSequentialConstIterator it(dev, {0, 0, width, height});
47 QByteArray res;
48 res.resize(width * height * 4);
49
50 quint8 rgbe[4] = {0, 0, 0, 0};
51
52 quint8 *ptr = reinterpret_cast<quint8 *>(res.data());
53 while (it.nextPixel()) {
54 auto *src = reinterpret_cast<const float *>(it.rawDataConst());
55 auto *dst = reinterpret_cast<quint8 *>(ptr);
56 float vMax = std::max(src[2], std::max(src[0], src[1]));
57 int exp;
58
59 if (vMax < 1e-32) {
60 rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
61 } else {
62 vMax = frexp(vMax, &exp) * 256.0f / vMax;
63 // Clamp negative values
64 rgbe[0] = static_cast<quint8>(std::max(src[0], 0.0f) * vMax);
65 rgbe[1] = static_cast<quint8>(std::max(src[1], 0.0f) * vMax);
66 rgbe[2] = static_cast<quint8>(std::max(src[2], 0.0f) * vMax);
67 rgbe[3] = static_cast<quint8>(exp + 128);
68 }
69
70 std::memcpy(dst, rgbe, 4);
71
72 ptr += 4;
73 }
74
75 return res;
76}
77
78inline void writeBytesRLE(QByteArray &rleBuffer, quint8 *data, int nBytes)
79{
80 static constexpr int minRunLen = 4;
81 int cur = 0;
82 int begRun;
83 int runCount;
84 int oldRunCount;
85 int nonRunCount;
86
87 quint8 buf[2];
88
89 while (cur < nBytes) {
90 begRun = cur;
91
92 runCount = oldRunCount = 0;
93 while ((runCount < minRunLen) && (begRun < nBytes)) {
94 begRun += runCount;
95 oldRunCount = runCount;
96 runCount = 1;
97 while ((begRun + runCount < nBytes) && (runCount < 127) && (data[begRun] == data[begRun + runCount])) {
98 runCount++;
99 }
100 }
101
102 if ((oldRunCount > 1) && (oldRunCount == begRun - cur)) {
103 buf[0] = 128 + oldRunCount;
104 buf[1] = data[cur];
105 rleBuffer.append(reinterpret_cast<const char *>(buf), sizeof(buf));
106 cur = begRun;
107 }
108
109 while (cur < begRun) {
110 nonRunCount = begRun - cur;
111 if (nonRunCount > 128) {
112 nonRunCount = 128;
113 }
114 buf[0] = nonRunCount;
115 rleBuffer.append(buf[0]);
116 rleBuffer.append(reinterpret_cast<const char *>(data + cur), sizeof(data[0]) * nonRunCount);
117 cur += nonRunCount;
118 }
119
120 if (runCount >= minRunLen) {
121 buf[0] = 128 + runCount;
122 buf[1] = data[begRun];
123 rleBuffer.append(reinterpret_cast<const char *>(buf), sizeof(buf));
124 cur += runCount;
125 }
126 }
127}
128}
129
130RGBEExport::RGBEExport(QObject *parent, const QVariantList &)
131 : KisImportExportFilter{parent}
132{
133}
134
136{
138
139 KisImageSP image = document->savingImage();
140 const QRect bounds = image->bounds();
141
142 const KoColorSpace *cs = image->colorSpace();
146 targetProfile);
147
148 if (image->root()->childCount() > 1) {
149 KisLayerUtils::flattenImage(image, nullptr);
150 image->waitForDone();
151 }
152
153 const bool isLinearSrgb = [&]() {
154 const bool hasPrimaries = cs->profile()->hasColorants();
156 if (hasPrimaries) {
157 const ColorPrimaries primaries = cs->profile()->getColorPrimaries();
158 if (gamma == TRC_LINEAR && primaries == PRIMARIES_ITU_R_BT_709_5) {
159 return true;
160 }
161 }
162 return false;
163 }();
164
165 // Color profile will be lost on RGBE export; so convert it to F32, linear sRGB
166 if (cs->colorModelId() != RGBAColorModelID || cs->colorDepthId() != Float32BitsColorDepthID || !isLinearSrgb) {
167 dbgFile << "Image is not in linear sRGB, converting...";
168 image->convertImageColorSpace(targetCs,
171 image->waitForDone();
172 }
173
174 // Fill transparent pixels with full opacity
175 KoColor bgColor(Qt::white, targetCs);
176 bgColor.fromKoColor(cfg->getColor("transparencyFillcolor"));
177
178 KisPaintDeviceSP dev = new KisPaintDevice(targetCs);
179 KisPainter gc(dev);
180
181 dev->fill(QRect(0, 0, image->width(), image->height()), bgColor);
182 gc.bitBlt(QPoint(0, 0), image->projection(), QRect(0, 0, image->width(), image->height()));
183 gc.end();
184
185 // Get pixel data and convert it to RGBE format
186 const QByteArray pixels = RGBE::floatToRGBE(bounds.width(), bounds.height(), dev);
187
188 QByteArray fileBuffer;
189 {
190 // Write header
191 QByteArray header;
192 header.append("#?RADIANCE\n");
193 header.append("# Created with Krita RGBE Export\n");
194 header.append("FORMAT=32-bit_rle_rgbe\n\n");
195 header.append(QStringLiteral("-Y %1 +X %2\n").arg(image->height()).arg(image->width()).toUtf8());
196
197 fileBuffer.append(header);
198 }
199
200 {
201 // Write pixel data
202 const int scanWidth = image->width();
203 const int scanHeight = image->height();
204
205 if ((scanWidth < 8) || (scanWidth > 0x7fff)) {
206 // Invalid width, save without RLE
207 fileBuffer.append(pixels);
208 io->write(fileBuffer);
209 } else {
210 // Save with RLE
211 QByteArray rleBuffer;
212 QByteArray outputBuffer;
213
214 int numScanline = scanHeight;
215 quint8 rgbe[4];
216
217 rleBuffer.resize(sizeof(quint8) * 4 * scanWidth);
218 auto *src = reinterpret_cast<const quint8 *>(pixels.data());
219 auto *rle = reinterpret_cast<quint8 *>(rleBuffer.data());
220
221 while (numScanline-- > 0) {
222 rgbe[0] = 2;
223 rgbe[1] = 2;
224 rgbe[2] = scanWidth >> 8;
225 rgbe[3] = scanWidth & 0xFF;
226 outputBuffer.append(reinterpret_cast<const char *>(rgbe), sizeof(rgbe));
227
228 for (int i = 0; i < scanWidth; i++) {
229 rle[i] = src[0];
230 rle[i + scanWidth] = src[1];
231 rle[i + 2 * scanWidth] = src[2];
232 rle[i + 3 * scanWidth] = src[3];
233 src += 4;
234 }
235
236 for (int i = 0; i < 4; i++) {
237 RGBE::writeBytesRLE(outputBuffer, &rle[i * scanWidth], scanWidth);
238 fileBuffer.append(outputBuffer);
239 outputBuffer.clear();
240 }
241 }
242
243 io->write(fileBuffer);
244 }
245 }
246
248}
249
251{
252 QList<QPair<KoID, KoID>> supportedColorModels;
258 supportedColorModels << QPair<KoID, KoID>() << QPair<KoID, KoID>(RGBAColorModelID, Float32BitsColorDepthID);
259 addSupportedColorModels(supportedColorModels, "RGBE");
260}
261
263RGBEExport::createConfigurationWidget(QWidget *parent, const QByteArray & /*from*/, const QByteArray & /*to*/) const
264{
265 return new KisWdgOptionsRGBE(parent);
266}
267
268KisPropertiesConfigurationSP RGBEExport::defaultConfiguration(const QByteArray &, const QByteArray &) const
269{
271
272 KoColor background(KoColorSpaceRegistry::instance()->rgb8());
273 background.fromQColor(Qt::white);
274 QVariant v;
275 v.setValue(background);
276
277 cfg->setProperty("transparencyFillcolor", v);
278
279 return cfg;
280}
281
282#include <RGBEExport.moc>
qreal v
VertexDescriptor get(PredecessorMap const &m, VertexDescriptor v)
const KoID Float32BitsColorDepthID("F32", ki18n("32-bit float/channel"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
ColorPrimaries
The colorPrimaries enum Enum of colorants, follows ITU H.273 for values 0 to 255, and has extra known...
@ PRIMARIES_ITU_R_BT_709_5
TransferCharacteristics
The transferCharacteristics enum Enum of transfer characteristics, follows ITU H.273 for values 0 to ...
static KisExportCheckRegistry * instance()
void waitForDone()
const KoColorSpace * colorSpace() const
void convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
KisPaintDeviceSP projection() const
qint32 width() const
qint32 height() const
QRect bounds() const override
The base class for import and export filters.
void addSupportedColorModels(QList< QPair< KoID, KoID > > supportedColorModels, const QString &name, KisExportCheckBase::Level level=KisExportCheckBase::PARTIALLY)
void addCapability(KisExportCheckBase *capability)
void fill(const QRect &rc, const KoColor &color)
void bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight)
virtual KoID colorModelId() const =0
virtual KoID colorDepthId() const =0
virtual const KoColorProfile * profile() const =0
void fromQColor(const QColor &c)
Convenient function for converting from a QColor.
Definition KoColor.cpp:213
void fromKoColor(const KoColor &src)
Definition KoColor.cpp:294
QString id() const
Definition KoID.cpp:63
void initializeCapabilities() override
KisConfigWidget * createConfigurationWidget(QWidget *parent, const QByteArray &from="", const QByteArray &to="") const override
createConfigurationWidget creates a widget that can be used to define the settings for a given import...
RGBEExport(QObject *parent, const QVariantList &)
KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP cfg=nullptr) override
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray &from="", const QByteArray &to="") const override
defaultConfiguration defines the default settings for the given import export filter
K_PLUGIN_FACTORY_WITH_JSON(KritaASCCDLFactory, "kritaasccdl.json", registerPlugin< KritaASCCDL >();) KritaASCCDL
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define bounds(x, a, b)
#define dbgFile
Definition kis_debug.h:53
void flattenImage(KisImageSP image, KisNodeSP activeNode, MergeFlags flags)
void writeBytesRLE(QByteArray &rleBuffer, quint8 *data, int nBytes)
QByteArray floatToRGBE(const int width, const int height, KisPaintDeviceSP &dev)
quint32 childCount() const
Definition kis_node.cpp:414
virtual ColorPrimaries getColorPrimaries() const
getColorPrimaries
virtual bool hasColorants() const =0
virtual TransferCharacteristics getTransferCharacteristics() const
getTransferCharacteristics This function should be subclassed at some point so we can get the value f...
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
const KoColorProfile * p709G10Profile() const