Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qjp2handler.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2016 Petroules Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:critical reason:data-parser
5
7
8#include "qimage.h"
9#include "qvariant.h"
10#include "qcolor.h"
11#include "qimagereader.h"
12
13#include <jasper/jasper.h>
14#include <math.h> // for pow
15
16QT_BEGIN_NAMESPACE
17
18class QJp2HandlerPrivate
19{
20 Q_DECLARE_PUBLIC(QJp2Handler)
21 Q_DISABLE_COPY(QJp2HandlerPrivate)
22public:
23 int writeQuality;
24 QByteArray subType;
25 QJp2Handler *q_ptr;
26 QJp2HandlerPrivate(QJp2Handler *q_ptr);
27};
28
30
31/*
32 \class Jpeg2000JasperReader
33 \brief Jpeg2000JasperReader implements reading and writing of JPEG 2000
34 image files.
35
36 \internal
37
38 This class is designed to be used together with the an QImageIO IOHandler,
39 and it should probably not be necessary to instantiate it directly.
40
41 Internally it used the Jasper library for coding the image data.
42*/
44{
45public:
46 Jpeg2000JasperReader(QIODevice *iod, const SubFormat format = Jp2Format);
47
49
50 bool read(QImage *pImage);
51 bool write(const QImage &image, int quality);
52private:
53 typedef void (Jpeg2000JasperReader::*ScanlineFunc)(jas_seqent_t** const, uchar*);
54 typedef void (Jpeg2000JasperReader::*ScanlineFuncWrite)(jas_matrix_t**, uchar*);
55
56 void copyJasperQt(ScanlineFunc scanlinecopier);
57 void copyJasperQtGeneric();
58 void copyScanlineJasperQtRGB(jas_seqent_t ** const jasperRow, uchar *qtScanLine);
59 void copyScanlineJasperQtRGBA(jas_seqent_t ** const jasperRow, uchar *qtScanLine);
60 void copyScanlineJasperQtGray(jas_seqent_t ** const jasperRow, uchar *qtScanLine);
61 void copyScanlineJasperQtGrayA(jas_seqent_t ** const jasperRow, uchar *qtScanLine);
62
63 void copyQtJasper(const ScanlineFuncWrite scanlinecopier);
64 void copyScanlineQtJasperRGB(jas_matrix_t ** jasperRow, uchar *qtScanLine);
65 void copyScanlineQtJasperRGBA(jas_matrix_t ** jasperRow, uchar *qtScanLine);
66 void copyScanlineQtJasperColormapRGB(jas_matrix_t ** jasperRow, uchar *qtScanLine);
67 void copyScanlineQtJasperColormapRGBA(jas_matrix_t ** jasperRow, uchar *qtScanLine);
68 void copyScanlineQtJasperColormapGrayscale(jas_matrix_t ** jasperRow, uchar *qtScanLine);
69 void copyScanlineQtJasperColormapGrayscaleA(jas_matrix_t ** jasperRow, uchar *qtScanLine);
70
71 bool attemptColorspaceChange(int wantedColorSpace);
72 bool createJasperMatrix(jas_matrix_t **&matrix);
73 bool freeJasperMatrix(jas_matrix_t **matrix);
74 void printColorSpaceError();
75 jas_image_cmptparm_t createComponentMetadata(const int width, const int height);
76 jas_image_t *newRGBAImage(const int width, const int height, bool alpha);
77 jas_image_t *newGrayscaleImage(const int width, const int height, bool alpha);
78 bool decodeColorSpace(int clrspc, QString &family, QString &specific);
79 void printMetadata(jas_image_t *image);
80
81 bool jasperOk;
82
83 QIODevice *ioDevice;
84 QImage qtImage;
85 SubFormat format;
86
87 // Qt image properties
88 int qtWidth;
89 int qtHeight;
90 int qtDepth;
91 int qtNumComponents;
92
93 jas_image_t *jasper_image;
94 // jasper image properties
95 int jasNumComponents;
96 int jasComponentPrecicion[4];
97 int computedComponentWidth ;
98 int computedComponentHeight;
99 int computedComponentHorizontalSubsampling;
100 int computedComponentVerticalSubsampling;
101 int jasperColorspaceFamily;
102 // maps color to component (ex: colorComponentMapping[RED]
103 // gives the component that contains the red color)
104 int colorComponentMapping[4];
105 bool hasAlpha;
106};
107
108QJp2HandlerPrivate::QJp2HandlerPrivate(QJp2Handler *q_ptr)
109 : writeQuality(100), subType("jp2"), q_ptr(q_ptr)
110{
111}
112
113/*!
114 \class QJp2Handler
115 \brief The QJp2Handler class provides support for reading and writing
116 JPEG 2000 image files with the Qt plugin system.
117 Currently, it only supports dynamically-loaded plugins.
118
119 JPEG files comes in two subtypes: the JPEG 2000 file format (\c .jp2) and
120 the JPEG 2000 code stream format (\c .j2k, \c .jpc, or \c .j2c).
121 QJp2Handler can read and write both types.
122
123 To select a subtype, use the setOption() function with the
124 QImageIOHandler::SubType option and a parameter value of "jp2" or "j2k".
125
126 To set the image quality when writing, you can use setOption() with the
127 QImageIOHandler::Quality option, or QImageWriter::setQuality() if your are
128 using a QImageWriter object to write the image. The image quality is
129 specified as an int in the range 0 to 100. 0 means maximum compression and
130 99 means minimum compression. 100 selects lossless encoding, and this is the
131 default value.
132
133 The JPEG handler is only available as a plugin,
134 and this enables you to use normal QImage and QPixmap functions to read and
135 write images. For example:
136
137 \code
138 myLabel->setPixmap(QPixmap("myimage.jp2"));
139
140 QPixmap myPixmap;
141 myPixmap.save("myimage.jp2", "JP2");
142 \endcode
143*/
144
145/*!
146 Constructs an instance of QJp2Handler.
147*/
149 : d_ptr(new QJp2HandlerPrivate(this))
150{
151}
152
153/*!
154 Destructor for QJp2Handler.
155*/
157{
158
159}
160
161/*!
162 Verifies if some values (magic bytes) are set as expected in the
163 header of the file. If the magic bytes were found, we assume that we
164 can read the file. The function will assume that the \a iod is
165 pointing to the beginning of the JPEG 2000 header. (i.e. it will for
166 instance not seek to the beginning of a file before reading).
167
168 If \a subType is not 0, it will contain "jp2" or "j2k" upon
169 successful return.
170*/
171bool QJp2Handler::canRead(QIODevice *iod, QByteArray *subType)
172{
173 bool bCanRead = false;
174 if (iod) {
175 const QByteArray header = iod->peek(12);
176 if (header.startsWith(QByteArrayLiteral("\000\000\000\fjP \r\n\207\n"))) {
177 // Jp2 is the JPEG 2000 file format
178 bCanRead = true;
179 if (subType)
180 *subType = QByteArray("jp2");
181 } else if (header.startsWith(QByteArrayLiteral("\377\117\377\121\000"))) {
182 // J2c is the JPEG 2000 code stream
183 bCanRead = true;
184 if (subType)
185 *subType = QByteArray("j2k");
186 }
187 }
188 return bCanRead;
189}
190
191/*! \reimp
192*/
193bool QJp2Handler::canRead() const
194{
195 QByteArray subType;
196 if (canRead(device(), &subType)) {
197 setFormat(subType);
198 return true;
199 }
200 return false;
201}
202
203/*! \reimp
204*/
205bool QJp2Handler::read(QImage *image)
206{
208 return reader.read(image);
209}
210
211/*! \reimp
212*/
213bool QJp2Handler::write(const QImage &image)
214{
215 Q_D(const QJp2Handler);
216 SubFormat subFormat;
217 if (d->subType == QByteArray("jp2"))
218 subFormat = Jp2Format;
219 else
220 subFormat = J2kFormat;
221
222 Jpeg2000JasperReader writer(device(), subFormat);
223 return writer.write(image, d->writeQuality);
224}
225
226/*!
227 Get the value associated with \a option.
228 \sa setOption()
229*/
230QVariant QJp2Handler::option(ImageOption option) const
231{
232 Q_D(const QJp2Handler);
233 if (option == Quality) {
234 return QVariant(d->writeQuality);
235 } else if (option == SubType) {
236 return QVariant(d->subType);
237 }
238 return QVariant();
239}
240
241/*!
242 The JPEG 2000 handler supports two options.
243 Set \a option to QImageIOHandler::Quality to balance between quality and the
244 compression level, where \a value should be an integer in the interval
245 [0-100]. 0 is maximum compression (low quality) and 100 is lossless
246 compression (high quality).
247
248 Set \a option to QImageIOHandler::Subtype to choose the subtype,
249 where \a value should be a string equal to either "jp2" or "j2k"
250 \sa option()
251*/
252void QJp2Handler::setOption(ImageOption option, const QVariant &value)
253{
254 Q_D(QJp2Handler);
255 if (option == Quality) {
256 bool ok;
257 const int quality = value.toInt(&ok);
258 if (ok)
259 d->writeQuality = quality;
260 } else if (option == SubType) {
261 const QByteArray subTypeCandidate = value.toByteArray();
262 // Test for default Jpeg2000 file format (jp2), or stream format (j2k).
263 if (subTypeCandidate == QByteArrayLiteral("jp2") ||
264 subTypeCandidate == QByteArrayLiteral("j2k"))
265 d->subType = subTypeCandidate;
266 }
267}
268
269/*!
270 This function will return true if \a option is set to either
271 QImageIOHandler::Quality or QImageIOHandler::Subtype.
272*/
273bool QJp2Handler::supportsOption(ImageOption option) const
274{
275 return (option == Quality || option == SubType);
276}
277
278/*!
279 Automatic resource handling for a jas_image_t*.
280*/
282{
283public:
284 // Take reference to the pointer here, because the pointer
285 // may change when we change color spaces.
286 ScopedJasperImage(jas_image_t *&image):image(image) { }
287 ~ScopedJasperImage() { jas_image_destroy(image); }
288private:
289 jas_image_t *&image;
290};
291
292/*! \internal
293 Construct a Jpeg2000JasperReader using the provided \a imageIO.
294 Note that currently the jasper library is initialized in this constructor,
295 (and freed in the destructor) which means that:
296 - Only one instance of this class may exist at one time
297 - No thread safety
298*/
300 : jasperOk(true), ioDevice(iod), format(format), hasAlpha(false)
301{
302#if JAS_VERSION_MAJOR < 3
303 if (jas_init()) {
304 jasperOk = false;
305 qDebug("Jasper Library initialization failed");
306 }
307#else
308 jas_conf_clear();
309#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
310 if (QImageReader::allocationLimit() > 0)
311 jas_conf_set_max_mem_usage(qsizetype(QImageReader::allocationLimit()) * 1024 * 1024);
312#else
313 // 128MB seems to be enough.
314 jas_conf_set_max_mem_usage(128 * 1024 * 1024);
315#endif
316 if (jas_init_library()) {
317 jasperOk = false;
318 qDebug("Jasper library initialization failed");
319 }
320 if (jas_init_thread()) {
321 jas_cleanup_library();
322 jasperOk = false;
323 qDebug("Jasper thread initialization failed");
324 }
325#endif
326}
327
329{
330#if JAS_VERSION_MAJOR < 3
331 if (jasperOk)
332 jas_cleanup();
333#else
334 if (jasperOk) {
335 if (jas_cleanup_thread()) {
336 qDebug("Jasper thread cleanup failed");
337 }
338 if (jas_cleanup_library()) {
339 qDebug("Jasper library cleanup failed");
340 }
341 }
342#endif
343}
344
345/*! \internal
346 Opens the file data and attempts to decode it using the Jasper library.
347 Returns true if successful, false on failure
348*/
349bool Jpeg2000JasperReader::read(QImage *pImage)
350{
351 if (!jasperOk)
352 return false;
353
354 /*
355 Reading proceeds approximately as follows:
356 1. Open stream and decode using Jasper
357 2. Get image metadata
358 3. Change colorspace if necessary
359 4. Create a QImage of the appropriate type (32-bit for RGB,
360 8-bit for grayscale)
361 5. Copy image data from Jasper to the QImage
362
363 When copying the image data from the Jasper data structures to the
364 QImage, a generic copy function (copyJasperQt) iterates through the
365 scanlines and calls the provided (via the scanlineCopier argument)
366 scanline copy function for each scanline. The scanline copy function
367 selected according to image metadata such as color space and the
368 presence of an alpha channel.
369 */
370 QByteArray fileContents = ioDevice->readAll();
371 jas_stream_t *imageData = jas_stream_memopen(fileContents.data(),
372 fileContents.size());
373 jasper_image = jas_image_decode(imageData, jas_image_getfmt(imageData), 0);
374 jas_stream_close(imageData);
375 if (!jasper_image) {
376 qDebug("Jasper library can't decode Jpeg2000 image data");
377 return false;
378 }
379 ScopedJasperImage scopedImage(jasper_image);
380 //printMetadata(jasper_image);
381
382 qtWidth = jas_image_width(jasper_image);
383 qtHeight = jas_image_height(jasper_image);
384 jasNumComponents = jas_image_numcmpts(jasper_image);
385 jasperColorspaceFamily = jas_clrspc_fam(jas_image_clrspc(jasper_image));
386
387 bool needColorspaceChange = false;
388 if (jasperColorspaceFamily != JAS_CLRSPC_FAM_RGB &&
389 jasperColorspaceFamily != JAS_CLRSPC_FAM_GRAY)
390 needColorspaceChange = true;
391
392 // Get per-component data
393 int c;
394 for (c = 0; c < jasNumComponents; ++c) {
395 jasComponentPrecicion[c] = jas_image_cmptprec(jasper_image, c);
396
397 // Test for precision
398 if (jasComponentPrecicion[c] > 8 || jasComponentPrecicion[c] < 8)
399 needColorspaceChange = true;
400
401 // Test for subsampling
402 if (jas_image_cmpthstep(jasper_image, c) != 1 ||
403 jas_image_cmptvstep(jasper_image, c) != 1)
404 needColorspaceChange = true;
405
406 // Test for signed components
407 if (jas_image_cmptsgnd(jasper_image, c) != 0)
408 needColorspaceChange = true;
409 }
410
411 /*
412 If we encounter a different color space than RGB
413 (such as XYZ or YCbCr) we change that to RGB.
414 Also, if any component has "funny" metadata (such as precicion != 8 bits
415 or subsampling != 1) we also do a colorspace
416 change in order to convert it to something we can load.
417 */
418
419 bool decodeOk = true;
420 if (needColorspaceChange)
421 decodeOk = attemptColorspaceChange(JAS_CLRSPC_SRGB);
422
423 if (!decodeOk) {
424 printColorSpaceError();
425 return false;
426 }
427
428 // Image metadata may have changed, get from Jasper.
429 qtWidth = jas_image_width(jasper_image);
430 qtHeight = jas_image_height(jasper_image);
431 jasNumComponents = jas_image_numcmpts(jasper_image);
432 jasperColorspaceFamily = jas_clrspc_fam(jas_image_clrspc(jasper_image));
433 for (c = 0; c < jasNumComponents; ++c) {
434 jasComponentPrecicion[c] = jas_image_cmptprec(jasper_image, c);
435 }
436
437 if (jasperColorspaceFamily != JAS_CLRSPC_FAM_RGB &&
438 jasperColorspaceFamily != JAS_CLRSPC_FAM_GRAY) {
439 qDebug("The Qt JPEG 2000 reader was unable to convert colorspace to RGB or grayscale");
440 return false;
441 }
442
443 // If a component has a subsampling factor != 1, we can't trust
444 // jas_image_height/width, so we need to figure it out ourselves
445 bool oddComponentSubsampling = false;
446 for (c = 0; c < jasNumComponents; ++c) {
447 if (jas_image_cmpthstep(jasper_image, c) != 1 ||
448 jas_image_cmptvstep(jasper_image, c) != 1) {
449 oddComponentSubsampling = true;
450 }
451 }
452
453 if (oddComponentSubsampling) {
454 // Check if all components have the same vertical/horizontal dim and
455 // subsampling
456 computedComponentWidth = jas_image_cmptwidth(jasper_image, 0);
457 computedComponentHeight = jas_image_cmptheight(jasper_image, 0);
458 computedComponentHorizontalSubsampling = jas_image_cmpthstep(jasper_image, 0);
459 computedComponentVerticalSubsampling = jas_image_cmptvstep(jasper_image, 0);
460
461 for (c = 1; c < jasNumComponents; ++c) {
462 if (computedComponentWidth != jas_image_cmptwidth(jasper_image, c) ||
463 computedComponentWidth != jas_image_cmptwidth(jasper_image, c) ||
464 computedComponentHorizontalSubsampling != jas_image_cmpthstep(jasper_image, c) ||
465 computedComponentVerticalSubsampling != jas_image_cmptvstep(jasper_image, c)) {
466 qDebug("The Qt JPEG 2000 reader does not support images where "
467 "component geometry differs from image geometry");
468 return false;
469 }
470 }
471 qtWidth = computedComponentWidth * computedComponentHorizontalSubsampling;
472 qtHeight = computedComponentHeight * computedComponentVerticalSubsampling;
473 }
474
475 // Sanity check each component
476 for (c = 0; c < jasNumComponents; ++c) {
477 // Test for precision
478 if (jasComponentPrecicion[c]>8 || jasComponentPrecicion[c]<8) {
479 qDebug("The Qt JPEG 2000 reader does not support components with "
480 "precision != 8");
481 decodeOk = false;
482 }
483#if 0
484 // Test the subsampling factor (space between pixels on the image grid)
485 if (oddComponentSubsampling) {
486 qDebug("The Qt JPEG 2000 reader does not support components with "
487 "a subsampling factor != 1 (yet)");
488 decodeOk = false;
489 }
490#endif
491 // Test for signed components
492 if (jas_image_cmptsgnd(jasper_image, c) != 0) {
493 qDebug("Qt JPEG 2000 reader does not support signed components");
494 decodeOk = false;
495 }
496
497 // Test for component/image geomoetry mismach.
498 // If oddComponentSubsampling, then this is already taken care of above.
499 if (!oddComponentSubsampling)
500 if (jas_image_cmpttlx(jasper_image,c) != 0 ||
501 jas_image_cmpttly(jasper_image,c) != 0 ||
502 jas_image_cmptbrx(jasper_image,c) != jas_image_brx(jasper_image) ||
503 jas_image_cmptbry(jasper_image,c) != jas_image_bry(jasper_image) ||
504 jas_image_cmptwidth (jasper_image, c) != jas_image_width (jasper_image) ||
505 jas_image_cmptheight(jasper_image, c) != jas_image_height(jasper_image )) {
506 qDebug("The Qt JPEG 2000 reader does not support images where "
507 "component geometry differs from image geometry");
508 printMetadata(jasper_image);
509 decodeOk = false;
510 }
511 }
512 if (!decodeOk)
513 return false;
514
515 // At this point, the colorspace should be either RGB or grayscale,
516 // and each component should have eight bits of precision and
517 // no unsupported geometry.
518 //printMetadata(jasper_image);
519
520 // Get color components
521 jasperColorspaceFamily = jas_clrspc_fam(jas_image_clrspc(jasper_image));
522 if (jasperColorspaceFamily == JAS_CLRSPC_FAM_RGB) {
523 if (jasNumComponents > 4)
524 qDebug("JPEG 2000 reader expected 3 or 4 components, got %d",
525 jasNumComponents);
526
527 // Set up mapping from R,G,B -> component num.
528 colorComponentMapping[0] = jas_image_getcmptbytype(jasper_image,
529 JAS_IMAGE_CT_RGB_R);
530 colorComponentMapping[1] = jas_image_getcmptbytype(jasper_image,
531 JAS_IMAGE_CT_RGB_G);
532 colorComponentMapping[2] = jas_image_getcmptbytype(jasper_image,
533 JAS_IMAGE_CT_RGB_B);
534 qtNumComponents = 3;
535 } else if (jasperColorspaceFamily == JAS_CLRSPC_FAM_GRAY) {
536 if (jasNumComponents > 2)
537 qDebug("JPEG 2000 reader expected 1 or 2 components, got %d",
538 jasNumComponents);
539 colorComponentMapping[0] = jas_image_getcmptbytype(jasper_image,
540 JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_GRAY_Y));
541 qtNumComponents = 1;
542 } else {
543 printColorSpaceError();
544 return false;
545 }
546
547 // Get alpha component if one exists. Due to the lack of test images,
548 // loading images with alpha channels is a bit untested. It works
549 // with images saved with this implementation though.
550 const int posibleAlphaComponent1 = 3;
551 const int posibleAlphaComponent2 = 48;
552
553 if (jasNumComponents == qtNumComponents + 1) {
554 colorComponentMapping[qtNumComponents] = jas_image_getcmptbytype(jasper_image, posibleAlphaComponent1);
555 if (colorComponentMapping[qtNumComponents] < 0) {
556 colorComponentMapping[qtNumComponents] = jas_image_getcmptbytype(jasper_image, posibleAlphaComponent2);
557 }
558 if (colorComponentMapping[qtNumComponents] > 0) {
559 hasAlpha = true;
560 qtNumComponents++;
561 }
562 }
563
564 // Check for missing components
565 for (c = 0; c < qtNumComponents; ++c) {
566 if (colorComponentMapping[c] < 0) {
567 qDebug("JPEG 2000 reader missing a color component");
568 return false;
569 }
570 }
571
572 // Create a QImage of the correct type
573 QImage::Format qtFormat = QImage::Format_Invalid;
574 if (jasperColorspaceFamily == JAS_CLRSPC_FAM_RGB)
575 qtFormat = hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32;
576 else if (jasperColorspaceFamily == JAS_CLRSPC_FAM_GRAY)
577 qtFormat = hasAlpha ? QImage::Format_ARGB32 : QImage::Format_Grayscale8;
578 if (!QImageIOHandler::allocateImage(QSize(qtWidth, qtHeight), qtFormat, &qtImage))
579 return false;
580
581 // Copy data
582 if (oddComponentSubsampling) {
583 // This is a hack really, copying of data with component subsampling
584 // != 1 doesn't fit in with the rest of the scanline copying framework.
585 copyJasperQtGeneric();
586 } else if (jasperColorspaceFamily == JAS_CLRSPC_FAM_RGB) {
587 if (hasAlpha)
588 copyJasperQt(&Jpeg2000JasperReader::copyScanlineJasperQtRGBA);
589 else
590 copyJasperQt(&Jpeg2000JasperReader::copyScanlineJasperQtRGB);
591 } else if (jasperColorspaceFamily == JAS_CLRSPC_FAM_GRAY) {
592 if (hasAlpha)
593 copyJasperQt(&Jpeg2000JasperReader::copyScanlineJasperQtGrayA);
594 else
595 copyJasperQt(&Jpeg2000JasperReader::copyScanlineJasperQtGray);
596 }
597 if (decodeOk)
598 *pImage = qtImage;
599
600 return decodeOk;
601}
602
603/*!
604 \internal
605*/
606void Jpeg2000JasperReader::copyJasperQtGeneric()
607{
608 // Create scanline data poinetrs
609 jas_matrix_t **jasperMatrix;
610 jas_seqent_t **jasperRow;
611 createJasperMatrix(jasperMatrix);
612 jasperRow = (jas_seqent_t**)malloc(jasNumComponents * sizeof(jas_seqent_t *));
613 Q_CHECK_PTR(jasperRow);
614
615 int imageY = 0;
616 for (int componentY = 0; componentY < computedComponentHeight; ++componentY) {
617 for (int c = 0; c < jasNumComponents; ++c) {
618 jas_image_readcmpt(jasper_image, colorComponentMapping[c], 0,
619 componentY, computedComponentWidth, 1,
620 jasperMatrix[c]);
621 jasperRow[c] = jas_matrix_getref(jasperMatrix[c], 0, 0);
622 }
623 for (int verticalSubsample = 0;
624 verticalSubsample < computedComponentVerticalSubsampling;
625 ++verticalSubsample) {
626 uchar *scanLineUchar = qtImage.scanLine(imageY);
627 QRgb *scanLineQRgb = reinterpret_cast<QRgb *>(scanLineUchar);
628 for (int componentX = 0; componentX < computedComponentWidth;
629 ++componentX) {
630 for (int horizontalSubsample = 0;
631 horizontalSubsample <
632 computedComponentHorizontalSubsampling;
633 ++horizontalSubsample) {
634 if (jasperColorspaceFamily == JAS_CLRSPC_FAM_RGB) {
635 if (hasAlpha) {
636 *scanLineQRgb++ = (jasperRow[3][componentX] << 24) |
637 (jasperRow[0][componentX] << 16) |
638 (jasperRow[1][componentX] << 8) |
639 jasperRow[2][componentX];
640 } else {
641 *scanLineQRgb++ = (jasperRow[0][componentX] << 16) |
642 (jasperRow[1][componentX] << 8) |
643 jasperRow[2][componentX];
644 }
645 } else if (jasperColorspaceFamily == JAS_CLRSPC_FAM_GRAY) {
646 if (hasAlpha) {
647 *scanLineQRgb++ = (jasperRow[1][componentX] << 24) |
648 (jasperRow[0][componentX] << 16) |
649 (jasperRow[0][componentX] << 8) |
650 jasperRow[0][componentX];
651 } else {
652 *scanLineUchar++ = jasperRow[0][componentX];
653 }
654 }
655 }
656 }
657 ++imageY;
658 }
659 }
660}
661
662/*!
663 \internal
664 Copies data from Jasper to QImage. The scanlineCopier parameter specifies
665 which function to use for handling each scan line.
666*/
667void Jpeg2000JasperReader::copyJasperQt(const ScanlineFunc scanlineCopier)
668{
669 // Create scanline data poinetrs
670 jas_matrix_t **jasperMatrix;
671 jas_seqent_t **jasperRow;
672
673 createJasperMatrix(jasperMatrix);
674 jasperRow = (jas_seqent_t**)malloc(jasNumComponents * sizeof(jas_seqent_t *));
675 Q_CHECK_PTR(jasperRow);
676
677 for (int scanline = 0; scanline < qtHeight; ++scanline) {
678 for (int c = 0; c < jasNumComponents; ++c) {
679 jas_image_readcmpt(jasper_image, colorComponentMapping[c], 0,
680 scanline, qtWidth, 1, jasperMatrix[c]);
681 jasperRow[c] = jas_matrix_getref(jasperMatrix[c], 0, 0);
682 }
683 (this->*scanlineCopier)(jasperRow, qtImage.scanLine(scanline));
684 }
685
686 freeJasperMatrix(jasperMatrix);
687 free(jasperRow);
688}
689
690/*!
691 \internal
692 Copies RGB data from Jasper to a 32-bit QImage.
693*/
694void Jpeg2000JasperReader::copyScanlineJasperQtRGB(
695 jas_seqent_t ** const jasperRow, uchar *qtScanLine)
696{
697 QRgb *scanLine = reinterpret_cast<QRgb *>(qtScanLine);
698 for (int c = 0; c < qtWidth; ++c) {
699 *scanLine++ = (0xFF << 24) |
700 (jasperRow[0][c] << 16) |
701 (jasperRow[1][c] << 8) |
702 jasperRow[2][c];
703 }
704}
705
706/*!
707 \internal
708 Copies RGBA data from Jasper to a 32-bit QImage.
709*/
710void Jpeg2000JasperReader::copyScanlineJasperQtRGBA(jas_seqent_t ** const jasperRow, uchar *qtScanLine)
711{
712 QRgb *scanLine = reinterpret_cast<QRgb *>(qtScanLine);
713 for (int c = 0; c < qtWidth; ++c) {
714 *scanLine++ = (jasperRow[3][c] << 24) |
715 (jasperRow[0][c] << 16) |
716 (jasperRow[1][c] << 8) |
717 jasperRow[2][c];
718 }
719}
720
721/*!
722 \internal
723 Copies data from a grayscale image to an 8-bit QImage.
724*/
725void Jpeg2000JasperReader::copyScanlineJasperQtGray(jas_seqent_t ** const jasperRow, uchar *qtScanLine)
726{
727 for (int c = 0; c < qtWidth; ++c) {
728 // *qtScanLine++ = (jasperRow[0][c] >> (jasComponentPrecicion[0] - 8));
729 *qtScanLine++ = jasperRow[0][c];
730 }
731}
732
733/*!
734 \internal
735 Copies data from a grayscale image to a 32-bit QImage.
736 Note that in this case we use an 32-bit image for grayscale data, since the
737 alpha value is per-pixel, not per-color (per-color alpha is supported by
738 8-bit QImage).
739*/
740void Jpeg2000JasperReader::copyScanlineJasperQtGrayA(jas_seqent_t ** const jasperRow, uchar *qtScanLine)
741{
742 QRgb *scanLine = reinterpret_cast<QRgb *>(qtScanLine);
743 for (int c = 0; c < qtWidth; ++c) {
744 *scanLine++ = (jasperRow[1][c] << 24) |
745 (jasperRow[0][c] << 16) |
746 (jasperRow[0][c] << 8) |
747 jasperRow[0][c];
748 }
749}
750
751/*!
752 Opens the file data and attempts to decode it using the Jasper library.
753 Returns true on success, false on failure.
754
755 32-bit and color mapped color images are encoded as RGB images,
756 color mapped grayscale images are encoded as grayscale images
757*/
758bool Jpeg2000JasperReader::write(const QImage &image, int quality)
759{
760 if (!jasperOk)
761 return false;
762
763 qtImage = image;
764
765 qtHeight = qtImage.height();
766 qtWidth = qtImage.width();
767 qtDepth = qtImage.depth();
768
769 if (qtDepth == 32) { // RGB(A)
770 jasper_image = newRGBAImage(qtWidth, qtHeight, qtImage.hasAlphaChannel());
771 if (!jasper_image)
772 return false;
773
774 if (qtImage.hasAlphaChannel())
775 copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperRGBA);
776 else
777 copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperRGB);
778 } else if (qtDepth == 8) {
779 // Color mapped grayscale
780 if (qtImage.allGray()) {
781 jasper_image = newGrayscaleImage(qtWidth, qtHeight, qtImage.hasAlphaChannel());
782 if (!jasper_image)
783 return false;
784
785 if (qtImage.hasAlphaChannel())
786 copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperColormapGrayscaleA);
787 else
788 copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperColormapGrayscale);
789 } else {
790 // Color mapped color
791 jasper_image = newRGBAImage(qtWidth, qtHeight, qtImage.hasAlphaChannel());
792 if (!jasper_image)
793 return false;
794
795 if (qtImage.hasAlphaChannel())
796 copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperColormapRGBA);
797 else
798 copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperColormapRGB);
799 }
800 } else {
801 qDebug("Unable to handle color depth %d", qtDepth);
802 return false;
803 }
804
805 int fmtid;
806 if (format == Jp2Format)
807 fmtid = jas_image_strtofmt(const_cast<char*>("jp2"));
808 else /* if (format == J2cFormat) */
809 // JasPer refers to the code stream format as jpc
810 fmtid = jas_image_strtofmt(const_cast<char*>("jpc"));
811
812 const int minQuality = 0;
813 const int maxQuality = 100;
814
815 if (quality == -1)
816 quality = 100;
817 if (quality <= minQuality)
818 quality = minQuality;
819 if (quality > maxQuality)
820 quality = maxQuality;
821
822 // Qt specifies quality as an integer in the range 0..100. Jasper specifies
823 // compression rate as an real in the range 0..1, where 1 corresponds to no
824 // compression. Computing the rate from quality is difficult, large images
825 // get better image quality than small images at the same rate. If the rate
826 // is too low, Jasper will generate a completely black image.
827 // minirate is the smallest safe rate value.
828 const double minRate = 0.001;
829
830 // maxRate specifies maximum target rate, which give the minimum amount
831 // of compression. Tests show that maxRates higer than 0.3 give no
832 // additional image quality for most images. Large images could use an even
833 // smaller maxRate value.
834 const double maxRate = 0.3;
835
836 // Set jasperRate to a value in the range minRate..maxRate. Distribute the
837 // quality steps more densely at the lower end if the rate scale.
838 const double jasperRate = minRate + pow((double(quality) / double(maxQuality)), 2) * maxRate;
839
840 // The Jasper format string contains two options:
841 // rate: rate=x
842 // lossy/lossless compression : mode=real/mode=int
843 QString jasperFormatString;
844
845 // If quality is not maxQuality, we set lossy encoding.
846 // (lossless is default)
847 if (quality != maxQuality) {
848 jasperFormatString += QLatin1String("mode=real");
849 jasperFormatString += QString(QLatin1String(" rate=%1")).arg(jasperRate);
850 }
851
852 // Open an empty jasper stream that grows automatically
853 jas_stream_t * memory_stream = jas_stream_memopen(0, 0);
854
855 // Jasper wants a non-const string.
856 char *str = qstrdup(jasperFormatString.toLatin1().constData());
857 jas_image_encode(jasper_image, memory_stream, fmtid, str);
858 delete[] str;
859 jas_stream_flush(memory_stream);
860
861 // jas_stream_t::obj_ is a void* which points to the stream implementation,
862 // e.g a file stream or a memory stream. But in our case we know that it is
863 // a memory stream since we created the object, so we just reiterpret_cast
864 // here..
865 char *buffer = reinterpret_cast<char *>(reinterpret_cast<jas_stream_memobj_t*>(memory_stream->obj_)->buf_);
866 qint64 length = jas_stream_length(memory_stream);
867 ioDevice->write(buffer, length);
868
869 jas_stream_close(memory_stream);
870 jas_image_destroy(jasper_image);
871
872 return true;
873}
874
875/*!
876 \internal
877 Copies data from qtImage to JasPer. The scanlineCopier parameter specifies
878 which function to use for handling each scan line.
879*/
880void Jpeg2000JasperReader::copyQtJasper(const ScanlineFuncWrite scanlinecopier)
881{
882 // Create jasper matrix for holding one scanline
883 jas_matrix_t **jasperMatrix;
884 createJasperMatrix(jasperMatrix);
885
886 for (int scanline = 0; scanline < qtHeight; ++scanline) {
887 (this->*scanlinecopier)(jasperMatrix, qtImage.scanLine(scanline));
888
889 // Write a scanline of data to jasper_image
890 for (int c = 0; c < jasNumComponents; ++c)
891 jas_image_writecmpt(jasper_image, c, 0, scanline, qtWidth, 1,
892 jasperMatrix[c]);
893 }
894 freeJasperMatrix(jasperMatrix);
895}
896
897/*!
898 \internal
899*/
900void Jpeg2000JasperReader::copyScanlineQtJasperRGB(jas_matrix_t ** jasperRow,
901 uchar *qtScanLine)
902{
903 QRgb *scanLineBuffer = reinterpret_cast<QRgb *>(qtScanLine);
904 for (int col = 0; col < qtWidth; ++col) {
905 jas_matrix_set(jasperRow[0], 0, col, (*scanLineBuffer & 0xFF0000) >> 16);
906 jas_matrix_set(jasperRow[1], 0, col, (*scanLineBuffer & 0x00FF00) >> 8);
907 jas_matrix_set(jasperRow[2], 0, col, *scanLineBuffer & 0x0000FF);
908 ++scanLineBuffer;
909 }
910}
911
912/*!
913 \internal
914*/
915void Jpeg2000JasperReader::copyScanlineQtJasperRGBA(jas_matrix_t ** jasperRow,
916 uchar *qtScanLine)
917{
918 QRgb *scanLineBuffer = reinterpret_cast<QRgb *>(qtScanLine);
919 for (int col = 0; col < qtWidth; ++col) {
920 jas_matrix_set(jasperRow[3], 0, col, (*scanLineBuffer & 0xFF000000) >> 24);
921 jas_matrix_set(jasperRow[0], 0, col, (*scanLineBuffer & 0x00FF0000) >> 16);
922 jas_matrix_set(jasperRow[1], 0, col, (*scanLineBuffer & 0x0000FF00) >> 8);
923 jas_matrix_set(jasperRow[2], 0, col, *scanLineBuffer & 0x000000FF);
924 ++scanLineBuffer;
925 }
926}
927
928/*!
929 \internal
930*/
931void Jpeg2000JasperReader::copyScanlineQtJasperColormapRGB(jas_matrix_t ** jasperRow,
932 uchar *qtScanLine)
933{
934 for (int col = 0; col < qtWidth; ++col) {
935 QRgb color = qtImage.color(*qtScanLine);
936 jas_matrix_set(jasperRow[0], 0, col, qRed(color));
937 jas_matrix_set(jasperRow[1], 0, col, qGreen(color));
938 jas_matrix_set(jasperRow[2], 0, col, qBlue(color));
939 ++qtScanLine;
940 }
941}
942
943/*!
944 \internal
945*/
946void Jpeg2000JasperReader::copyScanlineQtJasperColormapRGBA(jas_matrix_t ** jasperRow,
947 uchar *qtScanLine)
948{
949 for (int col = 0; col < qtWidth; ++col) {
950 QRgb color = qtImage.color(*qtScanLine);
951 jas_matrix_set(jasperRow[0], 0, col, qRed(color));
952 jas_matrix_set(jasperRow[1], 0, col, qGreen(color));
953 jas_matrix_set(jasperRow[2], 0, col, qBlue(color));
954 jas_matrix_set(jasperRow[3], 0, col, qAlpha(color));
955 ++qtScanLine;
956 }
957}
958
959/*!
960 \internal
961*/
962void Jpeg2000JasperReader::copyScanlineQtJasperColormapGrayscale(jas_matrix_t ** jasperRow,
963 uchar *qtScanLine)
964{
965 for (int col = 0; col < qtWidth; ++col) {
966 QRgb color = qtImage.color(*qtScanLine);
967 jas_matrix_set(jasperRow[0], 0, col, qGray(color));
968 ++qtScanLine;
969 }
970}
971
972/*!
973 \internal
974*/
975void Jpeg2000JasperReader::copyScanlineQtJasperColormapGrayscaleA(jas_matrix_t ** jasperRow,
976 uchar *qtScanLine)
977{
978 for (int col = 0; col < qtWidth; ++col) {
979 QRgb color = qtImage.color(*qtScanLine);
980 jas_matrix_set(jasperRow[0], 0, col, qGray(color));
981 jas_matrix_set(jasperRow[1], 0, col, qAlpha(color));
982 ++qtScanLine;
983 }
984}
985
986/*!
987 \internal
988 Attempts to change the color space for the image to wantedColorSpace using
989 the JasPer library
990*/
991bool Jpeg2000JasperReader::attemptColorspaceChange(int wantedColorSpace)
992{
993 //qDebug("Attemting color space change");
994 jas_cmprof_t *outprof;
995 if (!(outprof = jas_cmprof_createfromclrspc(wantedColorSpace)))
996 return false;
997
998 jas_image_t *newimage;
999 if (!(newimage = jas_image_chclrspc(jasper_image, outprof,
1000 JAS_CMXFORM_INTENT_PER))) {
1001 jas_cmprof_destroy(outprof);
1002 return false;
1003 }
1004 jas_image_destroy(jasper_image);
1005 jas_cmprof_destroy(outprof);
1006 jasper_image = newimage;
1007 return true;
1008}
1009
1010/*!
1011 \internal
1012 Set up a component with parameters suitable for storing a QImage.
1013*/
1014jas_image_cmptparm_t Jpeg2000JasperReader::createComponentMetadata(
1015 const int width, const int height)
1016{
1017 jas_image_cmptparm_t param;
1018 param.tlx = 0;
1019 param.tly = 0;
1020 param.hstep = 1;
1021 param.vstep = 1;
1022 param.width = width;
1023 param.height = height;
1024 param.prec = 8;
1025 param.sgnd = 0;
1026 return param;
1027}
1028
1029/*!
1030 \internal
1031 Create a new RGB JasPer image with a possible alpha channel.
1032*/
1033jas_image_t* Jpeg2000JasperReader::newRGBAImage(const int width,
1034 const int height, bool alpha)
1035{
1036 jasNumComponents = alpha ? 4 : 3;
1037 jas_image_cmptparm_t *params = new jas_image_cmptparm_t[jasNumComponents];
1038 jas_image_cmptparm_t param = createComponentMetadata(width, height);
1039 for (int c=0; c < jasNumComponents; c++)
1040 params[c] = param;
1041 jas_image_t *newImage = jas_image_create(jasNumComponents, params,
1042 JAS_CLRSPC_SRGB);
1043
1044 if (!newImage) {
1045 delete[] params;
1046 return 0;
1047 }
1048
1049 jas_image_setcmpttype(newImage, 0, JAS_IMAGE_CT_RGB_R);
1050 jas_image_setcmpttype(newImage, 1, JAS_IMAGE_CT_RGB_G);
1051 jas_image_setcmpttype(newImage, 2, JAS_IMAGE_CT_RGB_B);
1052
1053 /*
1054 It is unclear how one stores opacity(alpha) components with JasPer,
1055 the following seems to have no effect. The opacity component gets
1056 type id 3 or 48 depending jp2 or j2c format no matter what one puts
1057 in here.
1058
1059 The symbols are defined as follows:
1060 #define JAS_IMAGE_CT_RGB_R 0
1061 #define JAS_IMAGE_CT_RGB_G 1
1062 #define JAS_IMAGE_CT_RGB_B 2
1063 #define JAS_IMAGE_CT_OPACITY 0x7FFF
1064 */
1065 if (alpha)
1066 jas_image_setcmpttype(newImage, 3, JAS_IMAGE_CT_OPACITY);
1067 delete[] params;
1068 return newImage;
1069}
1070
1071/*!
1072 \internal
1073 Create a new RGB JasPer image with a possible alpha channel.
1074*/
1075jas_image_t *Jpeg2000JasperReader::newGrayscaleImage(const int width,
1076 const int height,
1077 bool alpha)
1078{
1079 jasNumComponents = alpha ? 2 : 1;
1080 jas_image_cmptparm_t param = createComponentMetadata(width, height);
1081 jas_image_t *newImage = jas_image_create(1, &param, JAS_CLRSPC_SGRAY);
1082 if (!newImage)
1083 return 0;
1084
1085 jas_image_setcmpttype(newImage, 0, JAS_IMAGE_CT_GRAY_Y);
1086
1087 // See corresponding comment for newRGBAImage.
1088 if (alpha)
1089 jas_image_setcmpttype(newImage, 1, JAS_IMAGE_CT_OPACITY);
1090 return newImage;
1091}
1092
1093/*!
1094 \internal
1095 Allocate data structures that hold image data during transfer from the
1096 JasPer data structures to QImage.
1097*/
1098bool Jpeg2000JasperReader::createJasperMatrix(jas_matrix_t **&matrix)
1099{
1100 matrix = (jas_matrix_t**)malloc(jasNumComponents * sizeof(jas_matrix_t *));
1101 for (int c = 0; c < jasNumComponents; ++c)
1102 matrix[c] = jas_matrix_create(1, qtWidth);
1103 return true;
1104}
1105
1106/*!
1107 \internal
1108 Free data structures that hold image data during transfer from the
1109 JasPer data structures to QImage.
1110*/
1111bool Jpeg2000JasperReader::freeJasperMatrix(jas_matrix_t **matrix)
1112{
1113 for (int c = 0; c < jasNumComponents; ++c)
1114 jas_matrix_destroy(matrix[c]);
1115 free(matrix);
1116 return false;
1117}
1118
1119/*!
1120 \internal
1121*/
1122void Jpeg2000JasperReader::printColorSpaceError()
1123{
1124 QString colorspaceFamily, colorspaceSpecific;
1125 decodeColorSpace(jas_image_clrspc(jasper_image), colorspaceFamily,
1126 colorspaceSpecific);
1127 qDebug("Jpeg2000 decoder is not able to handle color space %s - %s",
1128 qPrintable(colorspaceFamily), qPrintable(colorspaceSpecific));
1129}
1130/*!
1131 \internal
1132*/
1133bool Jpeg2000JasperReader::decodeColorSpace(int clrspc, QString &family,
1134 QString &specific)
1135{
1136 int fam = jas_clrspc_fam(clrspc);
1137 int mbr = jas_clrspc_mbr(clrspc);
1138
1139 switch (fam) {
1140 case 0: family = QLatin1String("JAS_CLRSPC_FAM_UNKNOWN"); break;
1141 case 1: family = QLatin1String("JAS_CLRSPC_FAM_XYZ"); break;
1142 case 2: family = QLatin1String("JAS_CLRSPC_FAM_LAB"); break;
1143 case 3: family = QLatin1String("JAS_CLRSPC_FAM_GRAY"); break;
1144 case 4: family = QLatin1String("JAS_CLRSPC_FAM_RGB"); break;
1145 case 5: family = QLatin1String("JAS_CLRSPC_FAM_YCBCR"); break;
1146 default: family = QLatin1String("Unknown"); return false;
1147 }
1148
1149 switch (mbr) {
1150 case 0:
1151 switch (fam) {
1152 case 1: specific = QLatin1String("JAS_CLRSPC_CIEXYZ"); break;
1153 case 2: specific = QLatin1String("JAS_CLRSPC_CIELAB"); break;
1154 case 3: specific = QLatin1String("JAS_CLRSPC_SGRAY"); break;
1155 case 4: specific = QLatin1String("JAS_CLRSPC_SRGB"); break;
1156 case 5: specific = QLatin1String("JAS_CLRSPC_SYCBCR"); break;
1157 default: specific = QLatin1String("Unknown"); return false;
1158 }
1159 break;
1160 case 1:
1161 switch (fam) {
1162 case 3: specific = QLatin1String("JAS_CLRSPC_GENGRAY"); break;
1163 case 4: specific = QLatin1String("JAS_CLRSPC_GENRGB"); break;
1164 case 5: specific = QLatin1String("JAS_CLRSPC_GENYCBCR"); break;
1165 default: specific = QLatin1String("Unknown"); return false;
1166 }
1167 break;
1168 default:
1169 return false;
1170 }
1171 return true;
1172}
1173/*!
1174 \internal
1175*/
1176void Jpeg2000JasperReader::printMetadata(jas_image_t *image)
1177{
1178#ifndef QT_NO_DEBUG
1179 // jas_image_cmptparm_t param
1180 qDebug("Image width: %ld", long(jas_image_width(image)));
1181 qDebug("Image height: %ld", long(jas_image_height(image)));
1182 qDebug("Coordinates on reference grid: (%ld,%ld) (%ld,%ld)",
1183 long(jas_image_tlx(image)), long(jas_image_tly(image)),
1184 long(jas_image_brx(image)), long(jas_image_bry(image)));
1185 qDebug("Number of image components: %d", jas_image_numcmpts(image));
1186
1187 QString colorspaceFamily;
1188 QString colorspaceSpecific;
1189 decodeColorSpace(jas_image_clrspc(image), colorspaceFamily, colorspaceSpecific);
1190 qDebug("Color model (space): %d, %s - %s", jas_image_clrspc(image),
1191 qPrintable(colorspaceFamily), qPrintable(colorspaceSpecific));
1192
1193 qDebug("Component metadata:");
1194
1195 for (int c = 0; c < static_cast<int>(jas_image_numcmpts(image)); ++c) {
1196 qDebug("Component %d:", c);
1197 qDebug(" Component type: %ld", long(jas_image_cmpttype(image, c)));
1198 qDebug(" Width: %ld", long(jas_image_cmptwidth(image, c)));
1199 qDebug(" Height: %ld", long(jas_image_cmptheight(image, c)));
1200 qDebug(" Signedness: %d", jas_image_cmptsgnd(image, c));
1201 qDebug(" Precision: %d", jas_image_cmptprec(image, c));
1202 qDebug(" Horizontal subsampling factor: %ld",long(jas_image_cmpthstep(image, c)));
1203 qDebug(" Vertical subsampling factor: %ld", long(jas_image_cmptvstep(image, c)));
1204 qDebug(" Coordinates on reference grid: (%ld,%ld) (%ld,%ld)",
1205 long(jas_image_cmpttlx(image, c)), long(jas_image_cmpttly(image, c)),
1206 long(jas_image_cmptbrx(image, c)), long(jas_image_cmptbry(image, c)));
1207 }
1208#else
1209 Q_UNUSED(image);
1210#endif
1211}
1212
1213QT_END_NAMESPACE
bool read(QImage *pImage)
Jpeg2000JasperReader(QIODevice *iod, const SubFormat format=Jp2Format)
bool write(const QImage &image, int quality)
Opens the file data and attempts to decode it using the Jasper library.
QIODevice * device() const
Returns the device currently assigned to QImageReader, or \nullptr if no device has been assigned.
The QJp2Handler class provides support for reading and writing JPEG 2000 image files with the Qt plug...
~QJp2Handler()
Destructor for QJp2Handler.
QJp2Handler()
Constructs an instance of QJp2Handler.
static bool canRead(QIODevice *iod, QByteArray *subType)
Verifies if some values (magic bytes) are set as expected in the header of the file.
bool write(const QImage &image) override
\reimp
bool read(QImage *image) override
\reimp
bool canRead() const override
\reimp
Automatic resource handling for a jas_image_t*.
ScopedJasperImage(jas_image_t *&image)
SubFormat
@ Jp2Format
@ J2kFormat