KImageFormats

jp2.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.1-or-later
6*/
7
8#include "jp2_p.h"
9#include "scanlineconverter_p.h"
10#include "util_p.h"
11
12#include <QColorSpace>
13#include <QIODevice>
14#include <QImage>
15#include <QImageReader>
16#include <QThread>
17
18#include <openjpeg.h>
19
20/* *** JP2_MAX_IMAGE_WIDTH and JP2_MAX_IMAGE_HEIGHT ***
21 * The maximum size in pixel allowed by the plugin.
22 */
23#ifndef JP2_MAX_IMAGE_WIDTH
24#define JP2_MAX_IMAGE_WIDTH 300000
25#endif
26#ifndef JP2_MAX_IMAGE_HEIGHT
27#define JP2_MAX_IMAGE_HEIGHT JP2_MAX_IMAGE_WIDTH
28#endif
29
30/* *** JP2_ENABLE_HDR ***
31 * Enable float image formats. Disabled by default
32 * due to lack of test images.
33 */
34#ifndef JP2_ENABLE_HDR
35// #define JP2_ENABLE_HDR
36#endif
37
38#define JP2_SUBTYPE QByteArrayLiteral("JP2")
39#define J2K_SUBTYPE QByteArrayLiteral("J2K")
40
41static void error_callback(const char *msg, void *client_data)
42{
43 Q_UNUSED(client_data)
44 qCritical() << msg;
45}
46
47static void warning_callback(const char *msg, void *client_data)
48{
49 Q_UNUSED(client_data)
50 qWarning() << msg;
51}
52
53static void info_callback(const char *msg, void *client_data)
54{
55 Q_UNUSED(client_data)
56 qInfo() << msg;
57}
58
59static OPJ_SIZE_T jp2_read(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data)
60{
61 auto dev = (QIODevice*)p_user_data;
62 if (dev == nullptr) {
63 return OPJ_SIZE_T(-1);
64 }
65 return OPJ_SIZE_T(dev->read((char*)p_buffer, (qint64)p_nb_bytes));
66}
67
68static OPJ_SIZE_T jp2_write(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data)
69{
70 auto dev = (QIODevice*)p_user_data;
71 if (dev == nullptr) {
72 return OPJ_SIZE_T(-1);
73 }
74 return OPJ_SIZE_T(dev->write((char*)p_buffer, (qint64)p_nb_bytes));
75}
76
77static OPJ_BOOL jp2_seek(OPJ_OFF_T p_nb_bytes, void *p_user_data)
78{
79 auto dev = (QIODevice*)p_user_data;
80 if (dev == nullptr) {
81 return OPJ_FALSE;
82 }
83 return dev->seek(p_nb_bytes) ? OPJ_TRUE : OPJ_FALSE;
84}
85
86static OPJ_OFF_T jp2_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data)
87{
88 auto dev = (QIODevice*)p_user_data;
89 if (dev == nullptr) {
90 return OPJ_OFF_T();
91 }
92 if (dev->seek(dev->pos() + p_nb_bytes)) {
93 return p_nb_bytes;
94 }
95 return OPJ_OFF_T();
96}
97
98class JP2HandlerPrivate
99{
100public:
101 JP2HandlerPrivate()
102 : m_jp2_stream(nullptr)
103 , m_jp2_image(nullptr)
104 , m_jp2_codec(nullptr)
105 , m_quality(-1)
106 , m_subtype(JP2_SUBTYPE)
107 {
108
109 }
110 ~JP2HandlerPrivate()
111 {
112 if (m_jp2_image) {
113 opj_image_destroy(m_jp2_image);
114 m_jp2_image = nullptr;
115 }
116 if (m_jp2_stream) {
117 opj_stream_destroy(m_jp2_stream);
118 m_jp2_stream = nullptr;
119 }
120 if (m_jp2_codec) {
121 opj_destroy_codec(m_jp2_codec);
122 m_jp2_codec = nullptr;
123 }
124 }
125
126 /*!
127 * \brief detectDecoderFormat
128 * \param device
129 * \return The codec JP2 found.
130 */
131 OPJ_CODEC_FORMAT detectDecoderFormat(QIODevice *device) const
132 {
133 auto ba = device->peek(32);
134 if (ba.left(12) == QByteArray::fromHex("0000000c6a5020200d0a870a")) {
135 // if (ba.mid(20, 4) == QByteArray::fromHex("6a707820")) // 'jpx '
136 // return OPJ_CODEC_JPX; // JPEG 2000 Part 2 (not supported -> try reading as JP2)
137 return OPJ_CODEC_JP2;
138 }
139 if (ba.left(5) == QByteArray::fromHex("ff4fff5100")) {
140 return OPJ_CODEC_J2K;
141 }
142 return OPJ_CODEC_UNKNOWN;
143 }
144
145 bool createStream(QIODevice *device, bool read)
146 {
147 if (device == nullptr) {
148 return false;
149 }
150 if (m_jp2_stream == nullptr) {
151 m_jp2_stream = opj_stream_default_create(read ? OPJ_TRUE : OPJ_FALSE);
152 }
153 if (m_jp2_stream == nullptr) {
154 return false;
155 }
156 opj_stream_set_user_data(m_jp2_stream, device, nullptr);
157 opj_stream_set_user_data_length(m_jp2_stream, read ? device->size() : 0);
158 opj_stream_set_read_function(m_jp2_stream, jp2_read);
159 opj_stream_set_write_function(m_jp2_stream, jp2_write);
160 opj_stream_set_skip_function(m_jp2_stream, jp2_skip);
161 opj_stream_set_seek_function(m_jp2_stream, jp2_seek);
162 return true;
163 }
164
165 bool isImageValid(const opj_image_t *i) const
166 {
167 return i && i->comps && i->numcomps > 0;
168 }
169
170 void enableThreads(opj_codec_t *codec) const
171 {
172 if (!opj_has_thread_support()) {
173 qInfo() << "OpenJPEG doesn't support multi-threading!";
174 } else if (!opj_codec_set_threads(codec, std::max(1, QThread::idealThreadCount() / 2))) {
175 qWarning() << "Unable to enable multi-threading!";
176 }
177 }
178
179 bool createDecoder(QIODevice *device)
180 {
181 if (m_jp2_codec) {
182 return true;
183 }
184 auto jp2Format = detectDecoderFormat(device);
185 if (jp2Format == OPJ_CODEC_UNKNOWN) {
186 return false;
187 }
188 m_jp2_codec = opj_create_decompress(jp2Format);
189 if (m_jp2_codec == nullptr) {
190 return false;
191 }
192 enableThreads(m_jp2_codec);
193#ifdef QT_DEBUG
194 // opj_set_info_handler(m_jp2_codec, info_callback, nullptr);
195 // opj_set_warning_handler(m_jp2_codec, warning_callback, nullptr);
196#endif
197 opj_set_error_handler(m_jp2_codec, error_callback, nullptr);
198 return true;
199 }
200
201 bool readHeader(QIODevice *device)
202 {
203 if (!createStream(device, true)) {
204 return false;
205 }
206
207 if (m_jp2_image) {
208 return true;
209 }
210
211 if (!createDecoder(device)) {
212 return false;
213 }
214
215 opj_set_default_decoder_parameters(&m_dparameters);
216 if (!opj_setup_decoder(m_jp2_codec, &m_dparameters)) {
217 qCritical() << "Failed to setup JP2 decoder!";
218 return false;
219 }
220
221 if (!opj_read_header(m_jp2_stream, m_jp2_codec, &m_jp2_image)) {
222 qCritical() << "Failed to read JP2 header!";
223 return false;
224 }
225
226 return isImageValid(m_jp2_image);
227 }
228
229 template<class T>
230 bool jp2ToImage(QImage *img) const
231 {
232 Q_ASSERT(img->depth() == 8 * sizeof(T) || img->depth() == 32 * sizeof(T));
233 for (qint32 c = 0, cc = m_jp2_image->numcomps; c < cc; ++c) {
234 auto cs = cc == 1 ? 1 : 4;
235 auto &&jc = m_jp2_image->comps[c];
236 if (jc.data == nullptr)
237 return false;
238 if (qint32(jc.w) != img->width() || qint32(jc.h) != img->height())
239 return false;
240
241 // discriminate between int and float (avoid complicating things by creating classes with template specializations)
242 if (std::numeric_limits<T>::is_integer) {
243 auto divisor = 1;
244 if (jc.prec > sizeof(T) * 8) {
245 // convert to the wanted precision (e.g. 16-bit -> 8-bit: divisor = 65535 / 255 = 257)
246 divisor = std::max(1, int(((1ll << jc.prec) - 1) / ((1ll << (sizeof(T) * 8)) - 1)));
247 }
248 for (qint32 y = 0, h = img->height(); y < h; ++y) {
249 auto ptr = reinterpret_cast<T *>(img->scanLine(y));
250 for (qint32 x = 0, w = img->width(); x < w; ++x) {
251 qint32 v = jc.data[y * w + x] / divisor;
252 if (jc.sgnd) // never seen
253 v -= std::numeric_limits<typename std::make_signed<T>::type>::min();
254 *(ptr + x * cs + c) = std::clamp(v, qint32(std::numeric_limits<T>::lowest()), qint32(std::numeric_limits<T>::max()));
255 }
256 }
257 } else { // float
258 for (qint32 y = 0, h = img->height(); y < h; ++y) {
259 auto ptr = reinterpret_cast<T *>(img->scanLine(y));
260 for (qint32 x = 0, w = img->width(); x < w; ++x) {
261 *(ptr + x * cs + c) = jc.data[y * w + x];
262 }
263 }
264 }
265 }
266 return true;
267 }
268
269 template<class T>
270 void alphaFix(QImage *img) const
271 {
272 if (m_jp2_image->numcomps == 3) {
273 Q_ASSERT(img->depth() == 32 * sizeof(T));
274 for (qint32 y = 0, h = img->height(); y < h; ++y) {
275 auto ptr = reinterpret_cast<T *>(img->scanLine(y));
276 for (qint32 x = 0, w = img->width(); x < w; ++x) {
277 *(ptr + x * 4 + 3) = std::numeric_limits<T>::is_iec559 ? 1 : std::numeric_limits<T>::max();
278 }
279 }
280 }
281 }
282
283 QImage readImage(QIODevice *device)
284 {
285 if (!readHeader(device)) {
286 return {};
287 }
288
289 auto img = imageAlloc(size(), format());
290 if (img.isNull()) {
291 return {};
292 }
293
294 if (!opj_decode(m_jp2_codec, m_jp2_stream, m_jp2_image)) {
295 qCritical() << "Failed to decoding JP2 image!";
296 return {};
297 }
298
299 auto f = img.format();
301 if (!jp2ToImage<quint32>(&img))
302 return {};
303 alphaFix<float>(&img);
305 if (!jp2ToImage<quint16>(&img))
306 return {};
307 alphaFix<quint16>(&img);
308 } else {
309 if (!jp2ToImage<quint8>(&img))
310 return {};
311 alphaFix<quint8>(&img);
312 }
313
314 img.setColorSpace(colorSpace());
315
316 return img;
317 }
318
319 bool checkSizeLimits(qint32 width, qint32 height, qint32 nchannels) const
320 {
321 if (width > JP2_MAX_IMAGE_WIDTH || height > JP2_MAX_IMAGE_HEIGHT || width < 1 || height < 1) {
322 qCritical() << "Maximum image size is limited to" << JP2_MAX_IMAGE_WIDTH << "x" << JP2_MAX_IMAGE_HEIGHT << "pixels";
323 return false;
324 }
325
326 // OpenJPEG uses a shadow copy @32-bit/channel so we need to do a check
327 auto maxBytes = qint64(QImageReader::allocationLimit()) * 1024 * 1024;
328 auto neededBytes = qint64(width) * height * nchannels * 4;
329 if (maxBytes > 0 && neededBytes > maxBytes) {
330 qCritical() << "Allocation limit set to" << (maxBytes / 1024 / 1024) << "MiB but" << (neededBytes / 1024 / 1024) << "MiB are needed!";
331 return false;
332 }
333
334 return true;
335 }
336
337 bool checkSizeLimits(const QSize &size, qint32 nchannels) const
338 {
339 return checkSizeLimits(size.width(), size.height(), nchannels);
340 }
341
342 QSize size() const
343 {
344 QSize sz;
345 if (isImageValid(m_jp2_image)) {
346 auto &&c0 = m_jp2_image->comps[0];
347 auto tmp = QSize(c0.w, c0.h);
348 if (checkSizeLimits(tmp, m_jp2_image->numcomps))
349 sz = tmp;
350 }
351 return sz;
352 }
353
354 QImage::Format format() const
355 {
356 auto fmt = QImage::Format_Invalid;
357 if (isImageValid(m_jp2_image)) {
358 auto &&c0 = m_jp2_image->comps[0];
359 auto prec = c0.prec;
360 for (quint32 c = 1; c < m_jp2_image->numcomps; ++c) {
361 auto &&cc = m_jp2_image->comps[c];
362 if (cc.prec != prec)
363 prec = 0;
364 }
365 auto jp2cs = m_jp2_image->color_space;
366 if (jp2cs == OPJ_CLRSPC_UNKNOWN || jp2cs == OPJ_CLRSPC_UNSPECIFIED) {
367 if (m_jp2_image->numcomps == 1)
368 jp2cs = OPJ_CLRSPC_GRAY;
369 else
370 jp2cs = OPJ_CLRSPC_SRGB;
371 }
372
373 // *** IMPORTANT: To keep the code simple, the returned formats must have 1 or 4 channels (8/16/32-bits)
374 if (jp2cs == OPJ_CLRSPC_SRGB) {
375 if (m_jp2_image->numcomps == 3 || m_jp2_image->numcomps == 4) {
376 auto hasAlpha = m_jp2_image->numcomps == 4;
377 if (prec == 8)
379 else if (prec == 16)
381#ifdef JP2_ENABLE_HDR
382 else if (prec == 32) // not sure about this
384#endif
385 }
386 } else if (jp2cs == OPJ_CLRSPC_GRAY) {
387 if (m_jp2_image->numcomps == 1) {
388 if (prec == 8)
390 else if (prec == 16)
392 }
393 } else if (jp2cs == OPJ_CLRSPC_CMYK) {
394 if (m_jp2_image->numcomps == 4) {
395#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
396 if (prec == 8 || prec == 16)
397 fmt = QImage::Format_CMYK8888;
398#endif
399 }
400 }
401 }
402 return fmt;
403 }
404
405 QColorSpace colorSpace() const
406 {
407 QColorSpace cs;
408 if (m_jp2_image) {
409 if (m_jp2_image->icc_profile_buf && m_jp2_image->icc_profile_len > 0) {
410 cs = QColorSpace::fromIccProfile(QByteArray((char *)m_jp2_image->icc_profile_buf, m_jp2_image->icc_profile_len));
411 }
412 if (!cs.isValid()) {
413 if (m_jp2_image->color_space == OPJ_CLRSPC_SRGB)
414 cs = QColorSpace(QColorSpace::SRgb);
415 }
416 }
417 return cs;
418 }
419
420 /*!
421 * \brief isSupported
422 * \return True if the current JP2 image i ssupported by the plugin. Otherwise false.
423 */
424 bool isSupported() const
425 {
426 return format() != QImage::Format_Invalid;
427 }
428
429 QByteArray subType() const
430 {
431 return m_subtype;
432 }
433 void setSubType(const QByteArray &type)
434 {
435 m_subtype = type;
436 }
437
438 qint32 quality() const
439 {
440 return m_quality;
441 }
442 void setQuality(qint32 quality)
443 {
444 m_quality = std::clamp(quality, -1, 100);
445 }
446
447 /*!
448 * \brief encoderFormat
449 * \return The encoder format set by subType.
450 */
451 OPJ_CODEC_FORMAT encoderFormat() const
452 {
453 return subType() == J2K_SUBTYPE ? OPJ_CODEC_J2K : OPJ_CODEC_JP2;
454 }
455
456 bool imageToJp2(const QImage &image)
457 {
458 auto ncomp = image.hasAlphaChannel() ? 4 : 3;
459 auto prec = 8;
460 auto cs = OPJ_CLRSPC_SRGB;
461 auto convFormat = image.format();
462 auto isFloat = false;
463
464 switch (image.format()) {
469 ncomp = 1;
470 cs = OPJ_CLRSPC_GRAY;
471 convFormat = QImage::Format_Grayscale8;
472 break;
474 if (image.isGrayscale()) {
475 ncomp = 1;
476 cs = OPJ_CLRSPC_GRAY;
477 convFormat = QImage::Format_Grayscale8;
478 } else {
479 ncomp = 4;
480 cs = OPJ_CLRSPC_SRGB;
481 convFormat = QImage::Format_RGBA8888;
482 }
483 break;
485 ncomp = 1;
486 prec = 16;
487 cs = OPJ_CLRSPC_GRAY;
488 convFormat = QImage::Format_Grayscale16;
489 break;
492 isFloat = true;
493#ifdef JP2_ENABLE_HDR
494 prec = 32;
495 convFormat = QImage::Format_RGBX32FPx4;
496 cs = OPJ_CLRSPC_UNSPECIFIED;
497 break;
498#else
499 Q_FALLTHROUGH();
500#endif
504 prec = 16;
505 convFormat = QImage::Format_RGBX64;
506 break;
507
512 isFloat = true;
513#ifdef JP2_ENABLE_HDR
514 prec = 32;
515 convFormat = QImage::Format_RGBA32FPx4;
516 cs = OPJ_CLRSPC_UNSPECIFIED;
517 break;
518#else
519 Q_FALLTHROUGH();
520#endif
525 prec = 16;
526 convFormat = QImage::Format_RGBA64;
527 break;
528#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
529 case QImage::Format_CMYK8888: // requires OpenJPEG 2.5.3+
530 if (strcmp(opj_version(), "2.5.3") >= 0) {
531 ncomp = 4;
532 cs = OPJ_CLRSPC_CMYK;
533 break;
534 } else {
535 Q_FALLTHROUGH();
536 }
537#endif
538 default:
539 if (image.depth() > 32) {
540 qWarning() << "The image is saved losing precision!";
541 }
542 convFormat = ncomp == 4 ? QImage::Format_RGBA8888 : QImage::Format_RGBX8888;
543 break;
544 }
545
546 if (!checkSizeLimits(image.size(), ncomp)) {
547 return false;
548 }
549
550 opj_set_default_encoder_parameters(&m_cparameters);
551 m_cparameters.cod_format = encoderFormat();
552 m_cparameters.tile_size_on = 1;
553 m_cparameters.cp_tdx = 1024;
554 m_cparameters.cp_tdy = 1024;
555
556 if (m_quality > -1 && m_quality < 100) {
557 m_cparameters.irreversible = 1;
558 m_cparameters.tcp_numlayers = 1;
559 m_cparameters.cp_disto_alloc = 1;
560 m_cparameters.tcp_rates[0] = 100.0 - (m_quality < 10 ? m_quality : 10 + (std::log10(m_quality) - 1) * 90);
561 }
562
563 std::unique_ptr<opj_image_cmptparm_t> cmptparm(new opj_image_cmptparm_t[ncomp]);
564 for (int i = 0; i < ncomp; ++i) {
565 auto &&p = cmptparm.get() + i;
566 memset(p, 0, sizeof(opj_image_cmptparm_t));
567 p->dx = m_cparameters.subsampling_dx;
568 p->dy = m_cparameters.subsampling_dy;
569 p->w = image.width();
570 p->h = image.height();
571 p->x0 = 0;
572 p->y0 = 0;
573 p->prec = prec;
574 p->sgnd = 0;
575 }
576
577 m_jp2_image = opj_image_create(ncomp, cmptparm.get(), cs);
578 if (m_jp2_image == nullptr) {
579 return false;
580 }
581 if (int(m_jp2_image->numcomps) != ncomp) {
582 return false; // paranoia
583 }
584 m_jp2_image->x1 = image.width();
585 m_jp2_image->y1 = image.height();
586
587 ScanLineConverter scl(convFormat);
588 if (prec < 32 && isFloat) {
589 scl.setDefaultSourceColorSpace(QColorSpace(QColorSpace::SRgbLinear));
590 }
591 if (cs == OPJ_CLRSPC_SRGB) {
592 scl.setTargetColorSpace(QColorSpace(QColorSpace::SRgb));
593 } else {
594 scl.setTargetColorSpace(image.colorSpace());
595 }
596 for (qint32 c = 0; c < ncomp; ++c) {
597 auto &&comp = m_jp2_image->comps[c];
598 auto mul = ncomp == 1 ? 1 : 4;
599 for (qint32 y = 0, h = image.height(); y < h; ++y) {
600 if (prec == 8) {
601 auto ptr = reinterpret_cast<const quint8 *>(scl.convertedScanLine(image, y));
602 for (qint32 x = 0, w = image.width(); x < w; ++x)
603 comp.data[y * w + x] = ptr[x * mul + c];
604 } else if (prec == 16) {
605 auto ptr = reinterpret_cast<const quint16 *>(scl.convertedScanLine(image, y));
606 for (qint32 x = 0, w = image.width(); x < w; ++x)
607 comp.data[y * w + x] = ptr[x * mul + c];
608 } else if (prec == 32) {
609 auto ptr = reinterpret_cast<const quint32 *>(scl.convertedScanLine(image, y));
610 for (qint32 x = 0, w = image.width(); x < w; ++x)
611 comp.data[y * w + x] = ptr[x * mul + c];
612 }
613 }
614 }
615
616 // With SRGB, Gray and CMYK, writing the colorspace gives an assert
617 if (cs == OPJ_CLRSPC_UNKNOWN || cs == OPJ_CLRSPC_UNSPECIFIED) {
618 auto colorSpace = scl.targetColorSpace().iccProfile();
619 if (!colorSpace.isEmpty()) {
620 m_jp2_image->icc_profile_buf = reinterpret_cast<OPJ_BYTE *>(malloc(colorSpace.size()));
621 if (m_jp2_image->icc_profile_buf) {
622 memcpy(m_jp2_image->icc_profile_buf, colorSpace.data(), colorSpace.size());
623 m_jp2_image->icc_profile_len = colorSpace.size();
624 }
625 }
626 }
627
628 return true;
629 }
630
631 bool writeImage(QIODevice *device, const QImage &image)
632 {
633 if (!imageToJp2(image)) {
634 qCritical() << "Error while creating JP2 image!";
635 return false;
636 }
637
638 std::unique_ptr<opj_codec_t, std::function<void(opj_codec_t *)>> codec(opj_create_compress(encoderFormat()), opj_destroy_codec);
639 if (codec == nullptr) {
640 qCritical() << "Error while creating encoder!";
641 return false;
642 }
643 enableThreads(codec.get());
644#ifdef QT_DEBUG
645 // opj_set_info_handler(m_jp2_codec, info_callback, nullptr);
646 // opj_set_warning_handler(m_jp2_codec, warning_callback, nullptr);
647#endif
648 opj_set_error_handler(m_jp2_codec, error_callback, nullptr);
649
650 if (!opj_setup_encoder(codec.get(), &m_cparameters, m_jp2_image)) {
651 return false;
652 }
653
654 if (!createStream(device, false)) {
655 return false;
656 }
657
658 if (!opj_start_compress(codec.get(), m_jp2_image, m_jp2_stream)) {
659 return false;
660 }
661 if (!opj_encode(codec.get(), m_jp2_stream)) {
662 return false;
663 }
664 if (!opj_end_compress(codec.get(), m_jp2_stream)) {
665 return false;
666 }
667
668 return true;
669 }
670
671private:
672 // common
673 opj_stream_t *m_jp2_stream;
674
675 opj_image_t *m_jp2_image;
676
677 // read
678 opj_codec_t *m_jp2_codec;
679
680 opj_dparameters_t m_dparameters;
681
682 // write
683 opj_cparameters_t m_cparameters;
684
685 qint32 m_quality;
686
687 QByteArray m_subtype;
688};
689
690
691JP2Handler::JP2Handler()
693 , d(new JP2HandlerPrivate)
694{
695}
696
697bool JP2Handler::canRead() const
698{
699 if (canRead(device())) {
700 setFormat("jp2");
701 return true;
702 }
703 return false;
704}
705
706bool JP2Handler::canRead(QIODevice *device)
707{
708 if (!device) {
709 qWarning("JP2Handler::canRead() called with no device");
710 return false;
711 }
712
713 if (device->isSequential()) {
714 return false;
715 }
716
717 JP2HandlerPrivate handler;
718 return handler.detectDecoderFormat(device) != OPJ_CODEC_UNKNOWN;
719}
720
721bool JP2Handler::read(QImage *image)
722{
723 auto dev = device();
724 if (dev == nullptr) {
725 return false;
726 }
727 auto img = d->readImage(dev);
728 if (img.isNull()) {
729 return false;
730 }
731 *image = img;
732 return true;
733}
734
735bool JP2Handler::write(const QImage &image)
736{
737 if (image.isNull()) {
738 return false;
739 }
740 auto dev = device();
741 if (dev == nullptr) {
742 return false;
743 }
744 return d->writeImage(dev, image);
745}
746
747bool JP2Handler::supportsOption(ImageOption option) const
748{
749 if (option == QImageIOHandler::Size) {
750 return true;
751 }
752 if (option == QImageIOHandler::ImageFormat) {
753 return true;
754 }
755 if (option == QImageIOHandler::SubType) {
756 return true;
757 }
759 return true;
760 }
761 if (option == QImageIOHandler::Quality) {
762 return true;
763 }
764 return false;
765}
766
767void JP2Handler::setOption(ImageOption option, const QVariant &value)
768{
769 if (option == QImageIOHandler::SubType) {
770 auto st = value.toByteArray();
771 if (this->option(QImageIOHandler::SupportedSubTypes).toList().contains(st))
772 d->setSubType(st);
773 }
774 if (option == QImageIOHandler::Quality) {
775 auto ok = false;
776 auto q = value.toInt(&ok);
777 if (ok) {
778 d->setQuality(q);
779 }
780 }
781}
782
783QVariant JP2Handler::option(ImageOption option) const
784{
785 QVariant v;
786
787 if (option == QImageIOHandler::Size) {
788 if (d->readHeader(device())) {
789 v = d->size();
790 }
791 }
792
793 if (option == QImageIOHandler::ImageFormat) {
794 if (d->readHeader(device())) {
795 v = d->format();
796 }
797 }
798
799 if (option == QImageIOHandler::SubType) {
800 v = d->subType();
801 }
802
804 v = QVariant::fromValue(QList<QByteArray>() << JP2_SUBTYPE << J2K_SUBTYPE);
805 }
806
807 if (option == QImageIOHandler::Quality) {
808 v = d->quality();
809 }
810
811 return v;
812}
813
814QImageIOPlugin::Capabilities JP2Plugin::capabilities(QIODevice *device, const QByteArray &format) const
815{
816 if (format == "jp2" || format == "j2k") {
817 return Capabilities(CanRead | CanWrite);
818 }
819 // NOTE: JPF is the default extension of Photoshop for JP2 files.
820 if (format == "jpf") {
821 return Capabilities(CanRead);
822 }
823 if (!format.isEmpty()) {
824 return {};
825 }
826 if (!device->isOpen()) {
827 return {};
828 }
829
830 Capabilities cap;
831 if (device->isReadable() && JP2Handler::canRead(device)) {
832 cap |= CanRead;
833 }
834 if (device->isWritable()) {
835 cap |= CanWrite;
836 }
837 return cap;
838}
839
840QImageIOHandler *JP2Plugin::create(QIODevice *device, const QByteArray &format) const
841{
842 QImageIOHandler *handler = new JP2Handler;
843 handler->setDevice(device);
844 handler->setFormat(format);
845 return handler;
846}
847
848#include "moc_jp2_p.cpp"
QFlags< Capability > Capabilities
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
QByteArray fromHex(const QByteArray &hexEncoded)
bool isEmpty() const const
QColorSpace fromIccProfile(const QByteArray &iccProfile)
bool isValid() const const
Format_RGBA32FPx4
QColorSpace colorSpace() const const
int depth() const const
Format format() const const
bool hasAlphaChannel() const const
int height() const const
bool isGrayscale() const const
bool isNull() const const
uchar * scanLine(int i)
void setColorSpace(const QColorSpace &colorSpace)
QSize size() const const
int width() const const
void setDevice(QIODevice *device)
void setFormat(const QByteArray &format)
typedef Capabilities
int allocationLimit()
bool isOpen() const const
bool isReadable() const const
virtual bool isSequential() const const
bool isWritable() const const
QByteArray peek(qint64 maxSize)
virtual qint64 size() const const
int idealThreadCount()
QVariant fromValue(T &&value)
QByteArray toByteArray() const const
int toInt(bool *ok) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:53:42 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.