39#ifndef EXR_MAX_IMAGE_WIDTH
40#define EXR_MAX_IMAGE_WIDTH 300000
42#ifndef EXR_MAX_IMAGE_HEIGHT
43#define EXR_MAX_IMAGE_HEIGHT EXR_MAX_IMAGE_WIDTH
58#ifndef EXR_LINES_PER_BLOCK
59#define EXR_LINES_PER_BLOCK 128
63#include "scanlineconverter_p.h"
66#include <IexThrowErrnoExc.h>
69#include <ImfBoxAttribute.h>
70#include <ImfChannelListAttribute.h>
71#include <ImfCompressionAttribute.h>
72#include <ImfConvert.h>
73#include <ImfFloatAttribute.h>
74#include <ImfInputFile.h>
76#include <ImfIntAttribute.h>
77#include <ImfLineOrderAttribute.h>
78#include <ImfPreviewImage.h>
79#include <ImfRgbaFile.h>
80#include <ImfStandardAttributes.h>
81#include <ImfVersion.h>
90#include <QImageIOPlugin>
97#if !defined(EXR_USE_LEGACY_CONVERSIONS)
99#define EXR_USE_QT6_FLOAT_IMAGE
102class K_IStream :
public Imf::IStream
106 : IStream(fileName.data())
111 bool read(
char c[],
int n)
override;
112#if OPENEXR_VERSION_MAJOR > 2
113 uint64_t tellg()
override;
114 void seekg(uint64_t pos)
override;
116 Imf::Int64 tellg()
override;
117 void seekg(Imf::Int64 pos)
override;
119 void clear()
override;
125bool K_IStream::read(
char c[],
int n)
127 qint64 result = m_dev->
read(c, n);
130 }
else if (result == 0) {
131 throw Iex::InputExc(
"Unexpected end of file");
133 Iex::throwErrnoExc(
"Error in read", result);
138#if OPENEXR_VERSION_MAJOR > 2
139uint64_t K_IStream::tellg()
141Imf::Int64 K_IStream::tellg()
147#if OPENEXR_VERSION_MAJOR > 2
148void K_IStream::seekg(uint64_t pos)
150void K_IStream::seekg(Imf::Int64 pos)
156void K_IStream::clear()
161class K_OStream :
public Imf::OStream
165 : OStream(fileName.data())
170 void write(
const char c[],
int n)
override;
171#if OPENEXR_VERSION_MAJOR > 2
172 uint64_t tellp()
override;
173 void seekp(uint64_t pos)
override;
175 Imf::Int64 tellp()
override;
176 void seekp(Imf::Int64 pos)
override;
183void K_OStream::write(
const char c[],
int n)
185 qint64 result = m_dev->
write(c, n);
189 Iex::throwErrnoExc(
"Error in write", result);
194#if OPENEXR_VERSION_MAJOR > 2
195uint64_t K_OStream::tellp()
197Imf::Int64 K_OStream::tellg()
203#if OPENEXR_VERSION_MAJOR > 2
204void K_OStream::seekp(uint64_t pos)
206void K_OStream::seekg(Imf::Int64 pos)
212#ifdef EXR_USE_LEGACY_CONVERSIONS
214inline unsigned char gamma(
float x)
216 x = std::pow(5.5555f * std::max(0.f, x), 0.4545f) * 84.66f;
217 return (
unsigned char)qBound(0.f, x, 255.f);
219inline QRgb RgbaToQrgba(
struct Imf::Rgba &imagePixel)
221 return qRgba(gamma(
float(imagePixel.r)),
222 gamma(
float(imagePixel.g)),
223 gamma(
float(imagePixel.b)),
224 (
unsigned char)(qBound(0.f, imagePixel.a * 255.f, 255.f) + 0.5f));
228EXRHandler::EXRHandler()
229 : m_compressionRatio(-1)
239bool EXRHandler::canRead()
const
241 if (canRead(device())) {
250 auto isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
251#if defined(EXR_USE_LEGACY_CONVERSIONS)
253#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
268 if (
auto views = h.findTypedAttribute<Imf::StringVectorAttribute>(
"multiView")) {
269 for (
auto &&v : views->value()) {
277void printAttributes(
const Imf::Header &h)
279 for (
auto i = h.begin(); i != h.end(); ++i) {
280 qDebug() << i.name();
289static void readMetadata(
const Imf::Header &header,
QImage &image)
292 if (
auto comments = header.findTypedAttribute<Imf::StringAttribute>(
"comments")) {
296 if (
auto owner = header.findTypedAttribute<Imf::StringAttribute>(
"owner")) {
300 if (
auto lat = header.findTypedAttribute<Imf::FloatAttribute>(
"latitude")) {
304 if (
auto lon = header.findTypedAttribute<Imf::FloatAttribute>(
"longitude")) {
308 if (
auto alt = header.findTypedAttribute<Imf::FloatAttribute>(
"altitude")) {
312 if (
auto capDate = header.findTypedAttribute<Imf::StringAttribute>(
"capDate")) {
314 if (
auto utcOffset = header.findTypedAttribute<Imf::FloatAttribute>(
"utcOffset")) {
315 off = utcOffset->value();
318 if (dateTime.isValid()) {
324 if (
auto xDensity = header.findTypedAttribute<Imf::FloatAttribute>(
"xDensity")) {
326 if (
auto pixelAspectRatio = header.findTypedAttribute<Imf::FloatAttribute>(
"pixelAspectRatio")) {
327 par = pixelAspectRatio->value();
334 if (
auto xmp = header.findTypedAttribute<Imf::StringAttribute>(
"xmp")) {
361static void readColorSpace(
const Imf::Header &header,
QImage &image)
364#ifndef EXR_USE_LEGACY_CONVERSIONS
367 if (
auto chroma = header.findTypedAttribute<Imf::ChromaticitiesAttribute>(
"chromaticities")) {
368 auto &&v =
chroma->value();
373 QColorSpace::TransferFunction::Linear);
380#ifdef EXR_CONVERT_TO_SRGB
387bool EXRHandler::read(
QImage *outImage)
393 if (!d->isSequential()) {
394 if (m_startPos < 0) {
395 m_startPos = d->pos();
402 Imf::RgbaInputFile file(istr);
403 auto &&header = file.header();
406 if (m_imageNumber > -1) {
407 auto views = viewList(header);
408 if (m_imageNumber < views.count()) {
409 file.setLayerName(views.at(m_imageNumber).toStdString());
414 Imath::Box2i dw = file.dataWindow();
415 qint32 width = dw.max.x - dw.min.x + 1;
416 qint32 height = dw.max.y - dw.min.y + 1;
419 if (width > EXR_MAX_IMAGE_WIDTH || height > EXR_MAX_IMAGE_HEIGHT) {
420 qWarning() <<
"The maximum image size is limited to" << EXR_MAX_IMAGE_WIDTH <<
"x" << EXR_MAX_IMAGE_HEIGHT <<
"px";
425 QImage image = imageAlloc(width, height, imageFormat(file));
427 qWarning() <<
"Failed to allocate image, invalid size?" <<
QSize(width, height);
431 Imf::Array2D<Imf::Rgba> pixels;
432 pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
436 for (
int y = 0, n = 0; y < height; y += n) {
437 auto my = dw.min.y + y;
442 file.setFrameBuffer(&pixels[0][0] - dw.min.x - qint64(my) * width, 1, width);
443 file.readPixels(my, std::min(my + EXR_LINES_PER_BLOCK - 1, dw.max.y));
445 for (n = 0; n < std::min(EXR_LINES_PER_BLOCK, height - y); ++n) {
446#if defined(EXR_USE_LEGACY_CONVERSIONS)
448 auto scanLine =
reinterpret_cast<QRgb *
>(image.
scanLine(y + n));
449 for (
int x = 0; x < width; ++x) {
450 *(scanLine + x) = RgbaToQrgba(pixels[n][x]);
452#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
454 for (
int x = 0; x < width; ++x) {
456 *(scanLine + xcs) =
qfloat16(qBound(0.f,
float(pixels[n][x].r), 1.f));
457 *(scanLine + xcs + 1) =
qfloat16(qBound(0.f,
float(pixels[n][x].g), 1.f));
458 *(scanLine + xcs + 2) =
qfloat16(qBound(0.f,
float(pixels[n][x].b), 1.f));
459 *(scanLine + xcs + 3) =
qfloat16(isRgba ? qBound(0.f,
float(pixels[n][x].a), 1.f) : 1.f);
463 for (
int x = 0; x < width; ++x) {
464 *(scanLine + x) =
QRgba64::fromRgba64(quint16(qBound(0.f,
float(pixels[n][x].r) * 65535.f + 0.5f, 65535.f)),
465 quint16(qBound(0.f,
float(pixels[n][x].g) * 65535.f + 0.5f, 65535.f)),
466 quint16(qBound(0.f,
float(pixels[n][x].b) * 65535.f + 0.5f, 65535.f)),
467 isRgba ? quint16(qBound(0.f,
float(pixels[n][x].a) * 65535.f + 0.5f, 65535.f)) : quint16(65535));
474 readMetadata(header, image);
476 readColorSpace(header, image);
481 }
catch (
const std::exception &) {
490bool makePreview(
const QImage &image, Imf::Array2D<Imf::PreviewRgba> &pixels)
492 auto w = image.
width();
507 pixels.resizeErase(h, w);
510 for (
int y = 0; y < h; ++y) {
511 auto scanLine =
reinterpret_cast<const QRgb *
>(preview.
constScanLine(y));
512 for (
int x = 0; x < w; ++x) {
513 auto &&out = pixels[y][x];
514 out.r = qRed(*(scanLine + x));
515 out.g = qGreen(*(scanLine + x));
516 out.b = qBlue(*(scanLine + x));
517 out.a = qAlpha(*(scanLine + x));
528static void setMetadata(
const QImage &image, Imf::Header &header)
531 for (
auto &&key : image.textKeys()) {
532 auto text = image.
text(key);
534 header.
insert(
"comments", Imf::StringAttribute(text.toStdString()));
538 header.
insert(
"owner", Imf::StringAttribute(text.toStdString()));
549 header.insert(qPrintable(key.toLower()), Imf::FloatAttribute(value));
560#ifndef EXR_DISABLE_XMP_ATTRIBUTE
562 header.insert(
"xmp", Imf::StringAttribute(text.toStdString()));
566 if (dateTime.isValid()) {
567 header.insert(
"capDate", Imf::StringAttribute(dateTime.toString(QStringLiteral(
"yyyy:MM:dd HH:mm:ss")).toStdString()));
568 header.insert(
"utcOffset", Imf::FloatAttribute(dateTime.offsetFromUtc()));
572 header.insert(
"xDensity", Imf::FloatAttribute(image.
dotsPerMeterX() * 2.54f / 100.f));
585bool EXRHandler::write(
const QImage &image)
589 qint32 width = image.
width();
590 qint32 height = image.
height();
593 if (width > EXR_MAX_IMAGE_WIDTH || height > EXR_MAX_IMAGE_HEIGHT) {
594 qWarning() <<
"The maximum image size is limited to" << EXR_MAX_IMAGE_WIDTH <<
"x" << EXR_MAX_IMAGE_HEIGHT <<
"px";
598 Imf::Header header(width, height);
600 header.compression() = Imf::Compression::PIZ_COMPRESSION;
601 if (m_compressionRatio >= qint32(Imf::Compression::NO_COMPRESSION) && m_compressionRatio < qint32(Imf::Compression::NUM_COMPRESSION_METHODS)) {
602 header.compression() = Imf::Compression(m_compressionRatio);
605 if (m_quality > -1 && m_quality <= 100) {
606 header.dwaCompressionLevel() = float(m_quality);
609 header.zipCompressionLevel() = 1;
612 if (width > 1024 || height > 1024) {
613 Imf::Array2D<Imf::PreviewRgba> previewPixels;
614 if (makePreview(image, previewPixels)) {
615 header.setPreviewImage(Imf::PreviewImage(previewPixels.width(), previewPixels.height(), &previewPixels[0][0]));
620 setMetadata(image, header);
624 auto channelsType = image.
hasAlphaChannel() ? Imf::RgbaChannels::WRITE_RGBA : Imf::RgbaChannels::WRITE_RGB;
629 channelsType = Imf::RgbaChannels::WRITE_Y;
631 Imf::RgbaOutputFile file(ostr, header, channelsType);
632 Imf::Array2D<Imf::Rgba> pixels;
633 pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
636#if defined(EXR_USE_QT6_FLOAT_IMAGE)
641 ScanLineConverter slc(convFormat);
644 for (
int y = 0, n = 0; y < height; y += n) {
645 for (n = 0; n < std::min(EXR_LINES_PER_BLOCK, height - y); ++n) {
646#if defined(EXR_USE_QT6_FLOAT_IMAGE)
647 auto scanLine =
reinterpret_cast<const qfloat16 *
>(slc.convertedScanLine(image, y + n));
648 if (scanLine ==
nullptr) {
651 for (
int x = 0; x < width; ++x) {
653 pixels[n][x].r = float(*(scanLine + xcs));
654 pixels[n][x].g = float(*(scanLine + xcs + 1));
655 pixels[n][x].b = float(*(scanLine + xcs + 2));
656 pixels[n][x].a = float(*(scanLine + xcs + 3));
659 auto scanLine =
reinterpret_cast<const QRgba64 *
>(slc.convertedScanLine(image, y + n));
660 if (scanLine ==
nullptr) {
663 for (
int x = 0; x < width; ++x) {
664 pixels[n][x].r = float((scanLine + x)->
red() / 65535.f);
665 pixels[n][x].g = float((scanLine + x)->
green() / 65535.f);
666 pixels[n][x].b = float((scanLine + x)->
blue() / 65535.f);
667 pixels[n][x].a = float((scanLine + x)->alpha() / 65535.f);
671 file.setFrameBuffer(&pixels[0][0] - qint64(y) * width, 1, width);
674 }
catch (
const std::exception &) {
681void EXRHandler::setOption(ImageOption option,
const QVariant &value)
685 auto cr = value.
toInt(&ok);
687 m_compressionRatio = cr;
692 auto q = value.
toInt(&ok);
699bool EXRHandler::supportsOption(ImageOption option)
const
716QVariant EXRHandler::option(ImageOption option)
const
721 if (
auto d = device()) {
723 d->startTransaction();
726 Imf::RgbaInputFile file(istr);
727 if (m_imageNumber > -1) {
728 auto views = viewList(file.header());
729 if (m_imageNumber < views.count()) {
730 file.setLayerName(views.at(m_imageNumber).toStdString());
733 Imath::Box2i dw = file.dataWindow();
734 v =
QVariant(
QSize(dw.max.x - dw.min.x + 1, dw.max.y - dw.min.y + 1));
735 }
catch (
const std::exception &) {
738 d->rollbackTransaction();
743 if (
auto d = device()) {
745 d->startTransaction();
748 Imf::RgbaInputFile file(istr);
750 }
catch (
const std::exception &) {
753 d->rollbackTransaction();
768bool EXRHandler::jumpToNextImage()
770 return jumpToImage(m_imageNumber + 1);
773bool EXRHandler::jumpToImage(
int imageNumber)
775 if (imageNumber < 0 || imageNumber >= imageCount()) {
778 m_imageNumber = imageNumber;
782int EXRHandler::imageCount()
const
785 auto &&count = m_imageCount;
793 d->startTransaction();
797 Imf::RgbaInputFile file(istr);
798 auto views = viewList(file.header());
799 if (!views.isEmpty()) {
800 count = views.size();
802 }
catch (
const std::exception &) {
806 d->rollbackTransaction();
811int EXRHandler::currentImageNumber()
const
813 return m_imageNumber;
816bool EXRHandler::canRead(
QIODevice *device)
819 qWarning(
"EXRHandler::canRead() called with no device");
825 return Imf::isImfMagic(head.
data());
830 if (format ==
"exr") {
841 if (device->
isReadable() && EXRHandler::canRead(device)) {
858#include "moc_exr_p.cpp"
char * toString(const EngineQuery &query)
KGUIADDONS_EXPORT qreal chroma(const QColor &)
QFlags< Capability > Capabilities
KEDUVOCDOCUMENT_EXPORT QStringList comments(const QString &language=QString())
bool isEmpty() const const
bool isValid() const const
QDateTime currentDateTime()
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
const uchar * constScanLine(int i) const const
void convertToColorSpace(const QColorSpace &colorSpace)
int dotsPerMeterX() const const
int dotsPerMeterY() const const
bool hasAlphaChannel() const const
bool isNull() const const
QImage scaledToHeight(int height, Qt::TransformationMode mode) const const
QImage scaledToWidth(int width, Qt::TransformationMode mode) const const
void setColorSpace(const QColorSpace &colorSpace)
void setDotsPerMeterX(int x)
void setDotsPerMeterY(int y)
void setText(const QString &key, const QString &text)
QString text(const QString &key) const const
virtual int imageCount() const const
void setDevice(QIODevice *device)
bool isOpen() const const
bool isReadable() const const
bool isWritable() const const
QByteArray peek(qint64 maxSize)
QByteArray read(qint64 maxSize)
qint64 write(const QByteArray &data)
T value(qsizetype i) const const
float toFloat(QStringView s, bool *ok) const const
QRgba64 fromRgba64(quint16 r, quint16 g, quint16 b, quint16 a)
QString fromStdString(const std::string &str)
QString & insert(qsizetype position, QChar ch)
QTimeZone fromSecondsAheadOfUtc(int offset)
QVariant fromValue(T &&value)
int toInt(bool *ok) const const