summaryrefslogtreecommitdiffstats
path: root/src/plugins/imageformats/jpeg/qjpeghandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/imageformats/jpeg/qjpeghandler.cpp')
-rw-r--r--src/plugins/imageformats/jpeg/qjpeghandler.cpp1264
1 files changed, 1264 insertions, 0 deletions
diff --git a/src/plugins/imageformats/jpeg/qjpeghandler.cpp b/src/plugins/imageformats/jpeg/qjpeghandler.cpp
new file mode 100644
index 0000000..6d0bc1f
--- /dev/null
+++ b/src/plugins/imageformats/jpeg/qjpeghandler.cpp
@@ -0,0 +1,1264 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 char *parameters);
+
+ virtual ~QImageSmoothScaler(void);
+
+ QImage scale();
+
+protected:
+ int scaledWidth(void) const;
+
+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 char *parameters)
+{
+ char sModeStr[1024];
+ int t1;
+ int t2;
+ int dstWidth;
+ int dstHeight;
+
+ sModeStr[0] = '\0';
+
+ d = new QImageSmoothScalerPrivate;
+#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) && defined(_MSC_VER) && _MSC_VER >= 1400
+ sscanf_s(parameters, "Scale( %i, %i, %1023s )", &dstWidth, &dstHeight, sModeStr, sizeof(sModeStr));
+#else
+ sscanf(parameters, "Scale( %i, %i, %s )", &dstWidth, &dstHeight, sModeStr);
+#endif
+ QString sModeQStr = QString::fromLatin1(sModeStr);
+
+ t1 = srcWidth * dstHeight;
+ t2 = srcHeight * dstWidth;
+
+ if (((sModeQStr == QLatin1String("ScaleMin")) && (t1 > t2)) || ((sModeQStr == QLatin1String("ScaleMax")) && (t2 < t2))) {
+ dstHeight = t2 / srcWidth;
+ } else if (sModeQStr != QLatin1String("ScaleFree")) {
+ dstWidth = t1 / srcHeight;
+ }
+
+ 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;
+}
+
+int QImageSmoothScaler::scaledWidth() const
+{
+ return d->cols;
+}
+
+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);
+
+ if ( d->newrows != d->rows ) /* shortcut Y scaling if possible */
+ 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++);
+ 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 ( ! needcol ) {
+ 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 );
+ }
+ }
+ }
+ }
+
+ 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 char *params):
+ QImageSmoothScaler(info->output_width, info->output_height, params)
+ {
+ cinfo = info;
+ cols24Bit = scaledWidth() * 3;
+
+ cacheHeight = 1;
+ imageCache = QImage( info->output_width, cacheHeight, QImage::Format_RGB32 );
+ }
+
+private:
+ int cols24Bit;
+ QImage imageCache;
+ int cacheHeight;
+ 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();
+ jpeg_read_scanlines(cinfo, &data, 1);
+ out = (QRgb*)imageCache.scanLine(0);
+
+ //
+ // The smooth scale algorithm only works on 32-bit images;
+ // convert from (8|24) bits to 32.
+ //
+ if (cinfo->output_components == 1) {
+ in = (uchar*)out + scaledWidth();
+ for (uint i = scaledWidth(); i--; ) {
+ in--;
+ out[i] = qRgb(*in, *in, *in);
+ }
+ } else {
+ in = (uchar*)out + cols24Bit;
+ for (uint i = scaledWidth(); i--; ) {
+ in -= 3;
+ out[i] = qRgb(in[0], in[1], in[2]);
+ }
+ }
+
+ 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 void scaleSize(int &reqW, int &reqH, int imgW, int imgH, Qt::AspectRatioMode mode)
+{
+ if (mode == Qt::IgnoreAspectRatio)
+ return;
+ int t1 = imgW * reqH;
+ int t2 = reqW * imgH;
+ if ((mode == Qt::KeepAspectRatio && (t1 > t2)) || (mode == Qt::KeepAspectRatioByExpanding && (t1 < t2)))
+ reqH = t2 / imgW;
+ else
+ reqW = t1 / imgH;
+}
+
+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,
+ bool dummy = false)
+{
+ 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
+ }
+
+ const QSize size(info->output_width, info->output_height);
+ if (dest->size() != size || dest->format() != format) {
+ static uchar dummyImage[1];
+ if (dummy) // Create QImage but don't read the pixels
+ *dest = QImage(dummyImage, size.width(), size.height(), format);
+ else
+ *dest = QImage(size, format);
+
+ if (format == QImage::Format_Indexed8) {
+ dest->setNumColors(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,
+ const QByteArray &parameters, QSize scaledSize,
+ int inQuality )
+{
+#ifdef QT_NO_IMAGE_SMOOTHSCALE
+ Q_UNUSED( scaledSize );
+#endif
+
+ 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;
+
+ QString params = QString::fromLatin1(parameters);
+ params.simplified();
+ int sWidth = 0, sHeight = 0;
+ char sModeStr[1024] = "";
+ Qt::AspectRatioMode sMode;
+
+#ifndef QT_NO_IMAGE_SMOOTHSCALE
+ // If high quality not required, shrink image during decompression
+ if (scaledSize.isValid() && quality < HIGH_QUALITY_THRESHOLD && !params.contains(QLatin1String("GetHeaderInformation")) ) {
+ cinfo.scale_denom = qMin(cinfo.image_width / scaledSize.width(),
+ cinfo.image_width / 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;
+ }
+#endif
+
+
+ // 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_start_decompress(&cinfo);
+
+ if (params.contains(QLatin1String("GetHeaderInformation"))) {
+ if (!ensureValidImage(outImage, &cinfo, true))
+ longjmp(jerr.setjmp_buffer, 1);
+ } else if (params.contains(QLatin1String("Scale"))) {
+#if defined(_MSC_VER) && _MSC_VER >= 1400 && !defined(Q_OS_WINCE)
+ sscanf_s(params.toLatin1().data(), "Scale(%i, %i, %1023s)",
+ &sWidth, &sHeight, sModeStr, sizeof(sModeStr));
+#else
+ sscanf(params.toLatin1().data(), "Scale(%i, %i, %1023s)",
+ &sWidth, &sHeight, sModeStr);
+#endif
+
+ QString sModeQStr(QString::fromLatin1(sModeStr));
+ if (sModeQStr == QLatin1String("IgnoreAspectRatio")) {
+ sMode = Qt::IgnoreAspectRatio;
+ } else if (sModeQStr == QLatin1String("KeepAspectRatio")) {
+ sMode = Qt::KeepAspectRatio;
+ } else if (sModeQStr == QLatin1String("KeepAspectRatioByExpanding")) {
+ sMode = Qt::KeepAspectRatioByExpanding;
+ } else {
+ qDebug("read_jpeg_image: invalid aspect ratio mode \"%s\", see QImage::AspectRatioMode documentation", sModeStr);
+ sMode = Qt::KeepAspectRatio;
+ }
+
+// qDebug("Parameters ask to scale the image to %i x %i AspectRatioMode: %s", sWidth, sHeight, sModeStr);
+ scaleSize(sWidth, sHeight, cinfo.output_width, cinfo.output_height, sMode);
+// qDebug("Scaling the jpeg to %i x %i", sWidth, sHeight, sModeStr);
+
+ if (cinfo.output_components == 3 || cinfo.output_components == 4) {
+ if (outImage->size() != QSize(sWidth, sHeight) || outImage->format() != QImage::Format_RGB32)
+ *outImage = QImage(sWidth, sHeight, QImage::Format_RGB32);
+ } else if (cinfo.output_components == 1) {
+ if (outImage->size() != QSize(sWidth, sHeight) || outImage->format() != QImage::Format_Indexed8)
+ *outImage = QImage(sWidth, sHeight, QImage::Format_Indexed8);
+ outImage->setNumColors(256);
+ for (int i = 0; i < 256; ++i)
+ outImage->setColor(i, qRgb(i,i,i));
+ } else {
+ // Unsupported format
+ }
+ if (outImage->isNull())
+ longjmp(jerr.setjmp_buffer, 1);
+
+ if (!outImage->isNull()) {
+ QImage tmpImage(cinfo.output_width, 1, QImage::Format_RGB32);
+ uchar* inData = tmpImage.bits();
+ uchar* outData = outImage->bits();
+ int out_bpl = outImage->bytesPerLine();
+ while (cinfo.output_scanline < cinfo.output_height) {
+ int outputLine = sHeight * cinfo.output_scanline / cinfo.output_height;
+ (void) jpeg_read_scanlines(&cinfo, &inData, 1);
+ if (cinfo.output_components == 3) {
+ uchar *in = inData;
+ QRgb *out = (QRgb*)outData + outputLine * out_bpl;
+ for (uint i=0; i<cinfo.output_width; i++) {
+// ### Only scaling down an image works, I don't think scaling up will work at the moment
+// ### An idea I have to make this a smooth scale is to progressively add the pixel values up
+// When scaling down, multiple values are being over drawn in to the output buffer.
+// Instead, a weighting based on the distance the line or pixel is from the output pixel determines
+// the weight of it when added to the output buffer. At present it is a non-smooth scale which is
+// inefficently implemented, it still uncompresses all the jpeg, an optimization for progressive
+// jpegs could be made if scaling by say 50% or some other special cases
+ out[sWidth * i / cinfo.output_width] = qRgb(in[0], in[1], in[2]);
+ in += 3;
+ }
+ } else {
+// ### Need to test the case where the jpeg is grayscale, need some black and white jpegs to test
+// this code. (also only scales down and probably won't scale to a larger size)
+ uchar *in = inData;
+ uchar *out = outData + outputLine*out_bpl;
+ for (uint i=0; i<cinfo.output_width; i++) {
+ out[sWidth * i / cinfo.output_width] = in[i];
+ }
+ }
+ }
+ (void) jpeg_finish_decompress(&cinfo);
+ }
+#ifndef QT_NO_IMAGE_SMOOTHSCALE
+ } else if (scaledSize.isValid()) {
+
+ jpegSmoothScaler scaler(&cinfo, QString().sprintf("Scale( %d, %d, ScaleFree )",
+ scaledSize.width(),
+ scaledSize.height()).toLatin1().data());
+ *outImage = scaler.scale();
+#endif
+ } else {
+ if (!ensureValidImage(outImage, &cinfo))
+ longjmp(jerr.setjmp_buffer, 1);
+
+ uchar* data = outImage->bits();
+ int bpl = outImage->bytesPerLine();
+ while (cinfo.output_scanline < cinfo.output_height) {
+ uchar *d = data + cinfo.output_scanline * bpl;
+ (void) jpeg_read_scanlines(&cinfo,
+ &d,
+ 1);
+ }
+ (void) jpeg_finish_decompress(&cinfo);
+
+ if (cinfo.output_components == 3) {
+ // Expand 24->32 bpp.
+ for (uint j=0; j<cinfo.output_height; j++) {
+ uchar *in = outImage->scanLine(j) + cinfo.output_width * 3;
+ QRgb *out = (QRgb*)outImage->scanLine(j);
+
+ for (uint i=cinfo.output_width; i--;) {
+ in-=3;
+ out[i] = qRgb(in[0], in[1], in[2]);
+ }
+ }
+ } else if (cinfo.out_color_space == JCS_CMYK) {
+ for (uint j = 0; j < cinfo.output_height; ++j) {
+ uchar *in = outImage->scanLine(j) + cinfo.output_width * 4;
+ QRgb *out = (QRgb*)outImage->scanLine(j);
+
+ for (uint i = cinfo.output_width; i--; ) {
+ in-=4;
+ int k = in[3];
+ out[i] = qRgb(k * in[0] / 255, k * in[1] / 255, k * in[2] / 255);
+ }
+ }
+ }
+ 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));
+ }
+ }
+ }
+
+ jpeg_destroy_decompress(&cinfo);
+ delete iod_src;
+ 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.numColors(); 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;
+ }
+
+ return device->peek(2) == "\xFF\xD8";
+}
+
+bool QJpegHandler::read(QImage *image)
+{
+ if (!canRead())
+ return false;
+ return read_jpeg_image(device(), image, parameters, scaledSize, quality);
+}
+
+bool QJpegHandler::write(const QImage &image)
+{
+ return write_jpeg_image(image, device(), quality);
+}
+
+bool QJpegHandler::supportsOption(ImageOption option) const
+{
+ return option == Quality
+#ifndef QT_NO_IMAGE_SMOOTHSCALE
+ || option == ScaledSize
+#endif
+ || option == Size
+ || option == ImageFormat;
+}
+
+QVariant QJpegHandler::option(ImageOption option) const
+{
+ if (option == Quality) {
+ return quality;
+#ifndef QT_NO_IMAGE_SMOOTHSCALE
+ } else if (option == ScaledSize) {
+ return scaledSize;
+#endif
+ } 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();
+#ifndef QT_NO_IMAGE_SMOOTHSCALE
+ else if ( option == ScaledSize )
+ scaledSize = value.toSize();
+#endif
+}
+
+QByteArray QJpegHandler::name() const
+{
+ return "jpeg";
+}
+
+QT_END_NAMESPACE