30#ifndef EXR_MAX_IMAGE_WIDTH
31#define EXR_MAX_IMAGE_WIDTH 300000
33#ifndef EXR_MAX_IMAGE_HEIGHT
34#define EXR_MAX_IMAGE_HEIGHT EXR_MAX_IMAGE_WIDTH
49#ifndef EXR_LINES_PER_BLOCK
50#define EXR_LINES_PER_BLOCK 128
54#include "scanlineconverter_p.h"
57#include <IexThrowErrnoExc.h>
60#include <ImfBoxAttribute.h>
61#include <ImfChannelListAttribute.h>
62#include <ImfCompressionAttribute.h>
63#include <ImfConvert.h>
64#include <ImfFloatAttribute.h>
65#include <ImfInputFile.h>
67#include <ImfIntAttribute.h>
68#include <ImfLineOrderAttribute.h>
69#include <ImfPreviewImage.h>
70#include <ImfRgbaFile.h>
71#include <ImfStandardAttributes.h>
72#include <ImfVersion.h>
81#include <QImageIOPlugin>
86class K_IStream :
public Imf::IStream
90 : IStream(
"K_IStream")
95 bool read(
char c[],
int n)
override;
96#if OPENEXR_VERSION_MAJOR > 2
97 uint64_t tellg()
override;
98 void seekg(uint64_t pos)
override;
100 Imf::Int64 tellg()
override;
101 void seekg(Imf::Int64 pos)
override;
103 void clear()
override;
109bool K_IStream::read(
char c[],
int n)
111 qint64 result = m_dev->
read(c, n);
114 }
else if (result == 0) {
115 throw Iex::InputExc(
"Unexpected end of file");
117 Iex::throwErrnoExc(
"Error in read", result);
122#if OPENEXR_VERSION_MAJOR > 2
123uint64_t K_IStream::tellg()
125Imf::Int64 K_IStream::tellg()
131#if OPENEXR_VERSION_MAJOR > 2
132void K_IStream::seekg(uint64_t pos)
134void K_IStream::seekg(Imf::Int64 pos)
140void K_IStream::clear()
145class K_OStream :
public Imf::OStream
149 : OStream(
"K_OStream")
154 void write(
const char c[],
int n)
override;
155#if OPENEXR_VERSION_MAJOR > 2
156 uint64_t tellp()
override;
157 void seekp(uint64_t pos)
override;
159 Imf::Int64 tellp()
override;
160 void seekp(Imf::Int64 pos)
override;
167void K_OStream::write(
const char c[],
int n)
169 qint64 result = m_dev->
write(c, n);
173 Iex::throwErrnoExc(
"Error in write", result);
178#if OPENEXR_VERSION_MAJOR > 2
179uint64_t K_OStream::tellp()
181Imf::Int64 K_OStream::tellg()
187#if OPENEXR_VERSION_MAJOR > 2
188void K_OStream::seekp(uint64_t pos)
190void K_OStream::seekg(Imf::Int64 pos)
196EXRHandler::EXRHandler()
197 : m_compressionRatio(-1)
207bool EXRHandler::canRead()
const
209 if (canRead(device())) {
218 auto isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
230 if (
auto views = h.findTypedAttribute<Imf::StringVectorAttribute>(
"multiView")) {
231 for (
auto &&v : views->value()) {
239void printAttributes(
const Imf::Header &h)
241 for (
auto i = h.begin(); i != h.end(); ++i) {
242 qDebug() << i.name();
251static void readMetadata(
const Imf::Header &header,
QImage &image)
254 if (
auto comments = header.findTypedAttribute<Imf::StringAttribute>(
"comments")) {
258 if (
auto owner = header.findTypedAttribute<Imf::StringAttribute>(
"owner")) {
262 if (
auto lat = header.findTypedAttribute<Imf::FloatAttribute>(
"latitude")) {
266 if (
auto lon = header.findTypedAttribute<Imf::FloatAttribute>(
"longitude")) {
270 if (
auto alt = header.findTypedAttribute<Imf::FloatAttribute>(
"altitude")) {
274 if (
auto capDate = header.findTypedAttribute<Imf::StringAttribute>(
"capDate")) {
276 if (
auto utcOffset = header.findTypedAttribute<Imf::FloatAttribute>(
"utcOffset")) {
277 off = utcOffset->value();
280 if (dateTime.isValid()) {
286 if (
auto xDensity = header.findTypedAttribute<Imf::FloatAttribute>(
"xDensity")) {
288 if (
auto pixelAspectRatio = header.findTypedAttribute<Imf::FloatAttribute>(
"pixelAspectRatio")) {
289 par = pixelAspectRatio->value();
296 if (
auto xmp = header.findTypedAttribute<Imf::StringAttribute>(
"xmp")) {
301 if (
auto manufacturer = header.findTypedAttribute<Imf::StringAttribute>(
"cameraMake")) {
304 if (
auto model = header.findTypedAttribute<Imf::StringAttribute>(
"cameraModel")) {
307 if (
auto serial = header.findTypedAttribute<Imf::StringAttribute>(
"cameraSerialNumber")) {
312 if (
auto manufacturer = header.findTypedAttribute<Imf::StringAttribute>(
"lensMake")) {
315 if (
auto model = header.findTypedAttribute<Imf::StringAttribute>(
"lensModel")) {
318 if (
auto serial = header.findTypedAttribute<Imf::StringAttribute>(
"lensSerialNumber")) {
327static void readColorSpace(
const Imf::Header &header,
QImage &image)
331 if (
auto chroma = header.findTypedAttribute<Imf::ChromaticitiesAttribute>(
"chromaticities")) {
332 auto &&v =
chroma->value();
337 QColorSpace::TransferFunction::Linear);
344#ifdef EXR_CONVERT_TO_SRGB
349bool EXRHandler::read(
QImage *outImage)
355 if (!d->isSequential()) {
356 if (m_startPos < 0) {
357 m_startPos = d->pos();
364 Imf::RgbaInputFile file(istr);
365 auto &&header = file.header();
368 if (m_imageNumber > -1) {
369 auto views = viewList(header);
370 if (m_imageNumber < views.count()) {
371 file.setLayerName(views.at(m_imageNumber).toStdString());
376 Imath::Box2i dw = file.dataWindow();
377 qint32 width = dw.max.x - dw.min.x + 1;
378 qint32 height = dw.max.y - dw.min.y + 1;
381 if (width > EXR_MAX_IMAGE_WIDTH || height > EXR_MAX_IMAGE_HEIGHT) {
382 qWarning() <<
"The maximum image size is limited to" << EXR_MAX_IMAGE_WIDTH <<
"x" << EXR_MAX_IMAGE_HEIGHT <<
"px";
387 QImage image = imageAlloc(width, height, imageFormat(file));
389 qWarning() <<
"Failed to allocate image, invalid size?" <<
QSize(width, height);
393 Imf::Array2D<Imf::Rgba> pixels;
394 pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
397 for (
int y = 0, n = 0; y < height; y += n) {
398 auto my = dw.min.y + y;
403 file.setFrameBuffer(&pixels[0][0] - dw.min.x - qint64(my) * width, 1, width);
404 file.readPixels(my, std::min(my + EXR_LINES_PER_BLOCK - 1, dw.max.y));
406 for (n = 0; n < std::min(EXR_LINES_PER_BLOCK, height - y); ++n) {
408 for (
int x = 0; x < width; ++x) {
410 *(scanLine + xcs) =
qfloat16(
float(pixels[n][x].r));
411 *(scanLine + xcs + 1) =
qfloat16(
float(pixels[n][x].g));
412 *(scanLine + xcs + 2) =
qfloat16(
float(pixels[n][x].b));
413 *(scanLine + xcs + 3) =
qfloat16(isRgba ? std::clamp(
float(pixels[n][x].a), 0.f, 1.f) : 1.f);
419 readMetadata(header, image);
421 readColorSpace(header, image);
426 }
catch (
const std::exception &) {
435bool makePreview(
const QImage &image, Imf::Array2D<Imf::PreviewRgba> &pixels)
437 auto w = image.
width();
452 pixels.resizeErase(h, w);
455 for (
int y = 0; y < h; ++y) {
456 auto scanLine =
reinterpret_cast<const QRgb *
>(preview.
constScanLine(y));
457 for (
int x = 0; x < w; ++x) {
458 auto &&out = pixels[y][x];
459 out.r = qRed(*(scanLine + x));
460 out.g = qGreen(*(scanLine + x));
461 out.b = qBlue(*(scanLine + x));
462 out.a = qAlpha(*(scanLine + x));
473static void setMetadata(
const QImage &image, Imf::Header &header)
476 for (
auto &&key : image.
textKeys()) {
477 auto text = image.
text(key);
479 header.insert(
"comments", Imf::StringAttribute(text.toStdString()));
483 header.insert(
"owner", Imf::StringAttribute(text.toStdString()));
494 header.insert(qPrintable(key.toLower()), Imf::FloatAttribute(value));
505#ifndef EXR_DISABLE_XMP_ATTRIBUTE
507 header.insert(
"xmp", Imf::StringAttribute(text.toStdString()));
512 header.insert(
"cameraMake", Imf::StringAttribute(text.toStdString()));
515 header.insert(
"cameraModel", Imf::StringAttribute(text.toStdString()));
518 header.insert(
"cameraSerialNumber", Imf::StringAttribute(text.toStdString()));
522 header.insert(
"lensMake", Imf::StringAttribute(text.toStdString()));
525 header.insert(
"lensModel", Imf::StringAttribute(text.toStdString()));
528 header.insert(
"lensSerialNumber", Imf::StringAttribute(text.toStdString()));
531 if (dateTime.isValid()) {
532 header.insert(
"capDate", Imf::StringAttribute(dateTime.toString(QStringLiteral(
"yyyy:MM:dd HH:mm:ss")).toStdString()));
533 header.insert(
"utcOffset", Imf::FloatAttribute(dateTime.offsetFromUtc()));
537 header.insert(
"xDensity", Imf::FloatAttribute(image.
dotsPerMeterX() * 2.54f / 100.f));
548bool EXRHandler::write(
const QImage &image)
552 qint32 width = image.
width();
553 qint32 height = image.
height();
556 if (width > EXR_MAX_IMAGE_WIDTH || height > EXR_MAX_IMAGE_HEIGHT) {
557 qWarning() <<
"The maximum image size is limited to" << EXR_MAX_IMAGE_WIDTH <<
"x" << EXR_MAX_IMAGE_HEIGHT <<
"px";
561 Imf::Header header(width, height);
563 header.compression() = Imf::Compression::PIZ_COMPRESSION;
564 if (m_compressionRatio >= qint32(Imf::Compression::NO_COMPRESSION) && m_compressionRatio < qint32(Imf::Compression::NUM_COMPRESSION_METHODS)) {
565 header.compression() = Imf::Compression(m_compressionRatio);
568 if (m_quality > -1 && m_quality <= 100) {
569 header.dwaCompressionLevel() = float(m_quality);
572 header.zipCompressionLevel() = 1;
575 if (width > 1024 || height > 1024) {
576 Imf::Array2D<Imf::PreviewRgba> previewPixels;
577 if (makePreview(image, previewPixels)) {
578 header.setPreviewImage(Imf::PreviewImage(previewPixels.width(), previewPixels.height(), &previewPixels[0][0]));
583 setMetadata(image, header);
586 K_OStream ostr(device());
587 auto channelsType = image.
hasAlphaChannel() ? Imf::RgbaChannels::WRITE_RGBA : Imf::RgbaChannels::WRITE_RGB;
592 channelsType = Imf::RgbaChannels::WRITE_Y;
594 Imf::RgbaOutputFile file(ostr, header, channelsType);
595 Imf::Array2D<Imf::Rgba> pixels;
596 pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
600 ScanLineConverter slc(convFormat);
603 for (
int y = 0, n = 0; y < height; y += n) {
604 for (n = 0; n < std::min(EXR_LINES_PER_BLOCK, height - y); ++n) {
605 auto scanLine =
reinterpret_cast<const qfloat16 *
>(slc.convertedScanLine(image, y + n));
606 if (scanLine ==
nullptr) {
609 for (
int x = 0; x < width; ++x) {
611 pixels[n][x].r = float(*(scanLine + xcs));
612 pixels[n][x].g = float(*(scanLine + xcs + 1));
613 pixels[n][x].b = float(*(scanLine + xcs + 2));
614 pixels[n][x].a = float(*(scanLine + xcs + 3));
617 file.setFrameBuffer(&pixels[0][0] - qint64(y) * width, 1, width);
620 }
catch (
const std::exception &) {
627void EXRHandler::setOption(ImageOption option,
const QVariant &value)
631 auto cr = value.
toInt(&ok);
633 m_compressionRatio = cr;
638 auto q = value.
toInt(&ok);
645bool EXRHandler::supportsOption(ImageOption option)
const
648 if (
auto d = device())
649 return !d->isSequential();
652 if (
auto d = device())
653 return !d->isSequential();
664QVariant EXRHandler::option(ImageOption option)
const
669 if (
auto d = device()) {
671 d->startTransaction();
672 if (m_startPos > -1) {
677 Imf::RgbaInputFile file(istr);
678 if (m_imageNumber > -1) {
679 auto views = viewList(file.header());
680 if (m_imageNumber < views.count()) {
681 file.setLayerName(views.at(m_imageNumber).toStdString());
684 Imath::Box2i dw = file.dataWindow();
685 v =
QVariant(
QSize(dw.max.x - dw.min.x + 1, dw.max.y - dw.min.y + 1));
686 }
catch (
const std::exception &) {
689 d->rollbackTransaction();
694 if (
auto d = device()) {
696 d->startTransaction();
697 if (m_startPos > -1) {
702 Imf::RgbaInputFile file(istr);
704 }
catch (
const std::exception &) {
707 d->rollbackTransaction();
722bool EXRHandler::jumpToNextImage()
724 return jumpToImage(m_imageNumber + 1);
727bool EXRHandler::jumpToImage(
int imageNumber)
729 if (imageNumber < 0 || imageNumber >= imageCount()) {
732 m_imageNumber = imageNumber;
736int EXRHandler::imageCount()
const
739 auto &&count = m_imageCount;
747 d->startTransaction();
751 Imf::RgbaInputFile file(istr);
752 auto views = viewList(file.header());
753 if (!views.isEmpty()) {
754 count = views.size();
756 }
catch (
const std::exception &) {
760 d->rollbackTransaction();
765int EXRHandler::currentImageNumber()
const
767 return m_imageNumber;
770bool EXRHandler::canRead(
QIODevice *device)
773 qWarning(
"EXRHandler::canRead() called with no device");
777#if OPENEXR_VERSION_MAJOR == 3 && OPENEXR_VERSION_MINOR > 2
786 return Imf::isImfMagic(head.
data());
791 if (format ==
"exr") {
802 if (device->
isReadable() && EXRHandler::canRead(device)) {
819#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
QStringList textKeys() const const
virtual int imageCount() const const
void setDevice(QIODevice *device)
bool isOpen() const const
bool isReadable() const const
virtual bool isSequential() 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
QString fromStdString(const std::string &str)
QTimeZone fromSecondsAheadOfUtc(int offset)
QVariant fromValue(T &&value)
int toInt(bool *ok) const const