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 <QIODevice>
18#include <QImage>
19#include <QLoggingCategory>
20
21Q_DECLARE_LOGGING_CATEGORY(LOG_PFMPLUGIN)
22Q_LOGGING_CATEGORY(LOG_PFMPLUGIN, "kf.imageformats.plugins.pfm", QtWarningMsg)
23
24class PFMHeader
25{
26private:
27 /*!
28 * \brief m_bw True if grayscale.
29 */
30 bool m_bw;
31
32 /*!
33 * \brief m_ps True if saved by Photoshop (Photoshop variant).
34 *
35 * When \a false the format of the header is the following (GIMP):
36 * [type]
37 * [xres] [yres]
38 * [byte_order]
39 *
40 * When \a true the format of the header is the following (Photoshop):
41 * [type]
42 * [xres]
43 * [yres]
44 * [byte_order]
45 */
46 bool m_ps;
47
48 /*!
49 * \brief m_width The image width.
50 */
51 qint32 m_width;
52
53 /*!
54 * \brief m_height The image height.
55 */
56 qint32 m_height;
57
58 /*!
59 * \brief m_byteOrder The image byte orger.
60 */
61 QDataStream::ByteOrder m_byteOrder;
62
63public:
64 PFMHeader() :
65 m_bw(false),
66 m_ps(false),
67 m_width(0),
68 m_height(0),
69 m_byteOrder(QDataStream::BigEndian)
70 {
71
72 }
73
74 bool isValid() const
75 {
76 return (m_width > 0 && m_height > 0);
77 }
78
79 bool isBlackAndWhite() const
80 {
81 return m_bw;
82 }
83
84 bool isPhotoshop() const
85 {
86 return m_ps;
87 }
88
89 qint32 width() const
90 {
91 return m_width;
92 }
93
94 qint32 height() const
95 {
96 return m_height;
97 }
98
99 QSize size() const
100 {
101 return QSize(m_width, m_height);
102 }
103
104 QDataStream::ByteOrder byteOrder() const
105 {
106 return m_byteOrder;
107 }
108
109 QImage::Format format() const
110 {
111 if (isValid()) {
113 }
115 }
116
117 bool read(QIODevice *d)
118 {
119 auto pf = d->read(3);
120 if (pf == QByteArray("PF\n")) {
121 m_bw = false;
122 } else if (pf == QByteArray("Pf\n")) {
123 m_bw = true;
124 } else {
125 return false;
126 }
127 auto wh = QString::fromLatin1(d->readLine(128));
128 auto list = wh.split(QStringLiteral(" "));
129 if (list.size() == 1) {
130 m_ps = true; // try for Photoshop version
132 }
133 if (list.size() != 2) {
134 return false;
135 }
136 auto ok_o = false;
137 auto ok_w = false;
138 auto ok_h = false;
139 auto o = QString::fromLatin1(d->readLine(128)).toDouble(&ok_o);
140 auto w = list.first().toInt(&ok_w);
141 auto h = list.last().toInt(&ok_h);
142 if (!ok_o || !ok_w || !ok_h || o == 0) {
143 return false;
144 }
145 m_width = w;
146 m_height = h;
148 return isValid();
149 }
150
151 bool peek(QIODevice *d)
152 {
153 d->startTransaction();
154 auto ok = read(d);
156 return ok;
157 }
158};
159
160class PFMHandlerPrivate
161{
162public:
163 PFMHandlerPrivate() {}
164 ~PFMHandlerPrivate() {}
165
166 PFMHeader m_header;
167};
168
169PFMHandler::PFMHandler()
171 , d(new PFMHandlerPrivate)
172{
173}
174
175bool PFMHandler::canRead() const
176{
177 if (canRead(device())) {
178 setFormat("pfm");
179 return true;
180 }
181 return false;
182}
183
184bool PFMHandler::canRead(QIODevice *device)
185{
186 if (!device) {
187 qCWarning(LOG_PFMPLUGIN) << "PFMHandler::canRead() called with no device";
188 return false;
189 }
190
191 PFMHeader h;
192 if (!h.peek(device)) {
193 return false;
194 }
195
196 return h.isValid();
197}
198
199bool PFMHandler::read(QImage *image)
200{
201 auto&& header = d->m_header;
202 if (!header.read(device())) {
203 qCWarning(LOG_PFMPLUGIN) << "PFMHandler::read() invalid header";
204 return false;
205 }
206
207 QDataStream s(device());
208 s.setFloatingPointPrecision(QDataStream::SinglePrecision);
209 s.setByteOrder(header.byteOrder());
210
211 auto img = imageAlloc(header.size(), header.format());
212 if (img.isNull()) {
213 qCWarning(LOG_PFMPLUGIN) << "PFMHandler::read() error while allocating the image";
214 return false;
215 }
216
217 for (auto y = 0, h = img.height(); y < h; ++y) {
218 auto bw = header.isBlackAndWhite();
219 auto line = reinterpret_cast<float *>(img.scanLine(header.isPhotoshop() ? y : h - y - 1));
220 for (auto x = 0, n = img.width() * 4; x < n; x += 4) {
221 line[x + 3] = float(1);
222 s >> line[x];
223 if (bw) {
224 line[x + 1] = line[x];
225 line[x + 2] = line[x];
226 } else {
227 s >> line[x + 1];
228 s >> line[x + 2];
229 }
230 if (s.status() != QDataStream::Ok) {
231 qCWarning(LOG_PFMPLUGIN) << "PFMHandler::read() detected corrupted data";
232 return false;
233 }
234 }
235 }
236
237 img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
238
239 *image = img;
240 return true;
241}
242
243bool PFMHandler::supportsOption(ImageOption option) const
244{
245 if (option == QImageIOHandler::Size) {
246 return true;
247 }
248 if (option == QImageIOHandler::ImageFormat) {
249 return true;
250 }
251 if (option == QImageIOHandler::Endianness) {
252 return true;
253 }
254 return false;
255}
256
257QVariant PFMHandler::option(ImageOption option) const
258{
259 QVariant v;
260
261 if (option == QImageIOHandler::Size) {
262 auto&& h = d->m_header;
263 if (h.isValid()) {
264 v = QVariant::fromValue(h.size());
265 } else if (auto dev = device()) {
266 if (h.peek(dev)) {
267 v = QVariant::fromValue(h.size());
268 }
269 }
270 }
271
272 if (option == QImageIOHandler::ImageFormat) {
273 auto&& h = d->m_header;
274 if (h.isValid()) {
275 v = QVariant::fromValue(h.format());
276 } else if (auto dev = device()) {
277 if (h.peek(dev)) {
278 v = QVariant::fromValue(h.format());
279 }
280 }
281 }
282
283 if (option == QImageIOHandler::Endianness) {
284 auto&& h = d->m_header;
285 if (h.isValid()) {
286 v = QVariant::fromValue(h.byteOrder());
287 } else if (auto dev = device()) {
288 if (h.peek(dev)) {
289 v = QVariant::fromValue(h.byteOrder());
290 }
291 }
292 }
293
294 return v;
295}
296
297QImageIOPlugin::Capabilities PFMPlugin::capabilities(QIODevice *device, const QByteArray &format) const
298{
299 if (format == "pfm") {
300 return Capabilities(CanRead);
301 }
302 if (!format.isEmpty()) {
303 return {};
304 }
305 if (!device->isOpen()) {
306 return {};
307 }
308
309 Capabilities cap;
310 if (device->isReadable() && PFMHandler::canRead(device)) {
311 cap |= CanRead;
312 }
313 return cap;
314}
315
316QImageIOHandler *PFMPlugin::create(QIODevice *device, const QByteArray &format) const
317{
318 QImageIOHandler *handler = new PFMHandler;
319 handler->setDevice(device);
320 handler->setFormat(format);
321 return handler;
322}
323
324#include "moc_pfm_p.cpp"
QFlags< Capability > Capabilities
KIOCORE_EXPORT QStringList list(const QString &fileClass)
bool isEmpty() 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 Aug 30 2024 11:51:01 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.