util/src/plugins/imageformats/jpeg/qjpeghandler.cpp
changeset 7 f7bc934e204c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/src/plugins/imageformats/jpeg/qjpeghandler.cpp	Wed Mar 31 11:06:36 2010 +0300
@@ -0,0 +1,1269 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the plugins of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** 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, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qjpeghandler.h"
+
+#include <qimage.h>
+#include <qvariant.h>
+#include <qvector.h>
+
+#include <stdio.h>      // jpeglib needs this to be pre-included
+#include <setjmp.h>
+
+#ifdef FAR
+#undef FAR
+#endif
+
+// hw: optimize smoothscaler for returning 24-bit images
+
+// including jpeglib.h seems to be a little messy
+extern "C" {
+// mingw includes rpcndr.h but does not define boolean
+#if defined(Q_OS_WIN) && defined(Q_CC_GNU)
+#   if defined(__RPCNDR_H__) && !defined(boolean)
+        typedef unsigned char boolean;
+#       define HAVE_BOOLEAN
+#   endif
+#endif
+
+#define XMD_H           // shut JPEGlib up
+#if defined(Q_OS_UNIXWARE)
+#  define HAVE_BOOLEAN  // libjpeg under Unixware seems to need this
+#endif
+#include <jpeglib.h>
+#ifdef const
+#  undef const          // remove crazy C hackery in jconfig.h
+#endif
+}
+
+QT_BEGIN_NAMESPACE
+
+//#define QT_NO_IMAGE_SMOOTHSCALE
+#ifndef QT_NO_IMAGE_SMOOTHSCALE
+class QImageSmoothScalerPrivate;
+class QImageSmoothScaler
+{
+public:
+    QImageSmoothScaler(const int w, const int h, const QImage &src);
+    QImageSmoothScaler(const int srcWidth, const int srcHeight,
+                       const int dstWidth, const int dstHeight);
+
+    virtual ~QImageSmoothScaler(void);
+
+    QImage  scale();
+
+private:
+    QImageSmoothScalerPrivate	*d;
+    virtual QRgb *scanLine(const int line = 0, const QImage *src = 0);
+};
+
+class QImageSmoothScalerPrivate
+{
+public:
+    int	    cols;
+    int	    newcols;
+    int	    rows;
+    int	    newrows;
+    bool    hasAlpha;
+
+    const QImage  *src;
+
+    void setup(const int srcWidth, const int srcHeight, const int dstWidth,
+               const int dstHeight, bool hasAlphaChannel);
+};
+
+QImageSmoothScaler::QImageSmoothScaler(const int w, const int h,
+                                       const QImage &src)
+{
+    d = new QImageSmoothScalerPrivate;
+
+    d->setup(src.width(), src.height(), w, h, src.hasAlphaChannel() );
+    this->d->src = &src;
+}
+
+QImageSmoothScaler::QImageSmoothScaler(const int srcWidth, const int srcHeight,
+                                       const int dstWidth, const int dstHeight)
+{
+    d = new QImageSmoothScalerPrivate;
+    d->setup(srcWidth, srcHeight, dstWidth, dstHeight, 0);
+}
+
+void QImageSmoothScalerPrivate::setup(const int srcWidth, const int srcHeight,
+                                      const int dstWidth, const int dstHeight,
+                                      bool hasAlphaChannel)
+{
+    cols = srcWidth;
+    rows = srcHeight;
+    newcols = dstWidth;
+    newrows = dstHeight;
+    hasAlpha = hasAlphaChannel;
+}
+
+QImageSmoothScaler::~QImageSmoothScaler()
+{
+    delete d;
+}
+
+inline QRgb *QImageSmoothScaler::scanLine(const int line, const QImage *src)
+{
+    return (QRgb*)src->scanLine(line);
+}
+
+/*
+  This function uses code based on pnmscale.c by Jef Poskanzer.
+
+  pnmscale.c - read a portable anymap and scale it
+
+  Copyright (C) 1989, 1991 by Jef Poskanzer.
+
+  Permission to use, copy, modify, and distribute this software and its
+  documentation for any purpose and without fee is hereby granted, provided
+  that the above copyright notice appear in all copies and that both that
+  copyright notice and this permission notice appear in supporting
+  documentation.  This software is provided "as is" without express or
+  implied warranty.
+*/
+
+QImage QImageSmoothScaler::scale()
+{
+    long SCALE;
+    long HALFSCALE;
+    QRgb *xelrow = 0;
+    QRgb *tempxelrow = 0;
+    QRgb *xP;
+    QRgb *nxP;
+    int row, rowsread;
+    int col, needtoreadrow;
+    uchar maxval = 255;
+    qreal xscale, yscale;
+    long sxscale, syscale;
+    long fracrowtofill, fracrowleft;
+    long *as;
+    long *rs;
+    long *gs;
+    long *bs;
+    int rowswritten = 0;
+    QImage dst;
+
+    if (d->cols > 4096) {
+        SCALE = 4096;
+        HALFSCALE = 2048;
+    } else {
+        int fac = 4096;
+        while (d->cols * fac > 4096)
+            fac /= 2;
+
+        SCALE = fac * d->cols;
+        HALFSCALE = fac * d->cols / 2;
+    }
+
+    xscale = (qreal)d->newcols / (qreal)d->cols;
+    yscale = (qreal)d->newrows / (qreal)d->rows;
+    sxscale = (long)(xscale * SCALE);
+    syscale = (long)(yscale * SCALE);
+
+    // shortcut Y scaling if possible
+    if (d->newrows != d->rows)
+        tempxelrow = new QRgb[d->cols];
+
+    if (d->hasAlpha) {
+        as = new long[d->cols];
+        for (col = 0; col < d->cols; ++col)
+            as[col] = HALFSCALE;
+    } else {
+        as = 0;
+    }
+    rs = new long[d->cols];
+    gs = new long[d->cols];
+    bs = new long[d->cols];
+    rowsread = 0;
+    fracrowleft = syscale;
+    needtoreadrow = 1;
+    for (col = 0; col < d->cols; ++col)
+        rs[col] = gs[col] = bs[col] = HALFSCALE;
+    fracrowtofill = SCALE;
+
+    dst = QImage(d->newcols, d->newrows, d->hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
+
+    for (row = 0; row < d->newrows; ++row) {
+        // First scale Y from xelrow into tempxelrow.
+        if (d->newrows == d->rows) {
+            // shortcut Y scaling if possible
+            tempxelrow = xelrow = scanLine(rowsread++, d->src);
+        } else {
+            while (fracrowleft < fracrowtofill) {
+                if (needtoreadrow && rowsread < d->rows)
+                    xelrow = scanLine(rowsread++, d->src);
+                for (col = 0, xP = xelrow; col < d->cols; ++col, ++xP) {
+                    if (as) {
+                        as[col] += fracrowleft * qAlpha(*xP);
+                        rs[col] += fracrowleft * qRed(*xP) * qAlpha(*xP) / 255;
+                        gs[col] += fracrowleft * qGreen(*xP) * qAlpha(*xP) / 255;
+                        bs[col] += fracrowleft * qBlue(*xP) * qAlpha(*xP) / 255;
+                    } else {
+                        rs[col] += fracrowleft * qRed(*xP);
+                        gs[col] += fracrowleft * qGreen(*xP);
+                        bs[col] += fracrowleft * qBlue(*xP);
+                    }
+                }
+                fracrowtofill -= fracrowleft;
+                fracrowleft = syscale;
+                needtoreadrow = 1;
+            }
+            // Now fracrowleft is >= fracrowtofill, so we can produce a row.
+            if (needtoreadrow && rowsread < d->rows) {
+                xelrow = scanLine(rowsread++, d->src);
+                needtoreadrow = 0;
+            }
+            for (col = 0, xP = xelrow, nxP = tempxelrow; col < d->cols; ++col, ++xP, ++nxP) {
+                register long a, r, g, b;
+
+                if (as) {
+                    r = rs[col] + fracrowtofill * qRed(*xP) * qAlpha(*xP) / 255;
+                    g = gs[col] + fracrowtofill * qGreen(*xP) * qAlpha(*xP) / 255;
+                    b = bs[col] + fracrowtofill * qBlue(*xP) * qAlpha(*xP) / 255;
+                    a = as[col] + fracrowtofill * qAlpha(*xP);
+                    if (a) {
+                        r = r * 255 / a * SCALE;
+                        g = g * 255 / a * SCALE;
+                        b = b * 255 / a * SCALE;
+                    }
+                } else {
+                    r = rs[col] + fracrowtofill * qRed(*xP);
+                    g = gs[col] + fracrowtofill * qGreen(*xP);
+                    b = bs[col] + fracrowtofill * qBlue(*xP);
+                    a = 0; // unwarn
+                }
+                r /= SCALE;
+                if (r > maxval)
+                    r = maxval;
+                g /= SCALE;
+                if (g > maxval)
+                    g = maxval;
+                b /= SCALE;
+                if (b > maxval)
+                    b = maxval;
+                if (as) {
+                    a /= SCALE;
+                    if (a > maxval)
+                        a = maxval;
+                    *nxP = qRgba((int)r, (int)g, (int)b, (int)a);
+                    as[col] = HALFSCALE;
+                } else {
+                    *nxP = qRgb((int)r, (int)g, (int)b);
+                }
+                rs[col] = gs[col] = bs[col] = HALFSCALE;
+            }
+            fracrowleft -= fracrowtofill;
+            if (fracrowleft == 0) {
+                fracrowleft = syscale;
+                needtoreadrow = 1;
+            }
+            fracrowtofill = SCALE;
+        }
+
+        // Now scale X from tempxelrow into dst and write it out.
+        if (d->newcols == d->cols) {
+            // shortcut X scaling if possible
+            memcpy(dst.scanLine(rowswritten++), tempxelrow, d->newcols * 4);
+        } else {
+            register long a, r, g, b;
+            register long fraccoltofill, fraccolleft = 0;
+            register int needcol;
+
+            nxP = (QRgb *)dst.scanLine(rowswritten++);
+            QRgb *nxPEnd = nxP + d->newcols;
+            fraccoltofill = SCALE;
+            a = r = g = b = HALFSCALE;
+            needcol = 0;
+            for (col = 0, xP = tempxelrow; col < d->cols; ++col, ++xP) {
+                fraccolleft = sxscale;
+                while (fraccolleft >= fraccoltofill) {
+                    if (needcol) {
+                        ++nxP;
+                        a = r = g = b = HALFSCALE;
+                    }
+                    if (as) {
+                        r += fraccoltofill * qRed(*xP) * qAlpha(*xP) / 255;
+                        g += fraccoltofill * qGreen(*xP) * qAlpha(*xP) / 255;
+                        b += fraccoltofill * qBlue(*xP) * qAlpha(*xP) / 255;
+                        a += fraccoltofill * qAlpha(*xP);
+                        if (a) {
+                            r = r * 255 / a * SCALE;
+                            g = g * 255 / a * SCALE;
+                            b = b * 255 / a * SCALE;
+                        }
+                    } else {
+                        r += fraccoltofill * qRed(*xP);
+                        g += fraccoltofill * qGreen(*xP);
+                        b += fraccoltofill * qBlue(*xP);
+                    }
+                    r /= SCALE;
+                    if (r > maxval)
+                        r = maxval;
+                    g /= SCALE;
+                    if (g > maxval)
+                        g = maxval;
+                    b /= SCALE;
+                    if (b > maxval)
+                        b = maxval;
+                    if (as) {
+                        a /= SCALE;
+                        if (a > maxval)
+                            a = maxval;
+                        *nxP = qRgba((int)r, (int)g, (int)b, (int)a);
+                    } else {
+                        *nxP = qRgb((int)r, (int)g, (int)b);
+                    }
+                    fraccolleft -= fraccoltofill;
+                    fraccoltofill = SCALE;
+                    needcol = 1;
+                }
+                if (fraccolleft > 0) {
+                    if (needcol) {
+                        ++nxP;
+                        a = r = g = b = HALFSCALE;
+                        needcol = 0;
+                    }
+                    if (as) {
+                        a += fraccolleft * qAlpha(*xP);
+                        r += fraccolleft * qRed(*xP) * qAlpha(*xP) / 255;
+                        g += fraccolleft * qGreen(*xP) * qAlpha(*xP) / 255;
+                        b += fraccolleft * qBlue(*xP) * qAlpha(*xP) / 255;
+                    } else {
+                        r += fraccolleft * qRed(*xP);
+                        g += fraccolleft * qGreen(*xP);
+                        b += fraccolleft * qBlue(*xP);
+                    }
+                    fraccoltofill -= fraccolleft;
+                }
+            }
+            if (fraccoltofill > 0) {
+                --xP;
+                if (as) {
+                    a += fraccolleft * qAlpha(*xP);
+                    r += fraccoltofill * qRed(*xP) * qAlpha(*xP) / 255;
+                    g += fraccoltofill * qGreen(*xP) * qAlpha(*xP) / 255;
+                    b += fraccoltofill * qBlue(*xP) * qAlpha(*xP) / 255;
+                    if (a) {
+                        r = r * 255 / a * SCALE;
+                        g = g * 255 / a * SCALE;
+                        b = b * 255 / a * SCALE;
+                    }
+                } else {
+                    r += fraccoltofill * qRed(*xP);
+                    g += fraccoltofill * qGreen(*xP);
+                    b += fraccoltofill * qBlue(*xP);
+                }
+            }
+            if (nxP < nxPEnd) {
+                r /= SCALE;
+                if (r > maxval)
+                    r = maxval;
+                g /= SCALE;
+                if (g > maxval)
+                    g = maxval;
+                b /= SCALE;
+                if (b > maxval)
+                    b = maxval;
+                if (as) {
+                    a /= SCALE;
+                    if (a > maxval)
+                        a = maxval;
+                    *nxP = qRgba((int)r, (int)g, (int)b, (int)a);
+                } else {
+                    *nxP = qRgb((int)r, (int)g, (int)b);
+                }
+                while (++nxP != nxPEnd)
+                    nxP[0] = nxP[-1];
+            }
+        }
+    }
+
+    if (d->newrows != d->rows && tempxelrow)// Robust, tempxelrow might be 0 1 day
+        delete [] tempxelrow;
+    if (as)				// Avoid purify complaint
+        delete [] as;
+    if (rs)				// Robust, rs might be 0 one day
+        delete [] rs;
+    if (gs)				// Robust, gs might be 0 one day
+        delete [] gs;
+    if (bs)				// Robust, bs might be 0 one day
+        delete [] bs;
+
+    return dst;
+}
+
+class jpegSmoothScaler : public QImageSmoothScaler
+{
+public:
+    jpegSmoothScaler(struct jpeg_decompress_struct *info, const QSize& dstSize, const QRect& clipRect)
+        : QImageSmoothScaler(clipRect.width(), clipRect.height(),
+                             dstSize.width(), dstSize.height())
+    {
+        cinfo = info;
+        clip = clipRect;
+        imageCache = QImage(info->output_width, 1, QImage::Format_RGB32);
+    }
+
+private:
+    QRect   clip;
+    QImage  imageCache;
+    struct jpeg_decompress_struct *cinfo;
+
+    QRgb *scanLine(const int line = 0, const QImage *src = 0)
+    {
+	QRgb    *out;
+	uchar	*in;
+
+	Q_UNUSED(line);
+	Q_UNUSED(src);
+
+        uchar* data = imageCache.bits();
+
+        // Read ahead if we haven't reached the first clipped scanline yet.
+        while (int(cinfo->output_scanline) < clip.y() &&
+               cinfo->output_scanline < cinfo->output_height)
+	    jpeg_read_scanlines(cinfo, &data, 1);
+
+        // Read the next scanline.  We assume that "line"
+        // will never be >= clip.height().
+	jpeg_read_scanlines(cinfo, &data, 1);
+        if (cinfo->output_scanline == cinfo->output_height)
+            jpeg_finish_decompress(cinfo);
+
+	out = ((QRgb*)data) + clip.x();
+
+	//
+	// The smooth scale algorithm only works on 32-bit images;
+	// convert from (8|24) bits to 32.
+	//
+	if (cinfo->output_components == 1) {
+	    in = data + clip.right();
+	    for (int i = clip.width(); i--; ) {
+		out[i] = qRgb(*in, *in, *in);
+		in--;
+	    }
+        } else if (cinfo->out_color_space == JCS_CMYK) {
+            in = data + clip.right() * 4;
+            for (int i = clip.width(); i--; ) {
+                int k = in[3];
+                out[i] = qRgb(k * in[0] / 255, k * in[1] / 255, k * in[2] / 255);
+                in -= 4;
+            }
+        } else {
+	    in = data + clip.right() * 3;
+	    for (int i = clip.width(); i--; ) {
+		out[i] = qRgb(in[0], in[1], in[2]);
+		in -= 3;
+	    }
+	}
+
+	return out;
+    }
+
+};
+#endif
+
+struct my_error_mgr : public jpeg_error_mgr {
+    jmp_buf setjmp_buffer;
+};
+
+#if defined(Q_C_CALLBACKS)
+extern "C" {
+#endif
+
+static void my_error_exit (j_common_ptr cinfo)
+{
+    my_error_mgr* myerr = (my_error_mgr*) cinfo->err;
+    char buffer[JMSG_LENGTH_MAX];
+    (*cinfo->err->format_message)(cinfo, buffer);
+    qWarning("%s", buffer);
+    longjmp(myerr->setjmp_buffer, 1);
+}
+
+#if defined(Q_C_CALLBACKS)
+}
+#endif
+
+
+static const int max_buf = 4096;
+
+struct my_jpeg_source_mgr : public jpeg_source_mgr {
+    // Nothing dynamic - cannot rely on destruction over longjump
+    QIODevice *device;
+    JOCTET buffer[max_buf];
+
+public:
+    my_jpeg_source_mgr(QIODevice *device);
+};
+
+#if defined(Q_C_CALLBACKS)
+extern "C" {
+#endif
+
+static void qt_init_source(j_decompress_ptr)
+{
+}
+
+static boolean qt_fill_input_buffer(j_decompress_ptr cinfo)
+{
+    int num_read;
+    my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src;
+    src->next_input_byte = src->buffer;
+    num_read = src->device->read((char*)src->buffer, max_buf);
+    if (num_read <= 0) {
+        // Insert a fake EOI marker - as per jpeglib recommendation
+        src->buffer[0] = (JOCTET) 0xFF;
+        src->buffer[1] = (JOCTET) JPEG_EOI;
+        src->bytes_in_buffer = 2;
+    } else {
+        src->bytes_in_buffer = num_read;
+    }
+#if defined(Q_OS_UNIXWARE)
+    return B_TRUE;
+#else
+    return true;
+#endif
+}
+
+static void qt_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
+{
+    my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src;
+
+    // `dumb' implementation from jpeglib
+
+    /* Just a dumb implementation for now.  Could use fseek() except
+     * it doesn't work on pipes.  Not clear that being smart is worth
+     * any trouble anyway --- large skips are infrequent.
+     */
+    if (num_bytes > 0) {
+        while (num_bytes > (long) src->bytes_in_buffer) {
+            num_bytes -= (long) src->bytes_in_buffer;
+            (void) qt_fill_input_buffer(cinfo);
+            /* note we assume that qt_fill_input_buffer will never return false,
+            * so suspension need not be handled.
+            */
+        }
+        src->next_input_byte += (size_t) num_bytes;
+        src->bytes_in_buffer -= (size_t) num_bytes;
+    }
+}
+
+static void qt_term_source(j_decompress_ptr cinfo)
+{
+    my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src;
+    if (!src->device->isSequential())
+        src->device->seek(src->device->pos() - src->bytes_in_buffer);
+}
+
+#if defined(Q_C_CALLBACKS)
+}
+#endif
+
+inline my_jpeg_source_mgr::my_jpeg_source_mgr(QIODevice *device)
+{
+    jpeg_source_mgr::init_source = qt_init_source;
+    jpeg_source_mgr::fill_input_buffer = qt_fill_input_buffer;
+    jpeg_source_mgr::skip_input_data = qt_skip_input_data;
+    jpeg_source_mgr::resync_to_restart = jpeg_resync_to_restart;
+    jpeg_source_mgr::term_source = qt_term_source;
+    this->device = device;
+    bytes_in_buffer = 0;
+    next_input_byte = buffer;
+}
+
+
+static bool read_jpeg_size(QIODevice *device, int &w, int &h)
+{
+    bool rt = false;
+    struct jpeg_decompress_struct cinfo;
+
+    struct my_jpeg_source_mgr *iod_src = new my_jpeg_source_mgr(device);
+    struct my_error_mgr jerr;
+
+    jpeg_create_decompress(&cinfo);
+
+    cinfo.src = iod_src;
+
+    cinfo.err = jpeg_std_error(&jerr);
+    jerr.error_exit = my_error_exit;
+
+    if (!setjmp(jerr.setjmp_buffer)) {
+#if defined(Q_OS_UNIXWARE)
+        (void) jpeg_read_header(&cinfo, B_TRUE);
+#else
+        (void) jpeg_read_header(&cinfo, true);
+#endif
+        (void) jpeg_calc_output_dimensions(&cinfo);
+
+        w = cinfo.output_width;
+        h = cinfo.output_height;
+        rt = true;
+    }
+    jpeg_destroy_decompress(&cinfo);
+    delete iod_src;
+    return rt;
+}
+
+#define HIGH_QUALITY_THRESHOLD 50
+
+static bool read_jpeg_format(QIODevice *device, QImage::Format &format)
+{
+    bool result = false;
+    struct jpeg_decompress_struct cinfo;
+
+    struct my_jpeg_source_mgr *iod_src = new my_jpeg_source_mgr(device);
+    struct my_error_mgr jerr;
+
+    jpeg_create_decompress(&cinfo);
+
+    cinfo.src = iod_src;
+
+    cinfo.err = jpeg_std_error(&jerr);
+    jerr.error_exit = my_error_exit;
+
+    if (!setjmp(jerr.setjmp_buffer)) {
+#if defined(Q_OS_UNIXWARE)
+        (void) jpeg_read_header(&cinfo, B_TRUE);
+#else
+        (void) jpeg_read_header(&cinfo, true);
+#endif
+        // This does not allocate memory for the whole image
+        // or such, so we are safe.
+        (void) jpeg_start_decompress(&cinfo);
+        result = true;
+        switch (cinfo.output_components) {
+        case 1:
+            format = QImage::Format_Indexed8;
+            break;
+        case 3:
+        case 4:
+            format = QImage::Format_RGB32;
+            break;
+        default:
+            result = false;
+            break;
+        }
+        cinfo.output_scanline = cinfo.output_height;
+        (void) jpeg_finish_decompress(&cinfo);
+    }
+    jpeg_destroy_decompress(&cinfo);
+    delete iod_src;
+    return result;
+}
+
+static bool ensureValidImage(QImage *dest, struct jpeg_decompress_struct *info,
+                             const QSize& size)
+{
+    QImage::Format format;
+    switch (info->output_components) {
+    case 1:
+        format = QImage::Format_Indexed8;
+        break;
+    case 3:
+    case 4:
+        format = QImage::Format_RGB32;
+        break;
+    default:
+        return false; // unsupported format
+    }
+
+    if (dest->size() != size || dest->format() != format) {
+        *dest = QImage(size, format);
+
+        if (format == QImage::Format_Indexed8) {
+            dest->setColorCount(256);
+            for (int i = 0; i < 256; i++)
+                dest->setColor(i, qRgb(i,i,i));
+        }
+    }
+
+    return !dest->isNull();
+}
+
+static bool read_jpeg_image(QIODevice *device, QImage *outImage,
+                            QSize scaledSize, QRect scaledClipRect,
+                            QRect clipRect, int inQuality )
+{
+    struct jpeg_decompress_struct cinfo;
+
+    struct my_jpeg_source_mgr *iod_src = new my_jpeg_source_mgr(device);
+    struct my_error_mgr jerr;
+
+    jpeg_create_decompress(&cinfo);
+
+    cinfo.src = iod_src;
+
+    cinfo.err = jpeg_std_error(&jerr);
+    jerr.error_exit = my_error_exit;
+
+    if (!setjmp(jerr.setjmp_buffer)) {
+#if defined(Q_OS_UNIXWARE)
+        (void) jpeg_read_header(&cinfo, B_TRUE);
+#else
+        (void) jpeg_read_header(&cinfo, true);
+#endif
+
+        // -1 means default quality.
+        int quality = inQuality;
+        if (quality < 0)
+            quality = 75;
+
+        // If possible, merge the scaledClipRect into either scaledSize
+        // or clipRect to avoid doing a separate scaled clipping pass.
+        // Best results are achieved by clipping before scaling, not after.
+        if (!scaledClipRect.isEmpty()) {
+            if (scaledSize.isEmpty() && clipRect.isEmpty()) {
+                // No clipping or scaling before final clip.
+                clipRect = scaledClipRect;
+                scaledClipRect = QRect();
+            } else if (scaledSize.isEmpty()) {
+                // Clipping, but no scaling: combine the clip regions.
+                scaledClipRect.translate(clipRect.topLeft());
+                clipRect = scaledClipRect.intersected(clipRect);
+                scaledClipRect = QRect();
+            } else if (clipRect.isEmpty()) {
+                // No clipping, but scaling: if we can map back to an
+                // integer pixel boundary, then clip before scaling.
+                if ((cinfo.image_width % scaledSize.width()) == 0 &&
+                        (cinfo.image_height % scaledSize.height()) == 0) {
+                    int x = scaledClipRect.x() * cinfo.image_width /
+                            scaledSize.width();
+                    int y = scaledClipRect.y() * cinfo.image_height /
+                            scaledSize.height();
+                    int width = (scaledClipRect.right() + 1) *
+                                cinfo.image_width / scaledSize.width() - x;
+                    int height = (scaledClipRect.bottom() + 1) *
+                                 cinfo.image_height / scaledSize.height() - y;
+                    clipRect = QRect(x, y, width, height);
+                    scaledSize = scaledClipRect.size();
+                    scaledClipRect = QRect();
+                }
+            } else {
+                // Clipping and scaling: too difficult to figure out,
+                // and not a likely use case, so do it the long way.
+            }
+        }
+
+        // Determine the scale factor to pass to libjpeg for quick downscaling.
+        if (!scaledSize.isEmpty()) {
+            if (clipRect.isEmpty()) {
+                cinfo.scale_denom =
+                    qMin(cinfo.image_width / scaledSize.width(),
+                         cinfo.image_height / scaledSize.height());
+            } else {
+                cinfo.scale_denom =
+                    qMin(clipRect.width() / scaledSize.width(),
+                         clipRect.height() / scaledSize.height());
+            }
+            if (cinfo.scale_denom < 2) {
+                cinfo.scale_denom = 1;
+            } else if (cinfo.scale_denom < 4) {
+                cinfo.scale_denom = 2;
+            } else if (cinfo.scale_denom < 8) {
+                cinfo.scale_denom = 4;
+            } else {
+                cinfo.scale_denom = 8;
+            }
+            cinfo.scale_num = 1;
+            if (!clipRect.isEmpty()) {
+                // Correct the scale factor so that we clip accurately.
+                // It is recommended that the clip rectangle be aligned
+                // on an 8-pixel boundary for best performance.
+                while (cinfo.scale_denom > 1 &&
+                       ((clipRect.x() % cinfo.scale_denom) != 0 ||
+                        (clipRect.y() % cinfo.scale_denom) != 0 ||
+                        (clipRect.width() % cinfo.scale_denom) != 0 ||
+                        (clipRect.height() % cinfo.scale_denom) != 0)) {
+                    cinfo.scale_denom /= 2;
+                }
+            }
+        }
+
+        // If high quality not required, use fast decompression
+        if( quality < HIGH_QUALITY_THRESHOLD ) {
+            cinfo.dct_method = JDCT_IFAST;
+            cinfo.do_fancy_upsampling = FALSE;
+        }
+
+        (void) jpeg_calc_output_dimensions(&cinfo);
+
+        // Determine the clip region to extract.
+        QRect imageRect(0, 0, cinfo.output_width, cinfo.output_height);
+        QRect clip;
+        if (clipRect.isEmpty()) {
+            clip = imageRect;
+        } else if (cinfo.scale_denom == 1) {
+            clip = clipRect.intersected(imageRect);
+        } else {
+            // The scale factor was corrected above to ensure that
+            // we don't miss pixels when we scale the clip rectangle.
+            clip = QRect(clipRect.x() / int(cinfo.scale_denom),
+                         clipRect.y() / int(cinfo.scale_denom),
+                         clipRect.width() / int(cinfo.scale_denom),
+                         clipRect.height() / int(cinfo.scale_denom));
+            clip = clip.intersected(imageRect);
+        }
+
+#ifndef QT_NO_IMAGE_SMOOTHSCALE
+        if (scaledSize.isValid() && scaledSize != clip.size()
+            && quality >= HIGH_QUALITY_THRESHOLD) {
+
+            (void) jpeg_start_decompress(&cinfo);
+
+            jpegSmoothScaler scaler(&cinfo, scaledSize, clip);
+            *outImage = scaler.scale();
+        } else
+#endif
+        {
+            // Allocate memory for the clipped QImage.
+            if (!ensureValidImage(outImage, &cinfo, clip.size()))
+                longjmp(jerr.setjmp_buffer, 1);
+
+            // Avoid memcpy() overhead if grayscale with no clipping.
+            bool quickGray = (cinfo.output_components == 1 &&
+                              clip == imageRect);
+            if (!quickGray) {
+                // Ask the jpeg library to allocate a temporary row.
+                // The library will automatically delete it for us later.
+                // The libjpeg docs say we should do this before calling
+                // jpeg_start_decompress().  We can't use "new" here
+                // because we are inside the setjmp() block and an error
+                // in the jpeg input stream would cause a memory leak.
+                JSAMPARRAY rows = (cinfo.mem->alloc_sarray)
+                    ((j_common_ptr)&cinfo, JPOOL_IMAGE,
+                     cinfo.output_width * cinfo.output_components, 1);
+
+                (void) jpeg_start_decompress(&cinfo);
+
+                while (cinfo.output_scanline < cinfo.output_height) {
+                    int y = int(cinfo.output_scanline) - clip.y();
+                    if (y >= clip.height())
+                        break;      // We've read the entire clip region, so abort.
+
+                    (void) jpeg_read_scanlines(&cinfo, rows, 1);
+
+                    if (y < 0)
+                        continue;   // Haven't reached the starting line yet.
+
+                    if (cinfo.output_components == 3) {
+                        // Expand 24->32 bpp.
+                        uchar *in = rows[0] + clip.x() * 3;
+                        QRgb *out = (QRgb*)outImage->scanLine(y);
+                        for (int i = 0; i < clip.width(); ++i) {
+                            *out++ = qRgb(in[0], in[1], in[2]);
+                            in += 3;
+                        }
+                    } else if (cinfo.out_color_space == JCS_CMYK) {
+                        // Convert CMYK->RGB.
+                        uchar *in = rows[0] + clip.x() * 4;
+                        QRgb *out = (QRgb*)outImage->scanLine(y);
+                        for (int i = 0; i < clip.width(); ++i) {
+                            int k = in[3];
+                            *out++ = qRgb(k * in[0] / 255, k * in[1] / 255,
+                                          k * in[2] / 255);
+                            in += 4;
+                        }
+                    } else if (cinfo.output_components == 1) {
+                        // Grayscale.
+                        memcpy(outImage->scanLine(y),
+                               rows[0] + clip.x(), clip.width());
+                    }
+                }
+            } else {
+                // Load unclipped grayscale data directly into the QImage.
+                (void) jpeg_start_decompress(&cinfo);
+                while (cinfo.output_scanline < cinfo.output_height) {
+                    uchar *row = outImage->scanLine(cinfo.output_scanline);
+                    (void) jpeg_read_scanlines(&cinfo, &row, 1);
+                }
+            }
+
+            if (cinfo.output_scanline == cinfo.output_height)
+                (void) jpeg_finish_decompress(&cinfo);
+
+            if (cinfo.density_unit == 1) {
+                outImage->setDotsPerMeterX(int(100. * cinfo.X_density / 2.54));
+                outImage->setDotsPerMeterY(int(100. * cinfo.Y_density / 2.54));
+            } else if (cinfo.density_unit == 2) {
+                outImage->setDotsPerMeterX(int(100. * cinfo.X_density));
+                outImage->setDotsPerMeterY(int(100. * cinfo.Y_density));
+            }
+
+            if (scaledSize.isValid() && scaledSize != clip.size())
+                *outImage = outImage->scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::FastTransformation);
+        }
+    }
+
+    jpeg_destroy_decompress(&cinfo);
+    delete iod_src;
+    if (!scaledClipRect.isEmpty())
+        *outImage = outImage->copy(scaledClipRect);
+    return !outImage->isNull();
+}
+
+
+struct my_jpeg_destination_mgr : public jpeg_destination_mgr {
+    // Nothing dynamic - cannot rely on destruction over longjump
+    QIODevice *device;
+    JOCTET buffer[max_buf];
+
+public:
+    my_jpeg_destination_mgr(QIODevice *);
+};
+
+
+#if defined(Q_C_CALLBACKS)
+extern "C" {
+#endif
+
+static void qt_init_destination(j_compress_ptr)
+{
+}
+
+static boolean qt_empty_output_buffer(j_compress_ptr cinfo)
+{
+    my_jpeg_destination_mgr* dest = (my_jpeg_destination_mgr*)cinfo->dest;
+
+    int written = dest->device->write((char*)dest->buffer, max_buf);
+    if (written == -1)
+        (*cinfo->err->error_exit)((j_common_ptr)cinfo);
+
+    dest->next_output_byte = dest->buffer;
+    dest->free_in_buffer = max_buf;
+
+#if defined(Q_OS_UNIXWARE)
+    return B_TRUE;
+#else
+    return true;
+#endif
+}
+
+static void qt_term_destination(j_compress_ptr cinfo)
+{
+    my_jpeg_destination_mgr* dest = (my_jpeg_destination_mgr*)cinfo->dest;
+    qint64 n = max_buf - dest->free_in_buffer;
+
+    qint64 written = dest->device->write((char*)dest->buffer, n);
+    if (written == -1)
+        (*cinfo->err->error_exit)((j_common_ptr)cinfo);
+}
+
+#if defined(Q_C_CALLBACKS)
+}
+#endif
+
+inline my_jpeg_destination_mgr::my_jpeg_destination_mgr(QIODevice *device)
+{
+    jpeg_destination_mgr::init_destination = qt_init_destination;
+    jpeg_destination_mgr::empty_output_buffer = qt_empty_output_buffer;
+    jpeg_destination_mgr::term_destination = qt_term_destination;
+    this->device = device;
+    next_output_byte = buffer;
+    free_in_buffer = max_buf;
+}
+
+
+static bool write_jpeg_image(const QImage &sourceImage, QIODevice *device, int sourceQuality)
+{
+    bool success = false;
+    const QImage image = sourceImage;
+    const QVector<QRgb> cmap = image.colorTable();
+
+    struct jpeg_compress_struct cinfo;
+    JSAMPROW row_pointer[1];
+    row_pointer[0] = 0;
+
+    struct my_jpeg_destination_mgr *iod_dest = new my_jpeg_destination_mgr(device);
+    struct my_error_mgr jerr;
+
+    cinfo.err = jpeg_std_error(&jerr);
+    jerr.error_exit = my_error_exit;
+
+    if (!setjmp(jerr.setjmp_buffer)) {
+        // WARNING:
+        // this if loop is inside a setjmp/longjmp branch
+        // do not create C++ temporaries here because the destructor may never be called
+        // if you allocate memory, make sure that you can free it (row_pointer[0])
+        jpeg_create_compress(&cinfo);
+
+        cinfo.dest = iod_dest;
+
+        cinfo.image_width = image.width();
+        cinfo.image_height = image.height();
+
+        bool gray=false;
+        switch (image.format()) {
+        case QImage::Format_Mono:
+        case QImage::Format_MonoLSB:
+        case QImage::Format_Indexed8:
+            gray = true;
+            for (int i = image.colorCount(); gray && i--;) {
+                gray = gray & (qRed(cmap[i]) == qGreen(cmap[i]) &&
+                               qRed(cmap[i]) == qBlue(cmap[i]));
+            }
+            cinfo.input_components = gray ? 1 : 3;
+            cinfo.in_color_space = gray ? JCS_GRAYSCALE : JCS_RGB;
+            break;
+        default:
+            cinfo.input_components = 3;
+            cinfo.in_color_space = JCS_RGB;
+        }
+
+        jpeg_set_defaults(&cinfo);
+
+        qreal diffInch = qAbs(image.dotsPerMeterX()*2.54/100. - qRound(image.dotsPerMeterX()*2.54/100.))
+                         + qAbs(image.dotsPerMeterY()*2.54/100. - qRound(image.dotsPerMeterY()*2.54/100.));
+        qreal diffCm = (qAbs(image.dotsPerMeterX()/100. - qRound(image.dotsPerMeterX()/100.))
+                        + qAbs(image.dotsPerMeterY()/100. - qRound(image.dotsPerMeterY()/100.)))*2.54;
+        if (diffInch < diffCm) {
+            cinfo.density_unit = 1; // dots/inch
+            cinfo.X_density = qRound(image.dotsPerMeterX()*2.54/100.);
+            cinfo.Y_density = qRound(image.dotsPerMeterY()*2.54/100.);
+        } else {
+            cinfo.density_unit = 2; // dots/cm
+            cinfo.X_density = (image.dotsPerMeterX()+50) / 100;
+            cinfo.Y_density = (image.dotsPerMeterY()+50) / 100;
+        }
+
+
+        int quality = sourceQuality >= 0 ? qMin(sourceQuality,100) : 75;
+#if defined(Q_OS_UNIXWARE)
+        jpeg_set_quality(&cinfo, quality, B_TRUE /* limit to baseline-JPEG values */);
+        jpeg_start_compress(&cinfo, B_TRUE);
+#else
+        jpeg_set_quality(&cinfo, quality, true /* limit to baseline-JPEG values */);
+        jpeg_start_compress(&cinfo, true);
+#endif
+
+        row_pointer[0] = new uchar[cinfo.image_width*cinfo.input_components];
+        int w = cinfo.image_width;
+        while (cinfo.next_scanline < cinfo.image_height) {
+            uchar *row = row_pointer[0];
+            switch (image.format()) {
+            case QImage::Format_Mono:
+            case QImage::Format_MonoLSB:
+                if (gray) {
+                    const uchar* data = image.scanLine(cinfo.next_scanline);
+                    if (image.format() == QImage::Format_MonoLSB) {
+                        for (int i=0; i<w; i++) {
+                            bool bit = !!(*(data + (i >> 3)) & (1 << (i & 7)));
+                            row[i] = qRed(cmap[bit]);
+                        }
+                    } else {
+                        for (int i=0; i<w; i++) {
+                            bool bit = !!(*(data + (i >> 3)) & (1 << (7 -(i & 7))));
+                            row[i] = qRed(cmap[bit]);
+                        }
+                    }
+                } else {
+                    const uchar* data = image.scanLine(cinfo.next_scanline);
+                    if (image.format() == QImage::Format_MonoLSB) {
+                        for (int i=0; i<w; i++) {
+                            bool bit = !!(*(data + (i >> 3)) & (1 << (i & 7)));
+                            *row++ = qRed(cmap[bit]);
+                            *row++ = qGreen(cmap[bit]);
+                            *row++ = qBlue(cmap[bit]);
+                        }
+                    } else {
+                        for (int i=0; i<w; i++) {
+                            bool bit = !!(*(data + (i >> 3)) & (1 << (7 -(i & 7))));
+                            *row++ = qRed(cmap[bit]);
+                            *row++ = qGreen(cmap[bit]);
+                            *row++ = qBlue(cmap[bit]);
+                        }
+                    }
+                }
+                break;
+            case QImage::Format_Indexed8:
+                if (gray) {
+                    const uchar* pix = image.scanLine(cinfo.next_scanline);
+                    for (int i=0; i<w; i++) {
+                        *row = qRed(cmap[*pix]);
+                        ++row; ++pix;
+                    }
+                } else {
+                    const uchar* pix = image.scanLine(cinfo.next_scanline);
+                    for (int i=0; i<w; i++) {
+                        *row++ = qRed(cmap[*pix]);
+                        *row++ = qGreen(cmap[*pix]);
+                        *row++ = qBlue(cmap[*pix]);
+                        ++pix;
+                    }
+                }
+                break;
+            case QImage::Format_RGB888:
+                memcpy(row, image.scanLine(cinfo.next_scanline), w * 3);
+                break;
+            case QImage::Format_RGB32:
+            case QImage::Format_ARGB32:
+            case QImage::Format_ARGB32_Premultiplied: {
+                QRgb* rgb = (QRgb*)image.scanLine(cinfo.next_scanline);
+                for (int i=0; i<w; i++) {
+                    *row++ = qRed(*rgb);
+                    *row++ = qGreen(*rgb);
+                    *row++ = qBlue(*rgb);
+                    ++rgb;
+                }
+                break;
+            }
+            default:
+                qWarning("QJpegHandler: unable to write image of format %i",
+                         image.format());
+                break;
+            }
+            jpeg_write_scanlines(&cinfo, row_pointer, 1);
+        }
+
+        jpeg_finish_compress(&cinfo);
+        jpeg_destroy_compress(&cinfo);
+        success = true;
+    } else {
+        jpeg_destroy_compress(&cinfo);
+        success = false;
+    }
+
+    delete iod_dest;
+    delete [] row_pointer[0];
+    return success;
+}
+
+QJpegHandler::QJpegHandler()
+{
+    quality = 75;
+}
+
+bool QJpegHandler::canRead() const
+{
+    if (canRead(device())) {
+        setFormat("jpeg");
+        return true;
+    }
+    return false;
+}
+
+bool QJpegHandler::canRead(QIODevice *device)
+{
+    if (!device) {
+        qWarning("QJpegHandler::canRead() called with no device");
+        return false;
+    }
+
+    char buffer[2];
+    if (device->peek(buffer, 2) != 2)
+        return false;
+
+    return uchar(buffer[0]) == 0xff && uchar(buffer[1]) == 0xd8;
+}
+
+bool QJpegHandler::read(QImage *image)
+{
+    if (!canRead())
+        return false;
+    return read_jpeg_image(device(), image, scaledSize, scaledClipRect, clipRect, quality);
+}
+
+bool QJpegHandler::write(const QImage &image)
+{
+    return write_jpeg_image(image, device(), quality);
+}
+
+bool QJpegHandler::supportsOption(ImageOption option) const
+{
+    return option == Quality
+        || option == ScaledSize
+        || option == ScaledClipRect
+        || option == ClipRect
+        || option == Size
+        || option == ImageFormat;
+}
+
+QVariant QJpegHandler::option(ImageOption option) const
+{
+    if (option == Quality) {
+        return quality;
+    } else if  (option == ScaledSize) {
+        return scaledSize;
+    } else if  (option == ScaledClipRect) {
+        return scaledClipRect;
+    } else if  (option == ClipRect) {
+        return clipRect;
+    } else if (option == Size) {
+        if (canRead() && !device()->isSequential()) {
+            qint64 pos = device()->pos();
+            int width = 0;
+            int height = 0;
+            read_jpeg_size(device(), width, height);
+            device()->seek(pos);
+            return QSize(width, height);
+        }
+    } else if (option == ImageFormat) {
+        if (canRead() && !device()->isSequential()) {
+            qint64 pos = device()->pos();
+            QImage::Format format = QImage::Format_Invalid;
+            read_jpeg_format(device(), format);
+            device()->seek(pos);
+            return format;
+        }
+        return QImage::Format_Invalid;
+    }
+    return QVariant();
+}
+
+void QJpegHandler::setOption(ImageOption option, const QVariant &value)
+{
+    if (option == Quality)
+        quality = value.toInt();
+    else if ( option == ScaledSize )
+        scaledSize = value.toSize();
+    else if ( option == ScaledClipRect )
+        scaledClipRect = value.toRect();
+    else if ( option == ClipRect )
+        clipRect = value.toRect();
+}
+
+QByteArray QJpegHandler::name() const
+{
+    return "jpeg";
+}
+
+QT_END_NAMESPACE