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

source code of kimageformats/src/imageformats/jp2.cpp