KImageFormats

exr.cpp
1/*
2 The high dynamic range EXR format support for QImage.
3
4 SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net>
5 SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10/* *** EXR_CONVERT_TO_SRGB ***
11 * If defined, the linear data is converted to sRGB on read to accommodate
12 * programs that do not support color profiles.
13 * Otherwise the data are kept as is and it is the display program that
14 * must convert to the monitor profile.
15 */
16//#define EXR_CONVERT_TO_SRGB // default: commented -> you should define it in your cmake file
17
18/* *** EXR_STORE_XMP_ATTRIBUTE ***
19 * If defined, disables the stores XMP values in a non-standard attribute named "xmp".
20 * The QImage metadata used is "XML:com.adobe.xmp".
21 * NOTE: The use of non-standard attributes is possible but discouraged by the specification. However,
22 * metadata is essential for good image management and programs like darktable also set this
23 * attribute. Gimp reads the "xmp" attribute and Darktable writes it as well.
24 */
25//#define EXR_DISABLE_XMP_ATTRIBUTE // default: commented -> you should define it in your cmake file
26
27/* *** EXR_MAX_IMAGE_WIDTH and EXR_MAX_IMAGE_HEIGHT ***
28 * The maximum size in pixel allowed by the plugin.
29 */
30#ifndef EXR_MAX_IMAGE_WIDTH
31#define EXR_MAX_IMAGE_WIDTH 300000
32#endif
33#ifndef EXR_MAX_IMAGE_HEIGHT
34#define EXR_MAX_IMAGE_HEIGHT EXR_MAX_IMAGE_WIDTH
35#endif
36
37/* *** EXR_LINES_PER_BLOCK ***
38 * Allows certain compression schemes to work in multithreading
39 * Requires up to "LINES_PER_BLOCK * MAX_IMAGE_WIDTH * 8"
40 * additional RAM (e.g. if 128, up to 307MiB of RAM).
41 * There is a performance gain with the following parameters (based on empirical tests):
42 * - PIZ compression needs 64+ lines
43 * - ZIPS compression needs 8+ lines
44 * - ZIP compression needs 32+ lines
45 * - Others not tested
46 *
47 * NOTE: The OpenEXR documentation states that the higher the better :)
48 */
49#ifndef EXR_LINES_PER_BLOCK
50#define EXR_LINES_PER_BLOCK 128
51#endif
52
53#include "exr_p.h"
54#include "scanlineconverter_p.h"
55#include "util_p.h"
56
57#include <IexThrowErrnoExc.h>
58#include <ImathBox.h>
59#include <ImfArray.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>
66#include <ImfInt64.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>
73
74#include <iostream>
75
76#include <QColorSpace>
77#include <QDataStream>
78#include <QDebug>
79#include <QFloat16>
80#include <QImage>
81#include <QImageIOPlugin>
82#include <QLocale>
83#include <QThread>
84#include <QTimeZone>
85
86class K_IStream : public Imf::IStream
87{
88public:
89 K_IStream(QIODevice *dev)
90 : IStream("K_IStream")
91 , m_dev(dev)
92 {
93 }
94
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;
99#else
100 Imf::Int64 tellg() override;
101 void seekg(Imf::Int64 pos) override;
102#endif
103 void clear() override;
104
105private:
106 QIODevice *m_dev;
107};
108
109bool K_IStream::read(char c[], int n)
110{
111 qint64 result = m_dev->read(c, n);
112 if (result > 0) {
113 return true;
114 } else if (result == 0) {
115 throw Iex::InputExc("Unexpected end of file");
116 } else { // negative value {
117 Iex::throwErrnoExc("Error in read", result);
118 }
119 return false;
120}
121
122#if OPENEXR_VERSION_MAJOR > 2
123uint64_t K_IStream::tellg()
124#else
125Imf::Int64 K_IStream::tellg()
126#endif
127{
128 return m_dev->pos();
129}
130
131#if OPENEXR_VERSION_MAJOR > 2
132void K_IStream::seekg(uint64_t pos)
133#else
134void K_IStream::seekg(Imf::Int64 pos)
135#endif
136{
137 m_dev->seek(pos);
138}
139
140void K_IStream::clear()
141{
142 // TODO
143}
144
145class K_OStream : public Imf::OStream
146{
147public:
148 K_OStream(QIODevice *dev)
149 : OStream("K_OStream")
150 , m_dev(dev)
151 {
152 }
153
154 void write(const char c[/*n*/], int n) override;
155#if OPENEXR_VERSION_MAJOR > 2
156 uint64_t tellp() override;
157 void seekp(uint64_t pos) override;
158#else
159 Imf::Int64 tellp() override;
160 void seekp(Imf::Int64 pos) override;
161#endif
162
163private:
164 QIODevice *m_dev;
165};
166
167void K_OStream::write(const char c[], int n)
168{
169 qint64 result = m_dev->write(c, n);
170 if (result > 0) {
171 return;
172 } else { // negative value {
173 Iex::throwErrnoExc("Error in write", result);
174 }
175 return;
176}
177
178#if OPENEXR_VERSION_MAJOR > 2
179uint64_t K_OStream::tellp()
180#else
181Imf::Int64 K_OStream::tellg()
182#endif
183{
184 return m_dev->pos();
185}
186
187#if OPENEXR_VERSION_MAJOR > 2
188void K_OStream::seekp(uint64_t pos)
189#else
190void K_OStream::seekg(Imf::Int64 pos)
191#endif
192{
193 m_dev->seek(pos);
194}
195
196EXRHandler::EXRHandler()
197 : m_compressionRatio(-1)
198 , m_quality(-1)
199 , m_imageNumber(0)
200 , m_imageCount(0)
201 , m_startPos(-1)
202{
203 // Set the number of threads to use (0 is allowed)
204 Imf::setGlobalThreadCount(QThread::idealThreadCount() / 2);
205}
206
207bool EXRHandler::canRead() const
208{
209 if (canRead(device())) {
210 setFormat("exr");
211 return true;
212 }
213 return false;
214}
215
216static QImage::Format imageFormat(const Imf::RgbaInputFile &file)
217{
218 auto isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
220}
221
222/*!
223 * \brief viewList
224 * \param header
225 * \return The list of available views.
226 */
227static QStringList viewList(const Imf::Header &h)
228{
229 QStringList l;
230 if (auto views = h.findTypedAttribute<Imf::StringVectorAttribute>("multiView")) {
231 for (auto &&v : views->value()) {
233 }
234 }
235 return l;
236}
237
238#ifdef QT_DEBUG
239void printAttributes(const Imf::Header &h)
240{
241 for (auto i = h.begin(); i != h.end(); ++i) {
242 qDebug() << i.name();
243 }
244}
245#endif
246
247/*!
248 * \brief readMetadata
249 * Reads EXR attributes from the \a header and set its as metadata in the \a image.
250 */
251static void readMetadata(const Imf::Header &header, QImage &image)
252{
253 // set some useful metadata
254 if (auto comments = header.findTypedAttribute<Imf::StringAttribute>("comments")) {
255 image.setText(QStringLiteral(META_KEY_COMMENT), QString::fromStdString(comments->value()));
256 }
257
258 if (auto owner = header.findTypedAttribute<Imf::StringAttribute>("owner")) {
259 image.setText(QStringLiteral(META_KEY_OWNER), QString::fromStdString(owner->value()));
260 }
261
262 if (auto lat = header.findTypedAttribute<Imf::FloatAttribute>("latitude")) {
263 image.setText(QStringLiteral(META_KEY_LATITUDE), QLocale::c().toString(lat->value()));
264 }
265
266 if (auto lon = header.findTypedAttribute<Imf::FloatAttribute>("longitude")) {
267 image.setText(QStringLiteral(META_KEY_LONGITUDE), QLocale::c().toString(lon->value()));
268 }
269
270 if (auto alt = header.findTypedAttribute<Imf::FloatAttribute>("altitude")) {
271 image.setText(QStringLiteral(META_KEY_ALTITUDE), QLocale::c().toString(alt->value()));
272 }
273
274 if (auto capDate = header.findTypedAttribute<Imf::StringAttribute>("capDate")) {
275 float off = 0;
276 if (auto utcOffset = header.findTypedAttribute<Imf::FloatAttribute>("utcOffset")) {
277 off = utcOffset->value();
278 }
279 auto dateTime = QDateTime::fromString(QString::fromStdString(capDate->value()), QStringLiteral("yyyy:MM:dd HH:mm:ss"));
280 if (dateTime.isValid()) {
281 dateTime.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(off));
282 image.setText(QStringLiteral(META_KEY_CREATIONDATE), dateTime.toString(Qt::ISODate));
283 }
284 }
285
286 if (auto xDensity = header.findTypedAttribute<Imf::FloatAttribute>("xDensity")) {
287 float par = 1;
288 if (auto pixelAspectRatio = header.findTypedAttribute<Imf::FloatAttribute>("pixelAspectRatio")) {
289 par = pixelAspectRatio->value();
290 }
291 image.setDotsPerMeterX(qRound(xDensity->value() * 100.0 / 2.54));
292 image.setDotsPerMeterY(qRound(xDensity->value() * par * 100.0 / 2.54));
293 }
294
295 // Non-standard attribute
296 if (auto xmp = header.findTypedAttribute<Imf::StringAttribute>("xmp")) {
297 image.setText(QStringLiteral(META_KEY_XMP_ADOBE), QString::fromStdString(xmp->value()));
298 }
299
300 // camera metadata
301 if (auto manufacturer = header.findTypedAttribute<Imf::StringAttribute>("cameraMake")) {
302 image.setText(QStringLiteral(META_KEY_MANUFACTURER), QString::fromStdString(manufacturer->value()));
303 }
304 if (auto model = header.findTypedAttribute<Imf::StringAttribute>("cameraModel")) {
305 image.setText(QStringLiteral(META_KEY_MODEL), QString::fromStdString(model->value()));
306 }
307 if (auto serial = header.findTypedAttribute<Imf::StringAttribute>("cameraSerialNumber")) {
308 image.setText(QStringLiteral(META_KEY_SERIALNUMBER), QString::fromStdString(serial->value()));
309 }
310
311 // lens metadata
312 if (auto manufacturer = header.findTypedAttribute<Imf::StringAttribute>("lensMake")) {
313 image.setText(QStringLiteral(META_KEY_LENS_MANUFACTURER), QString::fromStdString(manufacturer->value()));
314 }
315 if (auto model = header.findTypedAttribute<Imf::StringAttribute>("lensModel")) {
316 image.setText(QStringLiteral(META_KEY_LENS_MODEL), QString::fromStdString(model->value()));
317 }
318 if (auto serial = header.findTypedAttribute<Imf::StringAttribute>("lensSerialNumber")) {
319 image.setText(QStringLiteral(META_KEY_LENS_SERIALNUMBER), QString::fromStdString(serial->value()));
320 }
321}
322
323/*!
324 * \brief readColorSpace
325 * Reads EXR chromaticities from the \a header and set its as color profile in the \a image.
326 */
327static void readColorSpace(const Imf::Header &header, QImage &image)
328{
329 // final color operations
330 QColorSpace cs;
331 if (auto chroma = header.findTypedAttribute<Imf::ChromaticitiesAttribute>("chromaticities")) {
332 auto &&v = chroma->value();
333 cs = QColorSpace(QPointF(v.white.x, v.white.y),
334 QPointF(v.red.x, v.red.y),
335 QPointF(v.green.x, v.green.y),
336 QPointF(v.blue.x, v.blue.y),
337 QColorSpace::TransferFunction::Linear);
338 }
339 if (!cs.isValid()) {
341 }
342 image.setColorSpace(cs);
343
344#ifdef EXR_CONVERT_TO_SRGB
346#endif
347}
348
349bool EXRHandler::read(QImage *outImage)
350{
351 try {
352 auto d = device();
353
354 // set the image position after the first run.
355 if (!d->isSequential()) {
356 if (m_startPos < 0) {
357 m_startPos = d->pos();
358 } else {
359 d->seek(m_startPos);
360 }
361 }
362
363 K_IStream istr(d);
364 Imf::RgbaInputFile file(istr);
365 auto &&header = file.header();
366
367 // set the image to load
368 if (m_imageNumber > -1) {
369 auto views = viewList(header);
370 if (m_imageNumber < views.count()) {
371 file.setLayerName(views.at(m_imageNumber).toStdString());
372 }
373 }
374
375 // get image info
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;
379
380 // limiting the maximum image size on a reasonable size (as done in other plugins)
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";
383 return false;
384 }
385
386 // creating the image
387 QImage image = imageAlloc(width, height, imageFormat(file));
388 if (image.isNull()) {
389 qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
390 return false;
391 }
392
393 Imf::Array2D<Imf::Rgba> pixels;
394 pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
395 bool isRgba = image.hasAlphaChannel();
396
397 for (int y = 0, n = 0; y < height; y += n) {
398 auto my = dw.min.y + y;
399 if (my > dw.max.y) { // paranoia check
400 break;
401 }
402
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));
405
406 for (n = 0; n < std::min(EXR_LINES_PER_BLOCK, height - y); ++n) {
407 auto scanLine = reinterpret_cast<qfloat16 *>(image.scanLine(y + n));
408 for (int x = 0; x < width; ++x) {
409 auto xcs = x * 4;
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);
414 }
415 }
416 }
417
418 // set some useful metadata
419 readMetadata(header, image);
420 // final color operations
421 readColorSpace(header, image);
422
423 *outImage = image;
424
425 return true;
426 } catch (const std::exception &) {
427 return false;
428 }
429}
430
431/*!
432 * \brief makePreview
433 * Creates a preview of maximum 256 x 256 pixels from the \a image.
434 */
435bool makePreview(const QImage &image, Imf::Array2D<Imf::PreviewRgba> &pixels)
436{
437 auto w = image.width();
438 auto h = image.height();
439
440 QImage preview;
441 if (w > h) {
443 } else {
445 }
446 if (preview.isNull()) {
447 return false;
448 }
449
450 w = preview.width();
451 h = preview.height();
452 pixels.resizeErase(h, w);
454
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));
463 }
464 }
465
466 return true;
467}
468
469/*!
470 * \brief setMetadata
471 * Reades the metadata from \a image and set its as attributes in the \a header.
472 */
473static void setMetadata(const QImage &image, Imf::Header &header)
474{
475 auto dateTime = QDateTime::currentDateTime();
476 for (auto &&key : image.textKeys()) {
477 auto text = image.text(key);
478 if (!key.compare(QStringLiteral(META_KEY_COMMENT), Qt::CaseInsensitive)) {
479 header.insert("comments", Imf::StringAttribute(text.toStdString()));
480 }
481
482 if (!key.compare(QStringLiteral(META_KEY_OWNER), Qt::CaseInsensitive)) {
483 header.insert("owner", Imf::StringAttribute(text.toStdString()));
484 }
485
486 // clang-format off
487 if (!key.compare(QStringLiteral(META_KEY_LATITUDE), Qt::CaseInsensitive) ||
488 !key.compare(QStringLiteral(META_KEY_LONGITUDE), Qt::CaseInsensitive) ||
489 !key.compare(QStringLiteral(META_KEY_ALTITUDE), Qt::CaseInsensitive)) {
490 // clang-format on
491 auto ok = false;
492 auto value = QLocale::c().toFloat(text, &ok);
493 if (ok) {
494 header.insert(qPrintable(key.toLower()), Imf::FloatAttribute(value));
495 }
496 }
497
498 if (!key.compare(QStringLiteral(META_KEY_CREATIONDATE), Qt::CaseInsensitive)) {
499 auto dt = QDateTime::fromString(text, Qt::ISODate);
500 if (dt.isValid()) {
501 dateTime = dt;
502 }
503 }
504
505#ifndef EXR_DISABLE_XMP_ATTRIBUTE // warning: Non-standard attribute!
506 if (!key.compare(QStringLiteral(META_KEY_XMP_ADOBE), Qt::CaseInsensitive)) {
507 header.insert("xmp", Imf::StringAttribute(text.toStdString()));
508 }
509#endif
510
511 if (!key.compare(QStringLiteral(META_KEY_MANUFACTURER), Qt::CaseInsensitive)) {
512 header.insert("cameraMake", Imf::StringAttribute(text.toStdString()));
513 }
514 if (!key.compare(QStringLiteral(META_KEY_MODEL), Qt::CaseInsensitive)) {
515 header.insert("cameraModel", Imf::StringAttribute(text.toStdString()));
516 }
517 if (!key.compare(QStringLiteral(META_KEY_SERIALNUMBER), Qt::CaseInsensitive)) {
518 header.insert("cameraSerialNumber", Imf::StringAttribute(text.toStdString()));
519 }
520
521 if (!key.compare(QStringLiteral(META_KEY_LENS_MANUFACTURER), Qt::CaseInsensitive)) {
522 header.insert("lensMake", Imf::StringAttribute(text.toStdString()));
523 }
524 if (!key.compare(QStringLiteral(META_KEY_LENS_MODEL), Qt::CaseInsensitive)) {
525 header.insert("lensModel", Imf::StringAttribute(text.toStdString()));
526 }
527 if (!key.compare(QStringLiteral(META_KEY_LENS_SERIALNUMBER), Qt::CaseInsensitive)) {
528 header.insert("lensSerialNumber", Imf::StringAttribute(text.toStdString()));
529 }
530 }
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()));
534 }
535
536 if (image.dotsPerMeterX() && image.dotsPerMeterY()) {
537 header.insert("xDensity", Imf::FloatAttribute(image.dotsPerMeterX() * 2.54f / 100.f));
538 header.insert("pixelAspectRatio", Imf::FloatAttribute(float(image.dotsPerMeterX()) / float(image.dotsPerMeterY())));
539 }
540
541 // set default chroma (default constructor ITU-R BT.709-3 -> sRGB)
542 // The image is converted to Linear sRGB so, the chroma is the default EXR value.
543 // If a file doesn’t have a chromaticities attribute, display software should assume that the
544 // file’s primaries and the white point match Rec. ITU-R BT.709-3.
545 // header.insert("chromaticities", Imf::ChromaticitiesAttribute(Imf::Chromaticities()));
546}
547
548bool EXRHandler::write(const QImage &image)
549{
550 try {
551 // create EXR header
552 qint32 width = image.width();
553 qint32 height = image.height();
554
555 // limiting the maximum image size on a reasonable size (as done in other plugins)
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";
558 return false;
559 }
560
561 Imf::Header header(width, height);
562 // set compression scheme (forcing PIZ as default)
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);
566 }
567 // set the DCT quality (used by DCT compressions only)
568 if (m_quality > -1 && m_quality <= 100) {
569 header.dwaCompressionLevel() = float(m_quality);
570 }
571 // make ZIP compression fast (used by ZIP compressions)
572 header.zipCompressionLevel() = 1;
573
574 // set preview (don't set it for small images)
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]));
579 }
580 }
581
582 // set metadata (EXR attributes)
583 setMetadata(image, header);
584
585 // write the EXR
586 K_OStream ostr(device());
587 auto channelsType = image.hasAlphaChannel() ? Imf::RgbaChannels::WRITE_RGBA : Imf::RgbaChannels::WRITE_RGB;
588 if (image.format() == QImage::Format_Mono ||
589 image.format() == QImage::Format_MonoLSB ||
592 channelsType = Imf::RgbaChannels::WRITE_Y;
593 }
594 Imf::RgbaOutputFile file(ostr, header, channelsType);
595 Imf::Array2D<Imf::Rgba> pixels;
596 pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
597
598 // convert the image and write into the stream
600 ScanLineConverter slc(convFormat);
601 slc.setDefaultSourceColorSpace(QColorSpace(QColorSpace::SRgb));
602 slc.setTargetColorSpace(QColorSpace(QColorSpace::SRgbLinear));
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) {
607 return false;
608 }
609 for (int x = 0; x < width; ++x) {
610 auto xcs = x * 4;
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));
615 }
616 }
617 file.setFrameBuffer(&pixels[0][0] - qint64(y) * width, 1, width);
618 file.writePixels(n);
619 }
620 } catch (const std::exception &) {
621 return false;
622 }
623
624 return true;
625}
626
627void EXRHandler::setOption(ImageOption option, const QVariant &value)
628{
629 if (option == QImageIOHandler::CompressionRatio) {
630 auto ok = false;
631 auto cr = value.toInt(&ok);
632 if (ok) {
633 m_compressionRatio = cr;
634 }
635 }
636 if (option == QImageIOHandler::Quality) {
637 auto ok = false;
638 auto q = value.toInt(&ok);
639 if (ok) {
640 m_quality = q;
641 }
642 }
643}
644
645bool EXRHandler::supportsOption(ImageOption option) const
646{
647 if (option == QImageIOHandler::Size) {
648 if (auto d = device())
649 return !d->isSequential();
650 }
651 if (option == QImageIOHandler::ImageFormat) {
652 if (auto d = device())
653 return !d->isSequential();
654 }
655 if (option == QImageIOHandler::CompressionRatio) {
656 return true;
657 }
658 if (option == QImageIOHandler::Quality) {
659 return true;
660 }
661 return false;
662}
663
664QVariant EXRHandler::option(ImageOption option) const
665{
666 QVariant v;
667
668 if (option == QImageIOHandler::Size) {
669 if (auto d = device()) {
670 // transactions works on both random and sequential devices
671 d->startTransaction();
672 if (m_startPos > -1) {
673 d->seek(m_startPos);
674 }
675 try {
676 K_IStream istr(d);
677 Imf::RgbaInputFile file(istr);
678 if (m_imageNumber > -1) { // set the image to read
679 auto views = viewList(file.header());
680 if (m_imageNumber < views.count()) {
681 file.setLayerName(views.at(m_imageNumber).toStdString());
682 }
683 }
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 &) {
687 // broken file or unsupported version
688 }
689 d->rollbackTransaction();
690 }
691 }
692
693 if (option == QImageIOHandler::ImageFormat) {
694 if (auto d = device()) {
695 // transactions works on both random and sequential devices
696 d->startTransaction();
697 if (m_startPos > -1) {
698 d->seek(m_startPos);
699 }
700 try {
701 K_IStream istr(d);
702 Imf::RgbaInputFile file(istr);
703 v = QVariant::fromValue(imageFormat(file));
704 } catch (const std::exception &) {
705 // broken file or unsupported version
706 }
707 d->rollbackTransaction();
708 }
709 }
710
711 if (option == QImageIOHandler::CompressionRatio) {
712 v = QVariant(m_compressionRatio);
713 }
714
715 if (option == QImageIOHandler::Quality) {
716 v = QVariant(m_quality);
717 }
718
719 return v;
720}
721
722bool EXRHandler::jumpToNextImage()
723{
724 return jumpToImage(m_imageNumber + 1);
725}
726
727bool EXRHandler::jumpToImage(int imageNumber)
728{
729 if (imageNumber < 0 || imageNumber >= imageCount()) {
730 return false;
731 }
732 m_imageNumber = imageNumber;
733 return true;
734}
735
736int EXRHandler::imageCount() const
737{
738 // NOTE: image count is cached for performance reason
739 auto &&count = m_imageCount;
740 if (count > 0) {
741 return count;
742 }
743
745
746 auto d = device();
747 d->startTransaction();
748
749 try {
750 K_IStream istr(d);
751 Imf::RgbaInputFile file(istr);
752 auto views = viewList(file.header());
753 if (!views.isEmpty()) {
754 count = views.size();
755 }
756 } catch (const std::exception &) {
757 // do nothing
758 }
759
760 d->rollbackTransaction();
761
762 return count;
763}
764
765int EXRHandler::currentImageNumber() const
766{
767 return m_imageNumber;
768}
769
770bool EXRHandler::canRead(QIODevice *device)
771{
772 if (!device) {
773 qWarning("EXRHandler::canRead() called with no device");
774 return false;
775 }
776
777#if OPENEXR_VERSION_MAJOR == 3 && OPENEXR_VERSION_MINOR > 2
778 // openexpr >= 3.3 uses seek and tell extensively
779 if (device->isSequential()) {
780 return false;
781 }
782#endif
783
784 const QByteArray head = device->peek(4);
785
786 return Imf::isImfMagic(head.data());
787}
788
789QImageIOPlugin::Capabilities EXRPlugin::capabilities(QIODevice *device, const QByteArray &format) const
790{
791 if (format == "exr") {
792 return Capabilities(CanRead | CanWrite);
793 }
794 if (!format.isEmpty()) {
795 return {};
796 }
797 if (!device->isOpen()) {
798 return {};
799 }
800
801 Capabilities cap;
802 if (device->isReadable() && EXRHandler::canRead(device)) {
803 cap |= CanRead;
804 }
805 if (device->isWritable()) {
806 cap |= CanWrite;
807 }
808 return cap;
809}
810
811QImageIOHandler *EXRPlugin::create(QIODevice *device, const QByteArray &format) const
812{
813 QImageIOHandler *handler = new EXRHandler;
814 handler->setDevice(device);
815 handler->setFormat(format);
816 return handler;
817}
818
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())
char * data()
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)
QImage convertToFormat(Format format, Qt::ImageConversionFlags flags) &&
int dotsPerMeterX() const const
int dotsPerMeterY() const const
Format format() const const
bool hasAlphaChannel() const const
int height() const const
bool isNull() const const
QImage scaledToHeight(int height, Qt::TransformationMode mode) const const
QImage scaledToWidth(int width, Qt::TransformationMode mode) const const
uchar * scanLine(int i)
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
int width() const const
virtual int imageCount() const const
void setDevice(QIODevice *device)
void setFormat(const QByteArray &format)
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
QLocale c()
float toFloat(QStringView s, bool *ok) const const
QString fromStdString(const std::string &str)
CaseInsensitive
int idealThreadCount()
QTimeZone fromSecondsAheadOfUtc(int offset)
QVariant fromValue(T &&value)
int toInt(bool *ok) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 22 2024 12:10:55 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.