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-landscape.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)
- OpenRaster (ora)
- Pixar raster (pxr)
- Portable FloatMap (pfm)
- Portable FloatMap/HalfMap (pfm, phm)
- Photoshop documents (psd, psb, pdd, psdt)
- Radiance HDR (hdr)
- 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 <QDataStream>
#include <QFloat16>
#include <QIODevice>
#include <QImage>
#include <QLoggingCategory>
@ -29,6 +30,11 @@ private:
*/
bool m_bw;
/*!
* \brief m_half True if half float.
*/
bool m_half;
/*!
* \brief m_ps True if saved by Photoshop (Photoshop variant).
*
@ -61,12 +67,13 @@ private:
QDataStream::ByteOrder m_byteOrder;
public:
PFMHeader() :
m_bw(false),
m_ps(false),
m_width(0),
m_height(0),
m_byteOrder(QDataStream::BigEndian)
PFMHeader()
: m_bw(false)
, m_half(false)
, m_ps(false)
, m_width(0)
, m_height(0)
, m_byteOrder(QDataStream::BigEndian)
{
}
@ -81,6 +88,11 @@ public:
return m_bw;
}
bool isHalfFloat() const
{
return m_half;
}
bool isPhotoshop() const
{
return m_ps;
@ -109,7 +121,7 @@ public:
QImage::Format format() const
{
if (isValid()) {
return QImage::Format_RGBX32FPx4;
return m_half ? QImage::Format_RGBX16FPx4 : QImage::Format_RGBX32FPx4;
}
return QImage::Format_Invalid;
}
@ -118,8 +130,16 @@ public:
{
auto pf = d->read(3);
if (pf == QByteArray("PF\n")) {
m_half = false;
m_bw = false;
} 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;
} else {
return false;
@ -196,6 +216,28 @@ bool PFMHandler::canRead(QIODevice *device)
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)
{
auto&& header = d->m_header;
@ -215,24 +257,17 @@ bool PFMHandler::read(QImage *image)
}
for (auto y = 0, h = img.height(); y < h; ++y) {
auto bw = header.isBlackAndWhite();
auto line = reinterpret_cast<float *>(img.scanLine(header.isPhotoshop() ? y : h - y - 1));
for (auto x = 0, n = img.width() * 4; x < n; x += 4) {
line[x + 3] = float(1);
s >> line[x];
if (bw) {
line[x + 1] = line[x];
line[x + 2] = line[x];
auto ok = false;
if (header.isHalfFloat()) {
ok = readScanLine<qfloat16>(y, s, img, header);
} else {
s >> line[x + 1];
s >> line[x + 2];
ok = readScanLine<float>(y, s, img, header);
}
if (s.status() != QDataStream::Ok) {
if (!ok) {
qCWarning(LOG_PFMPLUGIN) << "PFMHandler::read() detected corrupted data";
return false;
}
}
}
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
{
if (format == "pfm") {
if (format == "pfm" || format == "phm") {
return Capabilities(CanRead);
}
if (!format.isEmpty()) {

View File

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