1 /*
2 KImageIO Routines to read (and perhaps in the future, write) images
3 in the high dynamic range EXR format.
4
5 SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9
10 #include "exr_p.h"
11
12 #include <IexThrowErrnoExc.h>
13 #include <ImathBox.h>
14 #include <ImfArray.h>
15 #include <ImfBoxAttribute.h>
16 #include <ImfChannelListAttribute.h>
17 #include <ImfCompressionAttribute.h>
18 #include <ImfConvert.h>
19 #include <ImfFloatAttribute.h>
20 #include <ImfInputFile.h>
21 #include <ImfInt64.h>
22 #include <ImfIntAttribute.h>
23 #include <ImfLineOrderAttribute.h>
24 #include <ImfRgbaFile.h>
25 #include <ImfStandardAttributes.h>
26 #include <ImfStringAttribute.h>
27 #include <ImfVecAttribute.h>
28 #include <ImfVersion.h>
29
30 #include <iostream>
31
32 #include <QDataStream>
33 #include <QDebug>
34 #include <QImage>
35 #include <QImageIOPlugin>
36
37 class K_IStream : public Imf::IStream
38 {
39 public:
K_IStream(QIODevice * dev,const QByteArray & fileName)40 K_IStream(QIODevice *dev, const QByteArray &fileName)
41 : IStream(fileName.data())
42 , m_dev(dev)
43 {
44 }
45
46 bool read(char c[], int n) override;
47 #if OPENEXR_VERSION_MAJOR > 2
48 uint64_t tellg() override;
49 void seekg(uint64_t pos) override;
50 #else
51 uint64_t tellg() override;
52 void seekg(uint64_t pos) override;
53 #endif
54 void clear() override;
55
56 private:
57 QIODevice *m_dev;
58 };
59
read(char c[],int n)60 bool K_IStream::read(char c[], int n)
61 {
62 qint64 result = m_dev->read(c, n);
63 if (result > 0) {
64 return true;
65 } else if (result == 0) {
66 throw Iex::InputExc("Unexpected end of file");
67 } else { // negative value {
68 Iex::throwErrnoExc("Error in read", result);
69 }
70 return false;
71 }
72
73 #if OPENEXR_VERSION_MAJOR > 2
tellg()74 uint64_t K_IStream::tellg()
75 #else
76 uint64_t K_IStream::tellg()
77 #endif
78 {
79 return m_dev->pos();
80 }
81
82 #if OPENEXR_VERSION_MAJOR > 2
seekg(uint64_t pos)83 void K_IStream::seekg(uint64_t pos)
84 #else
85 void K_IStream::seekg(uint64_t pos)
86 #endif
87 {
88 m_dev->seek(pos);
89 }
90
clear()91 void K_IStream::clear()
92 {
93 // TODO
94 }
95
96 /* this does a conversion from the ILM Half (equal to Nvidia Half)
97 * format into the normal 32 bit pixel format. Process is from the
98 * ILM code.
99 */
RgbaToQrgba(struct Imf::Rgba & imagePixel)100 QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
101 {
102 float r;
103 float g;
104 float b;
105 float a;
106
107 // 1) Compensate for fogging by subtracting defog
108 // from the raw pixel values.
109 // Response: We work with defog of 0.0, so this is a no-op
110
111 // 2) Multiply the defogged pixel values by
112 // 2^(exposure + 2.47393).
113 // Response: We work with exposure of 0.0.
114 // (2^2.47393) is 5.55555
115 r = imagePixel.r * 5.55555;
116 g = imagePixel.g * 5.55555;
117 b = imagePixel.b * 5.55555;
118 a = imagePixel.a * 5.55555;
119
120 // 3) Values, which are now 1.0, are called "middle gray".
121 // If defog and exposure are both set to 0.0, then
122 // middle gray corresponds to a raw pixel value of 0.18.
123 // In step 6, middle gray values will be mapped to an
124 // intensity 3.5 f-stops below the display's maximum
125 // intensity.
126 // Response: no apparent content.
127
128 // 4) Apply a knee function. The knee function has two
129 // parameters, kneeLow and kneeHigh. Pixel values
130 // below 2^kneeLow are not changed by the knee
131 // function. Pixel values above kneeLow are lowered
132 // according to a logarithmic curve, such that the
133 // value 2^kneeHigh is mapped to 2^3.5 (in step 6,
134 // this value will be mapped to the display's
135 // maximum intensity).
136 // Response: kneeLow = 0.0 (2^0.0 => 1); kneeHigh = 5.0 (2^5 =>32)
137 if (r > 1.0) {
138 r = 1.0 + std::log((r - 1.0) * 0.184874 + 1) / 0.184874;
139 }
140 if (g > 1.0) {
141 g = 1.0 + std::log((g - 1.0) * 0.184874 + 1) / 0.184874;
142 }
143 if (b > 1.0) {
144 b = 1.0 + std::log((b - 1.0) * 0.184874 + 1) / 0.184874;
145 }
146 if (a > 1.0) {
147 a = 1.0 + std::log((a - 1.0) * 0.184874 + 1) / 0.184874;
148 }
149 //
150 // 5) Gamma-correct the pixel values, assuming that the
151 // screen's gamma is 0.4545 (or 1/2.2).
152 r = std::pow(r, 0.4545);
153 g = std::pow(g, 0.4545);
154 b = std::pow(b, 0.4545);
155 a = std::pow(a, 0.4545);
156
157 // 6) Scale the values such that pixels middle gray
158 // pixels are mapped to 84.66 (or 3.5 f-stops below
159 // the display's maximum intensity).
160 //
161 // 7) Clamp the values to [0, 255].
162 return qRgba((unsigned char)(Imath::clamp(r * 84.66f, 0.f, 255.f)),
163 (unsigned char)(Imath::clamp(g * 84.66f, 0.f, 255.f)),
164 (unsigned char)(Imath::clamp(b * 84.66f, 0.f, 255.f)),
165 (unsigned char)(Imath::clamp(a * 84.66f, 0.f, 255.f)));
166 }
167
EXRHandler()168 EXRHandler::EXRHandler()
169 {
170 }
171
canRead() const172 bool EXRHandler::canRead() const
173 {
174 if (canRead(device())) {
175 setFormat("exr");
176 return true;
177 }
178 return false;
179 }
180
read(QImage * outImage)181 bool EXRHandler::read(QImage *outImage)
182 {
183 try {
184 int width;
185 int height;
186
187 K_IStream istr(device(), QByteArray());
188 Imf::RgbaInputFile file(istr);
189 Imath::Box2i dw = file.dataWindow();
190
191 width = dw.max.x - dw.min.x + 1;
192 height = dw.max.y - dw.min.y + 1;
193
194 QImage image(width, height, QImage::Format_RGB32);
195 if (image.isNull()) {
196 qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
197 return false;
198 }
199
200 Imf::Array2D<Imf::Rgba> pixels;
201 pixels.resizeErase(height, width);
202
203 file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y * width, 1, width);
204 file.readPixels(dw.min.y, dw.max.y);
205
206 // somehow copy pixels into image
207 for (int y = 0; y < height; y++) {
208 for (int x = 0; x < width; x++) {
209 // copy pixels(x,y) into image(x,y)
210 image.setPixel(x, y, RgbaToQrgba(pixels[y][x]));
211 }
212 }
213
214 *outImage = image;
215
216 return true;
217 } catch (const std::exception &exc) {
218 // qDebug() << exc.what();
219 return false;
220 }
221 }
222
canRead(QIODevice * device)223 bool EXRHandler::canRead(QIODevice *device)
224 {
225 if (!device) {
226 qWarning("EXRHandler::canRead() called with no device");
227 return false;
228 }
229
230 const QByteArray head = device->peek(4);
231
232 return Imf::isImfMagic(head.data());
233 }
234
capabilities(QIODevice * device,const QByteArray & format) const235 QImageIOPlugin::Capabilities EXRPlugin::capabilities(QIODevice *device, const QByteArray &format) const
236 {
237 if (format == "exr") {
238 return Capabilities(CanRead);
239 }
240 if (!format.isEmpty()) {
241 return {};
242 }
243 if (!device->isOpen()) {
244 return {};
245 }
246
247 Capabilities cap;
248 if (device->isReadable() && EXRHandler::canRead(device)) {
249 cap |= CanRead;
250 }
251 return cap;
252 }
253
create(QIODevice * device,const QByteArray & format) const254 QImageIOHandler *EXRPlugin::create(QIODevice *device, const QByteArray &format) const
255 {
256 QImageIOHandler *handler = new EXRHandler;
257 handler->setDevice(device);
258 handler->setFormat(format);
259 return handler;
260 }
261