Qt 6.x
The Qt SDK
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
5#include "qjp2handler_p.h"
6
7#include "qimage.h"
8#include "qvariant.h"
9#include "qcolor.h"
10#include "qimagereader.h"
11
12#include <jasper/jasper.h>
13#include <math.h> // for pow
14
16
18{
19 Q_DECLARE_PUBLIC(QJp2Handler)
20 Q_DISABLE_COPY(QJp2HandlerPrivate)
21public:
26};
27
29
30/*
31 \class Jpeg2000JasperReader
32 \brief Jpeg2000JasperReader implements reading and writing of JPEG 2000
33 image files.
34
35 \internal
36
37 This class is designed to be used together with the an QImageIO IOHandler,
38 and it should probably not be necessary to instantiate it directly.
39
40 Internally it used the Jasper library for coding the image data.
41*/
43{
44public:
46
48
49 bool read(QImage *pImage);
50 bool write(const QImage &image, int quality);
51private:
52 typedef void (Jpeg2000JasperReader::*ScanlineFunc)(jas_seqent_t** const, uchar*);
53 typedef void (Jpeg2000JasperReader::*ScanlineFuncWrite)(jas_matrix_t**, uchar*);
54
55 void copyJasperQt(ScanlineFunc scanlinecopier);
56 void copyJasperQtGeneric();
57 void copyScanlineJasperQtRGB(jas_seqent_t ** const jasperRow, uchar *qtScanLine);
58 void copyScanlineJasperQtRGBA(jas_seqent_t ** const jasperRow, uchar *qtScanLine);
59 void copyScanlineJasperQtGray(jas_seqent_t ** const jasperRow, uchar *qtScanLine);
60 void copyScanlineJasperQtGrayA(jas_seqent_t ** const jasperRow, uchar *qtScanLine);
61
62 void copyQtJasper(const ScanlineFuncWrite scanlinecopier);
63 void copyScanlineQtJasperRGB(jas_matrix_t ** jasperRow, uchar *qtScanLine);
64 void copyScanlineQtJasperRGBA(jas_matrix_t ** jasperRow, uchar *qtScanLine);
65 void copyScanlineQtJasperColormapRGB(jas_matrix_t ** jasperRow, uchar *qtScanLine);
66 void copyScanlineQtJasperColormapRGBA(jas_matrix_t ** jasperRow, uchar *qtScanLine);
67 void copyScanlineQtJasperColormapGrayscale(jas_matrix_t ** jasperRow, uchar *qtScanLine);
68 void copyScanlineQtJasperColormapGrayscaleA(jas_matrix_t ** jasperRow, uchar *qtScanLine);
69
70 bool attemptColorspaceChange(int wantedColorSpace);
71 bool createJasperMatrix(jas_matrix_t **&matrix);
72 bool freeJasperMatrix(jas_matrix_t **matrix);
73 void printColorSpaceError();
74 jas_image_cmptparm_t createComponentMetadata(const int width, const int height);
75 jas_image_t *newRGBAImage(const int width, const int height, bool alpha);
76 jas_image_t *newGrayscaleImage(const int width, const int height, bool alpha);
77 bool decodeColorSpace(int clrspc, QString &family, QString &specific);
78 void printMetadata(jas_image_t *image);
79
80 bool jasperOk;
81
82 QIODevice *ioDevice;
83 QImage qtImage;
85
86 // Qt image properties
87 int qtWidth;
88 int qtHeight;
89 int qtDepth;
90 int qtNumComponents;
91
92 jas_image_t *jasper_image;
93 // jasper image properties
94 int jasNumComponents;
95 int jasComponentPrecicion[4];
96 int computedComponentWidth ;
97 int computedComponentHeight;
98 int computedComponentHorizontalSubsampling;
99 int computedComponentVerticalSubsampling;
100 int jasperColorspaceFamily;
101 // maps color to component (ex: colorComponentMapping[RED]
102 // gives the component that contains the red color)
103 int colorComponentMapping[4];
104 bool hasAlpha;
105};
106
108 : writeQuality(100), subType("jp2"), q_ptr(q_ptr)
109{
110}
111
148 : d_ptr(new QJp2HandlerPrivate(this))
149{
150}
151
156{
157
158}
159
171{
172 bool bCanRead = false;
173 if (iod) {
174 const QByteArray header = iod->peek(12);
175 if (header.startsWith(QByteArrayLiteral("\000\000\000\fjP \r\n\207\n"))) {
176 // Jp2 is the JPEG 2000 file format
177 bCanRead = true;
178 if (subType)
179 *subType = QByteArray("jp2");
180 } else if (header.startsWith(QByteArrayLiteral("\377\117\377\121\000"))) {
181 // J2c is the JPEG 2000 code stream
182 bCanRead = true;
183 if (subType)
184 *subType = QByteArray("j2k");
185 }
186 }
187 return bCanRead;
188}
189
193{
194 QByteArray subType;
195 if (canRead(device(), &subType)) {
196 setFormat(subType);
197 return true;
198 }
199 return false;
200}
201
205{
207 return reader.read(image);
208}
209
213{
214 Q_D(const QJp2Handler);
215 SubFormat subFormat;
216 if (d->subType == QByteArray("jp2"))
217 subFormat = Jp2Format;
218 else
219 subFormat = J2kFormat;
220
221 Jpeg2000JasperReader writer(device(), subFormat);
222 return writer.write(image, d->writeQuality);
223}
224
230{
231 Q_D(const QJp2Handler);
232 if (option == Quality) {
233 return QVariant(d->writeQuality);
234 } else if (option == SubType) {
235 return QVariant(d->subType);
236 }
237 return QVariant();
238}
239
252{
253 Q_D(QJp2Handler);
254 if (option == Quality) {
255 bool ok;
256 const int quality = value.toInt(&ok);
257 if (ok)
258 d->writeQuality = quality;
259 } else if (option == SubType) {
260 const QByteArray subTypeCandidate = value.toByteArray();
261 // Test for default Jpeg2000 file format (jp2), or stream format (j2k).
262 if (subTypeCandidate == QByteArrayLiteral("jp2") ||
263 subTypeCandidate == QByteArrayLiteral("j2k"))
264 d->subType = subTypeCandidate;
265 }
266}
267
273{
274 return (option == Quality || option == SubType);
275}
276
281{
282public:
283 // Take reference to the pointer here, because the pointer
284 // may change when we change color spaces.
285 ScopedJasperImage(jas_image_t *&image):image(image) { }
286 ~ScopedJasperImage() { jas_image_destroy(image); }
287private:
288 jas_image_t *&image;
289};
290
299 : jasperOk(true), ioDevice(iod), format(format), hasAlpha(false)
300{
301#if JAS_VERSION_MAJOR < 3
302 if (jas_init()) {
303 jasperOk = false;
304 qDebug("Jasper Library initialization failed");
305 }
306#else
307 jas_conf_clear();
308#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
309 jas_conf_set_max_mem_usage(QImageReader::allocationLimit() * 1024 * 1024);
310#else
311 // 128MB seems to be enough.
312 jas_conf_set_max_mem_usage(128 * 1024 * 1024);
313#endif
314 if (jas_init_library()) {
315 jasperOk = false;
316 qDebug("Jasper library initialization failed");
317 }
318 if (jas_init_thread()) {
319 jas_cleanup_library();
320 jasperOk = false;
321 qDebug("Jasper thread initialization failed");
322 }
323#endif
324}
325
327{
328#if JAS_VERSION_MAJOR < 3
329 if (jasperOk)
330 jas_cleanup();
331#else
332 if (jasperOk) {
333 if (jas_cleanup_thread()) {
334 qDebug("Jasper thread cleanup failed");
335 }
336 if (jas_cleanup_library()) {
337 qDebug("Jasper library cleanup failed");
338 }
339 }
340#endif
341}
342
348{
349 if (!jasperOk)
350 return false;
351
352 /*
353 Reading proceeds approximately as follows:
354 1. Open stream and decode using Jasper
355 2. Get image metadata
356 3. Change colorspace if necessary
357 4. Create a QImage of the appropriate type (32-bit for RGB,
358 8-bit for grayscale)
359 5. Copy image data from Jasper to the QImage
360
361 When copying the image data from the Jasper data structures to the
362 QImage, a generic copy function (copyJasperQt) iterates through the
363 scanlines and calls the provided (via the scanlineCopier argument)
364 scanline copy function for each scanline. The scanline copy function
365 selected according to image metadata such as color space and the
366 presence of an alpha channel.
367 */
368 QByteArray fileContents = ioDevice->readAll();
369 jas_stream_t *imageData = jas_stream_memopen(fileContents.data(),
370 fileContents.size());
371 jasper_image = jas_image_decode(imageData, jas_image_getfmt(imageData), 0);
372 jas_stream_close(imageData);
373 if (!jasper_image) {
374 qDebug("Jasper library can't decode Jpeg2000 image data");
375 return false;
376 }
377 ScopedJasperImage scopedImage(jasper_image);
378 //printMetadata(jasper_image);
379
380 qtWidth = jas_image_width(jasper_image);
381 qtHeight = jas_image_height(jasper_image);
382 jasNumComponents = jas_image_numcmpts(jasper_image);
383 jasperColorspaceFamily = jas_clrspc_fam(jas_image_clrspc(jasper_image));
384
385 bool needColorspaceChange = false;
386 if (jasperColorspaceFamily != JAS_CLRSPC_FAM_RGB &&
387 jasperColorspaceFamily != JAS_CLRSPC_FAM_GRAY)
388 needColorspaceChange = true;
389
390 // Get per-component data
391 int c;
392 for (c = 0; c < jasNumComponents; ++c) {
393 jasComponentPrecicion[c] = jas_image_cmptprec(jasper_image, c);
394
395 // Test for precision
396 if (jasComponentPrecicion[c] > 8 || jasComponentPrecicion[c] < 8)
397 needColorspaceChange = true;
398
399 // Test for subsampling
400 if (jas_image_cmpthstep(jasper_image, c) != 1 ||
401 jas_image_cmptvstep(jasper_image, c) != 1)
402 needColorspaceChange = true;
403
404 // Test for signed components
405 if (jas_image_cmptsgnd(jasper_image, c) != 0)
406 needColorspaceChange = true;
407 }
408
409 /*
410 If we encounter a different color space than RGB
411 (such as XYZ or YCbCr) we change that to RGB.
412 Also, if any component has "funny" metadata (such as precicion != 8 bits
413 or subsampling != 1) we also do a colorspace
414 change in order to convert it to something we can load.
415 */
416
417 bool decodeOk = true;
418 if (needColorspaceChange)
419 decodeOk = attemptColorspaceChange(JAS_CLRSPC_SRGB);
420
421 if (!decodeOk) {
422 printColorSpaceError();
423 return false;
424 }
425
426 // Image metadata may have changed, get from Jasper.
427 qtWidth = jas_image_width(jasper_image);
428 qtHeight = jas_image_height(jasper_image);
429 jasNumComponents = jas_image_numcmpts(jasper_image);
430 jasperColorspaceFamily = jas_clrspc_fam(jas_image_clrspc(jasper_image));
431 for (c = 0; c < jasNumComponents; ++c) {
432 jasComponentPrecicion[c] = jas_image_cmptprec(jasper_image, c);
433 }
434
435 if (jasperColorspaceFamily != JAS_CLRSPC_FAM_RGB &&
436 jasperColorspaceFamily != JAS_CLRSPC_FAM_GRAY) {
437 qDebug("The Qt JPEG 2000 reader was unable to convert colorspace to RGB or grayscale");
438 return false;
439 }
440
441 // If a component has a subsampling factor != 1, we can't trust
442 // jas_image_height/width, so we need to figure it out ourselves
443 bool oddComponentSubsampling = false;
444 for (c = 0; c < jasNumComponents; ++c) {
445 if (jas_image_cmpthstep(jasper_image, c) != 1 ||
446 jas_image_cmptvstep(jasper_image, c) != 1) {
447 oddComponentSubsampling = true;
448 }
449 }
450
451 if (oddComponentSubsampling) {
452 // Check if all components have the same vertical/horizontal dim and
453 // subsampling
454 computedComponentWidth = jas_image_cmptwidth(jasper_image, 0);
455 computedComponentHeight = jas_image_cmptheight(jasper_image, 0);
456 computedComponentHorizontalSubsampling = jas_image_cmpthstep(jasper_image, 0);
457 computedComponentVerticalSubsampling = jas_image_cmptvstep(jasper_image, 0);
458
459 for (c = 1; c < jasNumComponents; ++c) {
460 if (computedComponentWidth != jas_image_cmptwidth(jasper_image, c) ||
461 computedComponentWidth != jas_image_cmptwidth(jasper_image, c) ||
462 computedComponentHorizontalSubsampling != jas_image_cmpthstep(jasper_image, c) ||
463 computedComponentVerticalSubsampling != jas_image_cmptvstep(jasper_image, c)) {
464 qDebug("The Qt JPEG 2000 reader does not support images where "
465 "component geometry differs from image geometry");
466 return false;
467 }
468 }
469 qtWidth = computedComponentWidth * computedComponentHorizontalSubsampling;
470 qtHeight = computedComponentHeight * computedComponentVerticalSubsampling;
471 }
472
473 // Sanity check each component
474 for (c = 0; c < jasNumComponents; ++c) {
475 // Test for precision
476 if (jasComponentPrecicion[c]>8 || jasComponentPrecicion[c]<8) {
477 qDebug("The Qt JPEG 2000 reader does not support components with "
478 "precision != 8");
479 decodeOk = false;
480 }
481#if 0
482 // Test the subsampling factor (space between pixels on the image grid)
483 if (oddComponentSubsampling) {
484 qDebug("The Qt JPEG 2000 reader does not support components with "
485 "a subsampling factor != 1 (yet)");
486 decodeOk = false;
487 }
488#endif
489 // Test for signed components
490 if (jas_image_cmptsgnd(jasper_image, c) != 0) {
491 qDebug("Qt JPEG 2000 reader does not support signed components");
492 decodeOk = false;
493 }
494
495 // Test for component/image geomoetry mismach.
496 // If oddComponentSubsampling, then this is already taken care of above.
497 if (!oddComponentSubsampling)
498 if (jas_image_cmpttlx(jasper_image,c) != 0 ||
499 jas_image_cmpttly(jasper_image,c) != 0 ||
500 jas_image_cmptbrx(jasper_image,c) != jas_image_brx(jasper_image) ||
501 jas_image_cmptbry(jasper_image,c) != jas_image_bry(jasper_image) ||
502 jas_image_cmptwidth (jasper_image, c) != jas_image_width (jasper_image) ||
503 jas_image_cmptheight(jasper_image, c) != jas_image_height(jasper_image )) {
504 qDebug("The Qt JPEG 2000 reader does not support images where "
505 "component geometry differs from image geometry");
506 printMetadata(jasper_image);
507 decodeOk = false;
508 }
509 }
510 if (!decodeOk)
511 return false;
512
513 // At this point, the colorspace should be either RGB or grayscale,
514 // and each component should have eight bits of precision and
515 // no unsupported geometry.
516 //printMetadata(jasper_image);
517
518 // Get color components
519 jasperColorspaceFamily = jas_clrspc_fam(jas_image_clrspc(jasper_image));
520 if (jasperColorspaceFamily == JAS_CLRSPC_FAM_RGB) {
521 if (jasNumComponents > 4)
522 qDebug("JPEG 2000 reader expected 3 or 4 components, got %d",
523 jasNumComponents);
524
525 // Set up mapping from R,G,B -> component num.
526 colorComponentMapping[0] = jas_image_getcmptbytype(jasper_image,
527 JAS_IMAGE_CT_RGB_R);
528 colorComponentMapping[1] = jas_image_getcmptbytype(jasper_image,
529 JAS_IMAGE_CT_RGB_G);
530 colorComponentMapping[2] = jas_image_getcmptbytype(jasper_image,
531 JAS_IMAGE_CT_RGB_B);
532 qtNumComponents = 3;
533 } else if (jasperColorspaceFamily == JAS_CLRSPC_FAM_GRAY) {
534 if (jasNumComponents > 2)
535 qDebug("JPEG 2000 reader expected 1 or 2 components, got %d",
536 jasNumComponents);
537 colorComponentMapping[0] = jas_image_getcmptbytype(jasper_image,
538 JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_GRAY_Y));
539 qtNumComponents = 1;
540 } else {
541 printColorSpaceError();
542 return false;
543 }
544
545 // Get alpha component if one exists. Due to the lack of test images,
546 // loading images with alpha channels is a bit untested. It works
547 // with images saved with this implementation though.
548 const int posibleAlphaComponent1 = 3;
549 const int posibleAlphaComponent2 = 48;
550
551 if (jasNumComponents == qtNumComponents + 1) {
552 colorComponentMapping[qtNumComponents] = jas_image_getcmptbytype(jasper_image, posibleAlphaComponent1);
553 if (colorComponentMapping[qtNumComponents] < 0) {
554 colorComponentMapping[qtNumComponents] = jas_image_getcmptbytype(jasper_image, posibleAlphaComponent2);
555 }
556 if (colorComponentMapping[qtNumComponents] > 0) {
557 hasAlpha = true;
558 qtNumComponents++;
559 }
560 }
561
562 // Check for missing components
563 for (c = 0; c < qtNumComponents; ++c) {
564 if (colorComponentMapping[c] < 0) {
565 qDebug("JPEG 2000 reader missing a color component");
566 return false;
567 }
568 }
569
570 // Create a QImage of the correct type
572 if (jasperColorspaceFamily == JAS_CLRSPC_FAM_RGB)
573 qtFormat = hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32;
574 else if (jasperColorspaceFamily == JAS_CLRSPC_FAM_GRAY)
575 qtFormat = hasAlpha ? QImage::Format_ARGB32 : QImage::Format_Grayscale8;
576 if (!QImageIOHandler::allocateImage(QSize(qtWidth, qtHeight), qtFormat, &qtImage))
577 return false;
578
579 // Copy data
580 if (oddComponentSubsampling) {
581 // This is a hack really, copying of data with component subsampling
582 // != 1 doesn't fit in with the rest of the scanline copying framework.
583 copyJasperQtGeneric();
584 } else if (jasperColorspaceFamily == JAS_CLRSPC_FAM_RGB) {
585 if (hasAlpha)
586 copyJasperQt(&Jpeg2000JasperReader::copyScanlineJasperQtRGBA);
587 else
588 copyJasperQt(&Jpeg2000JasperReader::copyScanlineJasperQtRGB);
589 } else if (jasperColorspaceFamily == JAS_CLRSPC_FAM_GRAY) {
590 if (hasAlpha)
591 copyJasperQt(&Jpeg2000JasperReader::copyScanlineJasperQtGrayA);
592 else
593 copyJasperQt(&Jpeg2000JasperReader::copyScanlineJasperQtGray);
594 }
595 if (decodeOk)
596 *pImage = qtImage;
597
598 return decodeOk;
599}
600
604void Jpeg2000JasperReader::copyJasperQtGeneric()
605{
606 // Create scanline data poinetrs
607 jas_matrix_t **jasperMatrix;
608 jas_seqent_t **jasperRow;
609 createJasperMatrix(jasperMatrix);
610 jasperRow = (jas_seqent_t**)malloc(jasNumComponents * sizeof(jas_seqent_t *));
611 Q_CHECK_PTR(jasperRow);
612
613 int imageY = 0;
614 for (int componentY = 0; componentY < computedComponentHeight; ++componentY) {
615 for (int c = 0; c < jasNumComponents; ++c) {
616 jas_image_readcmpt(jasper_image, colorComponentMapping[c], 0,
617 componentY, computedComponentWidth, 1,
618 jasperMatrix[c]);
619 jasperRow[c] = jas_matrix_getref(jasperMatrix[c], 0, 0);
620 }
621 for (int verticalSubsample = 0;
622 verticalSubsample < computedComponentVerticalSubsampling;
623 ++verticalSubsample) {
624 uchar *scanLineUchar = qtImage.scanLine(imageY);
625 QRgb *scanLineQRgb = reinterpret_cast<QRgb *>(scanLineUchar);
626 for (int componentX = 0; componentX < computedComponentWidth;
627 ++componentX) {
628 for (int horizontalSubsample = 0;
629 horizontalSubsample <
630 computedComponentHorizontalSubsampling;
631 ++horizontalSubsample) {
632 if (jasperColorspaceFamily == JAS_CLRSPC_FAM_RGB) {
633 if (hasAlpha) {
634 *scanLineQRgb++ = (jasperRow[3][componentX] << 24) |
635 (jasperRow[0][componentX] << 16) |
636 (jasperRow[1][componentX] << 8) |
637 jasperRow[2][componentX];
638 } else {
639 *scanLineQRgb++ = (jasperRow[0][componentX] << 16) |
640 (jasperRow[1][componentX] << 8) |
641 jasperRow[2][componentX];
642 }
643 } else if (jasperColorspaceFamily == JAS_CLRSPC_FAM_GRAY) {
644 if (hasAlpha) {
645 *scanLineQRgb++ = (jasperRow[1][componentX] << 24) |
646 (jasperRow[0][componentX] << 16) |
647 (jasperRow[0][componentX] << 8) |
648 jasperRow[0][componentX];
649 } else {
650 *scanLineUchar++ = jasperRow[0][componentX];
651 }
652 }
653 }
654 }
655 ++imageY;
656 }
657 }
658}
659
665void Jpeg2000JasperReader::copyJasperQt(const ScanlineFunc scanlineCopier)
666{
667 // Create scanline data poinetrs
668 jas_matrix_t **jasperMatrix;
669 jas_seqent_t **jasperRow;
670
671 createJasperMatrix(jasperMatrix);
672 jasperRow = (jas_seqent_t**)malloc(jasNumComponents * sizeof(jas_seqent_t *));
673 Q_CHECK_PTR(jasperRow);
674
675 for (int scanline = 0; scanline < qtHeight; ++scanline) {
676 for (int c = 0; c < jasNumComponents; ++c) {
677 jas_image_readcmpt(jasper_image, colorComponentMapping[c], 0,
678 scanline, qtWidth, 1, jasperMatrix[c]);
679 jasperRow[c] = jas_matrix_getref(jasperMatrix[c], 0, 0);
680 }
681 (this->*scanlineCopier)(jasperRow, qtImage.scanLine(scanline));
682 }
683
684 freeJasperMatrix(jasperMatrix);
685 free(jasperRow);
686}
687
692void Jpeg2000JasperReader::copyScanlineJasperQtRGB(
693 jas_seqent_t ** const jasperRow, uchar *qtScanLine)
694{
695 QRgb *scanLine = reinterpret_cast<QRgb *>(qtScanLine);
696 for (int c = 0; c < qtWidth; ++c) {
697 *scanLine++ = (0xFF << 24) |
698 (jasperRow[0][c] << 16) |
699 (jasperRow[1][c] << 8) |
700 jasperRow[2][c];
701 }
702}
703
708void Jpeg2000JasperReader::copyScanlineJasperQtRGBA(jas_seqent_t ** const jasperRow, uchar *qtScanLine)
709{
710 QRgb *scanLine = reinterpret_cast<QRgb *>(qtScanLine);
711 for (int c = 0; c < qtWidth; ++c) {
712 *scanLine++ = (jasperRow[3][c] << 24) |
713 (jasperRow[0][c] << 16) |
714 (jasperRow[1][c] << 8) |
715 jasperRow[2][c];
716 }
717}
718
723void Jpeg2000JasperReader::copyScanlineJasperQtGray(jas_seqent_t ** const jasperRow, uchar *qtScanLine)
724{
725 for (int c = 0; c < qtWidth; ++c) {
726 // *qtScanLine++ = (jasperRow[0][c] >> (jasComponentPrecicion[0] - 8));
727 *qtScanLine++ = jasperRow[0][c];
728 }
729}
730
738void Jpeg2000JasperReader::copyScanlineJasperQtGrayA(jas_seqent_t ** const jasperRow, uchar *qtScanLine)
739{
740 QRgb *scanLine = reinterpret_cast<QRgb *>(qtScanLine);
741 for (int c = 0; c < qtWidth; ++c) {
742 *scanLine++ = (jasperRow[1][c] << 24) |
743 (jasperRow[0][c] << 16) |
744 (jasperRow[0][c] << 8) |
745 jasperRow[0][c];
746 }
747}
748
756bool Jpeg2000JasperReader::write(const QImage &image, int quality)
757{
758 if (!jasperOk)
759 return false;
760
761 qtImage = image;
762
763 qtHeight = qtImage.height();
764 qtWidth = qtImage.width();
765 qtDepth = qtImage.depth();
766
767 if (qtDepth == 32) { // RGB(A)
768 jasper_image = newRGBAImage(qtWidth, qtHeight, qtImage.hasAlphaChannel());
769 if (!jasper_image)
770 return false;
771
772 if (qtImage.hasAlphaChannel())
773 copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperRGBA);
774 else
775 copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperRGB);
776 } else if (qtDepth == 8) {
777 // Color mapped grayscale
778 if (qtImage.allGray()) {
779 jasper_image = newGrayscaleImage(qtWidth, qtHeight, qtImage.hasAlphaChannel());
780 if (!jasper_image)
781 return false;
782
783 if (qtImage.hasAlphaChannel())
784 copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperColormapGrayscaleA);
785 else
786 copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperColormapGrayscale);
787 } else {
788 // Color mapped color
789 jasper_image = newRGBAImage(qtWidth, qtHeight, qtImage.hasAlphaChannel());
790 if (!jasper_image)
791 return false;
792
793 if (qtImage.hasAlphaChannel())
794 copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperColormapRGBA);
795 else
796 copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperColormapRGB);
797 }
798 } else {
799 qDebug("Unable to handle color depth %d", qtDepth);
800 return false;
801 }
802
803 int fmtid;
804 if (format == Jp2Format)
805 fmtid = jas_image_strtofmt(const_cast<char*>("jp2"));
806 else /* if (format == J2cFormat) */
807 // JasPer refers to the code stream format as jpc
808 fmtid = jas_image_strtofmt(const_cast<char*>("jpc"));
809
810 const int minQuality = 0;
811 const int maxQuality = 100;
812
813 if (quality == -1)
814 quality = 100;
815 if (quality <= minQuality)
816 quality = minQuality;
817 if (quality > maxQuality)
818 quality = maxQuality;
819
820 // Qt specifies quality as an integer in the range 0..100. Jasper specifies
821 // compression rate as an real in the range 0..1, where 1 corresponds to no
822 // compression. Computing the rate from quality is difficult, large images
823 // get better image quality than small images at the same rate. If the rate
824 // is too low, Jasper will generate a completely black image.
825 // minirate is the smallest safe rate value.
826 const double minRate = 0.001;
827
828 // maxRate specifies maximum target rate, which give the minimum amount
829 // of compression. Tests show that maxRates higer than 0.3 give no
830 // additional image quality for most images. Large images could use an even
831 // smaller maxRate value.
832 const double maxRate = 0.3;
833
834 // Set jasperRate to a value in the range minRate..maxRate. Distribute the
835 // quality steps more densely at the lower end if the rate scale.
836 const double jasperRate = minRate + pow((double(quality) / double(maxQuality)), 2) * maxRate;
837
838 // The Jasper format string contains two options:
839 // rate: rate=x
840 // lossy/lossless compression : mode=real/mode=int
841 QString jasperFormatString;
842
843 // If quality is not maxQuality, we set lossy encoding.
844 // (lossless is default)
845 if (quality != maxQuality) {
846 jasperFormatString += QLatin1String("mode=real");
847 jasperFormatString += QString(QLatin1String(" rate=%1")).arg(jasperRate);
848 }
849
850 // Open an empty jasper stream that grows automatically
851 jas_stream_t * memory_stream = jas_stream_memopen(0, 0);
852
853 // Jasper wants a non-const string.
854 char *str = qstrdup(jasperFormatString.toLatin1().constData());
855 jas_image_encode(jasper_image, memory_stream, fmtid, str);
856 delete[] str;
857 jas_stream_flush(memory_stream);
858
859 // jas_stream_t::obj_ is a void* which points to the stream implementation,
860 // e.g a file stream or a memory stream. But in our case we know that it is
861 // a memory stream since we created the object, so we just reiterpret_cast
862 // here..
863 char *buffer = reinterpret_cast<char *>(reinterpret_cast<jas_stream_memobj_t*>(memory_stream->obj_)->buf_);
864 qint64 length = jas_stream_length(memory_stream);
865 ioDevice->write(buffer, length);
866
867 jas_stream_close(memory_stream);
868 jas_image_destroy(jasper_image);
869
870 return true;
871}
872
878void Jpeg2000JasperReader::copyQtJasper(const ScanlineFuncWrite scanlinecopier)
879{
880 // Create jasper matrix for holding one scanline
881 jas_matrix_t **jasperMatrix;
882 createJasperMatrix(jasperMatrix);
883
884 for (int scanline = 0; scanline < qtHeight; ++scanline) {
885 (this->*scanlinecopier)(jasperMatrix, qtImage.scanLine(scanline));
886
887 // Write a scanline of data to jasper_image
888 for (int c = 0; c < jasNumComponents; ++c)
889 jas_image_writecmpt(jasper_image, c, 0, scanline, qtWidth, 1,
890 jasperMatrix[c]);
891 }
892 freeJasperMatrix(jasperMatrix);
893}
894
898void Jpeg2000JasperReader::copyScanlineQtJasperRGB(jas_matrix_t ** jasperRow,
899 uchar *qtScanLine)
900{
901 QRgb *scanLineBuffer = reinterpret_cast<QRgb *>(qtScanLine);
902 for (int col = 0; col < qtWidth; ++col) {
903 jas_matrix_set(jasperRow[0], 0, col, (*scanLineBuffer & 0xFF0000) >> 16);
904 jas_matrix_set(jasperRow[1], 0, col, (*scanLineBuffer & 0x00FF00) >> 8);
905 jas_matrix_set(jasperRow[2], 0, col, *scanLineBuffer & 0x0000FF);
906 ++scanLineBuffer;
907 }
908}
909
913void Jpeg2000JasperReader::copyScanlineQtJasperRGBA(jas_matrix_t ** jasperRow,
914 uchar *qtScanLine)
915{
916 QRgb *scanLineBuffer = reinterpret_cast<QRgb *>(qtScanLine);
917 for (int col = 0; col < qtWidth; ++col) {
918 jas_matrix_set(jasperRow[3], 0, col, (*scanLineBuffer & 0xFF000000) >> 24);
919 jas_matrix_set(jasperRow[0], 0, col, (*scanLineBuffer & 0x00FF0000) >> 16);
920 jas_matrix_set(jasperRow[1], 0, col, (*scanLineBuffer & 0x0000FF00) >> 8);
921 jas_matrix_set(jasperRow[2], 0, col, *scanLineBuffer & 0x000000FF);
922 ++scanLineBuffer;
923 }
924}
925
929void Jpeg2000JasperReader::copyScanlineQtJasperColormapRGB(jas_matrix_t ** jasperRow,
930 uchar *qtScanLine)
931{
932 for (int col = 0; col < qtWidth; ++col) {
933 QRgb color = qtImage.color(*qtScanLine);
934 jas_matrix_set(jasperRow[0], 0, col, qRed(color));
935 jas_matrix_set(jasperRow[1], 0, col, qGreen(color));
936 jas_matrix_set(jasperRow[2], 0, col, qBlue(color));
937 ++qtScanLine;
938 }
939}
940
944void Jpeg2000JasperReader::copyScanlineQtJasperColormapRGBA(jas_matrix_t ** jasperRow,
945 uchar *qtScanLine)
946{
947 for (int col = 0; col < qtWidth; ++col) {
948 QRgb color = qtImage.color(*qtScanLine);
949 jas_matrix_set(jasperRow[0], 0, col, qRed(color));
950 jas_matrix_set(jasperRow[1], 0, col, qGreen(color));
951 jas_matrix_set(jasperRow[2], 0, col, qBlue(color));
952 jas_matrix_set(jasperRow[3], 0, col, qAlpha(color));
953 ++qtScanLine;
954 }
955}
956
960void Jpeg2000JasperReader::copyScanlineQtJasperColormapGrayscale(jas_matrix_t ** jasperRow,
961 uchar *qtScanLine)
962{
963 for (int col = 0; col < qtWidth; ++col) {
964 QRgb color = qtImage.color(*qtScanLine);
965 jas_matrix_set(jasperRow[0], 0, col, qGray(color));
966 ++qtScanLine;
967 }
968}
969
973void Jpeg2000JasperReader::copyScanlineQtJasperColormapGrayscaleA(jas_matrix_t ** jasperRow,
974 uchar *qtScanLine)
975{
976 for (int col = 0; col < qtWidth; ++col) {
977 QRgb color = qtImage.color(*qtScanLine);
978 jas_matrix_set(jasperRow[0], 0, col, qGray(color));
979 jas_matrix_set(jasperRow[1], 0, col, qAlpha(color));
980 ++qtScanLine;
981 }
982}
983
989bool Jpeg2000JasperReader::attemptColorspaceChange(int wantedColorSpace)
990{
991 //qDebug("Attemting color space change");
992 jas_cmprof_t *outprof;
993 if (!(outprof = jas_cmprof_createfromclrspc(wantedColorSpace)))
994 return false;
995
996 jas_image_t *newimage;
997 if (!(newimage = jas_image_chclrspc(jasper_image, outprof,
998 JAS_CMXFORM_INTENT_PER))) {
999 jas_cmprof_destroy(outprof);
1000 return false;
1001 }
1002 jas_image_destroy(jasper_image);
1003 jas_cmprof_destroy(outprof);
1004 jasper_image = newimage;
1005 return true;
1006}
1007
1012jas_image_cmptparm_t Jpeg2000JasperReader::createComponentMetadata(
1013 const int width, const int height)
1014{
1015 jas_image_cmptparm_t param;
1016 param.tlx = 0;
1017 param.tly = 0;
1018 param.hstep = 1;
1019 param.vstep = 1;
1020 param.width = width;
1021 param.height = height;
1022 param.prec = 8;
1023 param.sgnd = 0;
1024 return param;
1025}
1026
1031jas_image_t* Jpeg2000JasperReader::newRGBAImage(const int width,
1032 const int height, bool alpha)
1033{
1034 jasNumComponents = alpha ? 4 : 3;
1035 jas_image_cmptparm_t *params = new jas_image_cmptparm_t[jasNumComponents];
1036 jas_image_cmptparm_t param = createComponentMetadata(width, height);
1037 for (int c=0; c < jasNumComponents; c++)
1038 params[c] = param;
1039 jas_image_t *newImage = jas_image_create(jasNumComponents, params,
1040 JAS_CLRSPC_SRGB);
1041
1042 if (!newImage) {
1043 delete[] params;
1044 return 0;
1045 }
1046
1047 jas_image_setcmpttype(newImage, 0, JAS_IMAGE_CT_RGB_R);
1048 jas_image_setcmpttype(newImage, 1, JAS_IMAGE_CT_RGB_G);
1049 jas_image_setcmpttype(newImage, 2, JAS_IMAGE_CT_RGB_B);
1050
1051 /*
1052 It is unclear how one stores opacity(alpha) components with JasPer,
1053 the following seems to have no effect. The opacity component gets
1054 type id 3 or 48 depending jp2 or j2c format no matter what one puts
1055 in here.
1056
1057 The symbols are defined as follows:
1058 #define JAS_IMAGE_CT_RGB_R 0
1059 #define JAS_IMAGE_CT_RGB_G 1
1060 #define JAS_IMAGE_CT_RGB_B 2
1061 #define JAS_IMAGE_CT_OPACITY 0x7FFF
1062 */
1063 if (alpha)
1064 jas_image_setcmpttype(newImage, 3, JAS_IMAGE_CT_OPACITY);
1065 delete[] params;
1066 return newImage;
1067}
1068
1073jas_image_t *Jpeg2000JasperReader::newGrayscaleImage(const int width,
1074 const int height,
1075 bool alpha)
1076{
1077 jasNumComponents = alpha ? 2 : 1;
1078 jas_image_cmptparm_t param = createComponentMetadata(width, height);
1079 jas_image_t *newImage = jas_image_create(1, &param, JAS_CLRSPC_SGRAY);
1080 if (!newImage)
1081 return 0;
1082
1083 jas_image_setcmpttype(newImage, 0, JAS_IMAGE_CT_GRAY_Y);
1084
1085 // See corresponding comment for newRGBAImage.
1086 if (alpha)
1087 jas_image_setcmpttype(newImage, 1, JAS_IMAGE_CT_OPACITY);
1088 return newImage;
1089}
1090
1096bool Jpeg2000JasperReader::createJasperMatrix(jas_matrix_t **&matrix)
1097{
1098 matrix = (jas_matrix_t**)malloc(jasNumComponents * sizeof(jas_matrix_t *));
1099 for (int c = 0; c < jasNumComponents; ++c)
1100 matrix[c] = jas_matrix_create(1, qtWidth);
1101 return true;
1102}
1103
1109bool Jpeg2000JasperReader::freeJasperMatrix(jas_matrix_t **matrix)
1110{
1111 for (int c = 0; c < jasNumComponents; ++c)
1112 jas_matrix_destroy(matrix[c]);
1113 free(matrix);
1114 return false;
1115}
1116
1120void Jpeg2000JasperReader::printColorSpaceError()
1121{
1122 QString colorspaceFamily, colorspaceSpecific;
1123 decodeColorSpace(jas_image_clrspc(jasper_image), colorspaceFamily,
1124 colorspaceSpecific);
1125 qDebug("Jpeg2000 decoder is not able to handle color space %s - %s",
1126 qPrintable(colorspaceFamily), qPrintable(colorspaceSpecific));
1127}
1131bool Jpeg2000JasperReader::decodeColorSpace(int clrspc, QString &family,
1132 QString &specific)
1133{
1134 int fam = jas_clrspc_fam(clrspc);
1135 int mbr = jas_clrspc_mbr(clrspc);
1136
1137 switch (fam) {
1138 case 0: family = QLatin1String("JAS_CLRSPC_FAM_UNKNOWN"); break;
1139 case 1: family = QLatin1String("JAS_CLRSPC_FAM_XYZ"); break;
1140 case 2: family = QLatin1String("JAS_CLRSPC_FAM_LAB"); break;
1141 case 3: family = QLatin1String("JAS_CLRSPC_FAM_GRAY"); break;
1142 case 4: family = QLatin1String("JAS_CLRSPC_FAM_RGB"); break;
1143 case 5: family = QLatin1String("JAS_CLRSPC_FAM_YCBCR"); break;
1144 default: family = QLatin1String("Unknown"); return false;
1145 }
1146
1147 switch (mbr) {
1148 case 0:
1149 switch (fam) {
1150 case 1: specific = QLatin1String("JAS_CLRSPC_CIEXYZ"); break;
1151 case 2: specific = QLatin1String("JAS_CLRSPC_CIELAB"); break;
1152 case 3: specific = QLatin1String("JAS_CLRSPC_SGRAY"); break;
1153 case 4: specific = QLatin1String("JAS_CLRSPC_SRGB"); break;
1154 case 5: specific = QLatin1String("JAS_CLRSPC_SYCBCR"); break;
1155 default: specific = QLatin1String("Unknown"); return false;
1156 }
1157 break;
1158 case 1:
1159 switch (fam) {
1160 case 3: specific = QLatin1String("JAS_CLRSPC_GENGRAY"); break;
1161 case 4: specific = QLatin1String("JAS_CLRSPC_GENRGB"); break;
1162 case 5: specific = QLatin1String("JAS_CLRSPC_GENYCBCR"); break;
1163 default: specific = QLatin1String("Unknown"); return false;
1164 }
1165 break;
1166 default:
1167 return false;
1168 }
1169 return true;
1170}
1174void Jpeg2000JasperReader::printMetadata(jas_image_t *image)
1175{
1176#ifndef QT_NO_DEBUG
1177 // jas_image_cmptparm_t param
1178 qDebug("Image width: %ld", long(jas_image_width(image)));
1179 qDebug("Image height: %ld", long(jas_image_height(image)));
1180 qDebug("Coordinates on reference grid: (%ld,%ld) (%ld,%ld)",
1181 long(jas_image_tlx(image)), long(jas_image_tly(image)),
1182 long(jas_image_brx(image)), long(jas_image_bry(image)));
1183 qDebug("Number of image components: %d", jas_image_numcmpts(image));
1184
1185 QString colorspaceFamily;
1186 QString colorspaceSpecific;
1187 decodeColorSpace(jas_image_clrspc(image), colorspaceFamily, colorspaceSpecific);
1188 qDebug("Color model (space): %d, %s - %s", jas_image_clrspc(image),
1189 qPrintable(colorspaceFamily), qPrintable(colorspaceSpecific));
1190
1191 qDebug("Component metadata:");
1192
1193 for (int c = 0; c < static_cast<int>(jas_image_numcmpts(image)); ++c) {
1194 qDebug("Component %d:", c);
1195 qDebug(" Component type: %ld", long(jas_image_cmpttype(image, c)));
1196 qDebug(" Width: %ld", long(jas_image_cmptwidth(image, c)));
1197 qDebug(" Height: %ld", long(jas_image_cmptheight(image, c)));
1198 qDebug(" Signedness: %d", jas_image_cmptsgnd(image, c));
1199 qDebug(" Precision: %d", jas_image_cmptprec(image, c));
1200 qDebug(" Horizontal subsampling factor: %ld",long(jas_image_cmpthstep(image, c)));
1201 qDebug(" Vertical subsampling factor: %ld", long(jas_image_cmptvstep(image, c)));
1202 qDebug(" Coordinates on reference grid: (%ld,%ld) (%ld,%ld)",
1203 long(jas_image_cmpttlx(image, c)), long(jas_image_cmpttly(image, c)),
1204 long(jas_image_cmptbrx(image, c)), long(jas_image_cmptbry(image, c)));
1205 }
1206#else
1207 Q_UNUSED(image);
1208#endif
1209}
1210
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.
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:534
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:474
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
\inmodule QtCore \reentrant
Definition qiodevice.h:34
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
qint64 peek(char *data, qint64 maxlen)
ImageOption
This enum describes the different options supported by QImageIOHandler.
static bool allocateImage(QSize size, QImage::Format format, QImage *image)
QIODevice * device() const
Returns the device currently assigned to the QImageIOHandler.
void setFormat(const QByteArray &format)
Sets the format of the QImageIOHandler to format.
static int allocationLimit()
QImage read()
Reads an image from the device.
\inmodule QtGui
Definition qimage.h:37
bool hasAlphaChannel() const
Returns true if the image has a format that respects the alpha channel, otherwise returns false.
Definition qimage.cpp:4571
uchar * scanLine(int)
Returns a pointer to the pixel data at the scanline with index i.
Definition qimage.cpp:1615
bool allGray() const
Returns true if all the colors in the image are shades of gray (i.e.
Definition qimage.cpp:2854
QRgb color(int i) const
Returns the color in the color table at index i.
Definition qimage.cpp:1555
int width() const
Returns the width of the image.
int height() const
Returns the height of the image.
Format
The following image formats are available in Qt.
Definition qimage.h:41
@ Format_RGB32
Definition qimage.h:46
@ Format_Invalid
Definition qimage.h:42
@ Format_ARGB32
Definition qimage.h:47
@ Format_Grayscale8
Definition qimage.h:66
int depth() const
QJp2Handler * q_ptr
QJp2HandlerPrivate(QJp2Handler *q_ptr)
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.
void setOption(ImageOption option, const QVariant &value) override
The JPEG 2000 handler supports two options.
bool write(const QImage &image) override
\reimp
bool read(QImage *image) override
\reimp
bool supportsOption(ImageOption option) const override
This function will return true if option is set to either QImageIOHandler::Quality or QImageIOHandler...
QVariant option(ImageOption option) const override
Get the value associated with option.
bool canRead() const override
\reimp
\inmodule QtCore
Definition qsize.h:25
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
QByteArray toLatin1() const &
Definition qstring.h:559
bool startsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string starts with s; otherwise returns false.
Definition qstring.cpp:5299
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
Definition qstring.cpp:8606
\inmodule QtCore
Definition qvariant.h:64
Automatic resource handling for a jas_image_t*.
ScopedJasperImage(jas_image_t *&image)
#define this
Definition dialogs.cpp:9
QString str
[2]
Combined button and popup list for selecting options.
Definition image.cpp:4
#define QByteArrayLiteral(str)
Definition qbytearray.h:52
Q_CORE_EXPORT char * qstrdup(const char *)
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
static QString header(const QString &name)
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
SubFormat
@ Jp2Format
@ J2kFormat
#define qDebug
[1]
Definition qlogging.h:160
GLint GLsizei GLsizei height
GLenum GLuint GLenum GLsizei length
GLenum GLuint buffer
GLint GLsizei width
GLenum const GLint * param
GLint GLsizei GLsizei GLenum format
void ** params
const GLubyte * c
GLuint GLenum matrix
GLuint GLenum option
GLfloat GLfloat GLfloat alpha
Definition qopenglext.h:418
static bool hasAlpha(const QImage &image)
QT_BEGIN_NAMESPACE typedef unsigned int QRgb
Definition qrgb.h:13
constexpr int qRed(QRgb rgb)
Definition qrgb.h:18
constexpr int qGreen(QRgb rgb)
Definition qrgb.h:21
constexpr int qGray(int r, int g, int b)
Definition qrgb.h:36
constexpr int qBlue(QRgb rgb)
Definition qrgb.h:24
constexpr int qAlpha(QRgb rgb)
Definition qrgb.h:27
#define qPrintable(string)
Definition qstring.h:1391
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define Q_UNUSED(x)
unsigned char uchar
Definition qtypes.h:27
long long qint64
Definition qtypes.h:55
Q_CHECK_PTR(a=new int[80])
QByteArray imageData
[15]