mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-11-22 10:02:43 -05:00
Full range HDR support
EXR, HDR, JXR and PFM formats support High Dynamic Range images (FP values grater than 1).
In summary, here is the list of changes:
EXR, HDR, JXR and PFM: When working with FP formats, the clamp between 0 and 1 is no longer done.
EXR: Removed old SDR code and conversions. Due to the lack of a QImage Gray FP format, Gray images are output as RGB FP (recently added code for Qt 6.8 has been removed).
PFM: Due to the lack of a QImage Gray FP format, Gray images are output as RGB FP.
HDR: Added rotation and exposure support.
With this patch, EXR, JXR, HDR, PFM behave like Qt's TIFF plugin when working with FP images.
This commit is contained in:
committed by
Albert Astals Cid
parent
4c0f49295b
commit
f5a6de7280
@ -436,6 +436,13 @@ public:
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
<< QImage::Format_CMYK8888
|
||||
#endif
|
||||
#ifndef JXR_DENY_FLOAT_IMAGE
|
||||
<< QImage::Format_RGBA16FPx4
|
||||
<< QImage::Format_RGBX16FPx4
|
||||
<< QImage::Format_RGBA32FPx4
|
||||
<< QImage::Format_RGBA32FPx4_Premultiplied
|
||||
<< QImage::Format_RGBX32FPx4
|
||||
#endif // JXR_DENY_FLOAT_IMAGE
|
||||
<< QImage::Format_RGBA64
|
||||
<< QImage::Format_RGBA64_Premultiplied
|
||||
<< QImage::Format_RGBA8888
|
||||
@ -476,7 +483,18 @@ public:
|
||||
} else {
|
||||
qi = qi.convertToFormat(alpha ? QImage::Format_RGBA8888 : QImage::Format_RGB888);
|
||||
}
|
||||
#ifndef JXR_DENY_FLOAT_IMAGE
|
||||
} else if(qi.format() == QImage::Format_RGBA16FPx4 ||
|
||||
qi.format() == QImage::Format_RGBX16FPx4 ||
|
||||
qi.format() == QImage::Format_RGBA32FPx4 ||
|
||||
qi.format() == QImage::Format_RGBA32FPx4_Premultiplied ||
|
||||
qi.format() == QImage::Format_RGBX32FPx4) {
|
||||
auto cs = qi.colorSpace();
|
||||
if (cs.isValid() && cs.transferFunction() != QColorSpace::TransferFunction::Linear) {
|
||||
qi = qi.convertedToColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
||||
}
|
||||
}
|
||||
#endif // JXR_DENY_FLOAT_IMAGE
|
||||
|
||||
return qi;
|
||||
}
|
||||
@ -759,35 +777,6 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
inline T scRGBTosRGB(T f)
|
||||
{
|
||||
// convert from linear scRGB to non-linear sRGB
|
||||
if (f <= T(0)) {
|
||||
return T(0);
|
||||
}
|
||||
if (f <= T(0.0031308f)) {
|
||||
return qBound(T(0), f * T(12.92f), T(1));
|
||||
}
|
||||
if (f < T(1)) {
|
||||
return qBound(T(0), T(1.055f) * T(pow(f, T(1.0) / T(2.4))) - T(0.055), T(1));
|
||||
}
|
||||
return T(1);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline T alpha_scRGBTosRGB(T f)
|
||||
{
|
||||
// alpha is converted differently than RGB in scRGB
|
||||
if (f <= T(0)) {
|
||||
return T(0);
|
||||
}
|
||||
if (f < T(1.0)) {
|
||||
return T(f);
|
||||
}
|
||||
return T(1);
|
||||
}
|
||||
|
||||
bool JXRHandler::read(QImage *outImage)
|
||||
{
|
||||
if (!d->initForReading(device())) {
|
||||
@ -841,13 +830,11 @@ bool JXRHandler::read(QImage *outImage)
|
||||
} else { // additional buffer needed
|
||||
qint64 convStrideSize = (img.width() * d->pDecoder->WMP.wmiI.cBitsPerUnit + 7) / 8;
|
||||
qint64 buffSize = convStrideSize * img.height();
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
qint64 limit = QImageReader::allocationLimit();
|
||||
if (limit && (buffSize + img.sizeInBytes()) > limit * 1024 * 1024) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to covert due to allocation limit set:" << limit << "MiB";
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
QVector<quint8> ba(buffSize);
|
||||
if (auto err = pConverter->Copy(pConverter, &rect, ba.data(), convStrideSize)) {
|
||||
PKFormatConverter_Release(&pConverter);
|
||||
@ -866,34 +853,24 @@ bool JXRHandler::read(QImage *outImage)
|
||||
d->setTextMetadata(img);
|
||||
|
||||
#ifndef JXR_DENY_FLOAT_IMAGE
|
||||
// JXR float are stored in scRGB -> range -0,5 to 7,5 (source Wikipedia)
|
||||
if (img.format() == QImage::Format_RGBX16FPx4 || img.format() == QImage::Format_RGBA16FPx4 || img.format() == QImage::Format_RGBA16FPx4_Premultiplied) {
|
||||
// JXR float are stored in scRGB.
|
||||
if (img.format() == QImage::Format_RGBX16FPx4 || img.format() == QImage::Format_RGBA16FPx4 || img.format() == QImage::Format_RGBA16FPx4_Premultiplied ||
|
||||
img.format() == QImage::Format_RGBX32FPx4 || img.format() == QImage::Format_RGBA32FPx4 || img.format() == QImage::Format_RGBA32FPx4_Premultiplied) {
|
||||
auto hasAlpha = img.hasAlphaChannel();
|
||||
for (qint32 y = 0, h = img.height(); y < h; ++y) {
|
||||
qfloat16 *line = reinterpret_cast<qfloat16 *>(img.scanLine(y));
|
||||
for (int x = 0, w = img.width(); x < w; ++x) {
|
||||
const auto x4 = x * 4;
|
||||
line[x4 + 0] = scRGBTosRGB(line[x4 + 0]);
|
||||
line[x4 + 1] = scRGBTosRGB(line[x4 + 1]);
|
||||
line[x4 + 2] = scRGBTosRGB(line[x4 + 2]);
|
||||
line[x4 + 3] = hasAlpha ? alpha_scRGBTosRGB(line[x4 + 3]) : qfloat16(1);
|
||||
if (img.depth() == 64) {
|
||||
auto line = reinterpret_cast<qfloat16 *>(img.scanLine(y));
|
||||
for (int x = 0, w = img.width() * 4; x < w; x += 4)
|
||||
line[x + 3] = hasAlpha ? std::clamp(line[x + 3], qfloat16(0), qfloat16(1)) : qfloat16(1);
|
||||
} else {
|
||||
auto line = reinterpret_cast<float *>(img.scanLine(y));
|
||||
for (int x = 0, w = img.width() * 4; x < w; x += 4)
|
||||
line[x + 3] = hasAlpha ? std::clamp(line[x + 3], float(0), float(1)) : float(1);
|
||||
}
|
||||
}
|
||||
img.setColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
} else if (img.format() == QImage::Format_RGBX32FPx4 || img.format() == QImage::Format_RGBA32FPx4
|
||||
|| img.format() == QImage::Format_RGBA32FPx4_Premultiplied) {
|
||||
auto hasAlpha = img.hasAlphaChannel();
|
||||
for (qint32 y = 0, h = img.height(); y < h; ++y) {
|
||||
float *line = reinterpret_cast<float *>(img.scanLine(y));
|
||||
for (int x = 0, w = img.width(); x < w; ++x) {
|
||||
const auto x4 = x * 4;
|
||||
line[x4 + 0] = scRGBTosRGB(line[x4 + 0]);
|
||||
line[x4 + 1] = scRGBTosRGB(line[x4 + 1]);
|
||||
line[x4 + 2] = scRGBTosRGB(line[x4 + 2]);
|
||||
line[x4 + 3] = hasAlpha ? alpha_scRGBTosRGB(line[x4 + 3]) : float(1);
|
||||
}
|
||||
if(!img.colorSpace().isValid()) {
|
||||
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
||||
}
|
||||
img.setColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -922,19 +899,11 @@ bool JXRHandler::write(const QImage &image)
|
||||
#ifndef JXR_DISABLE_BGRA_HACK
|
||||
if (IsEqualGUID(jxlfmt, GUID_PKPixelFormat32bppRGBA)) {
|
||||
jxlfmt = GUID_PKPixelFormat32bppBGRA;
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
qi = qi.rgbSwapped();
|
||||
#else
|
||||
qi.rgbSwap();
|
||||
#endif
|
||||
}
|
||||
if (IsEqualGUID(jxlfmt, GUID_PKPixelFormat32bppPRGBA)) {
|
||||
jxlfmt = GUID_PKPixelFormat32bppPBGRA;
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
qi = qi.rgbSwapped();
|
||||
#else
|
||||
qi.rgbSwap();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -968,7 +937,7 @@ bool JXRHandler::write(const QImage &image)
|
||||
}
|
||||
|
||||
// setting metadata (a failure of setting metadata doesn't stop the encoding)
|
||||
auto cs = image.colorSpace().iccProfile();
|
||||
auto cs = qi.colorSpace().iccProfile();
|
||||
if (!cs.isEmpty()) {
|
||||
if (auto err = d->pEncoder->SetColorContext(d->pEncoder, reinterpret_cast<quint8 *>(cs.data()), cs.size())) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting ICC profile:" << err;
|
||||
@ -1043,28 +1012,28 @@ QVariant JXRHandler::option(ImageOption option) const
|
||||
if (d->initForReading(device())) {
|
||||
switch (d->pDecoder->WMP.oOrientationFromContainer) {
|
||||
case O_FLIPV:
|
||||
v = QImageIOHandler::TransformationFlip;
|
||||
v = int(QImageIOHandler::TransformationFlip);
|
||||
break;
|
||||
case O_FLIPH:
|
||||
v = QImageIOHandler::TransformationMirror;
|
||||
v = int(QImageIOHandler::TransformationMirror);
|
||||
break;
|
||||
case O_FLIPVH:
|
||||
v = QImageIOHandler::TransformationRotate180;
|
||||
v = int(QImageIOHandler::TransformationRotate180);
|
||||
break;
|
||||
case O_RCW:
|
||||
v = QImageIOHandler::TransformationRotate90;
|
||||
v = int(QImageIOHandler::TransformationRotate90);
|
||||
break;
|
||||
case O_RCW_FLIPV:
|
||||
v = QImageIOHandler::TransformationFlipAndRotate90;
|
||||
v = int(QImageIOHandler::TransformationFlipAndRotate90);
|
||||
break;
|
||||
case O_RCW_FLIPH:
|
||||
v = QImageIOHandler::TransformationMirrorAndRotate90;
|
||||
v = int(QImageIOHandler::TransformationMirrorAndRotate90);
|
||||
break;
|
||||
case O_RCW_FLIPVH:
|
||||
v = QImageIOHandler::TransformationRotate270;
|
||||
v = int(QImageIOHandler::TransformationRotate270);
|
||||
break;
|
||||
default:
|
||||
v = QImageIOHandler::TransformationNone;
|
||||
v = int(QImageIOHandler::TransformationNone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user