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

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