KImageFormats

jxr.cpp
1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8/*
9 * Info about JXR:
10 * - https://learn.microsoft.com/en-us/windows/win32/wic/jpeg-xr-codec
11 *
12 * Sample images:
13 * - http://fileformats.archiveteam.org/wiki/JPEG_XR
14 * - https://github.com/bvibber/hdrfix/tree/main/samples
15 */
16
17#include "jxr_p.h"
18#include "microexif_p.h"
19#include "util_p.h"
20
21#include <QColorSpace>
22#include <QCoreApplication>
23#include <QDataStream>
24#include <QFile>
25#include <QFloat16>
26#include <QHash>
27#include <QImage>
28#include <QImageReader>
29#include <QLoggingCategory>
30#include <QSet>
31#include <QSharedData>
32#include <QTemporaryDir>
33
34#include <JXRGlue.h>
35#include <cstring>
36
37Q_DECLARE_LOGGING_CATEGORY(LOG_JXRPLUGIN)
38Q_LOGGING_CATEGORY(LOG_JXRPLUGIN, "kf.imageformats.plugins.jxr", QtWarningMsg)
39
40/*!
41 * Support for float images
42 *
43 * NOTE: Float images have values greater than 1 so they need an additional in place conversion.
44 */
45// #define JXR_DENY_FLOAT_IMAGE
46
47/*!
48 * Remove the neeeds of additional memory by disabling the conversion between
49 * different color depths (e.g. RGBA64bpp to RGBA32bpp).
50 *
51 * NOTE: Leaving deptch conversion enabled (default) ensures maximum read compatibility.
52 */
53// #define JXR_DISABLE_DEPTH_CONVERSION // default commented
54
55/*!
56 * Windows displays and opens JXR files correctly out of the box. Unfortunately it doesn't
57 * seem to open (P)RGBA @32bpp files as it only wants (P)BGRA32bpp files (a format not supported by Qt).
58 * Only for this format an hack is activated to guarantee total compatibility of the plugin with Windows.
59 */
60// #define JXR_DISABLE_BGRA_HACK // default commented
61
62/*!
63 * The following functions are present in the Debian headers but not in the SUSE ones even if the source version is 1.0.1 on both.
64 *
65 * - ERR PKImageDecode_GetXMPMetadata_WMP(PKImageDecode *pID, U8 *pbXMPMetadata, U32 *pcbXMPMetadata);
66 * - ERR PKImageDecode_GetEXIFMetadata_WMP(PKImageDecode *pID, U8 *pbEXIFMetadata, U32 *pcbEXIFMetadata);
67 * - ERR PKImageDecode_GetGPSInfoMetadata_WMP(PKImageDecode *pID, U8 *pbGPSInfoMetadata, U32 *pcbGPSInfoMetadata);
68 * - ERR PKImageDecode_GetIPTCNAAMetadata_WMP(PKImageDecode *pID, U8 *pbIPTCNAAMetadata, U32 *pcbIPTCNAAMetadata);
69 * - ERR PKImageDecode_GetPhotoshopMetadata_WMP(PKImageDecode *pID, U8 *pbPhotoshopMetadata, U32 *pcbPhotoshopMetadata);
70 *
71 * As a result, their use is disabled by default. It is possible to activate their use by defining the
72 * JXR_ENABLE_ADVANCED_METADATA preprocessor directive
73 */
74
75// #define JXR_ENABLE_ADVANCED_METADATA
76
77#ifndef JXR_MAX_METADATA_SIZE
78/*!
79 * XMP and EXIF maximum size.
80 */
81#define JXR_MAX_METADATA_SIZE (4 * 1024 * 1024)
82#endif
83
84class JXRHandlerPrivate : public QSharedData
85{
86private:
87 QSharedPointer<QTemporaryDir> m_tempDir;
88 QSharedPointer<QFile> m_jxrFile;
89 MicroExif m_exif;
90 qint32 m_quality;
91 QImageIOHandler::Transformations m_transformations;
92 mutable QHash<QString, QString> m_txtMeta;
93
94public:
95 PKFactory *pFactory = nullptr;
96 PKCodecFactory *pCodecFactory = nullptr;
97 PKImageDecode *pDecoder = nullptr;
98 PKImageEncode *pEncoder = nullptr;
99
100 JXRHandlerPrivate()
101 : m_quality(-1)
102 , m_transformations(QImageIOHandler::TransformationNone)
103 {
104 m_tempDir = QSharedPointer<QTemporaryDir>(new QTemporaryDir);
105 if (PKCreateFactory(&pFactory, PK_SDK_VERSION) == WMP_errSuccess) {
106 PKCreateCodecFactory(&pCodecFactory, WMP_SDK_VERSION);
107 }
108 if (pFactory == nullptr || pCodecFactory == nullptr) {
109 qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() initialization error of JXR library!";
110 }
111 }
112 JXRHandlerPrivate(const JXRHandlerPrivate &other) = default;
113
114 ~JXRHandlerPrivate()
115 {
116 if (pCodecFactory) {
117 PKCreateCodecFactory_Release(&pCodecFactory);
118 }
119 if (pFactory) {
120 PKCreateFactory_Release(&pFactory);
121 }
122 if (pDecoder) {
123 PKImageDecode_Release(&pDecoder);
124 }
125 if (pEncoder) {
126 PKImageEncode_Release(&pEncoder);
127 }
128 }
129
130 QString fileName() const
131 {
132 return m_jxrFile->fileName();
133 }
134
135 /*!
136 * \brief setQuality
137 * Set the image quality (write only)
138 * \param q
139 */
140 void setQuality(qint32 q)
141 {
142 m_quality = q;
143 }
144 qint32 quality() const
145 {
146 return m_quality;
147 }
148
149 /*!
150 * \brief setTransformation
151 * Set the image transformation (read/write)
152 * \param t
153 */
154 void setTransformation(const QImageIOHandler::Transformations& t)
155 {
156 m_transformations = t;
157 }
158 QImageIOHandler::Transformations transformation() const
159 {
160 return m_transformations;
161 }
162
163 static QImageIOHandler::Transformations orientationToTransformation(const ORIENTATION& o)
164 {
166 switch (o) {
167 case O_FLIPV:
169 break;
170 case O_FLIPH:
172 break;
173 case O_FLIPVH:
175 break;
176 case O_RCW:
178 break;
179 case O_RCW_FLIPH:
181 break;
182 case O_RCW_FLIPV:
184 break;
185 case O_RCW_FLIPVH:
187 break;
188 default:
190 break;
191 }
192 return v;
193 }
194 static ORIENTATION transformationToOrientation(const QImageIOHandler::Transformations& t)
195 {
196 auto v = O_NONE;
197 switch (t) {
199 v = O_FLIPV;
200 break;
202 v = O_FLIPH;
203 break;
205 v = O_FLIPVH;
206 break;
208 v = O_RCW;
209 break;
211 v = O_RCW_FLIPH;
212 break;
214 v = O_RCW_FLIPV;
215 break;
217 v = O_RCW_FLIPVH;
218 break;
219 default:
220 v = O_NONE;
221 break;
222 }
223 return v;
224 }
225
226
227 /* *** READ *** */
228
229 /*!
230 * \brief initForReading
231 * Initialize the device for reading.
232 * \param device The source device.
233 * \return True on success, otherwise false.
234 */
235 bool initForReading(QIODevice *device)
236 {
237 if (!readDevice(device)) {
238 return false;
239 }
240 if (!initDecoder()) {
241 return false;
242 }
243 return true;
244 }
245
246 /*!
247 * \brief jxrFormat
248 * \return The JXR format.
249 */
250 PKPixelFormatGUID jxrFormat() const
251 {
252 PKPixelFormatGUID pixelFormatGUID = GUID_PKPixelFormatUndefined;
253 if (pDecoder) {
254 pDecoder->GetPixelFormat(pDecoder, &pixelFormatGUID);
255 }
256 return pixelFormatGUID;
257 }
258
259 /*!
260 * \brief imageFormat
261 * Calculate the image format from the JXR format. In conversionFormat it returns the possible conversion format of the JXR to match the returned Qt format.
262 * \return The QImage format. If invalid, the image cannot be read.
263 */
264 QImage::Format imageFormat(PKPixelFormatGUID *conversionFormat = nullptr) const
265 {
266 PKPixelFormatGUID tmp;
267 if (conversionFormat == nullptr) {
268 conversionFormat = &tmp;
269 }
270 *conversionFormat = GUID_PKPixelFormatUndefined;
271
272 auto jxrfmt = jxrFormat();
273 auto qtFormat = exactFormat(jxrfmt);
274 if (qtFormat != QImage::Format_Invalid) {
275 return qtFormat;
276 }
277
278 // *** CONVERSION WITH THE SAME DEPTH ***
279 // IMPORTANT: For supported conversions see JXRGluePFC.c
280
281 // 32-bit
282 if (IsEqualGUID(jxrfmt, GUID_PKPixelFormat32bppBGR)) {
283 *conversionFormat = GUID_PKPixelFormat24bppRGB;
285 };
286 if (IsEqualGUID(jxrfmt, GUID_PKPixelFormat32bppBGRA)) {
287 *conversionFormat = GUID_PKPixelFormat32bppRGBA;
289 };
290 if (IsEqualGUID(jxrfmt, GUID_PKPixelFormat32bppPBGRA)) {
291 *conversionFormat = GUID_PKPixelFormat32bppPRGBA;
293 };
294
295#ifndef JXR_DENY_FLOAT_IMAGE
296 if (IsEqualGUID(jxrfmt, GUID_PKPixelFormat128bppRGBAFixedPoint)) {
297 *conversionFormat = GUID_PKPixelFormat128bppRGBAFloat;
299 };
300#endif // !JXR_DENY_FLOAT_IMAGE
301
302 // *** CONVERSION TO A LOWER DEPTH ***
303 // IMPORTANT: For supported conversions see JXRGluePFC.c
304
305#ifndef JXR_DISABLE_DEPTH_CONVERSION
306
307#ifndef JXR_DENY_FLOAT_IMAGE
308 // RGB FLOAT
309 if (IsEqualGUID(jxrfmt, GUID_PKPixelFormat96bppRGBFloat)) {
310 *conversionFormat = GUID_PKPixelFormat64bppRGBHalf;
312 };
313#endif // !JXR_DENY_FLOAT_IMAGE
314
315 // RGBA
316 // clang-format off
317 if (IsEqualGUID(jxrfmt, GUID_PKPixelFormat64bppRGBAHalf) ||
318 IsEqualGUID(jxrfmt, GUID_PKPixelFormat64bppRGBAFixedPoint) ||
319 IsEqualGUID(jxrfmt, GUID_PKPixelFormat128bppRGBAFixedPoint) ||
320 IsEqualGUID(jxrfmt, GUID_PKPixelFormat128bppRGBAFloat)) {
321
322 *conversionFormat = GUID_PKPixelFormat32bppRGBA;
324 };
325 // clang-format on
326
327 // RGB
328 // clang-format off
329 if (IsEqualGUID(jxrfmt, GUID_PKPixelFormat128bppRGBFloat) ||
330 IsEqualGUID(jxrfmt, GUID_PKPixelFormat96bppRGBFloat) ||
331 IsEqualGUID(jxrfmt, GUID_PKPixelFormat64bppRGBFixedPoint) ||
332 IsEqualGUID(jxrfmt, GUID_PKPixelFormat96bppRGBFixedPoint) ||
333 IsEqualGUID(jxrfmt, GUID_PKPixelFormat128bppRGBFixedPoint) ||
334 IsEqualGUID(jxrfmt, GUID_PKPixelFormat48bppRGBHalf) ||
335 IsEqualGUID(jxrfmt, GUID_PKPixelFormat64bppRGBHalf) ||
336 IsEqualGUID(jxrfmt, GUID_PKPixelFormat48bppRGBFixedPoint) ||
337 IsEqualGUID(jxrfmt, GUID_PKPixelFormat32bppRGB101010) ||
338 IsEqualGUID(jxrfmt, GUID_PKPixelFormat48bppRGB) ||
339 IsEqualGUID(jxrfmt, GUID_PKPixelFormat32bppRGBE) ) {
340
341 *conversionFormat = GUID_PKPixelFormat24bppRGB;
343 };
344 // clang-format on
345
346 // Gray
347 // clang-format off
348 if (IsEqualGUID(jxrfmt, GUID_PKPixelFormat32bppGrayFloat) ||
349 IsEqualGUID(jxrfmt, GUID_PKPixelFormat16bppGrayFixedPoint) ||
350 IsEqualGUID(jxrfmt, GUID_PKPixelFormat32bppGrayFixedPoint) ||
351 IsEqualGUID(jxrfmt, GUID_PKPixelFormat16bppGrayHalf)) {
352
353 *conversionFormat = GUID_PKPixelFormat8bppGray;
355 };
356 // clang-format on
357#endif // !JXR_DISABLE_DEPTH_CONVERSION
358
360 }
361
362 /*!
363 * \brief imageSize
364 * \return The image size in pixels.
365 */
366 QSize imageSize() const
367 {
368 if (pDecoder) {
369 qint32 w, h;
370 pDecoder->GetSize(pDecoder, &w, &h);
371 return QSize(w, h);
372 }
373 return {};
374 }
375
376 /*!
377 * \brief colorSpace
378 * \return The ICC profile if exists.
379 */
380 QColorSpace colorSpace() const
381 {
382 QColorSpace cs;
383 if (pDecoder == nullptr) {
384 return cs;
385 }
386 quint32 size;
387 if (!pDecoder->GetColorContext(pDecoder, nullptr, &size) && size) {
388 QByteArray ba(size, 0);
389 if (!pDecoder->GetColorContext(pDecoder, reinterpret_cast<quint8 *>(ba.data()), &size)) {
391 }
392 }
393 return cs;
394 }
395
396 /*!
397 * \brief xmpData
398 * \return The XMP data if exists.
399 */
400 QString xmpData() const
401 {
402 QString xmp;
403 if (pDecoder == nullptr) {
404 return xmp;
405 }
406#ifdef JXR_ENABLE_ADVANCED_METADATA
407 quint32 size;
408 if (!PKImageDecode_GetXMPMetadata_WMP(pDecoder, nullptr, &size) && size > 0 && size < JXR_MAX_METADATA_SIZE) {
409 QByteArray ba(size, 0);
410 if (!PKImageDecode_GetXMPMetadata_WMP(pDecoder, reinterpret_cast<quint8 *>(ba.data()), &size)) {
411 xmp = QString::fromUtf8(ba);
412 }
413 }
414#endif
415 return xmp;
416 }
417
418 /*!
419 * \brief exifData
420 * \return The EXIF data.
421 */
422 MicroExif exifData() const
423 {
424 return m_exif;
425 }
426
427 /*!
428 * \brief setMetadata
429 * Set the metadata into \a image
430 * \param image Image on which to write metadata
431 */
432 void setMetadata(QImage& image)
433 {
434 auto xmp = xmpData();
435 if (!xmp.isEmpty()) {
436 image.setText(QStringLiteral(META_KEY_XMP_ADOBE), xmp);
437 }
438 auto descr = description();
439 if (!descr.isEmpty()) {
440 image.setText(QStringLiteral(META_KEY_DESCRIPTION), descr);
441 }
442 auto softw = software();
443 if (!softw.isEmpty()) {
444 image.setText(QStringLiteral(META_KEY_SOFTWARE), softw);
445 }
446 auto make = cameraMake();
447 if (!make.isEmpty()) {
448 image.setText(QStringLiteral(META_KEY_MANUFACTURER), make);
449 }
450 auto model = cameraModel();
451 if (!model.isEmpty()) {
452 image.setText(QStringLiteral(META_KEY_MODEL), model);
453 }
454 auto author = artist();
455 if (!author.isEmpty()) {
456 image.setText(QStringLiteral(META_KEY_AUTHOR), author);
457 }
458 auto copy = copyright();
459 if (!copy.isEmpty()) {
460 image.setText(QStringLiteral(META_KEY_COPYRIGHT), copy);
461 }
462 auto capt = caption();
463 if (!capt.isEmpty()) {
464 image.setText(QStringLiteral(META_KEY_TITLE), capt);
465 }
466 auto host = hostComputer();
467 if (!host.isEmpty()) {
468 image.setText(QStringLiteral(META_KEY_HOSTCOMPUTER), capt);
469 }
470 auto docn = documentName();
471 if (!docn.isEmpty()) {
472 image.setText(QStringLiteral(META_KEY_DOCUMENTNAME), docn);
473 }
474 auto exif = exifData();
475 if (!exif.isEmpty()) {
476 exif.toImageMetadata(image);
477 }
478 }
479
480#define META_TEXT(name, key) \
481 QString name() const \
482 { \
483 readTextMeta(); \
484 return m_txtMeta.value(QStringLiteral(key)); \
485 }
486
487 META_TEXT(description, META_KEY_DESCRIPTION)
488 META_TEXT(cameraMake, META_KEY_MANUFACTURER)
489 META_TEXT(cameraModel, META_KEY_MODEL)
490 META_TEXT(software, META_KEY_SOFTWARE)
491 META_TEXT(artist, META_KEY_AUTHOR)
492 META_TEXT(copyright, META_KEY_COPYRIGHT)
493 META_TEXT(caption, META_KEY_TITLE)
494 META_TEXT(documentName, META_KEY_DOCUMENTNAME)
495 META_TEXT(hostComputer, META_KEY_HOSTCOMPUTER)
496
497#undef META_TEXT
498
499 /* *** WRITE *** */
500
501 /*!
502 * \brief initForWriting
503 * Initialize the stream for writing.
504 * \return True on success, otherwise false.
505 */
506 bool initForWriting()
507 {
508 // I have to use QFile because, on Windows, the QTemporary file is locked (even if I close it)
509 auto fileName = QStringLiteral("%1.jxr").arg(m_tempDir->filePath(QUuid::createUuid().toString(QUuid::WithoutBraces).left(8)));
510 QSharedPointer<QFile> file(new QFile(fileName));
511 m_jxrFile = file;
512 return initEncoder();
513 }
514
515 /*!
516 * \brief finalizeWriting
517 * \param device
518 * Finalize the writing operation. Must be called as last peration.
519 * \return True on success, otherwise false.
520 */
521 bool finalizeWriting(QIODevice *device)
522 {
523 if (device == nullptr || pEncoder == nullptr) {
524 return false;
525 }
526 if (auto err = PKImageEncode_Release(&pEncoder)) {
527 qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::finalizeWriting() error while releasing the encoder:" << err;
528 return false;
529 }
530
531 if (!deviceCopy(device, m_jxrFile.data())) {
532 qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::finalizeWriting() error while writing in the target device";
533 return false;
534 }
535 return true;
536 }
537
538 /*!
539 * \brief imageToSave
540 * If necessary it converts the image to be saved into the appropriate format otherwise it does nothing.
541 * \param source The image to save.
542 * \return The image to use for save operation.
543 */
544 QImage imageToSave(const QImage &source) const
545 {
546 // IMPORTANT: these values must be in exactMatchingFormat()
547 // clang-format off
548 auto valid = QSet<QImage::Format>()
549#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
550 << QImage::Format_CMYK8888
551#endif
552#ifndef JXR_DENY_FLOAT_IMAGE
558#endif // JXR_DENY_FLOAT_IMAGE
571 // clang-format on
572
573 // To avoid complex code, I will save only integer formats.
574 auto qi = source;
575 if (qi.format() == QImage::Format_MonoLSB) {
576 qi = qi.convertToFormat(QImage::Format_Mono);
577 }
578 if (qi.format() == QImage::Format_Indexed8) {
579 if (qi.allGray())
580 qi = qi.convertToFormat(QImage::Format_Grayscale8);
581 else
582 qi = qi.convertToFormat(QImage::Format_RGBA8888);
583 }
584#ifndef JXR_DENY_FLOAT_IMAGE
585 if (qi.format() == QImage::Format_RGBA16FPx4_Premultiplied) {
586 qi = qi.convertToFormat(QImage::Format_RGBA16FPx4);
587 }
588#endif // JXR_DENY_FLOAT_IMAGE
589
590 // generic
591 if (!valid.contains(qi.format())) {
592 auto alpha = qi.hasAlphaChannel();
593 auto depth = qi.depth();
594 if (depth >= 12 && depth <= 24 && !alpha) {
595 qi = qi.convertToFormat(QImage::Format_RGB888);
596 } else if (depth >= 48) {
597 // JXR don't have RGBX64 format so I have two possibilities:
598 // - convert to 32 bpp (convertToFormat(alpha ? QImage::Format_RGBA64 : QImage::Format_RGB888))
599 // - convert to 64 bpp with fake alpha (preferred)
600 qi = qi.convertToFormat(QImage::Format_RGBA64);
601 } else {
602 qi = qi.convertToFormat(alpha ? QImage::Format_RGBA8888 : QImage::Format_RGB888);
603 }
604#ifndef JXR_DENY_FLOAT_IMAGE
605 // clang-format off
606 } else if (qi.format() == QImage::Format_RGBA16FPx4 ||
607 qi.format() == QImage::Format_RGBX16FPx4 ||
608 qi.format() == QImage::Format_RGBA32FPx4 ||
610 qi.format() == QImage::Format_RGBX32FPx4) {
611 // clang-format on
612 auto cs = qi.colorSpace();
613 if (cs.isValid() && cs.transferFunction() != QColorSpace::TransferFunction::Linear) {
614 qi = qi.convertedToColorSpace(QColorSpace(QColorSpace::SRgbLinear));
615 }
616 }
617#endif // JXR_DENY_FLOAT_IMAGE
618
619 return qi;
620 }
621
622 /*!
623 * \brief initCodecParameters
624 * Initialize the JXR codec parameters.
625 * \param wmiSCP
626 * \param image The image to save.
627 * \return True on success, otherwise false.
628 */
629 bool initCodecParameters(CWMIStrCodecParam *wmiSCP, const QImage &image)
630 {
631 if (wmiSCP == nullptr || image.isNull()) {
632 return false;
633 }
634 memset(wmiSCP, 0, sizeof(CWMIStrCodecParam));
635
636 auto fmt = image.format();
637
638 wmiSCP->bVerbose = FALSE;
640 wmiSCP->cfColorFormat = Y_ONLY;
641#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
642 else if (fmt == QImage::Format_CMYK8888)
643 wmiSCP->cfColorFormat = CMYK;
644#endif
645 else
646 wmiSCP->cfColorFormat = YUV_444;
647 wmiSCP->bdBitDepth = BD_LONG;
648 wmiSCP->bfBitstreamFormat = FREQUENCY;
649 wmiSCP->bProgressiveMode = TRUE;
650 wmiSCP->olOverlap = OL_ONE;
651 wmiSCP->cNumOfSliceMinus1H = wmiSCP->cNumOfSliceMinus1V = 0;
652 wmiSCP->sbSubband = SB_ALL;
653 wmiSCP->uAlphaMode = image.hasAlphaChannel() ? 2 : 0;
654 if (quality() > -1) {
655 wmiSCP->uiDefaultQPIndex = qBound(0, 100 - quality(), 100);
656 }
657 return true;
658 }
659
660 /*!
661 * \brief updateTextMetadata
662 * Read the metadata from the image and set it in the encoder.
663 * \param image The image to save.
664 */
665 void updateTextMetadata(const QImage &image)
666 {
667 if (pEncoder == nullptr) {
668 return;
669 }
670
671 DESCRIPTIVEMETADATA meta;
672 memset(&meta, 0, sizeof(meta));
673
674#define META_CTEXT(name, field) \
675 auto field = image.text(QStringLiteral(name)).toUtf8(); \
676 if (!field.isEmpty()) { \
677 meta.field.vt = DPKVT_LPSTR; \
678 meta.field.VT.pszVal = field.data(); \
679 }
680#define META_WTEXT(name, field) \
681 auto field = image.text(QStringLiteral(name)); \
682 if (!field.isEmpty()) { \
683 meta.field.vt = DPKVT_LPWSTR; \
684 meta.field.VT.pwszVal = const_cast<quint16 *>(field.utf16()); \
685 }
686
687 META_CTEXT(META_KEY_DESCRIPTION, pvarImageDescription)
688 META_CTEXT(META_KEY_MANUFACTURER, pvarCameraMake)
689 META_CTEXT(META_KEY_MODEL, pvarCameraModel)
690 META_CTEXT(META_KEY_AUTHOR, pvarArtist)
691 META_CTEXT(META_KEY_COPYRIGHT, pvarCopyright)
692 META_CTEXT(META_KEY_DOCUMENTNAME, pvarDocumentName)
693 META_CTEXT(META_KEY_HOSTCOMPUTER, pvarHostComputer)
694 META_WTEXT(META_KEY_TITLE, pvarCaption)
695
696#undef META_CTEXT
697#undef META_WTEXT
698
699 // Software must be updated
700 auto software = QStringLiteral("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion()).toUtf8();
701 if (!software.isEmpty()) {
702 meta.pvarSoftware.vt = DPKVT_LPSTR;
703 meta.pvarSoftware.VT.pszVal = software.data();
704 }
705
706 // Date and Time (TIFF format)
707 auto cDate = QDateTime::fromString(image.text(QStringLiteral(META_KEY_MODIFICATIONDATE)), Qt::ISODate);
708 auto sDate = cDate.isValid() ? cDate.toString(QStringLiteral("yyyy:MM:dd HH:mm:ss")).toLatin1() : QByteArray();
709 if (!sDate.isEmpty()) {
710 meta.pvarDateTime.vt = DPKVT_LPSTR;
711 meta.pvarDateTime.VT.pszVal = sDate.data();
712 }
713
714 auto xmp = image.text(QStringLiteral(META_KEY_XMP_ADOBE)).toUtf8();
715 if (!xmp.isNull()) {
716 if (auto err = PKImageEncode_SetXMPMetadata_WMP(pEncoder, reinterpret_cast<const quint8 *>(xmp.constData()), xmp.size())) {
717 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting XMP data:" << err;
718 }
719 }
720
721 auto exif = MicroExif::fromImage(image);
722 if (!exif.isEmpty()) {
723 auto exifIfd = exif.exifIfdByteArray(QDataStream::LittleEndian);
724 if (auto err = PKImageEncode_SetEXIFMetadata_WMP(pEncoder, reinterpret_cast<const quint8 *>(exifIfd.constData()), exifIfd.size())) {
725 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting EXIF data:" << err;
726 }
727 auto gpsIfd = exif.gpsIfdByteArray(QDataStream::LittleEndian);
728 if (auto err = PKImageEncode_SetGPSInfoMetadata_WMP(pEncoder, reinterpret_cast<const quint8 *>(gpsIfd.constData()), gpsIfd.size())) {
729 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting GPS data:" << err;
730 }
731 }
732
733 if (auto err = pEncoder->SetDescriptiveMetadata(pEncoder, &meta)) {
734 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting descriptive data:" << err;
735 }
736 }
737
738 /*!
739 * \brief exactFormat
740 * JXR and Qt use support image formats, some of which are identical. Use this function to convert a JXR format to Qt format.
741 * \param jxrFormat Format to be converted.
742 * \return A valid Qt format or QImage::Format_Invalid if there is no match
743 */
744 static QImage::Format exactFormat(const PKPixelFormatGUID &jxrFormat)
745 {
746 auto l = exactMatchingFormats();
747 for (auto &&p : l) {
748 if (IsEqualGUID(p.second, jxrFormat))
749 return p.first;
750 }
752 }
753
754 /*!
755 * \brief exactFormat
756 * JXR and Qt use support image formats, some of which are identical. Use this function to convert a JXR format to Qt format.
757 * \param qtFormat Format to be converted.
758 * \return A valid JXR format or GUID_PKPixelFormatUndefined if there is no match
759 */
760 static PKPixelFormatGUID exactFormat(const QImage::Format &qtFormat)
761 {
762 auto l = exactMatchingFormats();
763 for (auto &&p : l) {
764 if (p.first == qtFormat)
765 return p.second;
766 }
767 return GUID_PKPixelFormatUndefined;
768 }
769
770private:
771 static QList<std::pair<QImage::Format, PKPixelFormatGUID>> exactMatchingFormats()
772 {
773 // clang-format off
774 auto list = QList<std::pair<QImage::Format, PKPixelFormatGUID>>()
775#ifndef JXR_DENY_FLOAT_IMAGE
776 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGBA16FPx4, GUID_PKPixelFormat64bppRGBAHalf)
777 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGBX16FPx4, GUID_PKPixelFormat64bppRGBHalf)
778 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGBA32FPx4, GUID_PKPixelFormat128bppRGBAFloat)
779 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGBA32FPx4_Premultiplied, GUID_PKPixelFormat128bppPRGBAFloat)
780 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGBX32FPx4, GUID_PKPixelFormat128bppRGBFloat)
781#endif // JXR_DENY_FLOAT_IMAGE
782#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
783 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_CMYK8888, GUID_PKPixelFormat32bppCMYK)
784 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_CMYK8888, GUID_PKPixelFormat32bppCMYKDIRECT)
785#endif
786 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_Mono, GUID_PKPixelFormatBlackWhite)
787 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_Grayscale8, GUID_PKPixelFormat8bppGray)
788 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_Grayscale16, GUID_PKPixelFormat16bppGray)
789 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGB555, GUID_PKPixelFormat16bppRGB555)
790 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGB16, GUID_PKPixelFormat16bppRGB565)
791 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_BGR888, GUID_PKPixelFormat24bppBGR)
792 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGB888, GUID_PKPixelFormat24bppRGB)
793 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGBX8888, GUID_PKPixelFormat32bppRGB)
794 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGBA8888, GUID_PKPixelFormat32bppRGBA)
795 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGBA8888_Premultiplied, GUID_PKPixelFormat32bppPRGBA)
796 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGBA64, GUID_PKPixelFormat64bppRGBA)
797 << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGBA64_Premultiplied, GUID_PKPixelFormat64bppPRGBA);
798 // clang-format on
799 return list;
800 }
801
802 bool deviceCopy(QIODevice *target, QIODevice *source)
803 {
804 if (target == nullptr || source == nullptr) {
805 return false;
806 }
807 auto isTargetOpen = target->isOpen();
808 if (!isTargetOpen && !target->open(QIODevice::WriteOnly)) {
809 return false;
810 }
811 auto isSourceOpen = source->isOpen();
812 if (!isSourceOpen && !source->open(QIODevice::ReadOnly)) {
813 return false;
814 }
815 QByteArray buff(32768 * 4, char());
816 for (;;) {
817 auto read = source->read(buff.data(), buff.size());
818 if (read == 0) {
819 break;
820 }
821 if (read < 0) {
822 return false;
823 }
824 if (target->write(buff.data(), read) != read) {
825 return false;
826 }
827 }
828 if (!isSourceOpen) {
829 source->close();
830 }
831 if (!isTargetOpen) {
832 target->close();
833 }
834 return true;
835 }
836
837 bool readDevice(QIODevice *device)
838 {
839 if (device == nullptr) {
840 return false;
841 }
842 if (!m_jxrFile.isNull()) {
843 return true;
844 }
845 // I have to use QFile because, on Windows, the QTemporary file is locked (even if I close it)
846 auto fileName = QStringLiteral("%1.jxr").arg(m_tempDir->filePath(QUuid::createUuid().toString(QUuid::WithoutBraces).left(8)));
847 QSharedPointer<QFile> file(new QFile(fileName));
848 if (!file->open(QFile::WriteOnly)) {
849 return false;
850 }
851 if (!deviceCopy(file.data(), device)) {
852 qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::readDevice() error while writing in the target device";
853 return false;
854 }
855 file->close();
856 m_exif = MicroExif::fromDevice(file.data());
857 m_jxrFile = file;
858 return true;
859 }
860
861 bool initDecoder()
862 {
863 if (pDecoder) {
864 return true;
865 }
866 if (pCodecFactory == nullptr) {
867 return false;
868 }
869 if (auto err = pCodecFactory->CreateDecoderFromFile(qUtf8Printable(fileName()), &pDecoder)) {
870 qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::initDecoder() unable to create decoder:" << err;
871 return false;
872 }
873 setTransformation(JXRHandlerPrivate::orientationToTransformation(pDecoder->WMP.wmiI.oOrientation));
874 pDecoder->WMP.wmiI.oOrientation = O_NONE; // disable the library rotation application
875 return true;
876 }
877
878 bool initEncoder()
879 {
880 if (pDecoder) {
881 return true;
882 }
883 if (pCodecFactory == nullptr) {
884 return false;
885 }
886 if (auto err = pCodecFactory->CreateCodec(&IID_PKImageWmpEncode, (void **)&pEncoder)) {
887 qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::initEncoder() unable to create encoder:" << err;
888 return false;
889 }
890 pEncoder->WMP.oOrientation = JXRHandlerPrivate::transformationToOrientation(transformation());
891 return true;
892 }
893
894 bool readTextMeta() const {
895 if (pDecoder == nullptr) {
896 return false;
897 }
898 if (!m_txtMeta.isEmpty()) {
899 return true;
900 }
901
902 DESCRIPTIVEMETADATA meta;
903 if (pDecoder->GetDescriptiveMetadata(pDecoder, &meta)) {
904 return false;
905 }
906
907#define META_TEXT(name, field) \
908 if (meta.field.vt == DPKVT_LPSTR) \
909 m_txtMeta.insert(QStringLiteral(name), QString::fromUtf8(meta.field.VT.pszVal)); \
910 else if (meta.field.vt == DPKVT_LPWSTR) \
911 m_txtMeta.insert(QStringLiteral(name), QString::fromUtf16(reinterpret_cast<char16_t *>(meta.field.VT.pwszVal)));
912
913 META_TEXT(META_KEY_DESCRIPTION, pvarImageDescription)
914 META_TEXT(META_KEY_MANUFACTURER, pvarCameraMake)
915 META_TEXT(META_KEY_MODEL, pvarCameraModel)
916 META_TEXT(META_KEY_SOFTWARE, pvarSoftware)
917 META_TEXT(META_KEY_AUTHOR, pvarArtist)
918 META_TEXT(META_KEY_COPYRIGHT, pvarCopyright)
919 META_TEXT(META_KEY_TITLE, pvarCaption)
920 META_TEXT(META_KEY_DOCUMENTNAME, pvarDocumentName)
921 META_TEXT(META_KEY_HOSTCOMPUTER, pvarHostComputer)
922
923#undef META_TEXT
924
925 return true;
926 }
927};
928
929bool JXRHandler::read(QImage *outImage)
930{
931 if (!d->initForReading(device())) {
932 return false;
933 }
934
935 PKPixelFormatGUID convFmt;
936 auto imageFmt = d->imageFormat(&convFmt);
937 auto img = imageAlloc(d->imageSize(), imageFmt);
938 if (img.isNull()) {
939 return false;
940 }
941
942 // resolution
943 float hres, vres;
944 if (auto err = d->pDecoder->GetResolution(d->pDecoder, &hres, &vres)) {
945 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while reading resolution:" << err;
946 } else {
947 img.setDotsPerMeterX(qRound(hres * 1000 / 25.4));
948 img.setDotsPerMeterY(qRound(vres * 1000 / 25.4));
949 }
950
951 // alpha copy mode
952 if (img.hasAlphaChannel()) {
953 d->pDecoder->WMP.wmiSCP.uAlphaMode = 2; // or 1 (?)
954 }
955
956 PKRect rect = {0, 0, img.width(), img.height()};
957 if (IsEqualGUID(convFmt, GUID_PKPixelFormatUndefined)) { // direct storing
958 if (auto err = d->pDecoder->Copy(d->pDecoder, &rect, img.bits(), img.bytesPerLine())) {
959 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to copy data:" << err;
960 return false;
961 }
962 } else { // conversion to a known format
963 PKFormatConverter *pConverter = nullptr;
964 if (auto err = d->pCodecFactory->CreateFormatConverter(&pConverter)) {
965 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to create the converter:" << err;
966 return false;
967 }
968 if (auto err = pConverter->Initialize(pConverter, d->pDecoder, nullptr, convFmt)) {
969 PKFormatConverter_Release(&pConverter);
970 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to initialize the converter:" << err;
971 return false;
972 }
973 if (d->pDecoder->WMP.wmiI.cBitsPerUnit == size_t(img.depth())) { // in place conversion
974 if (auto err = pConverter->Copy(pConverter, &rect, img.bits(), img.bytesPerLine())) {
975 PKFormatConverter_Release(&pConverter);
976 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to copy converted data:" << err;
977 return false;
978 }
979 } else { // additional buffer needed
980 qint64 convStrideSize = (img.width() * d->pDecoder->WMP.wmiI.cBitsPerUnit + 7) / 8;
981 qint64 buffSize = convStrideSize * img.height();
982 qint64 limit = QImageReader::allocationLimit();
983 if (limit && (buffSize + img.sizeInBytes()) > limit * 1024 * 1024) {
984 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to covert due to allocation limit set:" << limit << "MiB";
985 return false;
986 }
987 QVector<quint8> ba(buffSize);
988 if (auto err = pConverter->Copy(pConverter, &rect, ba.data(), convStrideSize)) {
989 PKFormatConverter_Release(&pConverter);
990 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to copy converted data:" << err;
991 return false;
992 }
993 for (qint32 y = 0, h = img.height(); y < h; ++y) {
994 std::memcpy(img.scanLine(y), ba.data() + convStrideSize * y, (std::min)(convStrideSize, img.bytesPerLine()));
995 }
996 }
997 PKFormatConverter_Release(&pConverter);
998 }
999
1000 // Metadata (e.g.: icc profile, description, etc...)
1001 img.setColorSpace(d->colorSpace());
1002 d->setMetadata(img);
1003
1004#ifndef JXR_DENY_FLOAT_IMAGE
1005 // JXR float are stored in scRGB.
1006 if (img.format() == QImage::Format_RGBX16FPx4 || img.format() == QImage::Format_RGBA16FPx4 || img.format() == QImage::Format_RGBA16FPx4_Premultiplied ||
1007 img.format() == QImage::Format_RGBX32FPx4 || img.format() == QImage::Format_RGBA32FPx4 || img.format() == QImage::Format_RGBA32FPx4_Premultiplied) {
1008 auto hasAlpha = img.hasAlphaChannel();
1009 for (qint32 y = 0, h = img.height(); y < h; ++y) {
1010 if (img.depth() == 64) {
1011 auto line = reinterpret_cast<qfloat16 *>(img.scanLine(y));
1012 for (int x = 0, w = img.width() * 4; x < w; x += 4)
1013 line[x + 3] = hasAlpha ? std::clamp(line[x + 3], qfloat16(0), qfloat16(1)) : qfloat16(1);
1014 } else {
1015 auto line = reinterpret_cast<float *>(img.scanLine(y));
1016 for (int x = 0, w = img.width() * 4; x < w; x += 4)
1017 line[x + 3] = hasAlpha ? std::clamp(line[x + 3], float(0), float(1)) : float(1);
1018 }
1019 }
1020 if (!img.colorSpace().isValid()) {
1021 img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
1022 }
1023 }
1024#endif
1025
1026 *outImage = img;
1027 return true;
1028}
1029
1030bool JXRHandler::write(const QImage &image)
1031{
1032 // JXR is stored in a TIFF V6 container that is limited to 4GiB. The size
1033 // is limited to 4GB to leave room for IFDs, Metadata, etc...
1034 if (qint64(image.sizeInBytes()) > 4000000000ll) {
1035 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() image too large: the image cannot exceed 4GB.";
1036 return false;
1037 }
1038
1039 if (!d->initForWriting()) {
1040 return false;
1041 }
1042 struct WMPStream *pEncodeStream = nullptr;
1043 if (auto err = d->pFactory->CreateStreamFromFilename(&pEncodeStream, qUtf8Printable(d->fileName()), "wb")) {
1044 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() unable to create stream:" << err;
1045 return false;
1046 }
1047
1048 // convert the image to a supported format
1049 auto qi = d->imageToSave(image);
1050 auto jxlfmt = d->exactFormat(qi.format());
1051 if (IsEqualGUID(jxlfmt, GUID_PKPixelFormatUndefined)) {
1052 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() something wrong when calculating the target format for" << qi.format();
1053 return false;
1054 }
1055#ifndef JXR_DISABLE_BGRA_HACK
1056 if (IsEqualGUID(jxlfmt, GUID_PKPixelFormat32bppRGB)) {
1057 jxlfmt = GUID_PKPixelFormat32bppBGR;
1058 qi.rgbSwap();
1059 }
1060 if (IsEqualGUID(jxlfmt, GUID_PKPixelFormat32bppRGBA)) {
1061 jxlfmt = GUID_PKPixelFormat32bppBGRA;
1062 qi.rgbSwap();
1063 }
1064 if (IsEqualGUID(jxlfmt, GUID_PKPixelFormat32bppPRGBA)) {
1065 jxlfmt = GUID_PKPixelFormat32bppPBGRA;
1066 qi.rgbSwap();
1067 }
1068#endif
1069
1070 // initialize the codec parameters
1071 CWMIStrCodecParam wmiSCP;
1072 if (!d->initCodecParameters(&wmiSCP, qi)) {
1073 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() something wrong when calculating encoder parameters for" << qi.format();
1074 return false;
1075 }
1076 if (auto err = d->pEncoder->Initialize(d->pEncoder, pEncodeStream, &wmiSCP, sizeof(wmiSCP))) {
1077 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while initializing the encoder:" << err;
1078 return false;
1079 }
1080
1081 // setting mandatory image info
1082 if (auto err = d->pEncoder->SetPixelFormat(d->pEncoder, jxlfmt)) {
1083 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting the image format:" << err;
1084 return false;
1085 }
1086 if (auto err = d->pEncoder->SetSize(d->pEncoder, qi.width(), qi.height())) {
1087 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting the image size:" << err;
1088 return false;
1089 }
1090 if (auto err = d->pEncoder->SetResolution(d->pEncoder, qi.dotsPerMeterX() * 25.4 / 1000, qi.dotsPerMeterY() * 25.4 / 1000)) {
1091 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting the image resolution:" << err;
1092 return false;
1093 }
1094
1095 // setting metadata (a failure of setting metadata doesn't stop the encoding)
1096 auto cs = qi.colorSpace().iccProfile();
1097 if (!cs.isEmpty()) {
1098 if (auto err = d->pEncoder->SetColorContext(d->pEncoder, reinterpret_cast<quint8 *>(cs.data()), cs.size())) {
1099 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting ICC profile:" << err;
1100 }
1101 }
1102 d->updateTextMetadata(image);
1103
1104 // writing the image
1105 if (auto err = d->pEncoder->WritePixels(d->pEncoder, qi.height(), qi.bits(), qi.bytesPerLine())) {
1106 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while encoding the image:" << err;
1107 return false;
1108 }
1109 if (!d->finalizeWriting(device())) {
1110 return false;
1111 }
1112 return true;
1113}
1114
1115void JXRHandler::setOption(ImageOption option, const QVariant &value)
1116{
1117 if (option == QImageIOHandler::Quality) {
1118 auto ok = false;
1119 auto q = value.toInt(&ok);
1120 if (ok) {
1121 d->setQuality(q);
1122 }
1123 }
1124
1126 auto ok = false;
1127 auto t = value.toInt(&ok);
1128 if (ok) {
1129 d->setTransformation(QImageIOHandler::Transformation(t));
1130 }
1131 }
1132}
1133
1134bool JXRHandler::supportsOption(ImageOption option) const
1135{
1136 if (option == QImageIOHandler::Size) {
1137 return true;
1138 }
1139 if (option == QImageIOHandler::ImageFormat) {
1140 return true;
1141 }
1142 if (option == QImageIOHandler::Quality) {
1143 return true;
1144 }
1146 return true;
1147 }
1148 return false;
1149}
1150
1151QVariant JXRHandler::option(ImageOption option) const
1152{
1153 QVariant v;
1154
1155 if (option == QImageIOHandler::Size) {
1156 if (d->initForReading(device())) {
1157 auto size = d->imageSize();
1158 if (size.isValid()) {
1159 v = QVariant::fromValue(size);
1160 }
1161 }
1162 }
1163
1164 if (option == QImageIOHandler::ImageFormat) {
1165 if (d->initForReading(device())) {
1166 v = QVariant::fromValue(d->imageFormat());
1167 }
1168 }
1169
1170 if (option == QImageIOHandler::Quality) {
1171 v = d->quality();
1172 }
1173
1175 // ignore result: I might want to read the value set in writing
1176 d->initForReading(device());
1177 v = int(d->transformation());
1178 }
1179
1180 return v;
1181}
1182
1183JXRHandler::JXRHandler()
1184 : d(new JXRHandlerPrivate)
1185{
1186}
1187
1188bool JXRHandler::canRead() const
1189{
1190 if (canRead(device())) {
1191 setFormat("jxr");
1192 return true;
1193 }
1194 return false;
1195}
1196
1197bool JXRHandler::canRead(QIODevice *device)
1198{
1199 if (!device) {
1200 qCWarning(LOG_JXRPLUGIN) << "JXRHandler::canRead() called with no device";
1201 return false;
1202 }
1203
1204 // JPEG XR image data is stored in TIFF-like container format (II and 0xBC01 version)
1205 if (device->peek(4) == QByteArray::fromRawData("\x49\x49\xbc\x01", 4)) {
1206 return true;
1207 }
1208
1209 return false;
1210}
1211
1212QImageIOPlugin::Capabilities JXRPlugin::capabilities(QIODevice *device, const QByteArray &format) const
1213{
1214 if (format == "jxr") {
1215 return Capabilities(CanRead | CanWrite);
1216 }
1217 if (format == "wdp" || format == "hdp") {
1218 return Capabilities(CanRead);
1219 }
1220 if (!format.isEmpty()) {
1221 return {};
1222 }
1223 if (!device->isOpen()) {
1224 return {};
1225 }
1226
1227 Capabilities cap;
1228 if (device->isReadable() && JXRHandler::canRead(device)) {
1229 cap |= CanRead;
1230 }
1231 if (device->isWritable()) {
1232 cap |= CanWrite;
1233 }
1234 return cap;
1235}
1236
1237QImageIOHandler *JXRPlugin::create(QIODevice *device, const QByteArray &format) const
1238{
1239 QImageIOHandler *handler = new JXRHandler;
1240 handler->setDevice(device);
1241 handler->setFormat(format);
1242 return handler;
1243}
1244
1245#include "moc_jxr_p.cpp"
QFlags< Capability > Capabilities
QVariant read(const QByteArray &data, int versionOverride=0)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
QAction * copy(const QObject *recvr, const char *slot, QObject *parent)
QByteArray fromRawData(const char *data, qsizetype size)
bool isEmpty() const const
QColorSpace fromIccProfile(const QByteArray &iccProfile)
QByteArray iccProfile() const const
bool isValid() const const
TransferFunction transferFunction() const const
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
Format format() const const
bool hasAlphaChannel() const const
bool isNull() const const
void setText(const QString &key, const QString &text)
qsizetype sizeInBytes() const const
QString text(const QString &key) const const
typedef Transformations
void setDevice(QIODevice *device)
void setFormat(const QByteArray &format)
typedef Capabilities
int allocationLimit()
virtual void close()
bool isOpen() const const
bool isReadable() const const
bool isWritable() const const
virtual bool open(QIODeviceBase::OpenMode mode)
QByteArray peek(qint64 maxSize)
QByteArray read(qint64 maxSize)
qint64 write(const QByteArray &data)
const QChar * constData() const const
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
bool isNull() const const
QString left(qsizetype n) const const
qsizetype size() const const
QByteArray toUtf8() const const
QUuid createUuid()
QString toString(StringFormat mode) const const
QVariant fromValue(T &&value)
int toInt(bool *ok) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 28 2025 11:56:58 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.