KImageFormats

pfm.cpp
1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8/*
9 * See also: https://www.pauldebevec.com/Research/HDR/PFM/
10 */
11
12#include "pfm_p.h"
13#include "util_p.h"
14
15#include <QColorSpace>
16#include <QDataStream>
17#include <QFloat16>
18#include <QIODevice>
19#include <QImage>
20#include <QLoggingCategory>
21
22Q_DECLARE_LOGGING_CATEGORY(LOG_PFMPLUGIN)
23Q_LOGGING_CATEGORY(LOG_PFMPLUGIN, "kf.imageformats.plugins.pfm", QtWarningMsg)
24
25class PFMHeader
26{
27private:
28 /*!
29 * \brief m_bw True if grayscale.
30 */
31 bool m_bw;
32
33 /*!
34 * \brief m_half True if half float.
35 */
36 bool m_half;
37
38 /*!
39 * \brief m_ps True if saved by Photoshop (Photoshop variant).
40 *
41 * When \a false the format of the header is the following (GIMP):
42 * [type]
43 * [xres] [yres]
44 * [byte_order]
45 *
46 * When \a true the format of the header is the following (Photoshop):
47 * [type]
48 * [xres]
49 * [yres]
50 * [byte_order]
51 */
52 bool m_ps;
53
54 /*!
55 * \brief m_width The image width.
56 */
57 qint32 m_width;
58
59 /*!
60 * \brief m_height The image height.
61 */
62 qint32 m_height;
63
64 /*!
65 * \brief m_byteOrder The image byte orger.
66 */
67 QDataStream::ByteOrder m_byteOrder;
68
69public:
70 PFMHeader()
71 : m_bw(false)
72 , m_half(false)
73 , m_ps(false)
74 , m_width(0)
75 , m_height(0)
76 , m_byteOrder(QDataStream::BigEndian)
77 {
78
79 }
80
81 bool isValid() const
82 {
83 return (m_width > 0 && m_height > 0);
84 }
85
86 bool isBlackAndWhite() const
87 {
88 return m_bw;
89 }
90
91 bool isHalfFloat() const
92 {
93 return m_half;
94 }
95
96 bool isPhotoshop() const
97 {
98 return m_ps;
99 }
100
101 qint32 width() const
102 {
103 return m_width;
104 }
105
106 qint32 height() const
107 {
108 return m_height;
109 }
110
111 QSize size() const
112 {
113 return QSize(m_width, m_height);
114 }
115
116 QDataStream::ByteOrder byteOrder() const
117 {
118 return m_byteOrder;
119 }
120
121 QImage::Format format() const
122 {
123 if (isValid()) {
125 }
127 }
128
129 bool read(QIODevice *d)
130 {
131 auto pf = d->read(3);
132 if (pf == QByteArray("PF\n")) {
133 m_half = false;
134 m_bw = false;
135 } else if (pf == QByteArray("Pf\n")) {
136 m_half = false;
137 m_bw = true;
138 } else if (pf == QByteArray("PH\n")) {
139 m_half = true;
140 m_bw = false;
141 } else if (pf == QByteArray("Ph\n")) {
142 m_half = true;
143 m_bw = true;
144 } else {
145 return false;
146 }
147 auto wh = QString::fromLatin1(d->readLine(128));
148 auto list = wh.split(QStringLiteral(" "));
149 if (list.size() == 1) {
150 m_ps = true; // try for Photoshop version
152 }
153 if (list.size() != 2) {
154 return false;
155 }
156 auto ok_o = false;
157 auto ok_w = false;
158 auto ok_h = false;
159 auto o = QString::fromLatin1(d->readLine(128)).toDouble(&ok_o);
160 auto w = list.first().toInt(&ok_w);
161 auto h = list.last().toInt(&ok_h);
162 if (!ok_o || !ok_w || !ok_h || o == 0) {
163 return false;
164 }
165 m_width = w;
166 m_height = h;
168 return isValid();
169 }
170
171 bool peek(QIODevice *d)
172 {
173 d->startTransaction();
174 auto ok = read(d);
176 return ok;
177 }
178};
179
180class PFMHandlerPrivate
181{
182public:
183 PFMHandlerPrivate() {}
184 ~PFMHandlerPrivate() {}
185
186 PFMHeader m_header;
187};
188
189PFMHandler::PFMHandler()
191 , d(new PFMHandlerPrivate)
192{
193}
194
195bool PFMHandler::canRead() const
196{
197 if (canRead(device())) {
198 setFormat("pfm");
199 return true;
200 }
201 return false;
202}
203
204bool PFMHandler::canRead(QIODevice *device)
205{
206 if (!device) {
207 qCWarning(LOG_PFMPLUGIN) << "PFMHandler::canRead() called with no device";
208 return false;
209 }
210
211 PFMHeader h;
212 if (!h.peek(device)) {
213 return false;
214 }
215
216 return h.isValid();
217}
218
219template<class T>
220bool readScanLine(qint32 y, QDataStream &s, QImage &img, const PFMHeader &header)
221{
222 auto bw = header.isBlackAndWhite();
223 auto line = reinterpret_cast<T *>(img.scanLine(header.isPhotoshop() ? y : img.height() - y - 1));
224 for (auto x = 0, n = img.width() * 4; x < n; x += 4) {
225 line[x + 3] = T(1);
226 s >> line[x];
227 if (bw) {
228 line[x + 1] = line[x];
229 line[x + 2] = line[x];
230 } else {
231 s >> line[x + 1];
232 s >> line[x + 2];
233 }
234 if (s.status() != QDataStream::Ok) {
235 return false;
236 }
237 }
238 return true;
239}
240
241bool PFMHandler::read(QImage *image)
242{
243 auto&& header = d->m_header;
244 if (!header.read(device())) {
245 qCWarning(LOG_PFMPLUGIN) << "PFMHandler::read() invalid header";
246 return false;
247 }
248
249 QDataStream s(device());
251 s.setByteOrder(header.byteOrder());
252
253 auto img = imageAlloc(header.size(), header.format());
254 if (img.isNull()) {
255 qCWarning(LOG_PFMPLUGIN) << "PFMHandler::read() error while allocating the image";
256 return false;
257 }
258
259 for (auto y = 0, h = img.height(); y < h; ++y) {
260 auto ok = false;
261 if (header.isHalfFloat()) {
262 ok = readScanLine<qfloat16>(y, s, img, header);
263 } else {
264 ok = readScanLine<float>(y, s, img, header);
265 }
266 if (!ok) {
267 qCWarning(LOG_PFMPLUGIN) << "PFMHandler::read() detected corrupted data";
268 return false;
269 }
270 }
271
273
274 *image = img;
275 return true;
276}
277
278bool PFMHandler::supportsOption(ImageOption option) const
279{
280 if (option == QImageIOHandler::Size) {
281 return true;
282 }
283 if (option == QImageIOHandler::ImageFormat) {
284 return true;
285 }
286 if (option == QImageIOHandler::Endianness) {
287 return true;
288 }
289 return false;
290}
291
292QVariant PFMHandler::option(ImageOption option) const
293{
294 QVariant v;
295
296 if (option == QImageIOHandler::Size) {
297 auto&& h = d->m_header;
298 if (h.isValid()) {
299 v = QVariant::fromValue(h.size());
300 } else if (auto dev = device()) {
301 if (h.peek(dev)) {
302 v = QVariant::fromValue(h.size());
303 }
304 }
305 }
306
307 if (option == QImageIOHandler::ImageFormat) {
308 auto&& h = d->m_header;
309 if (h.isValid()) {
310 v = QVariant::fromValue(h.format());
311 } else if (auto dev = device()) {
312 if (h.peek(dev)) {
313 v = QVariant::fromValue(h.format());
314 }
315 }
316 }
317
318 if (option == QImageIOHandler::Endianness) {
319 auto&& h = d->m_header;
320 if (h.isValid()) {
321 v = QVariant::fromValue(h.byteOrder());
322 } else if (auto dev = device()) {
323 if (h.peek(dev)) {
324 v = QVariant::fromValue(h.byteOrder());
325 }
326 }
327 }
328
329 return v;
330}
331
332QImageIOPlugin::Capabilities PFMPlugin::capabilities(QIODevice *device, const QByteArray &format) const
333{
334 if (format == "pfm" || format == "phm") {
335 return Capabilities(CanRead);
336 }
337 if (!format.isEmpty()) {
338 return {};
339 }
340 if (!device->isOpen()) {
341 return {};
342 }
343
344 Capabilities cap;
345 if (device->isReadable() && PFMHandler::canRead(device)) {
346 cap |= CanRead;
347 }
348 return cap;
349}
350
351QImageIOHandler *PFMPlugin::create(QIODevice *device, const QByteArray &format) const
352{
353 QImageIOHandler *handler = new PFMHandler;
354 handler->setDevice(device);
355 handler->setFormat(format);
356 return handler;
357}
358
359#include "moc_pfm_p.cpp"
QFlags< Capability > Capabilities
KIOCORE_EXPORT QStringList list(const QString &fileClass)
bool isEmpty() const const
void setByteOrder(ByteOrder bo)
void setFloatingPointPrecision(FloatingPointPrecision precision)
Status status() const const
int height() const const
bool isNull() const const
uchar * scanLine(int i)
void setColorSpace(const QColorSpace &colorSpace)
int width() const const
void setDevice(QIODevice *device)
void setFormat(const QByteArray &format)
bool isOpen() const const
bool isReadable() const const
QByteArray read(qint64 maxSize)
QByteArray readLine(qint64 maxSize)
void rollbackTransaction()
void startTransaction()
T & first()
T & last()
qsizetype size() const const
QString fromLatin1(QByteArrayView str)
double toDouble(bool *ok) const const
QVariant fromValue(T &&value)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 22 2024 12:10:56 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.