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 <qbuffer.h>
43#include <qcolorspace.h>
44#include <qcolortransform.h>
45#include <qdebug.h>
46#include <qimage.h>
47#include <qlist.h>
48#include <qloggingcategory.h>
49#include <qmath.h>
50#include <qvariant.h>
51#include <private/qicc_p.h>
52#include <private/qsimd_p.h>
53#include <private/qimage_p.h> // for qt_getImageText
54
55#include <stdio.h> // jpeglib needs this to be pre-included
56#include <setjmp.h>
57
58#ifdef FAR
59#undef FAR
60#endif
61
62// including jpeglib.h seems to be a little messy
63extern "C" {
64// jpeglib.h->jmorecfg.h tries to typedef int boolean; but this conflicts with
65// some Windows headers that may or may not have been included
66#ifdef HAVE_BOOLEAN
67# undef HAVE_BOOLEAN
68#endif
69#define boolean jboolean
70
71#define XMD_H // shut JPEGlib up
72#include <jpeglib.h>
73#ifdef const
74# undef const // remove crazy C hackery in jconfig.h
75#endif
76}
77
78QT_BEGIN_NAMESPACE
79
80Q_LOGGING_CATEGORY(lcJpeg, "qt.gui.imageio.jpeg")
81
82QT_WARNING_DISABLE_GCC("-Wclobbered")
83
84Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32(quint32 *dst, const uchar *src, int len);
85typedef void (QT_FASTCALL *Rgb888ToRgb32Converter)(quint32 *dst, const uchar *src, int len);
86
87struct my_error_mgr : public jpeg_error_mgr {
88 jmp_buf setjmp_buffer;
89};
90
91extern "C" {
92
93static void my_error_exit (j_common_ptr cinfo)
94{
95 my_error_mgr* myerr = (my_error_mgr*) cinfo->err;
96 char buffer[JMSG_LENGTH_MAX];
97 (*cinfo->err->format_message)(cinfo, buffer);
98 qCWarning(lcJpeg, "%s", buffer);
99 longjmp(myerr->setjmp_buffer, 1);
100}
101
102static void my_output_message(j_common_ptr cinfo)
103{
104 char buffer[JMSG_LENGTH_MAX];
105 (*cinfo->err->format_message)(cinfo, buffer);
106 qCWarning(lcJpeg,"%s", buffer);
107}
108
109}
110
111
112static const int max_buf = 4096;
113
114struct my_jpeg_source_mgr : public jpeg_source_mgr {
115 // Nothing dynamic - cannot rely on destruction over longjump
116 QIODevice *device;
117 JOCTET buffer[max_buf];
118 const QBuffer *memDevice;
119
120public:
121 my_jpeg_source_mgr(QIODevice *device);
122};
123
124extern "C" {
125
126static void qt_init_source(j_decompress_ptr)
127{
128}
129
130static boolean qt_fill_input_buffer(j_decompress_ptr cinfo)
131{
132 my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src;
133 qint64 num_read = 0;
134 if (src->memDevice) {
135 src->next_input_byte = (const JOCTET *)(src->memDevice->data().constData() + src->memDevice->pos());
136 num_read = src->memDevice->data().size() - src->memDevice->pos();
137 src->device->seek(src->memDevice->data().size());
138 } else {
139 src->next_input_byte = src->buffer;
140 num_read = src->device->read((char*)src->buffer, max_buf);
141 }
142 if (num_read <= 0) {
143 // Insert a fake EOI marker - as per jpeglib recommendation
144 src->next_input_byte = src->buffer;
145 src->buffer[0] = (JOCTET) 0xFF;
146 src->buffer[1] = (JOCTET) JPEG_EOI;
147 src->bytes_in_buffer = 2;
148 } else {
149 src->bytes_in_buffer = num_read;
150 }
151 return TRUE;
152}
153
154static void qt_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
155{
156 my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src;
157
158 // `dumb' implementation from jpeglib
159
160 /* Just a dumb implementation for now. Could use fseek() except
161 * it doesn't work on pipes. Not clear that being smart is worth
162 * any trouble anyway --- large skips are infrequent.
163 */
164 if (num_bytes > 0) {
165 while (num_bytes > (long) src->bytes_in_buffer) { // Should not happen in case of memDevice
166 num_bytes -= (long) src->bytes_in_buffer;
167 (void) qt_fill_input_buffer(cinfo);
168 /* note we assume that qt_fill_input_buffer will never return false,
169 * so suspension need not be handled.
170 */
171 }
172 src->next_input_byte += (size_t) num_bytes;
173 src->bytes_in_buffer -= (size_t) num_bytes;
174 }
175}
176
177static void qt_term_source(j_decompress_ptr cinfo)
178{
179 my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src;
180 if (!src->device->isSequential())
181 src->device->seek(src->device->pos() - src->bytes_in_buffer);
182}
183
184}
185
186inline my_jpeg_source_mgr::my_jpeg_source_mgr(QIODevice *device)
187{
188 jpeg_source_mgr::init_source = qt_init_source;
189 jpeg_source_mgr::fill_input_buffer = qt_fill_input_buffer;
190 jpeg_source_mgr::skip_input_data = qt_skip_input_data;
191 jpeg_source_mgr::resync_to_restart = jpeg_resync_to_restart;
192 jpeg_source_mgr::term_source = qt_term_source;
193 this->device = device;
194 memDevice = qobject_cast<QBuffer *>(device);
195 bytes_in_buffer = 0;
196 next_input_byte = buffer;
197}
198
199
200inline static bool read_jpeg_size(int &w, int &h, j_decompress_ptr cinfo)
201{
202 (void) jpeg_calc_output_dimensions(cinfo);
203
204 w = cinfo->output_width;
205 h = cinfo->output_height;
206 return true;
207}
208
209#define HIGH_QUALITY_THRESHOLD 50
210
211inline static bool read_jpeg_format(QImage::Format &format, j_decompress_ptr cinfo)
212{
213
214 bool result = true;
215 switch (cinfo->output_components) {
216 case 1:
217 format = QImage::Format_Grayscale8;
218 break;
219 case 3:
220 case 4:
221 format = QImage::Format_RGB32;
222 break;
223 default:
224 result = false;
225 break;
226 }
227 cinfo->output_scanline = cinfo->output_height;
228 return result;
229}
230
231static bool ensureValidImage(QImage *dest, struct jpeg_decompress_struct *info,
232 const QSize& size)
233{
234 QImage::Format format;
235 switch (info->output_components) {
236 case 1:
237 format = QImage::Format_Grayscale8;
238 break;
239 case 3:
240 case 4:
241 format = QImage::Format_RGB32;
242 break;
243 default:
244 return false; // unsupported format
245 }
246
247 return QImageIOHandler::allocateImage(size, format, dest);
248}
249
250static bool read_jpeg_image(QImage *outImage,
251 QSize scaledSize, QRect scaledClipRect,
252 QRect clipRect, int quality,
253 Rgb888ToRgb32Converter converter,
254 j_decompress_ptr info, struct my_error_mgr* err )
255{
256 if (!setjmp(err->setjmp_buffer)) {
257 // -1 means default quality.
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 constexpr int maxMarkerSize = 65533;
496
497static inline void set_text(const QImage &image, j_compress_ptr cinfo, const QString &description)
498{
499 const QMap<QString, QString> text = qt_getImageText(image, description);
500 for (auto it = text.begin(), end = text.end(); it != end; ++it) {
501 QByteArray comment = it.key().toUtf8();
502 if (!comment.isEmpty())
503 comment += ": ";
504 comment += it.value().toUtf8();
505 if (comment.length() > maxMarkerSize)
506 comment.truncate(maxMarkerSize);
507 jpeg_write_marker(cinfo, JPEG_COM, (const JOCTET *)comment.constData(), comment.size());
508 }
509}
510
511static inline void write_icc_profile(const QImage &image, j_compress_ptr cinfo)
512{
513 const QByteArray iccProfile = image.colorSpace().iccProfile();
514 if (iccProfile.isEmpty())
515 return;
516
517 const QByteArray iccSignature("ICC_PROFILE", 12);
518 constexpr int maxIccMarkerSize = maxMarkerSize - (12 + 2);
519 int index = 0;
520 const int markers = (iccProfile.size() + (maxIccMarkerSize - 1)) / maxIccMarkerSize;
521 Q_ASSERT(markers < 256);
522 for (int marker = 1; marker <= markers; ++marker) {
523 const int len = qMin(iccProfile.size() - index, maxIccMarkerSize);
524 const QByteArray block = iccSignature
525 + QByteArray(1, char(marker)) + QByteArray(1, char(markers))
526 + iccProfile.mid(index, len);
527 jpeg_write_marker(cinfo, JPEG_APP0 + 2, reinterpret_cast<const JOCTET *>(block.constData()), block.size());
528 index += len;
529 }
530}
531
532static bool do_write_jpeg_image(struct jpeg_compress_struct &cinfo,
533 JSAMPROW *row_pointer,
534 const QImage &image,
535 QIODevice *device,
536 int sourceQuality,
537 const QString &description,
538 bool optimize,
539 bool progressive)
540{
541 bool success = false;
542 const QList<QRgb> cmap = image.colorTable();
543
544 if (image.format() == QImage::Format_Invalid || image.format() == QImage::Format_Alpha8)
545 return false;
546
547 struct my_jpeg_destination_mgr *iod_dest = new my_jpeg_destination_mgr(device);
548 struct my_error_mgr jerr;
549
550 cinfo.err = jpeg_std_error(&jerr);
551 jerr.error_exit = my_error_exit;
552 jerr.output_message = my_output_message;
553
554 if (!setjmp(jerr.setjmp_buffer)) {
555 // WARNING:
556 // this if loop is inside a setjmp/longjmp branch
557 // do not create C++ temporaries here because the destructor may never be called
558 // if you allocate memory, make sure that you can free it (row_pointer[0])
559 jpeg_create_compress(&cinfo);
560
561 cinfo.dest = iod_dest;
562
563 cinfo.image_width = image.width();
564 cinfo.image_height = image.height();
565
566 bool gray = false;
567 switch (image.format()) {
568 case QImage::Format_Mono:
569 case QImage::Format_MonoLSB:
570 case QImage::Format_Indexed8:
571 gray = true;
572 for (int i = image.colorCount(); gray && i; i--) {
573 gray = gray & qIsGray(cmap[i-1]);
574 }
575 cinfo.input_components = gray ? 1 : 3;
576 cinfo.in_color_space = gray ? JCS_GRAYSCALE : JCS_RGB;
577 break;
578 case QImage::Format_Grayscale8:
579 gray = true;
580 cinfo.input_components = 1;
581 cinfo.in_color_space = JCS_GRAYSCALE;
582 break;
583 default:
584 cinfo.input_components = 3;
585 cinfo.in_color_space = JCS_RGB;
586 }
587
588 jpeg_set_defaults(&cinfo);
589
590 qreal diffInch = qAbs(image.dotsPerMeterX()*2.54/100. - qRound(image.dotsPerMeterX()*2.54/100.))
591 + qAbs(image.dotsPerMeterY()*2.54/100. - qRound(image.dotsPerMeterY()*2.54/100.));
592 qreal diffCm = (qAbs(image.dotsPerMeterX()/100. - qRound(image.dotsPerMeterX()/100.))
593 + qAbs(image.dotsPerMeterY()/100. - qRound(image.dotsPerMeterY()/100.)))*2.54;
594 if (diffInch < diffCm) {
595 cinfo.density_unit = 1; // dots/inch
596 cinfo.X_density = qRound(image.dotsPerMeterX()*2.54/100.);
597 cinfo.Y_density = qRound(image.dotsPerMeterY()*2.54/100.);
598 } else {
599 cinfo.density_unit = 2; // dots/cm
600 cinfo.X_density = (image.dotsPerMeterX()+50) / 100;
601 cinfo.Y_density = (image.dotsPerMeterY()+50) / 100;
602 }
603
604 if (optimize)
605 cinfo.optimize_coding = true;
606
607 if (progressive)
608 jpeg_simple_progression(&cinfo);
609
610 int quality = sourceQuality >= 0 ? qMin(int(sourceQuality),100) : 75;
611 jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
612 jpeg_start_compress(&cinfo, TRUE);
613
614 set_text(image, &cinfo, description);
615 if (cinfo.in_color_space == JCS_RGB)
616 write_icc_profile(image, &cinfo);
617
618 row_pointer[0] = new uchar[cinfo.image_width*cinfo.input_components];
619 int w = cinfo.image_width;
620 while (cinfo.next_scanline < cinfo.image_height) {
621 uchar *row = row_pointer[0];
622 switch (image.format()) {
623 case QImage::Format_Mono:
624 case QImage::Format_MonoLSB:
625 if (gray) {
626 const uchar* data = image.constScanLine(cinfo.next_scanline);
627 if (image.format() == QImage::Format_MonoLSB) {
628 for (int i=0; i<w; i++) {
629 bool bit = !!(*(data + (i >> 3)) & (1 << (i & 7)));
630 row[i] = qRed(cmap[bit]);
631 }
632 } else {
633 for (int i=0; i<w; i++) {
634 bool bit = !!(*(data + (i >> 3)) & (1 << (7 -(i & 7))));
635 row[i] = qRed(cmap[bit]);
636 }
637 }
638 } else {
639 const uchar* data = image.constScanLine(cinfo.next_scanline);
640 if (image.format() == QImage::Format_MonoLSB) {
641 for (int i=0; i<w; i++) {
642 bool bit = !!(*(data + (i >> 3)) & (1 << (i & 7)));
643 *row++ = qRed(cmap[bit]);
644 *row++ = qGreen(cmap[bit]);
645 *row++ = qBlue(cmap[bit]);
646 }
647 } else {
648 for (int i=0; i<w; i++) {
649 bool bit = !!(*(data + (i >> 3)) & (1 << (7 -(i & 7))));
650 *row++ = qRed(cmap[bit]);
651 *row++ = qGreen(cmap[bit]);
652 *row++ = qBlue(cmap[bit]);
653 }
654 }
655 }
656 break;
657 case QImage::Format_Indexed8:
658 if (gray) {
659 const uchar* pix = image.constScanLine(cinfo.next_scanline);
660 for (int i=0; i<w; i++) {
661 *row = qRed(cmap[*pix]);
662 ++row; ++pix;
663 }
664 } else {
665 const uchar* pix = image.constScanLine(cinfo.next_scanline);
666 for (int i=0; i<w; i++) {
667 *row++ = qRed(cmap[*pix]);
668 *row++ = qGreen(cmap[*pix]);
669 *row++ = qBlue(cmap[*pix]);
670 ++pix;
671 }
672 }
673 break;
674 case QImage::Format_Grayscale8:
675 memcpy(row, image.constScanLine(cinfo.next_scanline), w);
676 break;
677 case QImage::Format_RGB888:
678 memcpy(row, image.constScanLine(cinfo.next_scanline), w * 3);
679 break;
680 case QImage::Format_RGB32:
681 case QImage::Format_ARGB32:
682 case QImage::Format_ARGB32_Premultiplied:
683 {
684 const QRgb* rgb = (const QRgb*)image.constScanLine(cinfo.next_scanline);
685 for (int i=0; i<w; i++) {
686 *row++ = qRed(*rgb);
687 *row++ = qGreen(*rgb);
688 *row++ = qBlue(*rgb);
689 ++rgb;
690 }
691 }
692 break;
693 default:
694 {
695 // (Testing shows that this way is actually faster than converting to RGB888 + memcpy)
696 QImage rowImg = image.copy(0, cinfo.next_scanline, w, 1).convertToFormat(QImage::Format_RGB32);
697 const QRgb* rgb = (const QRgb*)rowImg.constScanLine(0);
698 for (int i=0; i<w; i++) {
699 *row++ = qRed(*rgb);
700 *row++ = qGreen(*rgb);
701 *row++ = qBlue(*rgb);
702 ++rgb;
703 }
704 }
705 break;
706 }
707 jpeg_write_scanlines(&cinfo, row_pointer, 1);
708 }
709
710 jpeg_finish_compress(&cinfo);
711 jpeg_destroy_compress(&cinfo);
712 success = true;
713 } else {
714 jpeg_destroy_compress(&cinfo);
715 success = false;
716 }
717
718 delete iod_dest;
719 return success;
720}
721
722static bool write_jpeg_image(const QImage &image,
723 QIODevice *device,
724 int sourceQuality,
725 const QString &description,
726 bool optimize,
727 bool progressive)
728{
729 // protect these objects from the setjmp/longjmp pair inside
730 // do_write_jpeg_image (by making them non-local).
731 struct jpeg_compress_struct cinfo;
732 JSAMPROW row_pointer[1];
733 row_pointer[0] = nullptr;
734
735 const bool success = do_write_jpeg_image(cinfo, row_pointer,
736 image, device,
737 sourceQuality, description,
738 optimize, progressive);
739
740 delete [] row_pointer[0];
741 return success;
742}
743
744class QJpegHandlerPrivate
745{
746public:
747 enum State {
748 Ready,
749 ReadHeader,
750 ReadingEnd,
751 Error
752 };
753
754 QJpegHandlerPrivate(QJpegHandler *qq)
755 : quality(75), transformation(QImageIOHandler::TransformationNone), iod_src(nullptr),
756 rgb888ToRgb32ConverterPtr(qt_convert_rgb888_to_rgb32), state(Ready), optimize(false), progressive(false), q(qq)
757 {}
758
759 ~QJpegHandlerPrivate()
760 {
761 if(iod_src)
762 {
763 jpeg_destroy_decompress(&info);
764 delete iod_src;
765 iod_src = nullptr;
766 }
767 }
768
769 bool readJpegHeader(QIODevice*);
770 bool read(QImage *image);
771
772 int quality;
773 QImageIOHandler::Transformations transformation;
774 QVariant size;
775 QImage::Format format;
776 QSize scaledSize;
777 QRect scaledClipRect;
778 QRect clipRect;
779 QString description;
780 QStringList readTexts;
781 QByteArray iccProfile;
782
783 struct jpeg_decompress_struct info;
784 struct my_jpeg_source_mgr * iod_src;
785 struct my_error_mgr err;
786
787 Rgb888ToRgb32Converter rgb888ToRgb32ConverterPtr;
788
789 State state;
790
791 bool optimize;
792 bool progressive;
793
794 QJpegHandler *q;
795};
796
797static bool readExifHeader(QDataStream &stream)
798{
799 char prefix[6];
800 if (stream.readRawData(prefix, sizeof(prefix)) != sizeof(prefix))
801 return false;
802 static const char exifMagic[6] = {'E', 'x', 'i', 'f', 0, 0};
803 return memcmp(prefix, exifMagic, 6) == 0;
804}
805
806/*
807 * Returns -1 on error
808 * Returns 0 if no Exif orientation was found
809 * Returns 1 orientation is horizontal (normal)
810 * Returns 2 mirror horizontal
811 * Returns 3 rotate 180
812 * Returns 4 mirror vertical
813 * Returns 5 mirror horizontal and rotate 270 CCW
814 * Returns 6 rotate 90 CW
815 * Returns 7 mirror horizontal and rotate 90 CW
816 * Returns 8 rotate 270 CW
817 */
818static int getExifOrientation(QByteArray &exifData)
819{
820 // Current EXIF version (2.3) says there can be at most 5 IFDs,
821 // byte we allow for 10 so we're able to deal with future extensions.
822 const int maxIfdCount = 10;
823
824 QDataStream stream(&exifData, QIODevice::ReadOnly);
825
826 if (!readExifHeader(stream))
827 return -1;
828
829 quint16 val;
830 quint32 offset;
831 const qint64 headerStart = 6; // the EXIF header has a constant size
832 Q_ASSERT(headerStart == stream.device()->pos());
833
834 // read byte order marker
835 stream >> val;
836 if (val == 0x4949) // 'II' == Intel
837 stream.setByteOrder(QDataStream::LittleEndian);
838 else if (val == 0x4d4d) // 'MM' == Motorola
839 stream.setByteOrder(QDataStream::BigEndian);
840 else
841 return -1; // unknown byte order
842
843 // confirm byte order
844 stream >> val;
845 if (val != 0x2a)
846 return -1;
847
848 stream >> offset;
849
850 // read IFD
851 for (int n = 0; n < maxIfdCount; ++n) {
852 quint16 numEntries;
853
854 const qint64 bytesToSkip = offset - (stream.device()->pos() - headerStart);
855 if (bytesToSkip < 0 || (offset + headerStart >= exifData.size())) {
856 // disallow going backwards, though it's permitted in the spec
857 return -1;
858 } else if (bytesToSkip != 0) {
859 // seek to the IFD
860 if (!stream.device()->seek(offset + headerStart))
861 return -1;
862 }
863
864 stream >> numEntries;
865
866 for (; numEntries > 0 && stream.status() == QDataStream::Ok; --numEntries) {
867 quint16 tag;
868 quint16 type;
869 quint32 components;
870 quint16 value;
871 quint16 dummy;
872
873 stream >> tag >> type >> components >> value >> dummy;
874 if (tag == 0x0112) { // Tag Exif.Image.Orientation
875 if (components != 1)
876 return -1;
877 if (type != 3) // we are expecting it to be an unsigned short
878 return -1;
879 if (value < 1 || value > 8) // check for valid range
880 return -1;
881
882 // It is possible to include the orientation multiple times.
883 // Right now the first value is returned.
884 return value;
885 }
886 }
887
888 // read offset to next IFD
889 stream >> offset;
890 if (stream.status() != QDataStream::Ok)
891 return -1;
892 if (offset == 0) // this is the last IFD
893 return 0; // No Exif orientation was found
894 }
895
896 // too many IFDs
897 return -1;
898}
899
900static QImageIOHandler::Transformations exif2Qt(int exifOrientation)
901{
902 switch (exifOrientation) {
903 case 1: // normal
904 return QImageIOHandler::TransformationNone;
905 case 2: // mirror horizontal
906 return QImageIOHandler::TransformationMirror;
907 case 3: // rotate 180
908 return QImageIOHandler::TransformationRotate180;
909 case 4: // mirror vertical
910 return QImageIOHandler::TransformationFlip;
911 case 5: // mirror horizontal and rotate 270 CW
912 return QImageIOHandler::TransformationFlipAndRotate90;
913 case 6: // rotate 90 CW
914 return QImageIOHandler::TransformationRotate90;
915 case 7: // mirror horizontal and rotate 90 CW
916 return QImageIOHandler::TransformationMirrorAndRotate90;
917 case 8: // rotate 270 CW
918 return QImageIOHandler::TransformationRotate270;
919 }
920 qCWarning(lcJpeg, "Invalid EXIF orientation");
921 return QImageIOHandler::TransformationNone;
922}
923
924/*!
925 \internal
926*/
927bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device)
928{
929 if(state == Ready)
930 {
931 state = Error;
932 iod_src = new my_jpeg_source_mgr(device);
933
934 info.err = jpeg_std_error(&err);
935 err.error_exit = my_error_exit;
936 err.output_message = my_output_message;
937
938 jpeg_create_decompress(&info);
939 info.src = iod_src;
940
941 if (!setjmp(err.setjmp_buffer)) {
942 jpeg_save_markers(&info, JPEG_COM, 0xFFFF);
943 jpeg_save_markers(&info, JPEG_APP0 + 1, 0xFFFF); // Exif uses APP1 marker
944 jpeg_save_markers(&info, JPEG_APP0 + 2, 0xFFFF); // ICC uses APP2 marker
945
946 (void) jpeg_read_header(&info, TRUE);
947
948 int width = 0;
949 int height = 0;
950 read_jpeg_size(width, height, &info);
951 size = QSize(width, height);
952
953 format = QImage::Format_Invalid;
954 read_jpeg_format(format, &info);
955
956 QByteArray exifData;
957
958 for (jpeg_saved_marker_ptr marker = info.marker_list; marker != nullptr; marker = marker->next) {
959 if (marker->marker == JPEG_COM) {
960#ifndef QT_NO_IMAGEIO_TEXT_LOADING
961 QString key, value;
962 QString s = QString::fromUtf8((const char *)marker->data, marker->data_length);
963 int index = s.indexOf(QLatin1String(": "));
964 if (index == -1 || s.indexOf(QLatin1Char(' ')) < index) {
965 key = QLatin1String("Description");
966 value = s;
967 } else {
968 key = s.left(index);
969 value = s.mid(index + 2);
970 }
971 if (!description.isEmpty())
972 description += QLatin1String("\n\n");
973 description += key + QLatin1String(": ") + value.simplified();
974 readTexts.append(key);
975 readTexts.append(value);
976#endif
977 } else if (marker->marker == JPEG_APP0 + 1) {
978 exifData.append((const char*)marker->data, marker->data_length);
979 } else if (marker->marker == JPEG_APP0 + 2) {
980 if (marker->data_length > 128 + 4 + 14 && strcmp((const char *)marker->data, "ICC_PROFILE") == 0) {
981 iccProfile.append((const char*)marker->data + 14, marker->data_length - 14);
982 }
983 }
984 }
985
986 if (!exifData.isEmpty()) {
987 // Exif data present
988 int exifOrientation = getExifOrientation(exifData);
989 if (exifOrientation > 0)
990 transformation = exif2Qt(exifOrientation);
991 }
992
993 state = ReadHeader;
994 return true;
995 }
996 else
997 {
998 return false;
999 }
1000 }
1001 else if(state == Error)
1002 return false;
1003 return true;
1004}
1005
1006bool QJpegHandlerPrivate::read(QImage *image)
1007{
1008 if(state == Ready)
1009 readJpegHeader(q->device());
1010
1011 if(state == ReadHeader)
1012 {
1013 bool success = read_jpeg_image(image, scaledSize, scaledClipRect, clipRect, quality, rgb888ToRgb32ConverterPtr, &info, &err);
1014 if (success) {
1015 for (int i = 0; i < readTexts.size()-1; i+=2)
1016 image->setText(readTexts.at(i), readTexts.at(i+1));
1017
1018 if (!iccProfile.isEmpty())
1019 image->setColorSpace(QColorSpace::fromIccProfile(iccProfile));
1020
1021 state = ReadingEnd;
1022 return true;
1023 }
1024
1025 state = Error;
1026 }
1027
1028 return false;
1029}
1030
1031Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_neon(quint32 *dst, const uchar *src, int len);
1032Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_ssse3(quint32 *dst, const uchar *src, int len);
1033extern "C" void qt_convert_rgb888_to_rgb32_mips_dspr2_asm(quint32 *dst, const uchar *src, int len);
1034
1035QJpegHandler::QJpegHandler()
1036 : d(new QJpegHandlerPrivate(this))
1037{
1038#if defined(__ARM_NEON__)
1039 // from qimage_neon.cpp
1040 if (qCpuHasFeature(NEON))
1041 d->rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_neon;
1042#endif
1043
1044#if defined(QT_COMPILER_SUPPORTS_SSSE3)
1045 // from qimage_ssse3.cpps
1046 if (qCpuHasFeature(SSSE3)) {
1047 d->rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_ssse3;
1048 }
1049#endif // QT_COMPILER_SUPPORTS_SSSE3
1050#if defined(QT_COMPILER_SUPPORTS_MIPS_DSPR2)
1051 if (qCpuHasFeature(DSPR2)) {
1052 d->rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_mips_dspr2_asm;
1053 }
1054#endif // QT_COMPILER_SUPPORTS_DSPR2
1055}
1056
1057QJpegHandler::~QJpegHandler()
1058{
1059 delete d;
1060}
1061
1062bool QJpegHandler::canRead() const
1063{
1064 if(d->state == QJpegHandlerPrivate::Ready && !canRead(device()))
1065 return false;
1066
1067 if (d->state != QJpegHandlerPrivate::Error && d->state != QJpegHandlerPrivate::ReadingEnd) {
1068 setFormat("jpeg");
1069 return true;
1070 }
1071
1072 return false;
1073}
1074
1075bool QJpegHandler::canRead(QIODevice *device)
1076{
1077 if (!device) {
1078 qCWarning(lcJpeg, "QJpegHandler::canRead() called with no device");
1079 return false;
1080 }
1081
1082 char buffer[2];
1083 if (device->peek(buffer, 2) != 2)
1084 return false;
1085 return uchar(buffer[0]) == 0xff && uchar(buffer[1]) == 0xd8;
1086}
1087
1088bool QJpegHandler::read(QImage *image)
1089{
1090 if (!canRead())
1091 return false;
1092 return d->read(image);
1093}
1094
1095extern void qt_imageTransform(QImage &src, QImageIOHandler::Transformations orient);
1096
1097bool QJpegHandler::write(const QImage &image)
1098{
1099 if (d->transformation != QImageIOHandler::TransformationNone) {
1100 // We don't support writing EXIF headers so apply the transform to the data.
1101 QImage img = image;
1102 qt_imageTransform(img, d->transformation);
1103 return write_jpeg_image(img, device(), d->quality, d->description, d->optimize, d->progressive);
1104 }
1105 return write_jpeg_image(image, device(), d->quality, d->description, d->optimize, d->progressive);
1106}
1107
1108bool QJpegHandler::supportsOption(ImageOption option) const
1109{
1110 return option == Quality
1111 || option == ScaledSize
1112 || option == ScaledClipRect
1113 || option == ClipRect
1114 || option == Description
1115 || option == Size
1116 || option == ImageFormat
1117 || option == OptimizedWrite
1118 || option == ProgressiveScanWrite
1119 || option == ImageTransformation;
1120}
1121
1122QVariant QJpegHandler::option(ImageOption option) const
1123{
1124 switch(option) {
1125 case Quality:
1126 return d->quality;
1127 case ScaledSize:
1128 return d->scaledSize;
1129 case ScaledClipRect:
1130 return d->scaledClipRect;
1131 case ClipRect:
1132 return d->clipRect;
1133 case Description:
1134 d->readJpegHeader(device());
1135 return d->description;
1136 case Size:
1137 d->readJpegHeader(device());
1138 return d->size;
1139 case ImageFormat:
1140 d->readJpegHeader(device());
1141 return d->format;
1142 case OptimizedWrite:
1143 return d->optimize;
1144 case ProgressiveScanWrite:
1145 return d->progressive;
1146 case ImageTransformation:
1147 d->readJpegHeader(device());
1148 return int(d->transformation);
1149 default:
1150 break;
1151 }
1152
1153 return QVariant();
1154}
1155
1156void QJpegHandler::setOption(ImageOption option, const QVariant &value)
1157{
1158 switch(option) {
1159 case Quality:
1160 d->quality = value.toInt();
1161 break;
1162 case ScaledSize:
1163 d->scaledSize = value.toSize();
1164 break;
1165 case ScaledClipRect:
1166 d->scaledClipRect = value.toRect();
1167 break;
1168 case ClipRect:
1169 d->clipRect = value.toRect();
1170 break;
1171 case Description:
1172 d->description = value.toString();
1173 break;
1174 case OptimizedWrite:
1175 d->optimize = value.toBool();
1176 break;
1177 case ProgressiveScanWrite:
1178 d->progressive = value.toBool();
1179 break;
1180 case ImageTransformation: {
1181 int transformation = value.toInt();
1182 if (transformation > 0 && transformation < 8)
1183 d->transformation = QImageIOHandler::Transformations(transformation);
1184 }
1185 default:
1186 break;
1187 }
1188}
1189
1190QT_END_NAMESPACE
1191