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 *&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 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