PFM: extended to half float format

The Portable HalfMap is a format supported by ImageMagick. The test cases was generated by converting pfm to phm using ImageMagick: `convert image.pfm image.phm`.
This commit is contained in:
Mirco Miranda 2024-11-06 21:12:05 +00:00 committed by Albert Astals Cid
parent 2ea724c241
commit c38a1a0248
8 changed files with 64 additions and 27 deletions

2
.gitattributes vendored
View File

@ -11,3 +11,5 @@ autotests/read/hdr/fake_earth.hdr binary
autotests/read/hdr/rgb.hdr binary autotests/read/hdr/rgb.hdr binary
autotests/read/hdr/rgb-landscape.hdr binary autotests/read/hdr/rgb-landscape.hdr binary
autotests/read/hdr/rgb-portrait.hdr binary autotests/read/hdr/rgb-portrait.hdr binary
autotests/read/pfm/testcard_gray_half.phm binary
autotests/read/pfm/testcard_rgb_half.phm binary

View File

@ -19,7 +19,7 @@ The following image formats have read-only support:
- Krita (kra) - Krita (kra)
- OpenRaster (ora) - OpenRaster (ora)
- Pixar raster (pxr) - Pixar raster (pxr)
- Portable FloatMap (pfm) - Portable FloatMap/HalfMap (pfm, phm)
- Photoshop documents (psd, psb, pdd, psdt) - Photoshop documents (psd, psb, pdd, psdt)
- Radiance HDR (hdr) - Radiance HDR (hdr)
- Scitex CT (sct) - Scitex CT (sct)

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -14,6 +14,7 @@
#include <QColorSpace> #include <QColorSpace>
#include <QDataStream> #include <QDataStream>
#include <QFloat16>
#include <QIODevice> #include <QIODevice>
#include <QImage> #include <QImage>
#include <QLoggingCategory> #include <QLoggingCategory>
@ -29,6 +30,11 @@ private:
*/ */
bool m_bw; bool m_bw;
/*!
* \brief m_half True if half float.
*/
bool m_half;
/*! /*!
* \brief m_ps True if saved by Photoshop (Photoshop variant). * \brief m_ps True if saved by Photoshop (Photoshop variant).
* *
@ -61,12 +67,13 @@ private:
QDataStream::ByteOrder m_byteOrder; QDataStream::ByteOrder m_byteOrder;
public: public:
PFMHeader() : PFMHeader()
m_bw(false), : m_bw(false)
m_ps(false), , m_half(false)
m_width(0), , m_ps(false)
m_height(0), , m_width(0)
m_byteOrder(QDataStream::BigEndian) , m_height(0)
, m_byteOrder(QDataStream::BigEndian)
{ {
} }
@ -81,6 +88,11 @@ public:
return m_bw; return m_bw;
} }
bool isHalfFloat() const
{
return m_half;
}
bool isPhotoshop() const bool isPhotoshop() const
{ {
return m_ps; return m_ps;
@ -109,7 +121,7 @@ public:
QImage::Format format() const QImage::Format format() const
{ {
if (isValid()) { if (isValid()) {
return QImage::Format_RGBX32FPx4; return m_half ? QImage::Format_RGBX16FPx4 : QImage::Format_RGBX32FPx4;
} }
return QImage::Format_Invalid; return QImage::Format_Invalid;
} }
@ -118,8 +130,16 @@ public:
{ {
auto pf = d->read(3); auto pf = d->read(3);
if (pf == QByteArray("PF\n")) { if (pf == QByteArray("PF\n")) {
m_half = false;
m_bw = false; m_bw = false;
} else if (pf == QByteArray("Pf\n")) { } else if (pf == QByteArray("Pf\n")) {
m_half = false;
m_bw = true;
} else if (pf == QByteArray("PH\n")) {
m_half = true;
m_bw = false;
} else if (pf == QByteArray("Ph\n")) {
m_half = true;
m_bw = true; m_bw = true;
} else { } else {
return false; return false;
@ -196,6 +216,28 @@ bool PFMHandler::canRead(QIODevice *device)
return h.isValid(); return h.isValid();
} }
template<class T>
bool readScanLine(qint32 y, QDataStream &s, QImage &img, const PFMHeader &header)
{
auto bw = header.isBlackAndWhite();
auto line = reinterpret_cast<T *>(img.scanLine(header.isPhotoshop() ? y : img.height() - y - 1));
for (auto x = 0, n = img.width() * 4; x < n; x += 4) {
line[x + 3] = T(1);
s >> line[x];
if (bw) {
line[x + 1] = line[x];
line[x + 2] = line[x];
} else {
s >> line[x + 1];
s >> line[x + 2];
}
if (s.status() != QDataStream::Ok) {
return false;
}
}
return true;
}
bool PFMHandler::read(QImage *image) bool PFMHandler::read(QImage *image)
{ {
auto&& header = d->m_header; auto&& header = d->m_header;
@ -215,24 +257,17 @@ bool PFMHandler::read(QImage *image)
} }
for (auto y = 0, h = img.height(); y < h; ++y) { for (auto y = 0, h = img.height(); y < h; ++y) {
auto bw = header.isBlackAndWhite(); auto ok = false;
auto line = reinterpret_cast<float *>(img.scanLine(header.isPhotoshop() ? y : h - y - 1)); if (header.isHalfFloat()) {
for (auto x = 0, n = img.width() * 4; x < n; x += 4) { ok = readScanLine<qfloat16>(y, s, img, header);
line[x + 3] = float(1);
s >> line[x];
if (bw) {
line[x + 1] = line[x];
line[x + 2] = line[x];
} else { } else {
s >> line[x + 1]; ok = readScanLine<float>(y, s, img, header);
s >> line[x + 2];
} }
if (s.status() != QDataStream::Ok) { if (!ok) {
qCWarning(LOG_PFMPLUGIN) << "PFMHandler::read() detected corrupted data"; qCWarning(LOG_PFMPLUGIN) << "PFMHandler::read() detected corrupted data";
return false; return false;
} }
} }
}
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear)); img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
@ -296,7 +331,7 @@ QVariant PFMHandler::option(ImageOption option) const
QImageIOPlugin::Capabilities PFMPlugin::capabilities(QIODevice *device, const QByteArray &format) const QImageIOPlugin::Capabilities PFMPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{ {
if (format == "pfm") { if (format == "pfm" || format == "phm") {
return Capabilities(CanRead); return Capabilities(CanRead);
} }
if (!format.isEmpty()) { if (!format.isEmpty()) {

View File

@ -1,4 +1,4 @@
{ {
"Keys": [ "pfm" ], "Keys": [ "pfm", "phm" ],
"MimeTypes": [ "image/x-pfm" ] "MimeTypes": [ "image/x-pfm", "image/x-phm" ]
} }