diff -r 41300fa6a67c -r f7bc934e204c util/src/plugins/imageformats/jpeg/qjpeghandler.cpp --- /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 +#include +#include + +#include // jpeglib needs this to be pre-included +#include + +#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 +#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 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> 3)) & (1 << (i & 7))); + row[i] = qRed(cmap[bit]); + } + } else { + for (int i=0; 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> 3)) & (1 << (i & 7))); + *row++ = qRed(cmap[bit]); + *row++ = qGreen(cmap[bit]); + *row++ = qBlue(cmap[bit]); + } + } else { + for (int i=0; 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; ipeek(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