1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qjpeghandler_p.h"
41
42#include <qimage.h>
43#include <qcolorspace.h>
44#include <qcolortransform.h>
45#include <qdebug.h>
46#include <qvariant.h>
47#include <qvector.h>
48#include <qbuffer.h>
49#include <qmath.h>
50#include <private/qicc_p.h>
51#include <private/qsimd_p.h>
52#include <private/qimage_p.h> // for qt_getImageText
53
54#include <stdio.h> // jpeglib needs this to be pre-included
55#include <setjmp.h>
56
57#ifdef FAR
58#undef FAR
59#endif
60
61// including jpeglib.h seems to be a little messy
62extern "C" {
63// jpeglib.h->jmorecfg.h tries to typedef int boolean; but this conflicts with
64// some Windows headers that may or may not have been included
65#ifdef HAVE_BOOLEAN
66# undef HAVE_BOOLEAN
67#endif
68#define boolean jboolean
69
70#define XMD_H // shut JPEGlib up
71#include <jpeglib.h>
72#ifdef const
73# undef const // remove crazy C hackery in jconfig.h
74#endif
75}
76
77QT_BEGIN_NAMESPACE
78QT_WARNING_DISABLE_GCC("-Wclobbered")
79
80Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32(quint32 *dst, const uchar *src, int len);
81typedef void (QT_FASTCALL *Rgb888ToRgb32Converter)(quint32 *dst, const uchar *src, int len);
82
83struct my_error_mgr : public jpeg_error_mgr {
84 jmp_buf setjmp_buffer;
85};
86
87extern "C" {
88
89static void my_error_exit (j_common_ptr cinfo)
90{
91 my_error_mgr* myerr = (my_error_mgr*) cinfo->err;
92 char buffer[JMSG_LENGTH_MAX];
93 (*cinfo->err->format_message)(cinfo, buffer);
94 qWarning("%s", buffer);
95 longjmp(myerr->setjmp_buffer, 1);
96}
97
98static void my_output_message(j_common_ptr cinfo)
99{
100 char buffer[JMSG_LENGTH_MAX];
101 (*cinfo->err->format_message)(cinfo, buffer);
102 qWarning("%s", buffer);
103}
104
105}
106
107
108static const int max_buf = 4096;
109
110struct my_jpeg_source_mgr : public jpeg_source_mgr {
111 // Nothing dynamic - cannot rely on destruction over longjump
112 QIODevice *device;
113 JOCTET buffer[max_buf];
114 const QBuffer *memDevice;
115
116public:
117 my_jpeg_source_mgr(QIODevice *device);
118};
119
120extern "C" {
121
122static void qt_init_source(j_decompress_ptr)
123{
124}
125
126static boolean qt_fill_input_buffer(j_decompress_ptr cinfo)
127{
128 my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src;
129 qint64 num_read = 0;
130 if (src->memDevice) {
131 src->next_input_byte = (const JOCTET *)(src->memDevice->data().constData() + src->memDevice->pos());
132 num_read = src->memDevice->data().size() - src->memDevice->pos();
133 src->device->seek(src->memDevice->data().size());
134 } else {
135 src->next_input_byte = src->buffer;
136 num_read = src->device->read((char*)src->buffer, max_buf);
137 }
138 if (num_read <= 0) {
139 // Insert a fake EOI marker - as per jpeglib recommendation
140 src->next_input_byte = src->buffer;
141 src->buffer[0] = (JOCTET) 0xFF;
142 src->buffer[1] = (JOCTET) JPEG_EOI;
143 src->bytes_in_buffer = 2;
144 } else {
145 src->bytes_in_buffer = num_read;
146 }
147 return TRUE;
148}
149
150static void qt_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
151{
152 my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src;
153
154 // `dumb' implementation from jpeglib
155
156 /* Just a dumb implementation for now. Could use fseek() except
157 * it doesn't work on pipes. Not clear that being smart is worth
158 * any trouble anyway --- large skips are infrequent.
159 */
160 if (num_bytes > 0) {
161 while (num_bytes > (long) src->bytes_in_buffer) { // Should not happen in case of memDevice
162 num_bytes -= (long) src->bytes_in_buffer;
163 (void) qt_fill_input_buffer(cinfo);
164 /* note we assume that qt_fill_input_buffer will never return false,
165 * so suspension need not be handled.
166 */
167 }
168 src->next_input_byte += (size_t) num_bytes;
169 src->bytes_in_buffer -= (size_t) num_bytes;
170 }
171}
172
173static void qt_term_source(j_decompress_ptr cinfo)
174{
175 my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src;
176 if (!src->device->isSequential())
177 src->device->seek(src->device->pos() - src->bytes_in_buffer);
178}
179
180}
181
182inline my_jpeg_source_mgr::my_jpeg_source_mgr(QIODevice *device)
183{
184 jpeg_source_mgr::init_source = qt_init_source;
185 jpeg_source_mgr::fill_input_buffer = qt_fill_input_buffer;
186 jpeg_source_mgr::skip_input_data = qt_skip_input_data;
187 jpeg_source_mgr::resync_to_restart = jpeg_resync_to_restart;
188 jpeg_source_mgr::term_source = qt_term_source;
189 this->device = device;
190 memDevice = qobject_cast<QBuffer *>(device);
191 bytes_in_buffer = 0;
192 next_input_byte = buffer;
193}
194
195
196inline static bool read_jpeg_size(int &w, int &h, j_decompress_ptr cinfo)
197{
198 (void) jpeg_calc_output_dimensions(cinfo);
199
200 w = cinfo->output_width;
201 h = cinfo->output_height;
202 return true;
203}
204
205#define HIGH_QUALITY_THRESHOLD 50
206
207inline static bool read_jpeg_format(QImage::Format &format, j_decompress_ptr cinfo)
208{
209
210 bool result = true;
211 switch (cinfo->output_components) {
212 case 1:
213 format = QImage::Format_Grayscale8;
214 break;
215 case 3:
216 case 4:
217 format = QImage::Format_RGB32;
218 break;
219 default:
220 result = false;
221 break;
222 }
223 cinfo->output_scanline = cinfo->output_height;
224 return result;
225}
226
227static bool ensureValidImage(QImage *dest, struct jpeg_decompress_struct *info,
228 const QSize& size)
229{
230 QImage::Format format;
231 switch (info->output_components) {
232 case 1:
233 format = QImage::Format_Grayscale8;
234 break;
235 case 3:
236 case 4:
237 format = QImage::Format_RGB32;
238 break;
239 default:
240 return false; // unsupported format
241 }
242
243 if (dest->size() != size || dest->format() != format)
244 *dest = QImage(size, format);
245
246 return !dest->isNull();
247}
248
249static bool read_jpeg_image(QImage *outImage,
250 QSize scaledSize, QRect scaledClipRect,
251 QRect clipRect, volatile int inQuality,
252 Rgb888ToRgb32Converter converter,
253 j_decompress_ptr info, struct my_error_mgr* err )
254{
255 if (!setjmp(err->setjmp_buffer)) {
256 // -1 means default quality.
257 int quality = inQuality;
258 if (quality < 0)
259 quality = 75;
260
261 // If possible, merge the scaledClipRect into either scaledSize
262 // or clipRect to avoid doing a separate scaled clipping pass.
263 // Best results are achieved by clipping before scaling, not after.
264 if (!scaledClipRect.isEmpty()) {
265 if (scaledSize.isEmpty() && clipRect.isEmpty()) {
266 // No clipping or scaling before final clip.
267 clipRect = scaledClipRect;
268 scaledClipRect = QRect();
269 } else if (scaledSize.isEmpty()) {
270 // Clipping, but no scaling: combine the clip regions.
271 scaledClipRect.translate(clipRect.topLeft());
272 clipRect = scaledClipRect.intersected(clipRect);
273 scaledClipRect = QRect();
274 } else if (clipRect.isEmpty()) {
275 // No clipping, but scaling: if we can map back to an
276 // integer pixel boundary, then clip before scaling.
277 if ((info->image_width % scaledSize.width()) == 0 &&
278 (info->image_height % scaledSize.height()) == 0) {
279 int x = scaledClipRect.x() * info->image_width /
280 scaledSize.width();
281 int y = scaledClipRect.y() * info->image_height /
282 scaledSize.height();
283 int width = (scaledClipRect.right() + 1) *
284 info->image_width / scaledSize.width() - x;
285 int height = (scaledClipRect.bottom() + 1) *
286 info->image_height / scaledSize.height() - y;
287 clipRect = QRect(x, y, width, height);
288 scaledSize = scaledClipRect.size();
289 scaledClipRect = QRect();
290 }
291 } else {
292 // Clipping and scaling: too difficult to figure out,
293 // and not a likely use case, so do it the long way.
294 }
295 }
296
297 // Determine the scale factor to pass to libjpeg for quick downscaling.
298 if (!scaledSize.isEmpty() && info->image_width && info->image_height) {
299 if (clipRect.isEmpty()) {
300 double f = qMin(double(info->image_width) / scaledSize.width(),
301 double(info->image_height) / scaledSize.height());
302
303 // libjpeg supports M/8 scaling with M=[1,16]. All downscaling factors
304 // are a speed improvement, but upscaling during decode is slower.
305 info->scale_num = qBound(1, qCeil(8/f), 8);
306 info->scale_denom = 8;
307 } else {
308 info->scale_denom = qMin(clipRect.width() / scaledSize.width(),
309 clipRect.height() / scaledSize.height());
310
311 // Only scale by powers of two when clipping so we can
312 // keep the exact pixel boundaries
313 if (info->scale_denom < 2)
314 info->scale_denom = 1;
315 else if (info->scale_denom < 4)
316 info->scale_denom = 2;
317 else if (info->scale_denom < 8)
318 info->scale_denom = 4;
319 else
320 info->scale_denom = 8;
321 info->scale_num = 1;
322
323 // Correct the scale factor so that we clip accurately.
324 // It is recommended that the clip rectangle be aligned
325 // on an 8-pixel boundary for best performance.
326 while (info->scale_denom > 1 &&
327 ((clipRect.x() % info->scale_denom) != 0 ||
328 (clipRect.y() % info->scale_denom) != 0 ||
329 (clipRect.width() % info->scale_denom) != 0 ||
330 (clipRect.height() % info->scale_denom) != 0)) {
331 info->scale_denom /= 2;
332 }
333 }
334 }
335
336 // If high quality not required, use fast decompression
337 if( quality < HIGH_QUALITY_THRESHOLD ) {
338 info->dct_method = JDCT_IFAST;
339 info->do_fancy_upsampling = FALSE;
340 }
341
342 (void) jpeg_calc_output_dimensions(info);
343
344 // Determine the clip region to extract.
345 QRect imageRect(0, 0, info->output_width, info->output_height);
346 QRect clip;
347 if (clipRect.isEmpty()) {
348 clip = imageRect;
349 } else if (info->scale_denom == info->scale_num) {
350 clip = clipRect.intersected(imageRect);
351 } else {
352 // The scale factor was corrected above to ensure that
353 // we don't miss pixels when we scale the clip rectangle.
354 clip = QRect(clipRect.x() / int(info->scale_denom),
355 clipRect.y() / int(info->scale_denom),
356 clipRect.width() / int(info->scale_denom),
357 clipRect.height() / int(info->scale_denom));
358 clip = clip.intersected(imageRect);
359 }
360
361 // Allocate memory for the clipped QImage.
362 if (!ensureValidImage(outImage, info, clip.size()))
363 longjmp(err->setjmp_buffer, 1);
364
365 // Avoid memcpy() overhead if grayscale with no clipping.
366 bool quickGray = (info->output_components == 1 &&
367 clip == imageRect);
368 if (!quickGray) {
369 // Ask the jpeg library to allocate a temporary row.
370 // The library will automatically delete it for us later.
371 // The libjpeg docs say we should do this before calling
372 // jpeg_start_decompress(). We can't use "new" here
373 // because we are inside the setjmp() block and an error
374 // in the jpeg input stream would cause a memory leak.
375 JSAMPARRAY rows = (info->mem->alloc_sarray)
376 ((j_common_ptr)info, JPOOL_IMAGE,
377 info->output_width * info->output_components, 1);
378
379 (void) jpeg_start_decompress(info);
380
381 while (info->output_scanline < info->output_height) {
382 int y = int(info->output_scanline) - clip.y();
383 if (y >= clip.height())
384 break; // We've read the entire clip region, so abort.
385
386 (void) jpeg_read_scanlines(info, rows, 1);
387
388 if (y < 0)
389 continue; // Haven't reached the starting line yet.
390
391 if (info->output_components == 3) {
392 uchar *in = rows[0] + clip.x() * 3;
393 QRgb *out = (QRgb*)outImage->scanLine(y);
394 converter(out, in, clip.width());
395 } else if (info->out_color_space == JCS_CMYK) {
396 // Convert CMYK->RGB.
397 uchar *in = rows[0] + clip.x() * 4;
398 QRgb *out = (QRgb*)outImage->scanLine(y);
399 for (int i = 0; i < clip.width(); ++i) {
400 int k = in[3];
401 *out++ = qRgb(k * in[0] / 255, k * in[1] / 255,
402 k * in[2] / 255);
403 in += 4;
404 }
405 } else if (info->output_components == 1) {
406 // Grayscale.
407 memcpy(outImage->scanLine(y),
408 rows[0] + clip.x(), clip.width());
409 }
410 }
411 } else {
412 // Load unclipped grayscale data directly into the QImage.
413 (void) jpeg_start_decompress(info);
414 while (info->output_scanline < info->output_height) {
415 uchar *row = outImage->scanLine(info->output_scanline);
416 (void) jpeg_read_scanlines(info, &row, 1);
417 }
418 }
419
420 if (info->output_scanline == info->output_height)
421 (void) jpeg_finish_decompress(info);
422
423 if (info->density_unit == 1) {
424 outImage->setDotsPerMeterX(int(100. * info->X_density / 2.54));
425 outImage->setDotsPerMeterY(int(100. * info->Y_density / 2.54));
426 } else if (info->density_unit == 2) {
427 outImage->setDotsPerMeterX(int(100. * info->X_density));
428 outImage->setDotsPerMeterY(int(100. * info->Y_density));
429 }
430
431 if (scaledSize.isValid() && scaledSize != clip.size()) {
432 *outImage = outImage->scaled(scaledSize, Qt::IgnoreAspectRatio, quality >= HIGH_QUALITY_THRESHOLD ? Qt::SmoothTransformation : Qt::FastTransformation);
433 }
434
435 if (!scaledClipRect.isEmpty())
436 *outImage = outImage->copy(scaledClipRect);
437 return !outImage->isNull();
438 }
439 else
440 return false;
441}
442
443struct my_jpeg_destination_mgr : public jpeg_destination_mgr {
444 // Nothing dynamic - cannot rely on destruction over longjump
445 QIODevice *device;
446 JOCTET buffer[max_buf];
447
448public:
449 my_jpeg_destination_mgr(QIODevice *);
450};
451
452
453extern "C" {
454
455static void qt_init_destination(j_compress_ptr)
456{
457}
458
459static boolean qt_empty_output_buffer(j_compress_ptr cinfo)
460{
461 my_jpeg_destination_mgr* dest = (my_jpeg_destination_mgr*)cinfo->dest;
462
463 int written = dest->device->write((char*)dest->buffer, max_buf);
464 if (written == -1)
465 (*cinfo->err->error_exit)((j_common_ptr)cinfo);
466
467 dest->next_output_byte = dest->buffer;
468 dest->free_in_buffer = max_buf;
469
470 return TRUE;
471}
472
473static void qt_term_destination(j_compress_ptr cinfo)
474{
475 my_jpeg_destination_mgr* dest = (my_jpeg_destination_mgr*)cinfo->dest;
476 qint64 n = max_buf - dest->free_in_buffer;
477
478 qint64 written = dest->device->write((char*)dest->buffer, n);
479 if (written == -1)
480 (*cinfo->err->error_exit)((j_common_ptr)cinfo);
481}
482
483}
484
485inline my_jpeg_destination_mgr::my_jpeg_destination_mgr(QIODevice *device)
486{
487 jpeg_destination_mgr::init_destination = qt_init_destination;
488 jpeg_destination_mgr::empty_output_buffer = qt_empty_output_buffer;
489 jpeg_destination_mgr::term_destination = qt_term_destination;
490 this->device = device;
491 next_output_byte = buffer;
492 free_in_buffer = max_buf;
493}
494
495static inline void set_text(const QImage &image, j_compress_ptr cinfo, const QString &description)
496{
497 const QMap<QString, QString> text = qt_getImageText(image, description);
498 for (auto it = text.begin(), end = text.end(); it != end; ++it) {
499 QByteArray comment = it.key().toUtf8();
500 if (!comment.isEmpty())
501 comment += ": ";
502 comment += it.value().toUtf8();
503 if (comment.length() > 65530)
504 comment.truncate(65530);
505 jpeg_write_marker(cinfo, JPEG_COM, (const JOCTET *)comment.constData(), comment.size());
506 }
507}
508
509static bool write_jpeg_image(const QImage &image, QIODevice *device, volatile int sourceQuality, const QString &description, bool optimize, bool progressive)
510{
511 bool success = false;
512 const QVector<QRgb> cmap = image.colorTable();
513
514 if (image.format() == QImage::Format_Invalid || image.format() == QImage::Format_Alpha8)
515 return false;
516
517 struct jpeg_compress_struct cinfo;
518 JSAMPROW row_pointer[1];
519 row_pointer[0] = 0;
520
521 struct my_jpeg_destination_mgr *iod_dest = new my_jpeg_destination_mgr(device);
522 struct my_error_mgr jerr;
523
524 cinfo.err = jpeg_std_error(&jerr);
525 jerr.error_exit = my_error_exit;
526 jerr.output_message = my_output_message;
527
528 if (!setjmp(jerr.setjmp_buffer)) {
529 // WARNING:
530 // this if loop is inside a setjmp/longjmp branch
531 // do not create C++ temporaries here because the destructor may never be called
532 // if you allocate memory, make sure that you can free it (row_pointer[0])
533 jpeg_create_compress(&cinfo);
534
535 cinfo.dest = iod_dest;
536
537 cinfo.image_width = image.width();
538 cinfo.image_height = image.height();
539
540 bool gray = false;
541 switch (image.format()) {
542 case QImage::Format_Mono:
543 case QImage::Format_MonoLSB:
544 case QImage::Format_Indexed8:
545 gray = true;
546 for (int i = image.colorCount(); gray && i; i--) {
547 gray = gray & qIsGray(cmap[i-1]);
548 }
549 cinfo.input_components = gray ? 1 : 3;
550 cinfo.in_color_space = gray ? JCS_GRAYSCALE : JCS_RGB;
551 break;
552 case QImage::Format_Grayscale8:
553 gray = true;
554 cinfo.input_components = 1;
555 cinfo.in_color_space = JCS_GRAYSCALE;
556 break;
557 default:
558 cinfo.input_components = 3;
559 cinfo.in_color_space = JCS_RGB;
560 }
561
562 jpeg_set_defaults(&cinfo);
563
564 qreal diffInch = qAbs(image.dotsPerMeterX()*2.54/100. - qRound(image.dotsPerMeterX()*2.54/100.))
565 + qAbs(image.dotsPerMeterY()*2.54/100. - qRound(image.dotsPerMeterY()*2.54/100.));
566 qreal diffCm = (qAbs(image.dotsPerMeterX()/100. - qRound(image.dotsPerMeterX()/100.))
567 + qAbs(image.dotsPerMeterY()/100. - qRound(image.dotsPerMeterY()/100.)))*2.54;
568 if (diffInch < diffCm) {
569 cinfo.density_unit = 1; // dots/inch
570 cinfo.X_density = qRound(image.dotsPerMeterX()*2.54/100.);
571 cinfo.Y_density = qRound(image.dotsPerMeterY()*2.54/100.);
572 } else {
573 cinfo.density_unit = 2; // dots/cm
574 cinfo.X_density = (image.dotsPerMeterX()+50) / 100;
575 cinfo.Y_density = (image.dotsPerMeterY()+50) / 100;
576 }
577
578 if (optimize)
579 cinfo.optimize_coding = true;
580
581 if (progressive)
582 jpeg_simple_progression(&cinfo);
583
584 int quality = sourceQuality >= 0 ? qMin(int(sourceQuality),100) : 75;
585 jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
586 jpeg_start_compress(&cinfo, TRUE);
587
588 set_text(image, &cinfo, description);
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_Grayscale8:
647 memcpy(row, image.constScanLine(cinfo.next_scanline), w);
648 break;
649 case QImage::Format_RGB888:
650 memcpy(row, image.constScanLine(cinfo.next_scanline), w * 3);
651 break;
652 case QImage::Format_RGB32:
653 case QImage::Format_ARGB32:
654 case QImage::Format_ARGB32_Premultiplied:
655 {
656 const QRgb* rgb = (const QRgb*)image.constScanLine(cinfo.next_scanline);
657 for (int i=0; i<w; i++) {
658 *row++ = qRed(*rgb);
659 *row++ = qGreen(*rgb);
660 *row++ = qBlue(*rgb);
661 ++rgb;
662 }
663 }
664 break;
665 default:
666 {
667 // (Testing shows that this way is actually faster than converting to RGB888 + memcpy)
668 QImage rowImg = image.copy(0, cinfo.next_scanline, w, 1).convertToFormat(QImage::Format_RGB32);
669 const QRgb* rgb = (const QRgb*)rowImg.constScanLine(0);
670 for (int i=0; i<w; i++) {
671 *row++ = qRed(*rgb);
672 *row++ = qGreen(*rgb);
673 *row++ = qBlue(*rgb);
674 ++rgb;
675 }
676 }
677 break;
678 }
679 jpeg_write_scanlines(&cinfo, row_pointer, 1);
680 }
681
682 jpeg_finish_compress(&cinfo);
683 jpeg_destroy_compress(&cinfo);
684 success = true;
685 } else {
686 jpeg_destroy_compress(&cinfo);
687 success = false;
688 }
689
690 delete iod_dest;
691 delete [] row_pointer[0];
692 return success;
693}
694
695class QJpegHandlerPrivate
696{
697public:
698 enum State {
699 Ready,
700 ReadHeader,
701 ReadingEnd,
702 Error
703 };
704
705 QJpegHandlerPrivate(QJpegHandler *qq)
706 : quality(75), transformation(QImageIOHandler::TransformationNone), iod_src(0),
707 rgb888ToRgb32ConverterPtr(qt_convert_rgb888_to_rgb32), state(Ready), optimize(false), progressive(false), q(qq)
708 {}
709
710 ~QJpegHandlerPrivate()
711 {
712 if(iod_src)
713 {
714 jpeg_destroy_decompress(&info);
715 delete iod_src;
716 iod_src = 0;
717 }
718 }
719
720 bool readJpegHeader(QIODevice*);
721 bool read(QImage *image);
722
723 int quality;
724 QImageIOHandler::Transformations transformation;
725 QVariant size;
726 QImage::Format format;
727 QSize scaledSize;
728 QRect scaledClipRect;
729 QRect clipRect;
730 QString description;
731 QStringList readTexts;
732 QByteArray iccProfile;
733
734 struct jpeg_decompress_struct info;
735 struct my_jpeg_source_mgr * iod_src;
736 struct my_error_mgr err;
737
738 Rgb888ToRgb32Converter rgb888ToRgb32ConverterPtr;
739
740 State state;
741
742 bool optimize;
743 bool progressive;
744
745 QJpegHandler *q;
746};
747
748static bool readExifHeader(QDataStream &stream)
749{
750 char prefix[6];
751 if (stream.readRawData(prefix, sizeof(prefix)) != sizeof(prefix))
752 return false;
753 static const char exifMagic[6] = {'E', 'x', 'i', 'f', 0, 0};
754 return memcmp(prefix, exifMagic, 6) == 0;
755}
756
757/*
758 * Returns -1 on error
759 * Returns 0 if no Exif orientation was found
760 * Returns 1 orientation is horizontal (normal)
761 * Returns 2 mirror horizontal
762 * Returns 3 rotate 180
763 * Returns 4 mirror vertical
764 * Returns 5 mirror horizontal and rotate 270 CCW
765 * Returns 6 rotate 90 CW
766 * Returns 7 mirror horizontal and rotate 90 CW
767 * Returns 8 rotate 270 CW
768 */
769static int getExifOrientation(QByteArray &exifData)
770{
771 // Current EXIF version (2.3) says there can be at most 5 IFDs,
772 // byte we allow for 10 so we're able to deal with future extensions.
773 const int maxIfdCount = 10;
774
775 QDataStream stream(&exifData, QIODevice::ReadOnly);
776
777 if (!readExifHeader(stream))
778 return -1;
779
780 quint16 val;
781 quint32 offset;
782 const qint64 headerStart = 6; // the EXIF header has a constant size
783 Q_ASSERT(headerStart == stream.device()->pos());
784
785 // read byte order marker
786 stream >> val;
787 if (val == 0x4949) // 'II' == Intel
788 stream.setByteOrder(QDataStream::LittleEndian);
789 else if (val == 0x4d4d) // 'MM' == Motorola
790 stream.setByteOrder(QDataStream::BigEndian);
791 else
792 return -1; // unknown byte order
793
794 // confirm byte order
795 stream >> val;
796 if (val != 0x2a)
797 return -1;
798
799 stream >> offset;
800
801 // read IFD
802 for (int n = 0; n < maxIfdCount; ++n) {
803 quint16 numEntries;
804
805 const qint64 bytesToSkip = offset - (stream.device()->pos() - headerStart);
806 if (bytesToSkip < 0 || (offset + headerStart >= exifData.size())) {
807 // disallow going backwards, though it's permitted in the spec
808 return -1;
809 } else if (bytesToSkip != 0) {
810 // seek to the IFD
811 if (!stream.device()->seek(offset + headerStart))
812 return -1;
813 }
814
815 stream >> numEntries;
816
817 for (; numEntries > 0 && stream.status() == QDataStream::Ok; --numEntries) {
818 quint16 tag;
819 quint16 type;
820 quint32 components;
821 quint16 value;
822 quint16 dummy;
823
824 stream >> tag >> type >> components >> value >> dummy;
825 if (tag == 0x0112) { // Tag Exif.Image.Orientation
826 if (components != 1)
827 return -1;
828 if (type != 3) // we are expecting it to be an unsigned short
829 return -1;
830 if (value < 1 || value > 8) // check for valid range
831 return -1;
832
833 // It is possible to include the orientation multiple times.
834 // Right now the first value is returned.
835 return value;
836 }
837 }
838
839 // read offset to next IFD
840 stream >> offset;
841 if (stream.status() != QDataStream::Ok)
842 return -1;
843 if (offset == 0) // this is the last IFD
844 return 0; // No Exif orientation was found
845 }
846
847 // too many IFDs
848 return -1;
849}
850
851static QImageIOHandler::Transformations exif2Qt(int exifOrientation)
852{
853 switch (exifOrientation) {
854 case 1: // normal
855 return QImageIOHandler::TransformationNone;
856 case 2: // mirror horizontal
857 return QImageIOHandler::TransformationMirror;
858 case 3: // rotate 180
859 return QImageIOHandler::TransformationRotate180;
860 case 4: // mirror vertical
861 return QImageIOHandler::TransformationFlip;
862 case 5: // mirror horizontal and rotate 270 CW
863 return QImageIOHandler::TransformationFlipAndRotate90;
864 case 6: // rotate 90 CW
865 return QImageIOHandler::TransformationRotate90;
866 case 7: // mirror horizontal and rotate 90 CW
867 return QImageIOHandler::TransformationMirrorAndRotate90;
868 case 8: // rotate 270 CW
869 return QImageIOHandler::TransformationRotate270;
870 }
871 qWarning("Invalid EXIF orientation");
872 return QImageIOHandler::TransformationNone;
873}
874
875/*!
876 \internal
877*/
878bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device)
879{
880 if(state == Ready)
881 {
882 state = Error;
883 iod_src = new my_jpeg_source_mgr(device);
884
885 info.err = jpeg_std_error(&err);
886 err.error_exit = my_error_exit;
887 err.output_message = my_output_message;
888
889 jpeg_create_decompress(&info);
890 info.src = iod_src;
891
892 if (!setjmp(err.setjmp_buffer)) {
893 jpeg_save_markers(&info, JPEG_COM, 0xFFFF);
894 jpeg_save_markers(&info, JPEG_APP0 + 1, 0xFFFF); // Exif uses APP1 marker
895 jpeg_save_markers(&info, JPEG_APP0 + 2, 0xFFFF); // ICC uses APP2 marker
896
897 (void) jpeg_read_header(&info, TRUE);
898
899 int width = 0;
900 int height = 0;
901 read_jpeg_size(width, height, &info);
902 size = QSize(width, height);
903
904 format = QImage::Format_Invalid;
905 read_jpeg_format(format, &info);
906
907 QByteArray exifData;
908
909 for (jpeg_saved_marker_ptr marker = info.marker_list; marker != NULL; marker = marker->next) {
910 if (marker->marker == JPEG_COM) {
911 QString key, value;
912 QString s = QString::fromUtf8((const char *)marker->data, marker->data_length);
913 int index = s.indexOf(QLatin1String(": "));
914 if (index == -1 || s.indexOf(QLatin1Char(' ')) < index) {
915 key = QLatin1String("Description");
916 value = s;
917 } else {
918 key = s.left(index);
919 value = s.mid(index + 2);
920 }
921 if (!description.isEmpty())
922 description += QLatin1String("\n\n");
923 description += key + QLatin1String(": ") + value.simplified();
924 readTexts.append(key);
925 readTexts.append(value);
926 } else if (marker->marker == JPEG_APP0 + 1) {
927 exifData.append((const char*)marker->data, marker->data_length);
928 } else if (marker->marker == JPEG_APP0 + 2) {
929 if (marker->data_length > 128 + 4 + 14 && strcmp((const char *)marker->data, "ICC_PROFILE") == 0) {
930 iccProfile.append((const char*)marker->data + 14, marker->data_length - 14);
931 }
932 }
933 }
934
935 if (!exifData.isEmpty()) {
936 // Exif data present
937 int exifOrientation = getExifOrientation(exifData);
938 if (exifOrientation > 0)
939 transformation = exif2Qt(exifOrientation);
940 }
941
942 state = ReadHeader;
943 return true;
944 }
945 else
946 {
947 return false;
948 }
949 }
950 else if(state == Error)
951 return false;
952 return true;
953}
954
955bool QJpegHandlerPrivate::read(QImage *image)
956{
957 if(state == Ready)
958 readJpegHeader(q->device());
959
960 if(state == ReadHeader)
961 {
962 bool success = read_jpeg_image(image, scaledSize, scaledClipRect, clipRect, quality, rgb888ToRgb32ConverterPtr, &info, &err);
963 if (success) {
964 for (int i = 0; i < readTexts.size()-1; i+=2)
965 image->setText(readTexts.at(i), readTexts.at(i+1));
966
967 if (!iccProfile.isEmpty())
968 image->setColorSpace(QColorSpace::fromIccProfile(iccProfile));
969
970 state = ReadingEnd;
971 return true;
972 }
973
974 state = Error;
975 }
976
977 return false;
978}
979
980Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_neon(quint32 *dst, const uchar *src, int len);
981Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_ssse3(quint32 *dst, const uchar *src, int len);
982extern "C" void qt_convert_rgb888_to_rgb32_mips_dspr2_asm(quint32 *dst, const uchar *src, int len);
983
984QJpegHandler::QJpegHandler()
985 : d(new QJpegHandlerPrivate(this))
986{
987#if defined(__ARM_NEON__)
988 // from qimage_neon.cpp
989 if (qCpuHasFeature(NEON))
990 d->rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_neon;
991#endif
992
993#if defined(QT_COMPILER_SUPPORTS_SSSE3)
994 // from qimage_ssse3.cpps
995 if (qCpuHasFeature(SSSE3)) {
996 d->rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_ssse3;
997 }
998#endif // QT_COMPILER_SUPPORTS_SSSE3
999#if defined(QT_COMPILER_SUPPORTS_MIPS_DSPR2)
1000 if (qCpuHasFeature(DSPR2)) {
1001 d->rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_mips_dspr2_asm;
1002 }
1003#endif // QT_COMPILER_SUPPORTS_DSPR2
1004}
1005
1006QJpegHandler::~QJpegHandler()
1007{
1008 delete d;
1009}
1010
1011bool QJpegHandler::canRead() const
1012{
1013 if(d->state == QJpegHandlerPrivate::Ready && !canRead(device()))
1014 return false;
1015
1016 if (d->state != QJpegHandlerPrivate::Error && d->state != QJpegHandlerPrivate::ReadingEnd) {
1017 setFormat("jpeg");
1018 return true;
1019 }
1020
1021 return false;
1022}
1023
1024bool QJpegHandler::canRead(QIODevice *device)
1025{
1026 if (!device) {
1027 qWarning("QJpegHandler::canRead() called with no device");
1028 return false;
1029 }
1030
1031 char buffer[2];
1032 if (device->peek(buffer, 2) != 2)
1033 return false;
1034 return uchar(buffer[0]) == 0xff && uchar(buffer[1]) == 0xd8;
1035}
1036
1037bool QJpegHandler::read(QImage *image)
1038{
1039 if (!canRead())
1040 return false;
1041 return d->read(image);
1042}
1043
1044extern void qt_imageTransform(QImage &src, QImageIOHandler::Transformations orient);
1045
1046bool QJpegHandler::write(const QImage &image)
1047{
1048 if (d->transformation != QImageIOHandler::TransformationNone) {
1049 // We don't support writing EXIF headers so apply the transform to the data.
1050 QImage img = image;
1051 qt_imageTransform(img, d->transformation);
1052 return write_jpeg_image(img, device(), d->quality, d->description, d->optimize, d->progressive);
1053 }
1054 return write_jpeg_image(image, device(), d->quality, d->description, d->optimize, d->progressive);
1055}
1056
1057bool QJpegHandler::supportsOption(ImageOption option) const
1058{
1059 return option == Quality
1060 || option == ScaledSize
1061 || option == ScaledClipRect
1062 || option == ClipRect
1063 || option == Description
1064 || option == Size
1065 || option == ImageFormat
1066 || option == OptimizedWrite
1067 || option == ProgressiveScanWrite
1068 || option == ImageTransformation;
1069}
1070
1071QVariant QJpegHandler::option(ImageOption option) const
1072{
1073 switch(option) {
1074 case Quality:
1075 return d->quality;
1076 case ScaledSize:
1077 return d->scaledSize;
1078 case ScaledClipRect:
1079 return d->scaledClipRect;
1080 case ClipRect:
1081 return d->clipRect;
1082 case Description:
1083 d->readJpegHeader(device());
1084 return d->description;
1085 case Size:
1086 d->readJpegHeader(device());
1087 return d->size;
1088 case ImageFormat:
1089 d->readJpegHeader(device());
1090 return d->format;
1091 case OptimizedWrite:
1092 return d->optimize;
1093 case ProgressiveScanWrite:
1094 return d->progressive;
1095 case ImageTransformation:
1096 d->readJpegHeader(device());
1097 return int(d->transformation);
1098 default:
1099 break;
1100 }
1101
1102 return QVariant();
1103}
1104
1105void QJpegHandler::setOption(ImageOption option, const QVariant &value)
1106{
1107 switch(option) {
1108 case Quality:
1109 d->quality = value.toInt();
1110 break;
1111 case ScaledSize:
1112 d->scaledSize = value.toSize();
1113 break;
1114 case ScaledClipRect:
1115 d->scaledClipRect = value.toRect();
1116 break;
1117 case ClipRect:
1118 d->clipRect = value.toRect();
1119 break;
1120 case Description:
1121 d->description = value.toString();
1122 break;
1123 case OptimizedWrite:
1124 d->optimize = value.toBool();
1125 break;
1126 case ProgressiveScanWrite:
1127 d->progressive = value.toBool();
1128 break;
1129 case ImageTransformation: {
1130 int transformation = value.toInt();
1131 if (transformation > 0 && transformation < 8)
1132 d->transformation = QImageIOHandler::Transformations(transformation);
1133 }
1134 default:
1135 break;
1136 }
1137}
1138
1139#if QT_DEPRECATED_SINCE(5, 13)
1140QByteArray QJpegHandler::name() const
1141{
1142 return "jpeg";
1143}
1144#endif
1145
1146QT_END_NAMESPACE
1147