diff --git a/dist/changes-5.3.0 b/dist/changes-5.3.0 index 5a7a19b60dad854be8bcf269691687e2857b8e14..026d568d0222c5cd95ad5ef60c7396860537aa07 100644 --- a/dist/changes-5.3.0 +++ b/dist/changes-5.3.0 @@ -21,3 +21,4 @@ information about a particular change. - Add read/write support for Direct Draw Surface images. - Add read/write support for ICNS images. + - Add read/write support for JPEG 2000 images. diff --git a/src/imageformats/doc/qtimageformats-dita.qdocconf b/src/imageformats/doc/qtimageformats-dita.qdocconf index 82f9fdcaf7688abb2328e78284477286a51f52f6..053e02a5c6bef55bab7d38c93374c19baafeb186 100644 --- a/src/imageformats/doc/qtimageformats-dita.qdocconf +++ b/src/imageformats/doc/qtimageformats-dita.qdocconf @@ -15,7 +15,7 @@ exampledirs += ../examples HTML.nobreadcrumbs = "true" examples.fileextensions = "*.cpp *.h *.js *.svg *.xml *.ui *.qml" -examples.imageextensions = "*.png *.jpeg *.jpg *.gif *.mng" +examples.imageextensions = "*.png *.jp2 *.jpeg *.jpg *.gif *.mng" headers.fileextensions = "*.h *.ch *.h++ *.hh *.hpp *.hxx" sources.fileextensions = "*.cpp *.qdoc *.mm *.qml" diff --git a/src/imageformats/doc/src/qtimageformats.qdoc b/src/imageformats/doc/src/qtimageformats.qdoc index 2c9f8a3207e6c0ce48ec55e1a5c1dbfa48c4acf3..86cdd53e7f55adb0922bfc110993276a78d75fa8 100644 --- a/src/imageformats/doc/src/qtimageformats.qdoc +++ b/src/imageformats/doc/src/qtimageformats.qdoc @@ -54,6 +54,7 @@ libraries. If not found, it may fall back on using a bundled copy (in \header \li Format \li Description \li Support \li 3rd party codec \row \li DDS \li Direct Draw Surface \li Read/write \li No \row \li ICNS \li Apple Icon Image \li Read/write \li No +\row \li JP2 \li Joint Photographic Experts Group 2000 \li Read/write \li Yes (bundled) \row \li MNG \li Multiple-image Network Graphics \li Read \li Yes (bundled) \row \li TGA \li Truevision Graphics Adapter \li Read \li No \row \li TIFF \li Tagged Image File Format \li Read/write \li Yes (bundled) diff --git a/src/plugins/imageformats/imageformats.pro b/src/plugins/imageformats/imageformats.pro index c6aa4098dff6067bc7762dbfbb19cf1324280109..01d2ac02a365e75308caa4fc49e7de566666dc2e 100644 --- a/src/plugins/imageformats/imageformats.pro +++ b/src/plugins/imageformats/imageformats.pro @@ -1,11 +1,14 @@ TEMPLATE = subdirs SUBDIRS = \ - tga \ - wbmp \ + dds \ + icns \ + jp2 \ mng \ + tga \ tiff \ - dds \ - icns + wbmp + +wince:SUBDIRS -= jp2 winrt { SUBDIRS -= tiff \ diff --git a/src/plugins/imageformats/jp2/jp2.json b/src/plugins/imageformats/jp2/jp2.json new file mode 100644 index 0000000000000000000000000000000000000000..e3d7fe302ad088e9fa601fbc1c93d28ca74dfe55 --- /dev/null +++ b/src/plugins/imageformats/jp2/jp2.json @@ -0,0 +1,4 @@ +{ + "Keys": [ "jp2" ], + "MimeTypes": [ "image/jp2", "image/jpx", "image/jpm", "video/mj2" ] +} diff --git a/src/plugins/imageformats/jp2/jp2.pro b/src/plugins/imageformats/jp2/jp2.pro new file mode 100644 index 0000000000000000000000000000000000000000..864ec26929c30f07513cd1e7f260e7e676c3d0cf --- /dev/null +++ b/src/plugins/imageformats/jp2/jp2.pro @@ -0,0 +1,9 @@ +TARGET = qjp2 + +PLUGIN_TYPE = imageformats +PLUGIN_CLASS_NAME = QJp2Plugin +load(qt_plugin) + +include(qjp2handler.pri) +SOURCES += main.cpp +OTHER_FILES += jp2.json diff --git a/src/plugins/imageformats/jp2/main.cpp b/src/plugins/imageformats/jp2/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..700e96edb9895163cc0ccedb8cb6b285e7156c5e --- /dev/null +++ b/src/plugins/imageformats/jp2/main.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Petroules Corporation. +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qimageiohandler.h> +#include <qstringlist.h> + +#ifndef QT_NO_IMAGEFORMATPLUGIN + +#include "qjp2handler_p.h" + +#include <qiodevice.h> +#include <qbytearray.h> + +QT_BEGIN_NAMESPACE + +class QJp2Plugin : public QImageIOPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "jp2.json") + +public: + QStringList keys() const; + Capabilities capabilities(QIODevice *device, const QByteArray &format) const; + QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const; +}; + +QStringList QJp2Plugin::keys() const +{ + return QStringList() << QLatin1String("jp2") << QLatin1String("j2k"); +} + +QImageIOPlugin::Capabilities QJp2Plugin::capabilities(QIODevice *device, const QByteArray &format) const +{ + if (format == "jp2" || format == "j2k") + return Capabilities(CanRead | CanWrite); + if (!format.isEmpty()) + return 0; + if (!device->isOpen()) + return 0; + + Capabilities cap; + if (device->isReadable() && QJp2Handler::canRead(device, 0)) + cap |= CanRead; + if (device->isWritable()) + cap |= CanWrite; + return cap; +} + +QImageIOHandler *QJp2Plugin::create(QIODevice *device, const QByteArray &format) const +{ + QJp2Handler *handler = new QJp2Handler(); + handler->setDevice(device); + handler->setFormat(format); + return handler; +} + +QT_END_NAMESPACE + +#include "main.moc" + +#endif // !QT_NO_IMAGEFORMATPLUGIN diff --git a/src/plugins/imageformats/jp2/qjp2handler.cpp b/src/plugins/imageformats/jp2/qjp2handler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3a611d68d5317aa1132e137617c91270d3a36752 --- /dev/null +++ b/src/plugins/imageformats/jp2/qjp2handler.cpp @@ -0,0 +1,1218 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Petroules Corporation. +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qjp2handler_p.h" + +#include "qimage.h" +#include "qvariant.h" +#include "qcolor.h" + +#ifdef Q_CC_MSVC +#define JAS_WIN_MSVC_BUILD +#endif +#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); +} + +/*! + Return the common identifier of the format. + For JPEG 2000 this will return "jp2". + */ +QByteArray QJp2Handler::name() const +{ + return QByteArrayLiteral("jp2"); +} + +/*! + 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_Indexed8); + qtImage.setColorCount(256); + for (int c = 0; c < 256; ++c) + qtImage.setColor(c, qRgb(c,c,c)); + } + } + + // 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 (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 (qtImage.hasAlphaChannel()) + copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperColormapGrayscaleA); + else + copyQtJasper(&Jpeg2000JasperReader::copyScanlineQtJasperColormapGrayscale); + } else { + // Color mapped color + jasper_image = newRGBAImage(qtWidth, qtHeight, qtImage.hasAlphaChannel()); + 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); + + 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); + + 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: %d", jas_image_width(image)); + qDebug("Image height: %d", jas_image_height(image)); + qDebug("Coordinates on reference grid: (%d,%d) (%d,%d)", + jas_image_tlx(image), jas_image_tly(image), + jas_image_brx(image), 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: %d", jas_image_cmpttype(image, c)); + qDebug(" Width: %d", jas_image_cmptwidth(image, c)); + qDebug(" Height: %d", 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: %d",jas_image_cmpthstep(image, c)); + qDebug(" Vertical subsampling factor: %d", jas_image_cmptvstep(image, c)); + qDebug(" Coordinates on reference grid: (%d,%d) (%d,%d)", + jas_image_cmpttlx(image, c), jas_image_cmpttly(image, c), + jas_image_cmptbrx(image, c), jas_image_cmptbry(image, c)); + } +#endif +} + +QT_END_NAMESPACE diff --git a/src/plugins/imageformats/jp2/qjp2handler.pri b/src/plugins/imageformats/jp2/qjp2handler.pri new file mode 100644 index 0000000000000000000000000000000000000000..539daaabdff7db878eed52c69524a49c60656384 --- /dev/null +++ b/src/plugins/imageformats/jp2/qjp2handler.pri @@ -0,0 +1,10 @@ +# common to plugin and built-in forms +INCLUDEPATH *= $$PWD +HEADERS += $$PWD/qjp2handler_p.h +SOURCES += $$PWD/qjp2handler.cpp +config_jasper { + msvc: LIBS += libjasper.lib + else: LIBS += -ljasper +} else { + include($$PWD/../../../3rdparty/jasper.pri) +} diff --git a/src/plugins/imageformats/jp2/qjp2handler_p.h b/src/plugins/imageformats/jp2/qjp2handler_p.h new file mode 100644 index 0000000000000000000000000000000000000000..4895bb882dc3f39c06e13a84be93e026b00eca40 --- /dev/null +++ b/src/plugins/imageformats/jp2/qjp2handler_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Petroules Corporation. +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QJP2HANDLER_H +#define QJP2HANDLER_H + +#include <QtCore/qscopedpointer.h> +#include <QtGui/qimageiohandler.h> + +QT_BEGIN_NAMESPACE + +class QImage; +class QByteArray; +class QIODevice; +class QVariant; +class QJp2HandlerPrivate; + +class QJp2Handler : public QImageIOHandler +{ +public: + QJp2Handler(); + virtual ~QJp2Handler(); + static bool canRead(QIODevice *iod, QByteArray *subType); + virtual bool canRead() const; + virtual bool read(QImage *image); + virtual bool write(const QImage &image); + virtual QVariant option(ImageOption option) const; + virtual void setOption(ImageOption option, const QVariant &value); + virtual bool supportsOption(ImageOption option) const; + virtual QByteArray name() const; + +private: + Q_DECLARE_PRIVATE(QJp2Handler) + QScopedPointer<QJp2HandlerPrivate> d_ptr; +}; + +QT_END_NAMESPACE + +#endif // QJP2HANDLER_P_H diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 109f216ad891a83a138559ddf8e21e2b9748dc82..73f1014a6ac9630439f439fe9c92d126cdf440f8 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -3,6 +3,7 @@ SUBDIRS = \ tga \ wbmp \ dds \ - icns + icns \ + jp2 contains(QT_CONFIG, system-zlib): SUBDIRS += mng tiff diff --git a/tests/auto/jp2/jp2.pro b/tests/auto/jp2/jp2.pro new file mode 100644 index 0000000000000000000000000000000000000000..26c2f836b8628e53fe63192f7901a49ac76550af --- /dev/null +++ b/tests/auto/jp2/jp2.pro @@ -0,0 +1,8 @@ +TARGET = tst_qjp2 + +QT = core gui testlib +CONFIG -= app_bundle +CONFIG += testcase + +SOURCES += tst_qjp2.cpp +RESOURCES += $$PWD/../../shared/images/jp2.qrc diff --git a/tests/auto/jp2/tst_qjp2.cpp b/tests/auto/jp2/tst_qjp2.cpp new file mode 100644 index 0000000000000000000000000000000000000000..760874684f86a3e3ef6939f6d2532b6312d4a436 --- /dev/null +++ b/tests/auto/jp2/tst_qjp2.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Petroules Corporation. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the MNG autotests 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QtGui/QtGui> + +class tst_qjp2: public QObject +{ + Q_OBJECT + +private slots: + void readImage_data(); + void readImage(); +}; + +void tst_qjp2::readImage_data() +{ + QTest::addColumn<QString>("fileName"); + QTest::addColumn<QString>("referenceFileName"); + QTest::addColumn<QSize>("size"); + + QTest::newRow("logo") << QString("logo.jp2") << QString("logo.bmp") << QSize(498, 80); +} + +void tst_qjp2::readImage() +{ + QFETCH(QString, fileName); + QFETCH(QString, referenceFileName); + QFETCH(QSize, size); + + QString path = QString(":/jp2/") + fileName; + QImageReader reader(path); + QVERIFY(reader.canRead()); + QImage image = reader.read(); + QVERIFY(!image.isNull()); + QCOMPARE(image.size(), size); + + path = QString(":jp2/") + referenceFileName; + QImageReader referenceReader(path); + QVERIFY(referenceReader.canRead()); + QImage referenceImage = referenceReader.read(); + QVERIFY(!referenceImage.isNull()); + QCOMPARE(referenceImage.size(), size); + + QCOMPARE(image, referenceImage); +} + +QTEST_MAIN(tst_qjp2) +#include "tst_qjp2.moc" diff --git a/tests/shared/images/jp2.qrc b/tests/shared/images/jp2.qrc new file mode 100644 index 0000000000000000000000000000000000000000..f952076692454719c7a54ba1d2113de82a6397d6 --- /dev/null +++ b/tests/shared/images/jp2.qrc @@ -0,0 +1,6 @@ +<RCC> + <qresource prefix="/"> + <file>jp2/logo.bmp</file> + <file>jp2/logo.jp2</file> + </qresource> +</RCC> diff --git a/tests/shared/images/jp2/logo.bmp b/tests/shared/images/jp2/logo.bmp new file mode 100644 index 0000000000000000000000000000000000000000..735ba1b3dc1b353e562b79b4c631d0ab0e8e5de6 Binary files /dev/null and b/tests/shared/images/jp2/logo.bmp differ diff --git a/tests/shared/images/jp2/logo.jp2 b/tests/shared/images/jp2/logo.jp2 new file mode 100644 index 0000000000000000000000000000000000000000..f99343db6d9459cde4f7cbdf4d9144b973cc2d7b Binary files /dev/null and b/tests/shared/images/jp2/logo.jp2 differ