mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-05-28 00:30:23 -04:00
exr: multiple fixes
- Support for images with transparency - Precise colorspace conversion using QT color spaces - Set the correct resolution - Set useful metadata - Support for RGBX16FPx4 format in Qt 6 - Speed improvements 
This commit is contained in:
parent
c11c5eff4f
commit
7d63a1d8fa
Binary file not shown.
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@ -3,10 +3,27 @@
|
||||
in the high dynamic range EXR format.
|
||||
|
||||
SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net>
|
||||
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
/* *** EXR_USE_LEGACY_CONVERSIONS ***
|
||||
* If defined, the result image is an 8-bit RGB(A) converted
|
||||
* without icc profiles. Otherwise, a 16-bit images is generated.
|
||||
* NOTE: The use of legacy conversions are discouraged due to
|
||||
* imprecise image result.
|
||||
*/
|
||||
//#define EXR_USE_LEGACY_CONVERSIONS // default commented -> you should define it in your cmake file
|
||||
|
||||
/* *** EXR_ALLOW_LINEAR_COLORSPACE ***
|
||||
* If defined, the linear data is kept and it is the display program that
|
||||
* must convert to the monitor profile. Otherwise the data is converted to sRGB
|
||||
* to accommodate programs that do not support color profiles.
|
||||
* NOTE: If EXR_USE_LEGACY_CONVERSIONS is active, this is ignored.
|
||||
*/
|
||||
//#define EXR_ALLOW_LINEAR_COLORSPACE // default: commented -> you should define it in your cmake file
|
||||
|
||||
#include "exr_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
@ -30,11 +47,24 @@
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <QColorSpace>
|
||||
#include <QDataStream>
|
||||
#include <QDebug>
|
||||
#include <QFloat16>
|
||||
#include <QImage>
|
||||
#include <QImageIOPlugin>
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
#include <QTimeZone>
|
||||
#endif
|
||||
|
||||
// Allow the code to works on all QT versions supported by KDE
|
||||
// project (Qt 5.15 and Qt 6.x) to easy backports fixes.
|
||||
#if (QT_VERSION_MAJOR >= 6) && !defined(EXR_USE_LEGACY_CONVERSIONS)
|
||||
// If uncommented, the image is rendered in a float16 format, the result is very precise
|
||||
#define EXR_USE_QT6_FLOAT_IMAGE // default uncommented
|
||||
#endif
|
||||
|
||||
class K_IStream : public Imf::IStream
|
||||
{
|
||||
public:
|
||||
@ -94,77 +124,21 @@ void K_IStream::clear()
|
||||
// TODO
|
||||
}
|
||||
|
||||
/* this does a conversion from the ILM Half (equal to Nvidia Half)
|
||||
* format into the normal 32 bit pixel format. Process is from the
|
||||
* ILM code.
|
||||
*/
|
||||
QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
|
||||
#ifdef EXR_USE_LEGACY_CONVERSIONS
|
||||
// source: https://openexr.com/en/latest/ReadingAndWritingImageFiles.html
|
||||
inline unsigned char gamma(float x)
|
||||
{
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
float a;
|
||||
|
||||
// 1) Compensate for fogging by subtracting defog
|
||||
// from the raw pixel values.
|
||||
// Response: We work with defog of 0.0, so this is a no-op
|
||||
|
||||
// 2) Multiply the defogged pixel values by
|
||||
// 2^(exposure + 2.47393).
|
||||
// Response: We work with exposure of 0.0.
|
||||
// (2^2.47393) is 5.55555
|
||||
r = imagePixel.r * 5.55555;
|
||||
g = imagePixel.g * 5.55555;
|
||||
b = imagePixel.b * 5.55555;
|
||||
a = imagePixel.a * 5.55555;
|
||||
|
||||
// 3) Values, which are now 1.0, are called "middle gray".
|
||||
// If defog and exposure are both set to 0.0, then
|
||||
// middle gray corresponds to a raw pixel value of 0.18.
|
||||
// In step 6, middle gray values will be mapped to an
|
||||
// intensity 3.5 f-stops below the display's maximum
|
||||
// intensity.
|
||||
// Response: no apparent content.
|
||||
|
||||
// 4) Apply a knee function. The knee function has two
|
||||
// parameters, kneeLow and kneeHigh. Pixel values
|
||||
// below 2^kneeLow are not changed by the knee
|
||||
// function. Pixel values above kneeLow are lowered
|
||||
// according to a logarithmic curve, such that the
|
||||
// value 2^kneeHigh is mapped to 2^3.5 (in step 6,
|
||||
// this value will be mapped to the display's
|
||||
// maximum intensity).
|
||||
// Response: kneeLow = 0.0 (2^0.0 => 1); kneeHigh = 5.0 (2^5 =>32)
|
||||
if (r > 1.0) {
|
||||
r = 1.0 + std::log((r - 1.0) * 0.184874 + 1) / 0.184874;
|
||||
}
|
||||
if (g > 1.0) {
|
||||
g = 1.0 + std::log((g - 1.0) * 0.184874 + 1) / 0.184874;
|
||||
}
|
||||
if (b > 1.0) {
|
||||
b = 1.0 + std::log((b - 1.0) * 0.184874 + 1) / 0.184874;
|
||||
}
|
||||
if (a > 1.0) {
|
||||
a = 1.0 + std::log((a - 1.0) * 0.184874 + 1) / 0.184874;
|
||||
}
|
||||
//
|
||||
// 5) Gamma-correct the pixel values, assuming that the
|
||||
// screen's gamma is 0.4545 (or 1/2.2).
|
||||
r = std::pow(r, 0.4545);
|
||||
g = std::pow(g, 0.4545);
|
||||
b = std::pow(b, 0.4545);
|
||||
a = std::pow(a, 0.4545);
|
||||
|
||||
// 6) Scale the values such that pixels middle gray
|
||||
// pixels are mapped to 84.66 (or 3.5 f-stops below
|
||||
// the display's maximum intensity).
|
||||
//
|
||||
// 7) Clamp the values to [0, 255].
|
||||
return qRgba((unsigned char)(Imath::clamp(r * 84.66f, 0.f, 255.f)),
|
||||
(unsigned char)(Imath::clamp(g * 84.66f, 0.f, 255.f)),
|
||||
(unsigned char)(Imath::clamp(b * 84.66f, 0.f, 255.f)),
|
||||
(unsigned char)(Imath::clamp(a * 84.66f, 0.f, 255.f)));
|
||||
x = std::pow(5.5555f * std::max(0.f, x), 0.4545f) * 84.66f;
|
||||
return (unsigned char)qBound(0.f, x, 255.f);
|
||||
}
|
||||
inline QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
|
||||
{
|
||||
return qRgba(gamma(float(imagePixel.r)),
|
||||
gamma(float(imagePixel.g)),
|
||||
gamma(float(imagePixel.b)),
|
||||
(unsigned char)(qBound(0.f, imagePixel.a * 255.f, 255.f) + 0.5f));
|
||||
}
|
||||
#endif
|
||||
|
||||
EXRHandler::EXRHandler()
|
||||
{
|
||||
@ -188,29 +162,98 @@ bool EXRHandler::read(QImage *outImage)
|
||||
K_IStream istr(device(), QByteArray());
|
||||
Imf::RgbaInputFile file(istr);
|
||||
Imath::Box2i dw = file.dataWindow();
|
||||
bool isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
|
||||
|
||||
width = dw.max.x - dw.min.x + 1;
|
||||
height = dw.max.y - dw.min.y + 1;
|
||||
|
||||
QImage image = imageAlloc(width, height, QImage::Format_RGB32);
|
||||
#if defined(EXR_USE_LEGACY_CONVERSIONS)
|
||||
QImage image = imageAlloc(width, height, isRgba ? QImage::Format_ARGB32 : QImage::Format_RGB32);
|
||||
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
|
||||
QImage image = imageAlloc(width, height, isRgba ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4);
|
||||
#else
|
||||
QImage image = imageAlloc(width, height, isRgba ? QImage::Format_RGBA64 : QImage::Format_RGBX64);
|
||||
#endif
|
||||
if (image.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
Imf::Array2D<Imf::Rgba> pixels;
|
||||
pixels.resizeErase(height, width);
|
||||
|
||||
file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y * width, 1, width);
|
||||
file.readPixels(dw.min.y, dw.max.y);
|
||||
|
||||
// somehow copy pixels into image
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
// copy pixels(x,y) into image(x,y)
|
||||
image.setPixel(x, y, RgbaToQrgba(pixels[y][x]));
|
||||
// set some useful metadata
|
||||
auto &&h = file.header();
|
||||
if (auto comments = h.findTypedAttribute<Imf::StringAttribute>("comments")) {
|
||||
image.setText(QStringLiteral("Comment"), QString::fromStdString(comments->value()));
|
||||
}
|
||||
if (auto owner = h.findTypedAttribute<Imf::StringAttribute>("owner")) {
|
||||
image.setText(QStringLiteral("Owner"), QString::fromStdString(owner->value()));
|
||||
}
|
||||
if (auto capDate = h.findTypedAttribute<Imf::StringAttribute>("capDate")) {
|
||||
float off = 0;
|
||||
if (auto utcOffset = h.findTypedAttribute<Imf::FloatAttribute>("utcOffset")) {
|
||||
off = utcOffset->value();
|
||||
}
|
||||
auto dateTime = QDateTime::fromString(QString::fromStdString(capDate->value()), QStringLiteral("yyyy:MM:dd HH:mm:ss"));
|
||||
if (dateTime.isValid()) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
dateTime.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(off));
|
||||
#else
|
||||
dateTime.setOffsetFromUtc(off);
|
||||
#endif
|
||||
image.setText(QStringLiteral("Date"), dateTime.toString(Qt::ISODate));
|
||||
}
|
||||
}
|
||||
if (auto xDensity = h.findTypedAttribute<Imf::FloatAttribute>("xDensity")) {
|
||||
float par = 1;
|
||||
if (auto pixelAspectRatio = h.findTypedAttribute<Imf::FloatAttribute>("pixelAspectRatio")) {
|
||||
par = pixelAspectRatio->value();
|
||||
}
|
||||
image.setDotsPerMeterX(qRound(xDensity->value() * 100.0 / 2.54));
|
||||
image.setDotsPerMeterY(qRound(xDensity->value() * par * 100.0 / 2.54));
|
||||
}
|
||||
|
||||
Imf::Array<Imf::Rgba> pixels;
|
||||
pixels.resizeErase(width);
|
||||
|
||||
// somehow copy pixels into image
|
||||
for (int y = 0; y < height; ++y) {
|
||||
auto my = dw.min.y + y;
|
||||
if (my <= dw.max.y) { // paranoia check
|
||||
file.setFrameBuffer(&pixels[0] - dw.min.x - qint64(my) * width, 1, width);
|
||||
file.readPixels(my, my);
|
||||
|
||||
#if defined(EXR_USE_LEGACY_CONVERSIONS)
|
||||
auto scanLine = reinterpret_cast<QRgb *>(image.scanLine(y));
|
||||
for (int x = 0; x < width; ++x) {
|
||||
*(scanLine + x) = RgbaToQrgba(pixels[x]);
|
||||
}
|
||||
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
|
||||
auto scanLine = reinterpret_cast<qfloat16 *>(image.scanLine(y));
|
||||
for (int x = 0; x < width; ++x) {
|
||||
auto xcs = x * 4;
|
||||
*(scanLine + xcs) = qfloat16(qBound(0.f, float(pixels[x].r), 1.f));
|
||||
*(scanLine + xcs + 1) = qfloat16(qBound(0.f, float(pixels[x].g), 1.f));
|
||||
*(scanLine + xcs + 2) = qfloat16(qBound(0.f, float(pixels[x].b), 1.f));
|
||||
*(scanLine + xcs + 3) = qfloat16(isRgba ? qBound(0.f, float(pixels[x].a), 1.f) : 1.f);
|
||||
}
|
||||
#else
|
||||
auto scanLine = reinterpret_cast<QRgba64 *>(image.scanLine(y));
|
||||
for (int x = 0; x < width; ++x) {
|
||||
*(scanLine + x) = QRgba64::fromRgba64(quint16(qBound(0.f, float(pixels[x].r) * 65535.f + 0.5f, 65535.f)),
|
||||
quint16(qBound(0.f, float(pixels[x].g) * 65535.f + 0.5f, 65535.f)),
|
||||
quint16(qBound(0.f, float(pixels[x].b) * 65535.f + 0.5f, 65535.f)),
|
||||
isRgba ? quint16(qBound(0.f, float(pixels[x].a) * 65535.f + 0.5f, 65535.f)) : quint16(65535));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// final color operations
|
||||
#ifndef EXR_USE_LEGACY_CONVERSIONS
|
||||
image.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
||||
#ifndef EXR_ALLOW_LINEAR_COLORSPACE
|
||||
image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
#endif // !EXR_ALLOW_LINEAR_COLORSPACE
|
||||
#endif // !EXR_USE_LEGACY_CONVERSIONS
|
||||
|
||||
*outImage = image;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user