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 QString wh;
148 do { // read line and skip comments
149 wh = QString::fromLatin1(d->readLine(128));
150 } while (wh.startsWith(QStringLiteral("#")));
151 auto list = wh.split(QStringLiteral(" "));
152 if (list.size() == 1) {
153 m_ps = true; // try for Photoshop version
155 }
156 if (list.size() != 2) {
157 return false;
158 }
159 auto ok_o = false;
160 auto ok_w = false;
161 auto ok_h = false;
162 auto o = QString::fromLatin1(d->readLine(128)).toDouble(&ok_o);
163 auto w = list.first().toInt(&ok_w);
164 auto h = list.last().toInt(&ok_h);
165 if (!ok_o || !ok_w || !ok_h || o == 0) {
166 return false;
167 }
168 m_width = w;
169 m_height = h;
171 return isValid();
172 }
173
174 bool peek(QIODevice *d)
175 {
176 d->startTransaction();
177 auto ok = read(d);
179 return ok;
180 }
181};
182
183class PFMHandlerPrivate
184{
185public:
186 PFMHandlerPrivate() {}
187 ~PFMHandlerPrivate() {}
188
189 PFMHeader m_header;
190};
191
192PFMHandler::PFMHandler()
194 , d(new PFMHandlerPrivate)
195{
196}
197
198bool PFMHandler::canRead() const
199{
200 if (canRead(device())) {
201 setFormat("pfm");
202 return true;
203 }
204 return false;
205}
206
207bool PFMHandler::canRead(QIODevice *device)
208{
209 if (!device) {
210 qCWarning(LOG_PFMPLUGIN) << "PFMHandler::canRead() called with no device";
211 return false;
212 }
213
214 PFMHeader h;
215 if (!h.peek(device)) {
216 return false;
217 }
218
219 return h.isValid();
220}
221
222template<class T>
223bool readScanLine(qint32 y, QDataStream &s, QImage &img, const PFMHeader &header)
224{
225 auto bw = header.isBlackAndWhite();
226 auto line = reinterpret_cast<T *>(img.scanLine(header.isPhotoshop() ? y : img.height() - y - 1));
227 for (auto x = 0, n = img.width() * 4; x < n; x += 4) {
228 line[x + 3] = T(1);
229 s >> line[x];
230 if (bw) {
231 line[x + 1] = line[x];
232 line[x + 2] = line[x];
233 } else {
234 s >> line[x + 1];
235 s >> line[x + 2];
236 }
237 if (s.status() != QDataStream::Ok) {
238 return false;
239 }
240 }
241 return true;
242}
243
244bool PFMHandler::read(QImage *image)
245{
246 auto&& header = d->m_header;
247 if (!header.read(device())) {
248 qCWarning(LOG_PFMPLUGIN) << "PFMHandler::read() invalid header";
249 return false;
250 }
251
252 QDataStream s(device());
254 s.setByteOrder(header.byteOrder());
255
256 auto img = imageAlloc(header.size(), header.format());
257 if (img.isNull()) {
258 qCWarning(LOG_PFMPLUGIN) << "PFMHandler::read() error while allocating the image";
259 return false;
260 }
261
262 for (auto y = 0, h = img.height(); y < h; ++y) {
263 auto ok = false;
264 if (header.isHalfFloat()) {
265 ok = readScanLine<qfloat16>(y, s, img, header);
266 } else {
267 ok = readScanLine<float>(y, s, img, header);
268 }
269 if (!ok) {
270 qCWarning(LOG_PFMPLUGIN) << "PFMHandler::read() detected corrupted data";
271 return false;
272 }
273 }
274
276
277 *image = img;
278 return true;
279}
280
281bool PFMHandler::supportsOption(ImageOption option) const
282{
283 if (option == QImageIOHandler::Size) {
284 return true;
285 }
286 if (option == QImageIOHandler::ImageFormat) {
287 return true;
288 }
289 if (option == QImageIOHandler::Endianness) {
290 return true;
291 }
292 return false;
293}
294
295QVariant PFMHandler::option(ImageOption option) const
296{
297 QVariant v;
298
299 if (option == QImageIOHandler::Size) {
300 auto&& h = d->m_header;
301 if (h.isValid()) {
302 v = QVariant::fromValue(h.size());
303 } else if (auto dev = device()) {
304 if (h.peek(dev)) {
305 v = QVariant::fromValue(h.size());
306 }
307 }
308 }
309
310 if (option == QImageIOHandler::ImageFormat) {
311 auto&& h = d->m_header;
312 if (h.isValid()) {
313 v = QVariant::fromValue(h.format());
314 } else if (auto dev = device()) {
315 if (h.peek(dev)) {
316 v = QVariant::fromValue(h.format());
317 }
318 }
319 }
320
321 if (option == QImageIOHandler::Endianness) {
322 auto&& h = d->m_header;
323 if (h.isValid()) {
324 v = QVariant::fromValue(h.byteOrder());
325 } else if (auto dev = device()) {
326 if (h.peek(dev)) {
327 v = QVariant::fromValue(h.byteOrder());
328 }
329 }
330 }
331
332 return v;
333}
334
335QImageIOPlugin::Capabilities PFMPlugin::capabilities(QIODevice *device, const QByteArray &format) const
336{
337 if (format == "pfm" || format == "phm") {
338 return Capabilities(CanRead);
339 }
340 if (!format.isEmpty()) {
341 return {};
342 }
343 if (!device->isOpen()) {
344 return {};
345 }
346
347 Capabilities cap;
348 if (device->isReadable() && PFMHandler::canRead(device)) {
349 cap |= CanRead;
350 }
351 return cap;
352}
353
354QImageIOHandler *PFMPlugin::create(QIODevice *device, const QByteArray &format) const
355{
356 QImageIOHandler *handler = new PFMHandler;
357 handler->setDevice(device);
358 handler->setFormat(format);
359 return handler;
360}
361
362#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)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
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 Sat Dec 21 2024 17:00:39 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.