KImageFormats

exr.cpp
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 <[email protected]>
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:
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  Imf::Int64 tellg() override;
52  void seekg(Imf::Int64 pos) override;
53 #endif
54  void clear() override;
55 
56 private:
57  QIODevice *m_dev;
58 };
59 
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
74 uint64_t K_IStream::tellg()
75 #else
76 Imf::Int64 K_IStream::tellg()
77 #endif
78 {
79  return m_dev->pos();
80 }
81 
82 #if OPENEXR_VERSION_MAJOR > 2
83 void K_IStream::seekg(uint64_t pos)
84 #else
85 void K_IStream::seekg(Imf::Int64 pos)
86 #endif
87 {
88  m_dev->seek(pos);
89 }
90 
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  */
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 
168 EXRHandler::EXRHandler()
169 {
170 }
171 
172 bool EXRHandler::canRead() const
173 {
174  if (canRead(device())) {
175  setFormat("exr");
176  return true;
177  }
178  return false;
179 }
180 
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 
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 
235 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 
254 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 }
bool isEmpty() const const
bool isReadable() const const
qint64 peek(char *data, qint64 maxSize)
QVariant read(const QByteArray &data, int versionOverride=0)
void setDevice(QIODevice *device)
qint64 read(char *data, qint64 maxSize)
bool isOpen() const const
char * data()
typedef Capabilities
void setFormat(const QByteArray &format)
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Thu Dec 2 2021 22:45:39 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.