/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" |