KImageFormats

hdr.cpp
1 /*
2  This file is part of the KDE project
3  SPDX-FileCopyrightText: 2005 Christoph Hormann <[email protected]>
4  SPDX-FileCopyrightText: 2005 Ignacio CastaƱo <[email protected]>
5 
6  SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "hdr_p.h"
10 
11 #include <QDataStream>
12 #include <QImage>
13 #include <QLoggingCategory>
14 #include <QRegularExpressionMatch>
15 
16 #include <QDebug>
17 
18 typedef unsigned char uchar;
19 
20 Q_LOGGING_CATEGORY(HDRPLUGIN, "kf.imageformats.plugins.hdr", QtWarningMsg)
21 
22 namespace // Private.
23 {
24 #define MAXLINE 1024
25 #define MINELEN 8 // minimum scanline length for encoding
26 #define MAXELEN 0x7fff // maximum scanline length for encoding
27 
28 static inline uchar ClipToByte(float value)
29 {
30  if (value > 255.0f) {
31  return 255;
32  }
33  // else if (value < 0.0f) return 0; // we know value is positive.
34  return uchar(value);
35 }
36 
37 // read an old style line from the hdr image file
38 // if 'first' is true the first byte is already read
39 static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
40 {
41  int rshift = 0;
42  int i;
43 
44  while (width > 0) {
45  s >> image[0];
46  s >> image[1];
47  s >> image[2];
48  s >> image[3];
49 
50  if (s.atEnd()) {
51  return false;
52  }
53 
54  if ((image[0] == 1) && (image[1] == 1) && (image[2] == 1)) {
55  for (i = image[3] << rshift; i > 0; i--) {
56  // memcpy(image, image-4, 4);
57  (uint &)image[0] = (uint &)image[0 - 4];
58  image += 4;
59  width--;
60  }
61  rshift += 8;
62  } else {
63  image += 4;
64  width--;
65  rshift = 0;
66  }
67  }
68  return true;
69 }
70 
71 static void RGBE_To_QRgbLine(uchar *image, QRgb *scanline, int width)
72 {
73  for (int j = 0; j < width; j++) {
74  // v = ldexp(1.0, int(image[3]) - 128);
75  float v;
76  int e = int(image[3]) - 128;
77  if (e > 0) {
78  v = float(1 << e);
79  } else {
80  v = 1.0f / float(1 << -e);
81  }
82 
83  scanline[j] = qRgb(ClipToByte(float(image[0]) * v), ClipToByte(float(image[1]) * v), ClipToByte(float(image[2]) * v));
84 
85  image += 4;
86  }
87 }
88 
89 // Load the HDR image.
90 static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &img)
91 {
92  uchar val;
93  uchar code;
94 
95  // Create dst image.
96  img = QImage(width, height, QImage::Format_RGB32);
97  if (img.isNull()) {
98  qCDebug(HDRPLUGIN) << "Couldn't create image with size" << width << height << "and format RGB32";
99  return false;
100  }
101 
102  QByteArray lineArray;
103  lineArray.resize(4 * width);
104  uchar *image = (uchar *)lineArray.data();
105 
106  for (int cline = 0; cline < height; cline++) {
107  QRgb *scanline = (QRgb *)img.scanLine(cline);
108 
109  // determine scanline type
110  if ((width < MINELEN) || (MAXELEN < width)) {
111  Read_Old_Line(image, width, s);
112  RGBE_To_QRgbLine(image, scanline, width);
113  continue;
114  }
115 
116  s >> val;
117 
118  if (s.atEnd()) {
119  return true;
120  }
121 
122  if (val != 2) {
123  s.device()->ungetChar(val);
124  Read_Old_Line(image, width, s);
125  RGBE_To_QRgbLine(image, scanline, width);
126  continue;
127  }
128 
129  s >> image[1];
130  s >> image[2];
131  s >> image[3];
132 
133  if (s.atEnd()) {
134  return true;
135  }
136 
137  if ((image[1] != 2) || (image[2] & 128)) {
138  image[0] = 2;
139  Read_Old_Line(image + 4, width - 1, s);
140  RGBE_To_QRgbLine(image, scanline, width);
141  continue;
142  }
143 
144  if ((image[2] << 8 | image[3]) != width) {
145  qCDebug(HDRPLUGIN) << "Line of pixels had width" << (image[2] << 8 | image[3]) << "instead of" << width;
146  return false;
147  }
148 
149  // read each component
150  for (int i = 0; i < 4; i++) {
151  for (int j = 0; j < width;) {
152  s >> code;
153  if (s.atEnd()) {
154  qCDebug(HDRPLUGIN) << "Truncated HDR file";
155  return false;
156  }
157  if (code > 128) {
158  // run
159  code &= 127;
160  s >> val;
161  while (code != 0) {
162  image[i + j * 4] = val;
163  j++;
164  code--;
165  }
166  } else {
167  // non-run
168  while (code != 0) {
169  s >> image[i + j * 4];
170  j++;
171  code--;
172  }
173  }
174  }
175  }
176 
177  RGBE_To_QRgbLine(image, scanline, width);
178  }
179 
180  return true;
181 }
182 
183 } // namespace
184 
185 bool HDRHandler::read(QImage *outImage)
186 {
187  int len;
188  QByteArray line(MAXLINE + 1, Qt::Uninitialized);
189  QByteArray format;
190 
191  // Parse header
192  do {
193  len = device()->readLine(line.data(), MAXLINE);
194 
195  if (line.startsWith("FORMAT=")) {
196  format = line.mid(7, len - 7 - 1 /*\n*/);
197  }
198 
199  } while ((len > 0) && (line[0] != '\n'));
200 
201  if (format != "32-bit_rle_rgbe") {
202  qCDebug(HDRPLUGIN) << "Unknown HDR format:" << format;
203  return false;
204  }
205 
206  len = device()->readLine(line.data(), MAXLINE);
207  line.resize(len);
208 
209  /*
210  TODO: handle flipping and rotation, as per the spec below
211  The single resolution line consists of 4 values, a X and Y label each followed by a numerical
212  integer value. The X and Y are immediately preceded by a sign which can be used to indicate
213  flipping, the order of the X and Y indicate rotation. The standard coordinate system for
214  Radiance images would have the following resolution string -Y N +X N. This indicates that the
215  vertical axis runs down the file and the X axis is to the right (imagining the image as a
216  rectangular block of data). A -X would indicate a horizontal flip of the image. A +Y would
217  indicate a vertical flip. If the X value appears before the Y value then that indicates that
218  the image is stored in column order rather than row order, that is, it is rotated by 90 degrees.
219  The reader can convince themselves that the 8 combinations cover all the possible image orientations
220  and rotations.
221  */
222  QRegularExpression resolutionRegExp(QStringLiteral("([+\\-][XY]) ([0-9]+) ([+\\-][XY]) ([0-9]+)\n"));
223  QRegularExpressionMatch match = resolutionRegExp.match(QString::fromLatin1(line));
224  if (!match.hasMatch()) {
225  qCDebug(HDRPLUGIN) << "Invalid HDR file, the first line after the header didn't have the expected format:" << line;
226  return false;
227  }
228 
229  if ((match.captured(1).at(1) != u'Y') || (match.captured(3).at(1) != u'X')) {
230  qCDebug(HDRPLUGIN) << "Unsupported image orientation in HDR file.";
231  return false;
232  }
233 
234  const int width = match.captured(4).toInt();
235  const int height = match.captured(2).toInt();
236 
237  QDataStream s(device());
238 
239  QImage img;
240  if (!LoadHDR(s, width, height, img)) {
241  // qDebug() << "Error loading HDR file.";
242  return false;
243  }
244 
245  *outImage = img;
246  return true;
247 }
248 
249 HDRHandler::HDRHandler()
250 {
251 }
252 
253 bool HDRHandler::canRead() const
254 {
255  if (canRead(device())) {
256  setFormat("hdr");
257  return true;
258  }
259  return false;
260 }
261 
262 bool HDRHandler::canRead(QIODevice *device)
263 {
264  if (!device) {
265  qWarning("HDRHandler::canRead() called with no device");
266  return false;
267  }
268 
269  return device->peek(11) == "#?RADIANCE\n" || device->peek(7) == "#?RGBE\n";
270 }
271 
272 QImageIOPlugin::Capabilities HDRPlugin::capabilities(QIODevice *device, const QByteArray &format) const
273 {
274  if (format == "hdr") {
275  return Capabilities(CanRead);
276  }
277  if (!format.isEmpty()) {
278  return {};
279  }
280  if (!device->isOpen()) {
281  return {};
282  }
283 
284  Capabilities cap;
285  if (device->isReadable() && HDRHandler::canRead(device)) {
286  cap |= CanRead;
287  }
288  return cap;
289 }
290 
291 QImageIOHandler *HDRPlugin::create(QIODevice *device, const QByteArray &format) const
292 {
293  QImageIOHandler *handler = new HDRHandler;
294  handler->setDevice(device);
295  handler->setFormat(format);
296  return handler;
297 }
uchar * scanLine(int i)
QString captured(int nth) const const
bool isEmpty() const const
bool isNull() const const
bool isReadable() const const
qint64 peek(char *data, qint64 maxSize)
void resize(int size)
int toInt(bool *ok, int base) const const
void setDevice(QIODevice *device)
bool hasMatch() const const
bool isOpen() const const
QByteArray mid(int pos, int len) const const
bool atEnd() const const
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
const QChar at(int position) const const
char * data()
QString fromLatin1(const char *str, int size)
typedef Capabilities
void setFormat(const QByteArray &format)
QIODevice * device() const const
void ungetChar(char c)
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.