Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // Copyright (C) 2016 Petroules Corporation.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:critical reason:data-parser
++
++ #include "qjp2handler_p.h"
++
++ #include "qimage.h"
++ #include "qvariant.h"
++ #include "qcolor.h"
++ #include "qimagereader.h"
++ #include "qnumeric.h"
++
++ #include <jasper/jasper.h>
++ #include <math.h> // for pow
++ #include <limits>
++
++ QT_BEGIN_NAMESPACE
++
++ class QJp2HandlerPrivate
++ {
++ Q_DECLARE_PUBLIC(QJp2Handler)
++ Q_DISABLE_COPY(QJp2HandlerPrivate)
++ public:
++ int writeQuality;
++ QByteArray subType;
++ QJp2Handler *q_ptr;
++ QJp2HandlerPrivate(QJp2Handler *q_ptr);
++ };
++
++ enum SubFormat { Jp2Format, J2kFormat };
++
++ /*
++ \class Jpeg2000JasperReader
++ \brief Jpeg2000JasperReader implements reading and writing of JPEG 2000
++ image files.
++
++ \internal
++
++ This class is designed to be used together with the an QImageIO IOHandler,
++ and it should probably not be necessary to instantiate it directly.
++
++ Internally it used the Jasper library for coding the image data.
++ */
++ class Jpeg2000JasperReader
++ {
++ public:
++ Jpeg2000JasperReader(QIODevice *iod, const SubFormat format = Jp2Format);
++
++ ~Jpeg2000JasperReader();
++
++ bool read(QImage *pImage);
++ bool write(const QImage &image, int quality);
++ private:
++ typedef void (Jpeg2000JasperReader::*ScanlineFunc)(jas_seqent_t** const, uchar*);
++ typedef void (Jpeg2000JasperReader::*ScanlineFuncWrite)(jas_matrix_t**, uchar*);
++
++ void copyJasperQt(ScanlineFunc scanlinecopier);
++ void copyJasperQtGeneric();
++ void copyScanlineJasperQtRGB(jas_seqent_t ** const jasperRow, uchar *qtScanLine);
++ void copyScanlineJasperQtRGBA(jas_seqent_t ** const jasperRow, uchar *qtScanLine);
++ void copyScanlineJasperQtGray(jas_seqent_t ** const jasperRow, uchar *qtScanLine);
++ void copyScanlineJasperQtGrayA(jas_seqent_t ** const jasperRow, uchar *qtScanLine);
++
++ void copyQtJasper(const ScanlineFuncWrite scanlinecopier);
++ void copyScanlineQtJasperRGB(jas_matrix_t ** jasperRow, uchar *qtScanLine);
++ void copyScanlineQtJasperRGBA(jas_matrix_t ** jasperRow, uchar *qtScanLine);
++ void copyScanlineQtJasperColormapRGB(jas_matrix_t ** jasperRow, uchar *qtScanLine);
++ void copyScanlineQtJasperColormapRGBA(jas_matrix_t ** jasperRow, uchar *qtScanLine);
++ void copyScanlineQtJasperColormapGrayscale(jas_matrix_t ** jasperRow, uchar *qtScanLine);
++ void copyScanlineQtJasperColormapGrayscaleA(jas_matrix_t ** jasperRow, uchar *qtScanLine);
++
++ bool attemptColorspaceChange(int wantedColorSpace);
++ bool createJasperMatrix(jas_matrix_t **&matrix);
++ bool freeJasperMatrix(jas_matrix_t **matrix);
++ void printColorSpaceError();
++ jas_image_cmptparm_t createComponentMetadata(const int width, const int height);
++ jas_image_t *newRGBAImage(const int width, const int height, bool alpha);
++ jas_image_t *newGrayscaleImage(const int width, const int height, bool alpha);
++ bool decodeColorSpace(int clrspc, QString &family, QString &specific);
++ void printMetadata(jas_image_t *image);
++
++ bool jasperOk;
++
++ QIODevice *ioDevice;
++ QImage qtImage;
++ SubFormat format;
++
++ // Qt image properties
++ int qtWidth;
++ int qtHeight;
++ int qtDepth;
++ int qtNumComponents;
++
++ jas_image_t *jasper_image;
++ // jasper image properties
++ int jasNumComponents;
++ int jasComponentPrecicion[4];
++ int computedComponentWidth ;
++ int computedComponentHeight;
++ int computedComponentHorizontalSubsampling;
++ int computedComponentVerticalSubsampling;
++ int jasperColorspaceFamily;
++ // maps color to component (ex: colorComponentMapping[RED]
++ // gives the component that contains the red color)
++ int colorComponentMapping[4];
++ bool hasAlpha;
++ };
++
++ QJp2HandlerPrivate::QJp2HandlerPrivate(QJp2Handler *q_ptr)
++ : writeQuality(100), subType("jp2"), q_ptr(q_ptr)
++ {
++ }
++
++ /*!
++ \class QJp2Handler
++ \brief The QJp2Handler class provides support for reading and writing
++ JPEG 2000 image files with the Qt plugin system.
++ Currently, it only supports dynamically-loaded plugins.
++
++ JPEG files comes in two subtypes: the JPEG 2000 file format (\c .jp2) and
++ the JPEG 2000 code stream format (\c .j2k, \c .jpc, or \c .j2c).
++ QJp2Handler can read and write both types.
++
++ To select a subtype, use the setOption() function with the
++ QImageIOHandler::SubType option and a parameter value of "jp2" or "j2k".
++
++ To set the image quality when writing, you can use setOption() with the
++ QImageIOHandler::Quality option, or QImageWriter::setQuality() if your are
++ using a QImageWriter object to write the image. The image quality is
++ specified as an int in the range 0 to 100. 0 means maximum compression and
++ 99 means minimum compression. 100 selects lossless encoding, and this is the
++ default value.
++
++ The JPEG handler is only available as a plugin,
++ and this enables you to use normal QImage and QPixmap functions to read and
++ write images. For example:
++
++ \code
++ myLabel->setPixmap(QPixmap("myimage.jp2"));
++
++ QPixmap myPixmap;
++ myPixmap.save("myimage.jp2", "JP2");
++ \endcode
++ */
++
++ /*!
++ Constructs an instance of QJp2Handler.
++ */
++ QJp2Handler::QJp2Handler()
++ : d_ptr(new QJp2HandlerPrivate(this))
++ {
++ }
++
++ /*!
++ Destructor for QJp2Handler.
++ */
++ QJp2Handler::~QJp2Handler()
++ {
++
++ }
++
++ /*!
++ Verifies if some values (magic bytes) are set as expected in the
++ header of the file. If the magic bytes were found, we assume that we
++ can read the file. The function will assume that the \a iod is
++ pointing to the beginning of the JPEG 2000 header. (i.e. it will for
++ instance not seek to the beginning of a file before reading).
++
++ If \a subType is not 0, it will contain "jp2" or "j2k" upon
++ successful return.
++ */
++ bool QJp2Handler::canRead(QIODevice *iod, QByteArray *subType)
++ {
++ bool bCanRead = false;
++ if (iod) {
++ const QByteArray header = iod->peek(12);
++ if (header.startsWith(QByteArrayLiteral("\000\000\000\fjP \r\n\207\n"))) {
++ // Jp2 is the JPEG 2000 file format
++ bCanRead = true;
++ if (subType)
++ *subType = QByteArray("jp2");
++ } else if (header.startsWith(QByteArrayLiteral("\377\117\377\121\000"))) {
++ // J2c is the JPEG 2000 code stream
++ bCanRead = true;
++ if (subType)
++ *subType = QByteArray("j2k");
++ }
++ }
++ return bCanRead;
++ }
++
++ /*! \reimp
++ */
++ bool QJp2Handler::canRead() const
++ {
++ QByteArray subType;
++ if (canRead(device(), &subType)) {
++ setFormat(subType);
++ return true;
++ }
++ return false;
++ }
++
++ /*! \reimp
++ */
++ bool QJp2Handler::read(QImage *image)
++ {
++ Jpeg2000JasperReader reader(device());
++ return reader.read(image);
++ }
++
++ /*! \reimp
++ */
++ bool QJp2Handler::write(const QImage &image)
++ {
++ Q_D(const QJp2Handler);
++ SubFormat subFormat;
++ if (d->subType == QByteArray("jp2"))
++ subFormat = Jp2Format;
++ else
++ subFormat = J2kFormat;
++
++ Jpeg2000JasperReader writer(device(), subFormat);
++ return writer.write(image, d->writeQuality);
++ }
++
++ /*!
++ Get the value associated with \a option.
++ \sa setOption()
++ */
++ QVariant QJp2Handler::option(ImageOption option) const
++ {
++ Q_D(const QJp2Handler);
++ if (option == Quality) {
++ return QVariant(d->writeQuality);
++ } else if (option == SubType) {
++ return QVariant(d->subType);
++ }
++ return QVariant();
++ }
++
++ /*!
++ The JPEG 2000 handler supports two options.
++ Set \a option to QImageIOHandler::Quality to balance between quality and the
++ compression level, where \a value should be an integer in the interval
++ [0-100]. 0 is maximum compression (low quality) and 100 is lossless
++ compression (high quality).
++
++ Set \a option to QImageIOHandler::Subtype to choose the subtype,
++ where \a value should be a string equal to either "jp2" or "j2k"
++ \sa option()
++ */
++ void QJp2Handler::setOption(ImageOption option, const QVariant &value)
++ {
++ Q_D(QJp2Handler);
++ if (option == Quality) {
++ bool ok;
++ const int quality = value.toInt(&ok);
++ if (ok)
++ d->writeQuality = quality;
++ } else if (option == SubType) {
++ const QByteArray subTypeCandidate = value.toByteArray();
++ // Test for default Jpeg2000 file format (jp2), or stream format (j2k).
++ if (subTypeCandidate == QByteArrayLiteral("jp2") ||
++ subTypeCandidate == QByteArrayLiteral("j2k"))
++ d->subType = subTypeCandidate;
++ }
++ }
++
++ /*!
++ This function will return true if \a option is set to either
++ QImageIOHandler::Quality or QImageIOHandler::Subtype.
++ */
++ bool QJp2Handler::supportsOption(ImageOption option) const
++ {
++ return (option == Quality || option == SubType);
++ }
++
++ /*!
++ Automatic resource handling for a jas_image_t*.
++ */
++ class ScopedJasperImage
++ {
++ public:
++ // Take reference to the pointer here, because the pointer
++ // may change when we change color spaces.
++ ScopedJasperImage(jas_image_t *&image):image(image) { }
++ ~ScopedJasperImage() { jas_image_destroy(image); }
++ private:
++ jas_image_t *&image;
++ };
++
++ /*! \internal
++ Construct a Jpeg2000JasperReader using the provided \a imageIO.
++ Note that currently the jasper library is initialized in this constructor,
++ (and freed in the destructor) which means that:
++ - Only one instance of this class may exist at one time
++ - No thread safety
++ */
++ Jpeg2000JasperReader::Jpeg2000JasperReader(QIODevice *iod, SubFormat format)
++ : jasperOk(true), ioDevice(iod), format(format), hasAlpha(false)
++ {
++ #if JAS_VERSION_MAJOR < 3
++ if (jas_init()) {
++ jasperOk = false;
++ qDebug("Jasper Library initialization failed");
++ }
++ #else
++ jas_conf_clear();
++ #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
++ if (QImageReader::allocationLimit() > 0)
++ jas_conf_set_max_mem_usage(qsizetype(QImageReader::allocationLimit()) * 1024 * 1024);
++ #else
++ // 128MB seems to be enough.
++ jas_conf_set_max_mem_usage(128 * 1024 * 1024);
++ #endif
++ if (jas_init_library()) {
++ jasperOk = false;
++ qDebug("Jasper library initialization failed");
++ }
++ if (jas_init_thread()) {
++ jas_cleanup_library();
++ jasperOk = false;
++ qDebug("Jasper thread initialization failed");
++ }
++ #endif
++ }
++
++ Jpeg2000JasperReader::~Jpeg2000JasperReader()
++ {
++ #if JAS_VERSION_MAJOR < 3
++ if (jasperOk)
++ jas_cleanup();
++ #else
++ if (jasperOk) {
++ if (jas_cleanup_thread()) {
++ qDebug("Jasper thread cleanup failed");
++ }
++ if (jas_cleanup_library()) {
++ qDebug("Jasper library cleanup failed");
++ }
++ }
++ #endif
++ }
++
++ /*! \internal
++ Opens the file data and attempts to decode it using the Jasper library.
++ Returns true if successful, false on failure
++ */
++ bool Jpeg2000JasperReader::read(QImage *pImage)
++ {
++ if (!jasperOk)
++ return false;
++
++ const auto jasCoordInIntRange = [](jas_image_coord_t v) {
++ return v >= 1 && qint64(v) <= qint64(std::numeric_limits<int>::max());
++ };
++
++ /*
++ Reading proceeds approximately as follows:
++ 1. Open stream and decode using Jasper
++ 2. Get image metadata
++ 3. Change colorspace if necessary
++ 4. Create a QImage of the appropriate type (32-bit for RGB,
++ 8-bit for grayscale)
++ 5. Copy image data from Jasper to the QImage
++
++ When copying the image data from the Jasper data structures to the
++ QImage, a generic copy function (copyJasperQt) iterates through the
++ scanlines and calls the provided (via the scanlineCopier argument)
++ scanline copy function for each scanline. The scanline copy function
++ selected according to image metadata such as color space and the
++ presence of an alpha channel.
++ */
++ QByteArray fileContents = ioDevice->readAll();
++ jas_stream_t *imageData = jas_stream_memopen(fileContents.data(),
++ fileContents.size());
++ jasper_image = jas_image_decode(imageData, jas_image_getfmt(imageData), 0);
++ jas_stream_close(imageData);
++ if (!jasper_image) {
++ qDebug("Jasper library can't decode Jpeg2000 image data");
++ return false;
++ }
++ ScopedJasperImage scopedImage(jasper_image);
++ //printMetadata(jasper_image);
++
++ qtWidth = jas_image_width(jasper_image);
++ qtHeight = jas_image_height(jasper_image);
++ jasNumComponents = jas_image_numcmpts(jasper_image);
++ jasperColorspaceFamily = jas_clrspc_fam(jas_image_clrspc(jasper_image));
++
++ if (jasNumComponents < 1 || jasNumComponents > 4) {
++ qDebug("Unsupported number of components (%d) in JPEG 2000 image", jasNumComponents);
++ return false;
++ }
++
++ bool needColorspaceChange = false;
++ if (jasperColorspaceFamily != JAS_CLRSPC_FAM_RGB &&
++ jasperColorspaceFamily != JAS_CLRSPC_FAM_GRAY)
++ needColorspaceChange = true;
++
++ // Get per-component data
++ int c;
++ for (c = 0; c < jasNumComponents; ++c) {
++ jasComponentPrecicion[c] = jas_image_cmptprec(jasper_image, c);
++
++ // Test for precision
++ if (jasComponentPrecicion[c] > 8 || jasComponentPrecicion[c] < 8)
++ needColorspaceChange = true;
++
++ // Test for subsampling
++ if (jas_image_cmpthstep(jasper_image, c) != 1 ||
++ jas_image_cmptvstep(jasper_image, c) != 1)
++ needColorspaceChange = true;
++
++ // Test for signed components
++ if (jas_image_cmptsgnd(jasper_image, c) != 0)
++ needColorspaceChange = true;
++ }
++
++ /*
++ If we encounter a different color space than RGB
++ (such as XYZ or YCbCr) we change that to RGB.
++ Also, if any component has "funny" metadata (such as precicion != 8 bits
++ or subsampling != 1) we also do a colorspace
++ change in order to convert it to something we can load.
++ */
++
++ bool decodeOk = true;
++ if (needColorspaceChange)
++ decodeOk = attemptColorspaceChange(JAS_CLRSPC_SRGB);
++
++ if (!decodeOk) {
++ printColorSpaceError();
++ return false;
++ }
++
++ // Image metadata may have changed, get from Jasper.
++ const jas_image_coord_t imageWidth = jas_image_width(jasper_image);
++ const jas_image_coord_t imageHeight = jas_image_height(jasper_image);
++ if (!jasCoordInIntRange(imageWidth) || !jasCoordInIntRange(imageHeight)) {
++ qDebug("The Qt JPEG 2000 reader does not support image dimensions this large");
++ return false;
++ }
++ qtWidth = imageWidth;
++ qtHeight = imageHeight;
++ jasNumComponents = jas_image_numcmpts(jasper_image);
++ jasperColorspaceFamily = jas_clrspc_fam(jas_image_clrspc(jasper_image));
++ if (jasNumComponents < 1 || jasNumComponents > 4) {
++ qDebug("Unsupported number of components (%d) in JPEG 2000 image", jasNumComponents);
++ return false;
++ }
++ for (c = 0; c < jasNumComponents; ++c) {
++ jasComponentPrecicion[c] = jas_image_cmptprec(jasper_image, c);
++ }
++
++ if (jasperColorspaceFamily != JAS_CLRSPC_FAM_RGB &&
++ jasperColorspaceFamily != JAS_CLRSPC_FAM_GRAY) {
++ qDebug("The Qt JPEG 2000 reader was unable to convert colorspace to RGB or grayscale");
++ return false;
++ }
++
++ // If a component has a subsampling factor != 1, we can't trust
++ // jas_image_height/width, so we need to figure it out ourselves
++ bool oddComponentSubsampling = false;
++ for (c = 0; c < jasNumComponents; ++c) {
++ if (jas_image_cmpthstep(jasper_image, c) != 1 ||
++ jas_image_cmptvstep(jasper_image, c) != 1) {
++ oddComponentSubsampling = true;
++ }
++ }
++
++ if (oddComponentSubsampling) {
++ // Check if all components have the same vertical/horizontal dim and
++ // subsampling
++ const jas_image_coord_t cmptWidth = jas_image_cmptwidth(jasper_image, 0);
++ const jas_image_coord_t cmptHeight = jas_image_cmptheight(jasper_image, 0);
++ const jas_image_coord_t hstep = jas_image_cmpthstep(jasper_image, 0);
++ const jas_image_coord_t vstep = jas_image_cmptvstep(jasper_image, 0);
++ if (!jasCoordInIntRange(cmptWidth) || !jasCoordInIntRange(cmptHeight) ||
++ !jasCoordInIntRange(hstep) || !jasCoordInIntRange(vstep)) {
++ qDebug("The Qt JPEG 2000 reader does not support image dimensions this large");
++ return false;
++ }
++ computedComponentWidth = cmptWidth;
++ computedComponentHeight = cmptHeight;
++ computedComponentHorizontalSubsampling = hstep;
++ computedComponentVerticalSubsampling = vstep;
++
++ for (c = 1; c < jasNumComponents; ++c) {
++ if (computedComponentWidth != jas_image_cmptwidth(jasper_image, c) ||
++ computedComponentWidth != jas_image_cmptwidth(jasper_image, c) ||
++ computedComponentHorizontalSubsampling != jas_image_cmpthstep(jasper_image, c) ||
++ computedComponentVerticalSubsampling != jas_image_cmptvstep(jasper_image, c)) {
++ qDebug("The Qt JPEG 2000 reader does not support images where "
++ "component geometry differs from image geometry");
++ return false;
++ }
++ }
++ if (qMulOverflow(computedComponentWidth, computedComponentHorizontalSubsampling, &qtWidth) ||
++ qMulOverflow(computedComponentHeight, computedComponentVerticalSubsampling, &qtHeight)) {
++ qDebug("The Qt JPEG 2000 reader does not support image dimensions this large");
++ return false;
++ }
++ }
++
++ // Sanity check each component
++ for (c = 0; c < jasNumComponents; ++c) {
++ // Test for precision
++ if (jasComponentPrecicion[c]>8 || jasComponentPrecicion[c]<8) {
++ qDebug("The Qt JPEG 2000 reader does not support components with "
++ "precision != 8");
++ decodeOk = false;
++ }
++ #if 0
++ // Test the subsampling factor (space between pixels on the image grid)
++ if (oddComponentSubsampling) {
++ qDebug("The Qt JPEG 2000 reader does not support components with "
++ "a subsampling factor != 1 (yet)");
++ decodeOk = false;
++ }
++ #endif
++ // Test for signed components
++ if (jas_image_cmptsgnd(jasper_image, c) != 0) {
++ qDebug("Qt JPEG 2000 reader does not support signed components");
++ decodeOk = false;
++ }
++
++ // Test for component/image geomoetry mismach.
++ // If oddComponentSubsampling, then this is already taken care of above.
++ if (!oddComponentSubsampling)
++ if (jas_image_cmpttlx(jasper_image,c) != 0 ||
++ jas_image_cmpttly(jasper_image,c) != 0 ||
++ jas_image_cmptbrx(jasper_image,c) != jas_image_brx(jasper_image) ||
++ jas_image_cmptbry(jasper_image,c) != jas_image_bry(jasper_image) ||
++ jas_image_cmptwidth (jasper_image, c) != jas_image_width (jasper_image) ||
++ jas_image_cmptheight(jasper_image, c) != jas_image_height(jasper_image )) {
++ qDebug("The Qt JPEG 2000 reader does not support images where "
++ "component geometry differs from image geometry");
++ printMetadata(jasper_image);
++ decodeOk = false;
++ }
++ }
++ if (!decodeOk)
++ return false;
++
++ // At this point, the colorspace should be either RGB or grayscale,
++ // and each component should have eight bits of precision and
++ // no unsupported geometry.
++ //printMetadata(jasper_image);
++
++ // Get color components
++ jasperColorspaceFamily = jas_clrspc_fam(jas_image_clrspc(jasper_image));
++ if (jasperColorspaceFamily == JAS_CLRSPC_FAM_RGB) {
++ if (jasNumComponents > 4)
++ qDebug("JPEG 2000 reader expected 3 or 4 components, got %d",
++ jasNumComponents);
++
++ // Set up mapping from R,G,B -> component num.
++ colorComponentMapping[0] = jas_image_getcmptbytype(jasper_image,
++ JAS_IMAGE_CT_RGB_R);
++ colorComponentMapping[1] = jas_image_getcmptbytype(jasper_image,
++ JAS_IMAGE_CT_RGB_G);
++ colorComponentMapping[2] = jas_image_getcmptbytype(jasper_image,
++ JAS_IMAGE_CT_RGB_B);
++ qtNumComponents = 3;
++ } else if (jasperColorspaceFamily == JAS_CLRSPC_FAM_GRAY) {
++ if (jasNumComponents > 2)
++ qDebug("JPEG 2000 reader expected 1 or 2 components, got %d",
++ jasNumComponents);
++ colorComponentMapping[0] = jas_image_getcmptbytype(jasper_image,
++ JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_GRAY_Y));
++ qtNumComponents = 1;
++ } else {
++ printColorSpaceError();
++ return false;
++ }
++
++ // Get alpha component if one exists. Due to the lack of test images,
++ // loading images with alpha channels is a bit untested. It works
++ // with images saved with this implementation though.
++ const int posibleAlphaComponent1 = 3;
++ const int posibleAlphaComponent2 = 48;
++
++ if (jasNumComponents == qtNumComponents + 1) {
++ colorComponentMapping[qtNumComponents] = jas_image_getcmptbytype(jasper_image, posibleAlphaComponent1);
++ if (colorComponentMapping[qtNumComponents] < 0) {
++ colorComponentMapping[qtNumComponents] = jas_image_getcmptbytype(jasper_image, posibleAlphaComponent2);
++ }
++ if (colorComponentMapping[qtNumComponents] > 0) {
++ hasAlpha = true;
++ qtNumComponents++;
++ }
++ }
++
++ // Check for missing components
++ for (c = 0; c < qtNumComponents; ++c) {
++ if (colorComponentMapping[c] < 0) {
++ qDebug("JPEG 2000 reader missing a color component");
++ return false;
++ }
++ }
++
++ // Create a QImage of the correct type
++ QImage::Format qtFormat = QImage::Format_Invalid;
++ if (jasperColorspaceFamily == JAS_CLRSPC_FAM_RGB)
++ qtFormat = hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32;
++ else if (jasperColorspaceFamily == JAS_CLRSPC_FAM_GRAY)
++ qtFormat = hasAlpha ? QImage::Format_ARGB32 : QImage::Format_Grayscale8;
++ if (!QImageIOHandler::allocateImage(QSize(qtWidth, qtHeight), qtFormat, &qtImage))
++ return false;
++
++ // Copy data
++ if (oddComponentSubsampling) {
++ // This is a hack really, copying of data with component subsampling
++ // != 1 doesn't fit in with the rest of the scanline copying framework.
++ copyJasperQtGeneric();
++ } else if (jasperColorspaceFamily == JAS_CLRSPC_FAM_RGB) {
++ if (hasAlpha)
++ copyJasperQt(&Jpeg2000JasperReader::copyScanlineJasperQtRGBA);
++ else
++ copyJasperQt(&Jpeg2000JasperReader::copyScanlineJasperQtRGB);
++ } else if (jasperColorspaceFamily == JAS_CLRSPC_FAM_GRAY) {
++ if (hasAlpha)
++ copyJasperQt(&Jpeg2000JasperReader::copyScanlineJasperQtGrayA);
++ else
++ copyJasperQt(&Jpeg2000JasperReader::copyScanlineJasperQtGray);
++ }
++ if (decodeOk)
++ *pImage = qtImage;
++
++ return decodeOk;
++ }
++
++ /*!
++ \internal
++ */
++ void Jpeg2000JasperReader::copyJasperQtGeneric()
++ {
++ // Create scanline data poinetrs
++ jas_matrix_t **jasperMatrix;
++ jas_seqent_t **jasperRow;
++ createJasperMatrix(jasperMatrix);
++ jasperRow = (jas_seqent_t**)malloc(jasNumComponents * sizeof(jas_seqent_t *));
++ Q_CHECK_PTR(jasperRow);
++
++ int imageY = 0;
++ for (int componentY = 0; componentY < computedComponentHeight; ++componentY) {
++ for (int c = 0; c < jasNumComponents; ++c) {
++ jas_image_readcmpt(jasper_image, colorComponentMapping[c], 0,
++ componentY, computedComponentWidth, 1,
++ jasperMatrix[c]);
++ jasperRow[c] = jas_matrix_getref(jasperMatrix[c], 0, 0);
++ }
++ for (int verticalSubsample = 0;
++ verticalSubsample < computedComponentVerticalSubsampling;
++ ++verticalSubsample) {
++ uchar *scanLineUchar = qtImage.scanLine(imageY);
++ QRgb *scanLineQRgb = reinterpret_cast<QRgb *>(scanLineUchar);
++ for (int componentX = 0; componentX < computedComponentWidth;
++ ++componentX) {
++ for (int horizontalSubsample = 0;
++ horizontalSubsample <
++ computedComponentHorizontalSubsampling;
++ ++horizontalSubsample) {
++ if (jasperColorspaceFamily == JAS_CLRSPC_FAM_RGB) {
++ if (hasAlpha) {
++ *scanLineQRgb++ = (jasperRow[3][componentX] << 24) |
++ (jasperRow[0][componentX] << 16) |
++ (jasperRow[1][componentX] << 8) |
++ jasperRow[2][componentX];
++ } else {
++ *scanLineQRgb++ = (jasperRow[0][componentX] << 16) |
++ (jasperRow[1][componentX] << 8) |
++ jasperRow[2][componentX];
++ }
++ } else if (jasperColorspaceFamily == JAS_CLRSPC_FAM_GRAY) {
++ if (hasAlpha) {
++ *scanLineQRgb++ = (jasperRow[1][componentX] << 24) |
++ (jasperRow[0][componentX] << 16) |
++ (jasperRow[0][componentX] << 8) |
++ jasperRow[0][componentX];
++ } else {
++ *scanLineUchar++ = jasperRow[0][componentX];
++ }
++ }
++ }
++ }
++ ++imageY;
++ }
++ }
++ }
++
++ /*!
++ \internal
++ Copies data from Jasper to QImage. The scanlineCopier parameter specifies
++ which function to use for handling each scan line.
++ */
++ void Jpeg2000JasperReader::copyJasperQt(const ScanlineFunc scanlineCopier)
++ {
++ // Create scanline data poinetrs
++ jas_matrix_t **jasperMatrix;
++ jas_seqent_t **jasperRow;
++
++ createJasperMatrix(jasperMatrix);
++ jasperRow = (jas_seqent_t**)malloc(jasNumComponents * sizeof(jas_seqent_t *));
++ Q_CHECK_PTR(jasperRow);
++
++ for (int scanline = 0; scanline < qtHeight; ++scanline) {
++ for (int c = 0; c < jasNumComponents; ++c) {
++ jas_image_readcmpt(jasper_image, colorComponentMapping[c], 0,
++ scanline, qtWidth, 1, jasperMatrix[c]);
++ jasperRow[c] = jas_matrix_getref(jasperMatrix[c], 0, 0);
++ }
++ (this->*scanlineCopier)(jasperRow, qtImage.scanLine(scanline));
++ }
++
++ freeJasperMatrix(jasperMatrix);
++ free(jasperRow);
++ }
++
++ /*!
++ \internal
++ Copies RGB data from Jasper to a 32-bit QImage.
++ */
++ void Jpeg2000JasperReader::copyScanlineJasperQtRGB(
++ jas_seqent_t ** const jasperRow, uchar *qtScanLine)
++ {
++ QRgb *scanLine = reinterpret_cast<QRgb *>(qtScanLine);
++ for (int c = 0; c < qtWidth; ++c) {
++ *scanLine++ = (0xFF << 24) |
++ (jasperRow[0][c] << 16) |
++ (jasperRow[1][c] << 8) |
++ jasperRow[2][c];
++ }
++ }
++
++ /*!
++ \internal
++ Copies RGBA data from Jasper to a 32-bit QImage.
++ */
++ void Jpeg2000JasperReader::copyScanlineJasperQtRGBA(jas_seqent_t ** const jasperRow, uchar *qtScanLine)
++ {
++ QRgb *scanLine = reinterpret_cast<QRgb *>(qtScanLine);
++ for (int c = 0; c < qtWidth; ++c) {
++ *scanLine++ = (jasperRow[3][c] << 24) |
++ (jasperRow[0][c] << 16) |
++ (jasperRow[1][c] << 8) |
++ jasperRow[2][c];
++ }
++ }
++
++ /*!
++ \internal
++ Copies data from a grayscale image to an 8-bit QImage.
++ */
++ void Jpeg2000JasperReader::copyScanlineJasperQtGray(jas_seqent_t ** const jasperRow, uchar *qtScanLine)
++ {
++ for (int c = 0; c < qtWidth; ++c) {
++ // *qtScanLine++ = (jasperRow[0][c] >> (jasComponentPrecicion[0] - 8));
++ *qtScanLine++ = jasperRow[0][c];
++ }
++ }
++
++ /*!
++ \internal
++ Copies data from a grayscale image to a 32-bit QImage.
++ Note that in this case we use an 32-bit image for grayscale data, since the
++ alpha value is per-pixel, not per-color (per-color alpha is supported by
++ 8-bit QImage).
++ */
++ void Jpeg2000JasperReader::copyScanlineJasperQtGrayA(jas_seqent_t ** const jasperRow, uchar *qtScanLine)
++ {
++ QRgb *scanLine = reinterpret_cast<QRgb *>(qtScanLine);
++ for (int c = 0; c < qtWidth; ++c) {
++ *scanLine++ = (jasperRow[1][c] << 24) |
++ (jasperRow[0][c] << 16) |
++ (jasperRow[0][c] << 8) |
++ jasperRow[0][c];
++ }
++ }
++
++ /*!
++ Opens the file data and attempts to decode it using the Jasper library.
++ Returns true on success, false on failure.
++
++ 32-bit and color mapped color images are encoded as RGB images,
++ color mapped grayscale images are encoded as grayscale images
++ */
++ bool Jpeg2000JasperReader::write(const QImage &image, int quality)
++ {
++ if (!jasperOk)
++ return false;
++
++ qtImage = image;
++
++ qtHeight = qtImage.height();
++ qtWidth = qtImage.width();
++ qtDepth = qtImage.depth();
++
++ if (qtDepth == 32) { // RGB(A)
++ jasper_image = newRGBAImage(qtWidth, qtHeight, qtImage.hasAlphaChannel());
++ if (!jasper_image)
++ return false;
++
++ if (qtImage.hasAlphaChannel())
++ copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperRGBA);
++ else
++ copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperRGB);
++ } else if (qtDepth == 8) {
++ // Color mapped grayscale
++ if (qtImage.allGray()) {
++ jasper_image = newGrayscaleImage(qtWidth, qtHeight, qtImage.hasAlphaChannel());
++ if (!jasper_image)
++ return false;
++
++ if (qtImage.hasAlphaChannel())
++ copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperColormapGrayscaleA);
++ else
++ copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperColormapGrayscale);
++ } else {
++ // Color mapped color
++ jasper_image = newRGBAImage(qtWidth, qtHeight, qtImage.hasAlphaChannel());
++ if (!jasper_image)
++ return false;
++
++ if (qtImage.hasAlphaChannel())
++ copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperColormapRGBA);
++ else
++ copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperColormapRGB);
++ }
++ } else {
++ qDebug("Unable to handle color depth %d", qtDepth);
++ return false;
++ }
++
++ int fmtid;
++ if (format == Jp2Format)
++ fmtid = jas_image_strtofmt(const_cast<char*>("jp2"));
++ else /* if (format == J2cFormat) */
++ // JasPer refers to the code stream format as jpc
++ fmtid = jas_image_strtofmt(const_cast<char*>("jpc"));
++
++ const int minQuality = 0;
++ const int maxQuality = 100;
++
++ if (quality == -1)
++ quality = 100;
++ if (quality <= minQuality)
++ quality = minQuality;
++ if (quality > maxQuality)
++ quality = maxQuality;
++
++ // Qt specifies quality as an integer in the range 0..100. Jasper specifies
++ // compression rate as an real in the range 0..1, where 1 corresponds to no
++ // compression. Computing the rate from quality is difficult, large images
++ // get better image quality than small images at the same rate. If the rate
++ // is too low, Jasper will generate a completely black image.
++ // minirate is the smallest safe rate value.
++ const double minRate = 0.001;
++
++ // maxRate specifies maximum target rate, which give the minimum amount
++ // of compression. Tests show that maxRates higer than 0.3 give no
++ // additional image quality for most images. Large images could use an even
++ // smaller maxRate value.
++ const double maxRate = 0.3;
++
++ // Set jasperRate to a value in the range minRate..maxRate. Distribute the
++ // quality steps more densely at the lower end if the rate scale.
++ const double jasperRate = minRate + pow((double(quality) / double(maxQuality)), 2) * maxRate;
++
++ // The Jasper format string contains two options:
++ // rate: rate=x
++ // lossy/lossless compression : mode=real/mode=int
++ QString jasperFormatString;
++
++ // If quality is not maxQuality, we set lossy encoding.
++ // (lossless is default)
++ if (quality != maxQuality) {
++ jasperFormatString += QLatin1String("mode=real");
++ jasperFormatString += QString(QLatin1String(" rate=%1")).arg(jasperRate);
++ }
++
++ // Open an empty jasper stream that grows automatically
++ jas_stream_t * memory_stream = jas_stream_memopen(0, 0);
++
++ // Jasper wants a non-const string.
++ char *str = qstrdup(jasperFormatString.toLatin1().constData());
++ jas_image_encode(jasper_image, memory_stream, fmtid, str);
++ delete[] str;
++ jas_stream_flush(memory_stream);
++
++ // jas_stream_t::obj_ is a void* which points to the stream implementation,
++ // e.g a file stream or a memory stream. But in our case we know that it is
++ // a memory stream since we created the object, so we just reiterpret_cast
++ // here..
++ char *buffer = reinterpret_cast<char *>(reinterpret_cast<jas_stream_memobj_t*>(memory_stream->obj_)->buf_);
++ qint64 length = jas_stream_length(memory_stream);
++ ioDevice->write(buffer, length);
++
++ jas_stream_close(memory_stream);
++ jas_image_destroy(jasper_image);
++
++ return true;
++ }
++
++ /*!
++ \internal
++ Copies data from qtImage to JasPer. The scanlineCopier parameter specifies
++ which function to use for handling each scan line.
++ */
++ void Jpeg2000JasperReader::copyQtJasper(const ScanlineFuncWrite scanlinecopier)
++ {
++ // Create jasper matrix for holding one scanline
++ jas_matrix_t **jasperMatrix;
++ createJasperMatrix(jasperMatrix);
++
++ for (int scanline = 0; scanline < qtHeight; ++scanline) {
++ (this->*scanlinecopier)(jasperMatrix, qtImage.scanLine(scanline));
++
++ // Write a scanline of data to jasper_image
++ for (int c = 0; c < jasNumComponents; ++c)
++ jas_image_writecmpt(jasper_image, c, 0, scanline, qtWidth, 1,
++ jasperMatrix[c]);
++ }
++ freeJasperMatrix(jasperMatrix);
++ }
++
++ /*!
++ \internal
++ */
++ void Jpeg2000JasperReader::copyScanlineQtJasperRGB(jas_matrix_t ** jasperRow,
++ uchar *qtScanLine)
++ {
++ QRgb *scanLineBuffer = reinterpret_cast<QRgb *>(qtScanLine);
++ for (int col = 0; col < qtWidth; ++col) {
++ jas_matrix_set(jasperRow[0], 0, col, (*scanLineBuffer & 0xFF0000) >> 16);
++ jas_matrix_set(jasperRow[1], 0, col, (*scanLineBuffer & 0x00FF00) >> 8);
++ jas_matrix_set(jasperRow[2], 0, col, *scanLineBuffer & 0x0000FF);
++ ++scanLineBuffer;
++ }
++ }
++
++ /*!
++ \internal
++ */
++ void Jpeg2000JasperReader::copyScanlineQtJasperRGBA(jas_matrix_t ** jasperRow,
++ uchar *qtScanLine)
++ {
++ QRgb *scanLineBuffer = reinterpret_cast<QRgb *>(qtScanLine);
++ for (int col = 0; col < qtWidth; ++col) {
++ jas_matrix_set(jasperRow[3], 0, col, (*scanLineBuffer & 0xFF000000) >> 24);
++ jas_matrix_set(jasperRow[0], 0, col, (*scanLineBuffer & 0x00FF0000) >> 16);
++ jas_matrix_set(jasperRow[1], 0, col, (*scanLineBuffer & 0x0000FF00) >> 8);
++ jas_matrix_set(jasperRow[2], 0, col, *scanLineBuffer & 0x000000FF);
++ ++scanLineBuffer;
++ }
++ }
++
++ /*!
++ \internal
++ */
++ void Jpeg2000JasperReader::copyScanlineQtJasperColormapRGB(jas_matrix_t ** jasperRow,
++ uchar *qtScanLine)
++ {
++ for (int col = 0; col < qtWidth; ++col) {
++ QRgb color = qtImage.color(*qtScanLine);
++ jas_matrix_set(jasperRow[0], 0, col, qRed(color));
++ jas_matrix_set(jasperRow[1], 0, col, qGreen(color));
++ jas_matrix_set(jasperRow[2], 0, col, qBlue(color));
++ ++qtScanLine;
++ }
++ }
++
++ /*!
++ \internal
++ */
++ void Jpeg2000JasperReader::copyScanlineQtJasperColormapRGBA(jas_matrix_t ** jasperRow,
++ uchar *qtScanLine)
++ {
++ for (int col = 0; col < qtWidth; ++col) {
++ QRgb color = qtImage.color(*qtScanLine);
++ jas_matrix_set(jasperRow[0], 0, col, qRed(color));
++ jas_matrix_set(jasperRow[1], 0, col, qGreen(color));
++ jas_matrix_set(jasperRow[2], 0, col, qBlue(color));
++ jas_matrix_set(jasperRow[3], 0, col, qAlpha(color));
++ ++qtScanLine;
++ }
++ }
++
++ /*!
++ \internal
++ */
++ void Jpeg2000JasperReader::copyScanlineQtJasperColormapGrayscale(jas_matrix_t ** jasperRow,
++ uchar *qtScanLine)
++ {
++ for (int col = 0; col < qtWidth; ++col) {
++ QRgb color = qtImage.color(*qtScanLine);
++ jas_matrix_set(jasperRow[0], 0, col, qGray(color));
++ ++qtScanLine;
++ }
++ }
++
++ /*!
++ \internal
++ */
++ void Jpeg2000JasperReader::copyScanlineQtJasperColormapGrayscaleA(jas_matrix_t ** jasperRow,
++ uchar *qtScanLine)
++ {
++ for (int col = 0; col < qtWidth; ++col) {
++ QRgb color = qtImage.color(*qtScanLine);
++ jas_matrix_set(jasperRow[0], 0, col, qGray(color));
++ jas_matrix_set(jasperRow[1], 0, col, qAlpha(color));
++ ++qtScanLine;
++ }
++ }
++
++ /*!
++ \internal
++ Attempts to change the color space for the image to wantedColorSpace using
++ the JasPer library
++ */
++ bool Jpeg2000JasperReader::attemptColorspaceChange(int wantedColorSpace)
++ {
++ //qDebug("Attemting color space change");
++ jas_cmprof_t *outprof;
++ if (!(outprof = jas_cmprof_createfromclrspc(wantedColorSpace)))
++ return false;
++
++ jas_image_t *newimage;
++ if (!(newimage = jas_image_chclrspc(jasper_image, outprof,
++ JAS_CMXFORM_INTENT_PER))) {
++ jas_cmprof_destroy(outprof);
++ return false;
++ }
++ jas_image_destroy(jasper_image);
++ jas_cmprof_destroy(outprof);
++ jasper_image = newimage;
++ return true;
++ }
++
++ /*!
++ \internal
++ Set up a component with parameters suitable for storing a QImage.
++ */
++ jas_image_cmptparm_t Jpeg2000JasperReader::createComponentMetadata(
++ const int width, const int height)
++ {
++ jas_image_cmptparm_t param;
++ param.tlx = 0;
++ param.tly = 0;
++ param.hstep = 1;
++ param.vstep = 1;
++ param.width = width;
++ param.height = height;
++ param.prec = 8;
++ param.sgnd = 0;
++ return param;
++ }
++
++ /*!
++ \internal
++ Create a new RGB JasPer image with a possible alpha channel.
++ */
++ jas_image_t* Jpeg2000JasperReader::newRGBAImage(const int width,
++ const int height, bool alpha)
++ {
++ jasNumComponents = alpha ? 4 : 3;
++ jas_image_cmptparm_t *params = new jas_image_cmptparm_t[jasNumComponents];
++ jas_image_cmptparm_t param = createComponentMetadata(width, height);
++ for (int c=0; c < jasNumComponents; c++)
++ params[c] = param;
++ jas_image_t *newImage = jas_image_create(jasNumComponents, params,
++ JAS_CLRSPC_SRGB);
++
++ if (!newImage) {
++ delete[] params;
++ return 0;
++ }
++
++ jas_image_setcmpttype(newImage, 0, JAS_IMAGE_CT_RGB_R);
++ jas_image_setcmpttype(newImage, 1, JAS_IMAGE_CT_RGB_G);
++ jas_image_setcmpttype(newImage, 2, JAS_IMAGE_CT_RGB_B);
++
++ /*
++ It is unclear how one stores opacity(alpha) components with JasPer,
++ the following seems to have no effect. The opacity component gets
++ type id 3 or 48 depending jp2 or j2c format no matter what one puts
++ in here.
++
++ The symbols are defined as follows:
++ #define JAS_IMAGE_CT_RGB_R 0
++ #define JAS_IMAGE_CT_RGB_G 1
++ #define JAS_IMAGE_CT_RGB_B 2
++ #define JAS_IMAGE_CT_OPACITY 0x7FFF
++ */
++ if (alpha)
++ jas_image_setcmpttype(newImage, 3, JAS_IMAGE_CT_OPACITY);
++ delete[] params;
++ return newImage;
++ }
++
++ /*!
++ \internal
++ Create a new RGB JasPer image with a possible alpha channel.
++ */
++ jas_image_t *Jpeg2000JasperReader::newGrayscaleImage(const int width,
++ const int height,
++ bool alpha)
++ {
++ jasNumComponents = alpha ? 2 : 1;
++ jas_image_cmptparm_t param = createComponentMetadata(width, height);
++ jas_image_t *newImage = jas_image_create(1, &param, JAS_CLRSPC_SGRAY);
++ if (!newImage)
++ return 0;
++
++ jas_image_setcmpttype(newImage, 0, JAS_IMAGE_CT_GRAY_Y);
++
++ // See corresponding comment for newRGBAImage.
++ if (alpha)
++ jas_image_setcmpttype(newImage, 1, JAS_IMAGE_CT_OPACITY);
++ return newImage;
++ }
++
++ /*!
++ \internal
++ Allocate data structures that hold image data during transfer from the
++ JasPer data structures to QImage.
++ */
++ bool Jpeg2000JasperReader::createJasperMatrix(jas_matrix_t **&matrix)
++ {
++ matrix = (jas_matrix_t**)malloc(jasNumComponents * sizeof(jas_matrix_t *));
++ for (int c = 0; c < jasNumComponents; ++c)
++ matrix[c] = jas_matrix_create(1, qtWidth);
++ return true;
++ }
++
++ /*!
++ \internal
++ Free data structures that hold image data during transfer from the
++ JasPer data structures to QImage.
++ */
++ bool Jpeg2000JasperReader::freeJasperMatrix(jas_matrix_t **matrix)
++ {
++ for (int c = 0; c < jasNumComponents; ++c)
++ jas_matrix_destroy(matrix[c]);
++ free(matrix);
++ return false;
++ }
++
++ /*!
++ \internal
++ */
++ void Jpeg2000JasperReader::printColorSpaceError()
++ {
++ QString colorspaceFamily, colorspaceSpecific;
++ decodeColorSpace(jas_image_clrspc(jasper_image), colorspaceFamily,
++ colorspaceSpecific);
++ qDebug("Jpeg2000 decoder is not able to handle color space %s - %s",
++ qPrintable(colorspaceFamily), qPrintable(colorspaceSpecific));
++ }
++ /*!
++ \internal
++ */
++ bool Jpeg2000JasperReader::decodeColorSpace(int clrspc, QString &family,
++ QString &specific)
++ {
++ int fam = jas_clrspc_fam(clrspc);
++ int mbr = jas_clrspc_mbr(clrspc);
++
++ switch (fam) {
++ case 0: family = QLatin1String("JAS_CLRSPC_FAM_UNKNOWN"); break;
++ case 1: family = QLatin1String("JAS_CLRSPC_FAM_XYZ"); break;
++ case 2: family = QLatin1String("JAS_CLRSPC_FAM_LAB"); break;
++ case 3: family = QLatin1String("JAS_CLRSPC_FAM_GRAY"); break;
++ case 4: family = QLatin1String("JAS_CLRSPC_FAM_RGB"); break;
++ case 5: family = QLatin1String("JAS_CLRSPC_FAM_YCBCR"); break;
++ default: family = QLatin1String("Unknown"); return false;
++ }
++
++ switch (mbr) {
++ case 0:
++ switch (fam) {
++ case 1: specific = QLatin1String("JAS_CLRSPC_CIEXYZ"); break;
++ case 2: specific = QLatin1String("JAS_CLRSPC_CIELAB"); break;
++ case 3: specific = QLatin1String("JAS_CLRSPC_SGRAY"); break;
++ case 4: specific = QLatin1String("JAS_CLRSPC_SRGB"); break;
++ case 5: specific = QLatin1String("JAS_CLRSPC_SYCBCR"); break;
++ default: specific = QLatin1String("Unknown"); return false;
++ }
++ break;
++ case 1:
++ switch (fam) {
++ case 3: specific = QLatin1String("JAS_CLRSPC_GENGRAY"); break;
++ case 4: specific = QLatin1String("JAS_CLRSPC_GENRGB"); break;
++ case 5: specific = QLatin1String("JAS_CLRSPC_GENYCBCR"); break;
++ default: specific = QLatin1String("Unknown"); return false;
++ }
++ break;
++ default:
++ return false;
++ }
++ return true;
++ }
++ /*!
++ \internal
++ */
++ void Jpeg2000JasperReader::printMetadata(jas_image_t *image)
++ {
++ #ifndef QT_NO_DEBUG
++ // jas_image_cmptparm_t param
++ qDebug("Image width: %ld", long(jas_image_width(image)));
++ qDebug("Image height: %ld", long(jas_image_height(image)));
++ qDebug("Coordinates on reference grid: (%ld,%ld) (%ld,%ld)",
++ long(jas_image_tlx(image)), long(jas_image_tly(image)),
++ long(jas_image_brx(image)), long(jas_image_bry(image)));
++ qDebug("Number of image components: %d", jas_image_numcmpts(image));
++
++ QString colorspaceFamily;
++ QString colorspaceSpecific;
++ decodeColorSpace(jas_image_clrspc(image), colorspaceFamily, colorspaceSpecific);
++ qDebug("Color model (space): %d, %s - %s", jas_image_clrspc(image),
++ qPrintable(colorspaceFamily), qPrintable(colorspaceSpecific));
++
++ qDebug("Component metadata:");
++
++ for (int c = 0; c < static_cast<int>(jas_image_numcmpts(image)); ++c) {
++ qDebug("Component %d:", c);
++ qDebug(" Component type: %ld", long(jas_image_cmpttype(image, c)));
++ qDebug(" Width: %ld", long(jas_image_cmptwidth(image, c)));
++ qDebug(" Height: %ld", long(jas_image_cmptheight(image, c)));
++ qDebug(" Signedness: %d", jas_image_cmptsgnd(image, c));
++ qDebug(" Precision: %d", jas_image_cmptprec(image, c));
++ qDebug(" Horizontal subsampling factor: %ld",long(jas_image_cmpthstep(image, c)));
++ qDebug(" Vertical subsampling factor: %ld", long(jas_image_cmptvstep(image, c)));
++ qDebug(" Coordinates on reference grid: (%ld,%ld) (%ld,%ld)",
++ long(jas_image_cmpttlx(image, c)), long(jas_image_cmpttly(image, c)),
++ long(jas_image_cmptbrx(image, c)), long(jas_image_cmptbry(image, c)));
++ }
++ #else
++ Q_UNUSED(image);
++ #endif
++ }
++
++ QT_END_NAMESPACE