summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJake Petroules <jake.petroules@petroules.com>2014-01-06 07:52:54 -0500
committerThe Qt Project <gerrit-noreply@qt-project.org>2014-02-11 10:37:49 +0100
commit95b6cf26837dba0ac215db552ce47031f3abfc6e (patch)
tree1ed59372fc06dd9da780c84c0229e2cc99bf94b7
parent5c4036eeb2ecdfdac5a403ce43bdd1b0c9a53efb (diff)
Add JPEG 2000 plugin.
It is moving from Qt Solutions. Change-Id: Ie0dc44d258597f871544fa43238528f42628b799 Reviewed-by: Jake Petroules <jake.petroules@petroules.com> Reviewed-by: Shawn Rutledge <shawn.rutledge@digia.com>
-rw-r--r--dist/changes-5.3.01
-rw-r--r--src/imageformats/doc/qtimageformats-dita.qdocconf2
-rw-r--r--src/imageformats/doc/src/qtimageformats.qdoc1
-rw-r--r--src/plugins/imageformats/imageformats.pro11
-rw-r--r--src/plugins/imageformats/jp2/jp2.json4
-rw-r--r--src/plugins/imageformats/jp2/jp2.pro9
-rw-r--r--src/plugins/imageformats/jp2/main.cpp100
-rw-r--r--src/plugins/imageformats/jp2/qjp2handler.cpp1218
-rw-r--r--src/plugins/imageformats/jp2/qjp2handler.pri10
-rw-r--r--src/plugins/imageformats/jp2/qjp2handler_p.h78
-rw-r--r--tests/auto/auto.pro3
-rw-r--r--tests/auto/jp2/jp2.pro8
-rw-r--r--tests/auto/jp2/tst_qjp2.cpp88
-rw-r--r--tests/shared/images/jp2.qrc6
-rw-r--r--tests/shared/images/jp2/logo.bmpbin0 -> 119734 bytes
-rw-r--r--tests/shared/images/jp2/logo.jp2bin0 -> 27570 bytes
16 files changed, 1533 insertions, 6 deletions
diff --git a/dist/changes-5.3.0 b/dist/changes-5.3.0
index 5a7a19b..026d568 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 82f9fdc..053e02a 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 2c9f8a3..86cdd53 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 c6aa409..01d2ac0 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 0000000..e3d7fe3
--- /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 0000000..864ec26
--- /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 0000000..700e96e
--- /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 0000000..3a611d6
--- /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 *&image;
+};
+
+/*! \internal
+ Construct a Jpeg2000JasperReader using the provided \a imageIO.
+ Note that currently the jasper library is initialized in this constructor,
+ (and freed in the destructor) which means that:
+ - Only one instance of this class may exist at one time
+ - No thread safety
+*/
+Jpeg2000JasperReader::Jpeg2000JasperReader(QIODevice *iod, SubFormat format)
+ : jasperOk(true), ioDevice(iod), format(format), hasAlpha(false)
+{
+ if (jas_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, &param, 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 0000000..539daaa
--- /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 0000000..4895bb8
--- /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 109f216..73f1014 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 0000000..26c2f83
--- /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 0000000..7608746
--- /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 0000000..f952076
--- /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 0000000..735ba1b
--- /dev/null
+++ b/tests/shared/images/jp2/logo.bmp
Binary files differ
diff --git a/tests/shared/images/jp2/logo.jp2 b/tests/shared/images/jp2/logo.jp2
new file mode 100644
index 0000000..f99343d
--- /dev/null
+++ b/tests/shared/images/jp2/logo.jp2
Binary files differ