| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Copyright (C) 2016 Petroules Corporation. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the JP2 plugins in the Qt ImageFormats module. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qjp2handler_p.h" |
| |
| #include "qimage.h" |
| #include "qvariant.h" |
| #include "qcolor.h" |
| |
| #include <jasper/jasper.h> |
| |
| 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); |
| } |
| |
| #if QT_DEPRECATED_SINCE(5, 13) |
| /*! |
| Return the common identifier of the format. |
| For JPEG 2000 this will return "jp2". |
| */ |
| QByteArray QJp2Handler::name() const |
| { |
| return QByteArrayLiteral("jp2"); |
| } |
| #endif |
| |
| /*! |
| 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 *ℑ |
| }; |
| |
| /*! \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_init()) { |
| jasperOk = false; |
| qDebug("Jasper Library initialization failed"); |
| } |
| } |
| |
| Jpeg2000JasperReader::~Jpeg2000JasperReader() |
| { |
| if (jasperOk) |
| jas_cleanup(); |
| } |
| |
| /*! \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; |
| |
| /* |
| 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)); |
| |
| 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. |
| 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)); |
| 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 |
| computedComponentWidth = jas_image_cmptwidth(jasper_image, 0); |
| computedComponentHeight = jas_image_cmptheight(jasper_image, 0); |
| computedComponentHorizontalSubsampling = jas_image_cmpthstep(jasper_image, 0); |
| computedComponentVerticalSubsampling = jas_image_cmptvstep(jasper_image, 0); |
| |
| 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; |
| } |
| } |
| qtWidth = computedComponentWidth * computedComponentHorizontalSubsampling; |
| qtHeight = computedComponentHeight * computedComponentVerticalSubsampling; |
| } |
| |
| // 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 |
| if (jasperColorspaceFamily == JAS_CLRSPC_FAM_RGB) { |
| qtImage = QImage(qtWidth, qtHeight, hasAlpha |
| ? QImage::Format_ARGB32 |
| : QImage::Format_RGB32); |
| } else if (jasperColorspaceFamily == JAS_CLRSPC_FAM_GRAY) { |
| if (hasAlpha) { |
| qtImage = QImage(qtWidth, qtHeight, QImage::Format_ARGB32); |
| } else { |
| qtImage = QImage(qtWidth, qtHeight, QImage::Format_Grayscale8); |
| } |
| } |
| |
| // 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, -1); |
| |
| // 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, ¶m, 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 < 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 |