source: trunk/src/gui/image/qjpeghandler.cpp@ 1056

Last change on this file since 1056 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 29.3 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the plugins of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qjpeghandler_p.h"
43
44#include <qimage.h>
45#include <qvariant.h>
46#include <qvector.h>
47#include <qbuffer.h>
48#include <private/qsimd_p.h>
49
50#include <stdio.h> // jpeglib needs this to be pre-included
51#include <setjmp.h>
52
53#ifdef FAR
54#undef FAR
55#endif
56
57// including jpeglib.h seems to be a little messy
58extern "C" {
59// mingw includes rpcndr.h but does not define boolean
60#if defined(Q_OS_WIN) && defined(Q_CC_GNU)
61# if defined(__RPCNDR_H__) && !defined(boolean)
62 typedef unsigned char boolean;
63# define HAVE_BOOLEAN
64# endif
65#endif
66
67#define XMD_H // shut JPEGlib up
68#if defined(Q_OS_UNIXWARE)
69# define HAVE_BOOLEAN // libjpeg under Unixware seems to need this
70#endif
71#include <jpeglib.h>
72#ifdef const
73# undef const // remove crazy C hackery in jconfig.h
74#endif
75}
76
77QT_BEGIN_NAMESPACE
78
79void QT_FASTCALL convert_rgb888_to_rgb32_C(quint32 *dst, const uchar *src, int len)
80{
81 // Expand 24->32 bpp.
82 for (int i = 0; i < len; ++i) {
83 *dst++ = qRgb(src[0], src[1], src[2]);
84 src += 3;
85 }
86}
87
88typedef void (QT_FASTCALL *Rgb888ToRgb32Converter)(quint32 *dst, const uchar *src, int len);
89
90static Rgb888ToRgb32Converter rgb888ToRgb32ConverterPtr = convert_rgb888_to_rgb32_C;
91
92struct my_error_mgr : public jpeg_error_mgr {
93 jmp_buf setjmp_buffer;
94};
95
96#if defined(Q_C_CALLBACKS)
97extern "C" {
98#endif
99
100static void my_error_exit (j_common_ptr cinfo)
101{
102 my_error_mgr* myerr = (my_error_mgr*) cinfo->err;
103 char buffer[JMSG_LENGTH_MAX];
104 (*cinfo->err->format_message)(cinfo, buffer);
105 qWarning("%s", buffer);
106 longjmp(myerr->setjmp_buffer, 1);
107}
108
109#if defined(Q_C_CALLBACKS)
110}
111#endif
112
113
114static const int max_buf = 4096;
115
116struct my_jpeg_source_mgr : public jpeg_source_mgr {
117 // Nothing dynamic - cannot rely on destruction over longjump
118 QIODevice *device;
119 JOCTET buffer[max_buf];
120 const QBuffer *memDevice;
121
122public:
123 my_jpeg_source_mgr(QIODevice *device);
124};
125
126#if defined(Q_C_CALLBACKS)
127extern "C" {
128#endif
129
130static void qt_init_source(j_decompress_ptr)
131{
132}
133
134static boolean qt_fill_input_buffer(j_decompress_ptr cinfo)
135{
136 my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src;
137 qint64 num_read = 0;
138 if (src->memDevice) {
139 src->next_input_byte = (const JOCTET *)(src->memDevice->data().constData() + src->memDevice->pos());
140 num_read = src->memDevice->data().size() - src->memDevice->pos();
141 src->device->seek(src->memDevice->data().size());
142 } else {
143 src->next_input_byte = src->buffer;
144 num_read = src->device->read((char*)src->buffer, max_buf);
145 }
146 if (num_read <= 0) {
147 // Insert a fake EOI marker - as per jpeglib recommendation
148 src->next_input_byte = src->buffer;
149 src->buffer[0] = (JOCTET) 0xFF;
150 src->buffer[1] = (JOCTET) JPEG_EOI;
151 src->bytes_in_buffer = 2;
152 } else {
153 src->bytes_in_buffer = num_read;
154 }
155#if defined(Q_OS_UNIXWARE)
156 return B_TRUE;
157#else
158 return true;
159#endif
160}
161
162static void qt_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
163{
164 my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src;
165
166 // `dumb' implementation from jpeglib
167
168 /* Just a dumb implementation for now. Could use fseek() except
169 * it doesn't work on pipes. Not clear that being smart is worth
170 * any trouble anyway --- large skips are infrequent.
171 */
172 if (num_bytes > 0) {
173 while (num_bytes > (long) src->bytes_in_buffer) { // Should not happen in case of memDevice
174 num_bytes -= (long) src->bytes_in_buffer;
175 (void) qt_fill_input_buffer(cinfo);
176 /* note we assume that qt_fill_input_buffer will never return false,
177 * so suspension need not be handled.
178 */
179 }
180 src->next_input_byte += (size_t) num_bytes;
181 src->bytes_in_buffer -= (size_t) num_bytes;
182 }
183}
184
185static void qt_term_source(j_decompress_ptr cinfo)
186{
187 my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src;
188 if (!src->device->isSequential())
189 src->device->seek(src->device->pos() - src->bytes_in_buffer);
190}
191
192#if defined(Q_C_CALLBACKS)
193}
194#endif
195
196inline my_jpeg_source_mgr::my_jpeg_source_mgr(QIODevice *device)
197{
198 jpeg_source_mgr::init_source = qt_init_source;
199 jpeg_source_mgr::fill_input_buffer = qt_fill_input_buffer;
200 jpeg_source_mgr::skip_input_data = qt_skip_input_data;
201 jpeg_source_mgr::resync_to_restart = jpeg_resync_to_restart;
202 jpeg_source_mgr::term_source = qt_term_source;
203 this->device = device;
204 memDevice = qobject_cast<QBuffer *>(device);
205 bytes_in_buffer = 0;
206 next_input_byte = buffer;
207}
208
209
210inline static bool read_jpeg_size(int &w, int &h, j_decompress_ptr cinfo)
211{
212 (void) jpeg_calc_output_dimensions(cinfo);
213
214 w = cinfo->output_width;
215 h = cinfo->output_height;
216 return true;
217}
218
219#define HIGH_QUALITY_THRESHOLD 50
220
221inline static bool read_jpeg_format(QImage::Format &format, j_decompress_ptr cinfo)
222{
223
224 bool result = true;
225 switch (cinfo->output_components) {
226 case 1:
227 format = QImage::Format_Indexed8;
228 break;
229 case 3:
230 case 4:
231 format = QImage::Format_RGB32;
232 break;
233 default:
234 result = false;
235 break;
236 }
237 cinfo->output_scanline = cinfo->output_height;
238 return result;
239}
240
241static bool ensureValidImage(QImage *dest, struct jpeg_decompress_struct *info,
242 const QSize& size)
243{
244 QImage::Format format;
245 switch (info->output_components) {
246 case 1:
247 format = QImage::Format_Indexed8;
248 break;
249 case 3:
250 case 4:
251 format = QImage::Format_RGB32;
252 break;
253 default:
254 return false; // unsupported format
255 }
256
257 if (dest->size() != size || dest->format() != format) {
258 *dest = QImage(size, format);
259
260 if (format == QImage::Format_Indexed8) {
261 dest->setColorCount(256);
262 for (int i = 0; i < 256; i++)
263 dest->setColor(i, qRgb(i,i,i));
264 }
265 }
266
267 return !dest->isNull();
268}
269
270static bool read_jpeg_image(QImage *outImage,
271 QSize scaledSize, QRect scaledClipRect,
272 QRect clipRect, int inQuality, j_decompress_ptr info, struct my_error_mgr* err )
273{
274 if (!setjmp(err->setjmp_buffer)) {
275 // -1 means default quality.
276 int quality = inQuality;
277 if (quality < 0)
278 quality = 75;
279
280 // If possible, merge the scaledClipRect into either scaledSize
281 // or clipRect to avoid doing a separate scaled clipping pass.
282 // Best results are achieved by clipping before scaling, not after.
283 if (!scaledClipRect.isEmpty()) {
284 if (scaledSize.isEmpty() && clipRect.isEmpty()) {
285 // No clipping or scaling before final clip.
286 clipRect = scaledClipRect;
287 scaledClipRect = QRect();
288 } else if (scaledSize.isEmpty()) {
289 // Clipping, but no scaling: combine the clip regions.
290 scaledClipRect.translate(clipRect.topLeft());
291 clipRect = scaledClipRect.intersected(clipRect);
292 scaledClipRect = QRect();
293 } else if (clipRect.isEmpty()) {
294 // No clipping, but scaling: if we can map back to an
295 // integer pixel boundary, then clip before scaling.
296 if ((info->image_width % scaledSize.width()) == 0 &&
297 (info->image_height % scaledSize.height()) == 0) {
298 int x = scaledClipRect.x() * info->image_width /
299 scaledSize.width();
300 int y = scaledClipRect.y() * info->image_height /
301 scaledSize.height();
302 int width = (scaledClipRect.right() + 1) *
303 info->image_width / scaledSize.width() - x;
304 int height = (scaledClipRect.bottom() + 1) *
305 info->image_height / scaledSize.height() - y;
306 clipRect = QRect(x, y, width, height);
307 scaledSize = scaledClipRect.size();
308 scaledClipRect = QRect();
309 }
310 } else {
311 // Clipping and scaling: too difficult to figure out,
312 // and not a likely use case, so do it the long way.
313 }
314 }
315
316 // Determine the scale factor to pass to libjpeg for quick downscaling.
317 if (!scaledSize.isEmpty()) {
318 if (clipRect.isEmpty()) {
319 info->scale_denom =
320 qMin(info->image_width / scaledSize.width(),
321 info->image_height / scaledSize.height());
322 } else {
323 info->scale_denom =
324 qMin(clipRect.width() / scaledSize.width(),
325 clipRect.height() / scaledSize.height());
326 }
327 if (info->scale_denom < 2) {
328 info->scale_denom = 1;
329 } else if (info->scale_denom < 4) {
330 info->scale_denom = 2;
331 } else if (info->scale_denom < 8) {
332 info->scale_denom = 4;
333 } else {
334 info->scale_denom = 8;
335 }
336 info->scale_num = 1;
337 if (!clipRect.isEmpty()) {
338 // Correct the scale factor so that we clip accurately.
339 // It is recommended that the clip rectangle be aligned
340 // on an 8-pixel boundary for best performance.
341 while (info->scale_denom > 1 &&
342 ((clipRect.x() % info->scale_denom) != 0 ||
343 (clipRect.y() % info->scale_denom) != 0 ||
344 (clipRect.width() % info->scale_denom) != 0 ||
345 (clipRect.height() % info->scale_denom) != 0)) {
346 info->scale_denom /= 2;
347 }
348 }
349 }
350
351 // If high quality not required, use fast decompression
352 if( quality < HIGH_QUALITY_THRESHOLD ) {
353 info->dct_method = JDCT_IFAST;
354 info->do_fancy_upsampling = FALSE;
355 }
356
357 (void) jpeg_calc_output_dimensions(info);
358
359 // Determine the clip region to extract.
360 QRect imageRect(0, 0, info->output_width, info->output_height);
361 QRect clip;
362 if (clipRect.isEmpty()) {
363 clip = imageRect;
364 } else if (info->scale_denom == info->scale_num) {
365 clip = clipRect.intersected(imageRect);
366 } else {
367 // The scale factor was corrected above to ensure that
368 // we don't miss pixels when we scale the clip rectangle.
369 clip = QRect(clipRect.x() / int(info->scale_denom),
370 clipRect.y() / int(info->scale_denom),
371 clipRect.width() / int(info->scale_denom),
372 clipRect.height() / int(info->scale_denom));
373 clip = clip.intersected(imageRect);
374 }
375
376 // Allocate memory for the clipped QImage.
377 if (!ensureValidImage(outImage, info, clip.size()))
378 longjmp(err->setjmp_buffer, 1);
379
380 // Avoid memcpy() overhead if grayscale with no clipping.
381 bool quickGray = (info->output_components == 1 &&
382 clip == imageRect);
383 if (!quickGray) {
384 // Ask the jpeg library to allocate a temporary row.
385 // The library will automatically delete it for us later.
386 // The libjpeg docs say we should do this before calling
387 // jpeg_start_decompress(). We can't use "new" here
388 // because we are inside the setjmp() block and an error
389 // in the jpeg input stream would cause a memory leak.
390 JSAMPARRAY rows = (info->mem->alloc_sarray)
391 ((j_common_ptr)info, JPOOL_IMAGE,
392 info->output_width * info->output_components, 1);
393
394 (void) jpeg_start_decompress(info);
395
396 while (info->output_scanline < info->output_height) {
397 int y = int(info->output_scanline) - clip.y();
398 if (y >= clip.height())
399 break; // We've read the entire clip region, so abort.
400
401 (void) jpeg_read_scanlines(info, rows, 1);
402
403 if (y < 0)
404 continue; // Haven't reached the starting line yet.
405
406 if (info->output_components == 3) {
407 uchar *in = rows[0] + clip.x() * 3;
408 QRgb *out = (QRgb*)outImage->scanLine(y);
409 rgb888ToRgb32ConverterPtr(out, in, clip.width());
410 } else if (info->out_color_space == JCS_CMYK) {
411 // Convert CMYK->RGB.
412 uchar *in = rows[0] + clip.x() * 4;
413 QRgb *out = (QRgb*)outImage->scanLine(y);
414 for (int i = 0; i < clip.width(); ++i) {
415 int k = in[3];
416 *out++ = qRgb(k * in[0] / 255, k * in[1] / 255,
417 k * in[2] / 255);
418 in += 4;
419 }
420 } else if (info->output_components == 1) {
421 // Grayscale.
422 memcpy(outImage->scanLine(y),
423 rows[0] + clip.x(), clip.width());
424 }
425 }
426 } else {
427 // Load unclipped grayscale data directly into the QImage.
428 (void) jpeg_start_decompress(info);
429 while (info->output_scanline < info->output_height) {
430 uchar *row = outImage->scanLine(info->output_scanline);
431 (void) jpeg_read_scanlines(info, &row, 1);
432 }
433 }
434
435 if (info->output_scanline == info->output_height)
436 (void) jpeg_finish_decompress(info);
437
438 if (info->density_unit == 1) {
439 outImage->setDotsPerMeterX(int(100. * info->X_density / 2.54));
440 outImage->setDotsPerMeterY(int(100. * info->Y_density / 2.54));
441 } else if (info->density_unit == 2) {
442 outImage->setDotsPerMeterX(int(100. * info->X_density));
443 outImage->setDotsPerMeterY(int(100. * info->Y_density));
444 }
445
446 if (scaledSize.isValid() && scaledSize != clip.size()) {
447 *outImage = outImage->scaled(scaledSize, Qt::IgnoreAspectRatio, quality >= HIGH_QUALITY_THRESHOLD ? Qt::SmoothTransformation : Qt::FastTransformation);
448 }
449
450 if (!scaledClipRect.isEmpty())
451 *outImage = outImage->copy(scaledClipRect);
452 return !outImage->isNull();
453 }
454 else
455 return false;
456}
457
458struct my_jpeg_destination_mgr : public jpeg_destination_mgr {
459 // Nothing dynamic - cannot rely on destruction over longjump
460 QIODevice *device;
461 JOCTET buffer[max_buf];
462
463public:
464 my_jpeg_destination_mgr(QIODevice *);
465};
466
467
468#if defined(Q_C_CALLBACKS)
469extern "C" {
470#endif
471
472static void qt_init_destination(j_compress_ptr)
473{
474}
475
476static boolean qt_empty_output_buffer(j_compress_ptr cinfo)
477{
478 my_jpeg_destination_mgr* dest = (my_jpeg_destination_mgr*)cinfo->dest;
479
480 int written = dest->device->write((char*)dest->buffer, max_buf);
481 if (written == -1)
482 (*cinfo->err->error_exit)((j_common_ptr)cinfo);
483
484 dest->next_output_byte = dest->buffer;
485 dest->free_in_buffer = max_buf;
486
487#if defined(Q_OS_UNIXWARE)
488 return B_TRUE;
489#else
490 return true;
491#endif
492}
493
494static void qt_term_destination(j_compress_ptr cinfo)
495{
496 my_jpeg_destination_mgr* dest = (my_jpeg_destination_mgr*)cinfo->dest;
497 qint64 n = max_buf - dest->free_in_buffer;
498
499 qint64 written = dest->device->write((char*)dest->buffer, n);
500 if (written == -1)
501 (*cinfo->err->error_exit)((j_common_ptr)cinfo);
502}
503
504#if defined(Q_C_CALLBACKS)
505}
506#endif
507
508inline my_jpeg_destination_mgr::my_jpeg_destination_mgr(QIODevice *device)
509{
510 jpeg_destination_mgr::init_destination = qt_init_destination;
511 jpeg_destination_mgr::empty_output_buffer = qt_empty_output_buffer;
512 jpeg_destination_mgr::term_destination = qt_term_destination;
513 this->device = device;
514 next_output_byte = buffer;
515 free_in_buffer = max_buf;
516}
517
518
519static bool write_jpeg_image(const QImage &image, QIODevice *device, int sourceQuality)
520{
521 bool success = false;
522 const QVector<QRgb> cmap = image.colorTable();
523
524 struct jpeg_compress_struct cinfo;
525 JSAMPROW row_pointer[1];
526 row_pointer[0] = 0;
527
528 struct my_jpeg_destination_mgr *iod_dest = new my_jpeg_destination_mgr(device);
529 struct my_error_mgr jerr;
530
531 cinfo.err = jpeg_std_error(&jerr);
532 jerr.error_exit = my_error_exit;
533
534 if (!setjmp(jerr.setjmp_buffer)) {
535 // WARNING:
536 // this if loop is inside a setjmp/longjmp branch
537 // do not create C++ temporaries here because the destructor may never be called
538 // if you allocate memory, make sure that you can free it (row_pointer[0])
539 jpeg_create_compress(&cinfo);
540
541 cinfo.dest = iod_dest;
542
543 cinfo.image_width = image.width();
544 cinfo.image_height = image.height();
545
546 bool gray=false;
547 switch (image.format()) {
548 case QImage::Format_Mono:
549 case QImage::Format_MonoLSB:
550 case QImage::Format_Indexed8:
551 gray = true;
552 for (int i = image.colorCount(); gray && i--;) {
553 gray = gray & (qRed(cmap[i]) == qGreen(cmap[i]) &&
554 qRed(cmap[i]) == qBlue(cmap[i]));
555 }
556 cinfo.input_components = gray ? 1 : 3;
557 cinfo.in_color_space = gray ? JCS_GRAYSCALE : JCS_RGB;
558 break;
559 default:
560 cinfo.input_components = 3;
561 cinfo.in_color_space = JCS_RGB;
562 }
563
564 jpeg_set_defaults(&cinfo);
565
566 qreal diffInch = qAbs(image.dotsPerMeterX()*2.54/100. - qRound(image.dotsPerMeterX()*2.54/100.))
567 + qAbs(image.dotsPerMeterY()*2.54/100. - qRound(image.dotsPerMeterY()*2.54/100.));
568 qreal diffCm = (qAbs(image.dotsPerMeterX()/100. - qRound(image.dotsPerMeterX()/100.))
569 + qAbs(image.dotsPerMeterY()/100. - qRound(image.dotsPerMeterY()/100.)))*2.54;
570 if (diffInch < diffCm) {
571 cinfo.density_unit = 1; // dots/inch
572 cinfo.X_density = qRound(image.dotsPerMeterX()*2.54/100.);
573 cinfo.Y_density = qRound(image.dotsPerMeterY()*2.54/100.);
574 } else {
575 cinfo.density_unit = 2; // dots/cm
576 cinfo.X_density = (image.dotsPerMeterX()+50) / 100;
577 cinfo.Y_density = (image.dotsPerMeterY()+50) / 100;
578 }
579
580
581 int quality = sourceQuality >= 0 ? qMin(sourceQuality,100) : 75;
582#if defined(Q_OS_UNIXWARE)
583 jpeg_set_quality(&cinfo, quality, B_TRUE /* limit to baseline-JPEG values */);
584 jpeg_start_compress(&cinfo, B_TRUE);
585#else
586 jpeg_set_quality(&cinfo, quality, true /* limit to baseline-JPEG values */);
587 jpeg_start_compress(&cinfo, true);
588#endif
589
590 row_pointer[0] = new uchar[cinfo.image_width*cinfo.input_components];
591 int w = cinfo.image_width;
592 while (cinfo.next_scanline < cinfo.image_height) {
593 uchar *row = row_pointer[0];
594 switch (image.format()) {
595 case QImage::Format_Mono:
596 case QImage::Format_MonoLSB:
597 if (gray) {
598 const uchar* data = image.constScanLine(cinfo.next_scanline);
599 if (image.format() == QImage::Format_MonoLSB) {
600 for (int i=0; i<w; i++) {
601 bool bit = !!(*(data + (i >> 3)) & (1 << (i & 7)));
602 row[i] = qRed(cmap[bit]);
603 }
604 } else {
605 for (int i=0; i<w; i++) {
606 bool bit = !!(*(data + (i >> 3)) & (1 << (7 -(i & 7))));
607 row[i] = qRed(cmap[bit]);
608 }
609 }
610 } else {
611 const uchar* data = image.constScanLine(cinfo.next_scanline);
612 if (image.format() == QImage::Format_MonoLSB) {
613 for (int i=0; i<w; i++) {
614 bool bit = !!(*(data + (i >> 3)) & (1 << (i & 7)));
615 *row++ = qRed(cmap[bit]);
616 *row++ = qGreen(cmap[bit]);
617 *row++ = qBlue(cmap[bit]);
618 }
619 } else {
620 for (int i=0; i<w; i++) {
621 bool bit = !!(*(data + (i >> 3)) & (1 << (7 -(i & 7))));
622 *row++ = qRed(cmap[bit]);
623 *row++ = qGreen(cmap[bit]);
624 *row++ = qBlue(cmap[bit]);
625 }
626 }
627 }
628 break;
629 case QImage::Format_Indexed8:
630 if (gray) {
631 const uchar* pix = image.constScanLine(cinfo.next_scanline);
632 for (int i=0; i<w; i++) {
633 *row = qRed(cmap[*pix]);
634 ++row; ++pix;
635 }
636 } else {
637 const uchar* pix = image.constScanLine(cinfo.next_scanline);
638 for (int i=0; i<w; i++) {
639 *row++ = qRed(cmap[*pix]);
640 *row++ = qGreen(cmap[*pix]);
641 *row++ = qBlue(cmap[*pix]);
642 ++pix;
643 }
644 }
645 break;
646 case QImage::Format_RGB888:
647 memcpy(row, image.constScanLine(cinfo.next_scanline), w * 3);
648 break;
649 case QImage::Format_RGB32:
650 case QImage::Format_ARGB32:
651 case QImage::Format_ARGB32_Premultiplied:
652 {
653 const QRgb* rgb = (const QRgb*)image.constScanLine(cinfo.next_scanline);
654 for (int i=0; i<w; i++) {
655 *row++ = qRed(*rgb);
656 *row++ = qGreen(*rgb);
657 *row++ = qBlue(*rgb);
658 ++rgb;
659 }
660 }
661 break;
662 default:
663 {
664 // (Testing shows that this way is actually faster than converting to RGB888 + memcpy)
665 QImage rowImg = image.copy(0, cinfo.next_scanline, w, 1).convertToFormat(QImage::Format_RGB32);
666 const QRgb* rgb = (const QRgb*)rowImg.constScanLine(0);
667 for (int i=0; i<w; i++) {
668 *row++ = qRed(*rgb);
669 *row++ = qGreen(*rgb);
670 *row++ = qBlue(*rgb);
671 ++rgb;
672 }
673 }
674 break;
675 }
676 jpeg_write_scanlines(&cinfo, row_pointer, 1);
677 }
678
679 jpeg_finish_compress(&cinfo);
680 jpeg_destroy_compress(&cinfo);
681 success = true;
682 } else {
683 jpeg_destroy_compress(&cinfo);
684 success = false;
685 }
686
687 delete iod_dest;
688 delete [] row_pointer[0];
689 return success;
690}
691
692class QJpegHandlerPrivate
693{
694public:
695 enum State {
696 Ready,
697 ReadHeader,
698 Error
699 };
700
701 QJpegHandlerPrivate(QJpegHandler *qq)
702 : quality(75), iod_src(0), state(Ready), q(qq)
703 {}
704
705 ~QJpegHandlerPrivate()
706 {
707 if(iod_src)
708 {
709 jpeg_destroy_decompress(&info);
710 delete iod_src;
711 iod_src = 0;
712 }
713 }
714
715 bool readJpegHeader(QIODevice*);
716 bool read(QImage *image);
717
718 int quality;
719 QVariant size;
720 QImage::Format format;
721 QSize scaledSize;
722 QRect scaledClipRect;
723 QRect clipRect;
724 struct jpeg_decompress_struct info;
725 struct my_jpeg_source_mgr * iod_src;
726 struct my_error_mgr err;
727
728 State state;
729
730 QJpegHandler *q;
731};
732
733/*!
734 \internal
735*/
736bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device)
737{
738 if(state == Ready)
739 {
740 state = Error;
741 iod_src = new my_jpeg_source_mgr(device);
742
743 jpeg_create_decompress(&info);
744 info.src = iod_src;
745 info.err = jpeg_std_error(&err);
746 err.error_exit = my_error_exit;
747
748 if (!setjmp(err.setjmp_buffer)) {
749 #if defined(Q_OS_UNIXWARE)
750 (void) jpeg_read_header(&info, B_TRUE);
751 #else
752 (void) jpeg_read_header(&info, true);
753 #endif
754
755 int width = 0;
756 int height = 0;
757 read_jpeg_size(width, height, &info);
758 size = QSize(width, height);
759
760 format = QImage::Format_Invalid;
761 read_jpeg_format(format, &info);
762 state = ReadHeader;
763 return true;
764 }
765 else
766 {
767 return false;
768 }
769 }
770 else if(state == Error)
771 return false;
772 return true;
773}
774
775bool QJpegHandlerPrivate::read(QImage *image)
776{
777 if(state == Ready)
778 readJpegHeader(q->device());
779
780 if(state == ReadHeader)
781 {
782 bool success = read_jpeg_image(image, scaledSize, scaledClipRect, clipRect, quality, &info, &err);
783 state = success ? Ready : Error;
784 return success;
785 }
786
787 return false;
788
789}
790
791QJpegHandler::QJpegHandler()
792 : d(new QJpegHandlerPrivate(this))
793{
794 const uint features = qDetectCPUFeatures();
795 Q_UNUSED(features);
796#if defined(QT_HAVE_NEON)
797 // from qimage_neon.cpp
798 Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_neon(quint32 *dst, const uchar *src, int len);
799
800 if (features & NEON)
801 rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_neon;
802#endif // QT_HAVE_NEON
803#if defined(QT_HAVE_SSSE3)
804 // from qimage_ssse3.cpp
805 Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_ssse3(quint32 *dst, const uchar *src, int len);
806
807 if (features & SSSE3)
808 rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_ssse3;
809#endif // QT_HAVE_SSSE3
810}
811
812QJpegHandler::~QJpegHandler()
813{
814 delete d;
815}
816
817bool QJpegHandler::canRead() const
818{
819 if(d->state == QJpegHandlerPrivate::Ready && !canRead(device()))
820 return false;
821
822 if (d->state != QJpegHandlerPrivate::Error) {
823 setFormat("jpeg");
824 return true;
825 }
826
827 return false;
828}
829
830bool QJpegHandler::canRead(QIODevice *device)
831{
832 if (!device) {
833 qWarning("QJpegHandler::canRead() called with no device");
834 return false;
835 }
836
837 char buffer[2];
838 if (device->peek(buffer, 2) != 2)
839 return false;
840 return uchar(buffer[0]) == 0xff && uchar(buffer[1]) == 0xd8;
841}
842
843bool QJpegHandler::read(QImage *image)
844{
845 if (!canRead())
846 return false;
847 return d->read(image);
848}
849
850bool QJpegHandler::write(const QImage &image)
851{
852 return write_jpeg_image(image, device(), d->quality);
853}
854
855bool QJpegHandler::supportsOption(ImageOption option) const
856{
857 return option == Quality
858 || option == ScaledSize
859 || option == ScaledClipRect
860 || option == ClipRect
861 || option == Size
862 || option == ImageFormat;
863}
864
865QVariant QJpegHandler::option(ImageOption option) const
866{
867 switch(option) {
868 case Quality:
869 return d->quality;
870 case ScaledSize:
871 return d->scaledSize;
872 case ScaledClipRect:
873 return d->scaledClipRect;
874 case ClipRect:
875 return d->clipRect;
876 case Size:
877 d->readJpegHeader(device());
878 return d->size;
879 case ImageFormat:
880 d->readJpegHeader(device());
881 return d->format;
882 default:
883 return QVariant();
884 }
885}
886
887void QJpegHandler::setOption(ImageOption option, const QVariant &value)
888{
889 switch(option) {
890 case Quality:
891 d->quality = value.toInt();
892 break;
893 case ScaledSize:
894 d->scaledSize = value.toSize();
895 break;
896 case ScaledClipRect:
897 d->scaledClipRect = value.toRect();
898 break;
899 case ClipRect:
900 d->clipRect = value.toRect();
901 break;
902 default:
903 break;
904 }
905}
906
907QByteArray QJpegHandler::name() const
908{
909 return "jpeg";
910}
911
912
913
914
915QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.