Coverage Report

Created: 2026-02-14 07:09

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