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(msg: "%s", buffer);
95 longjmp(env: myerr->setjmp_buffer, val: 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(msg: "%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(pos: src->memDevice->data().size());
134 } else {
135 src->next_input_byte = src->buffer;
136 num_read = src->device->read(data: (char*)src->buffer, maxlen: 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(pos: 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 *>(object: 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, int quality,
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 if (quality < 0)
258 quality = 75;
259
260 // If possible, merge the scaledClipRect into either scaledSize
261 // or clipRect to avoid doing a separate scaled clipping pass.
262 // Best results are achieved by clipping before scaling, not after.
263 if (!scaledClipRect.isEmpty()) {
264 if (scaledSize.isEmpty() && clipRect.isEmpty()) {
265 // No clipping or scaling before final clip.
266 clipRect = scaledClipRect;
267 scaledClipRect = QRect();
268 } else if (scaledSize.isEmpty()) {
269 // Clipping, but no scaling: combine the clip regions.
270 scaledClipRect.translate(p: clipRect.topLeft());
271 clipRect = scaledClipRect.intersected(other: clipRect);
272 scaledClipRect = QRect();
273 } else if (clipRect.isEmpty()) {
274 // No clipping, but scaling: if we can map back to an
275 // integer pixel boundary, then clip before scaling.
276 if ((info->image_width % scaledSize.width()) == 0 &&
277 (info->image_height % scaledSize.height()) == 0) {
278 int x = scaledClipRect.x() * info->image_width /
279 scaledSize.width();
280 int y = scaledClipRect.y() * info->image_height /
281 scaledSize.height();
282 int width = (scaledClipRect.right() + 1) *
283 info->image_width / scaledSize.width() - x;
284 int height = (scaledClipRect.bottom() + 1) *
285 info->image_height / scaledSize.height() - y;
286 clipRect = QRect(x, y, width, height);
287 scaledSize = scaledClipRect.size();
288 scaledClipRect = QRect();
289 }
290 } else {
291 // Clipping and scaling: too difficult to figure out,
292 // and not a likely use case, so do it the long way.
293 }
294 }
295
296 // Determine the scale factor to pass to libjpeg for quick downscaling.
297 if (!scaledSize.isEmpty() && info->image_width && info->image_height) {
298 if (clipRect.isEmpty()) {
299 double f = qMin(a: double(info->image_width) / scaledSize.width(),
300 b: double(info->image_height) / scaledSize.height());
301
302 // libjpeg supports M/8 scaling with M=[1,16]. All downscaling factors
303 // are a speed improvement, but upscaling during decode is slower.
304 info->scale_num = qBound(min: 1, val: qCeil(v: 8/f), max: 8);
305 info->scale_denom = 8;
306 } else {
307 info->scale_denom = qMin(a: clipRect.width() / scaledSize.width(),
308 b: clipRect.height() / scaledSize.height());
309
310 // Only scale by powers of two when clipping so we can
311 // keep the exact pixel boundaries
312 if (info->scale_denom < 2)
313 info->scale_denom = 1;
314 else if (info->scale_denom < 4)
315 info->scale_denom = 2;
316 else if (info->scale_denom < 8)
317 info->scale_denom = 4;
318 else
319 info->scale_denom = 8;
320 info->scale_num = 1;
321
322 // Correct the scale factor so that we clip accurately.
323 // It is recommended that the clip rectangle be aligned
324 // on an 8-pixel boundary for best performance.
325 while (info->scale_denom > 1 &&
326 ((clipRect.x() % info->scale_denom) != 0 ||
327 (clipRect.y() % info->scale_denom) != 0 ||
328 (clipRect.width() % info->scale_denom) != 0 ||
329 (clipRect.height() % info->scale_denom) != 0)) {
330 info->scale_denom /= 2;
331 }
332 }
333 }
334
335 // If high quality not required, use fast decompression
336 if( quality < HIGH_QUALITY_THRESHOLD ) {
337 info->dct_method = JDCT_IFAST;
338 info->do_fancy_upsampling = FALSE;
339 }
340
341 (void) jpeg_calc_output_dimensions(cinfo: info);
342
343 // Determine the clip region to extract.
344 QRect imageRect(0, 0, info->output_width, info->output_height);
345 QRect clip;
346 if (clipRect.isEmpty()) {
347 clip = imageRect;
348 } else if (info->scale_denom == info->scale_num) {
349 clip = clipRect.intersected(other: imageRect);
350 } else {
351 // The scale factor was corrected above to ensure that
352 // we don't miss pixels when we scale the clip rectangle.
353 clip = QRect(clipRect.x() / int(info->scale_denom),
354 clipRect.y() / int(info->scale_denom),
355 clipRect.width() / int(info->scale_denom),
356 clipRect.height() / int(info->scale_denom));
357 clip = clip.intersected(other: imageRect);
358 }
359
360 // Allocate memory for the clipped QImage.
361 if (!ensureValidImage(dest: outImage, info, size: clip.size()))
362 longjmp(env: err->setjmp_buffer, val: 1);
363
364 // Avoid memcpy() overhead if grayscale with no clipping.
365 bool quickGray = (info->output_components == 1 &&
366 clip == imageRect);
367 if (!quickGray) {
368 // Ask the jpeg library to allocate a temporary row.
369 // The library will automatically delete it for us later.
370 // The libjpeg docs say we should do this before calling
371 // jpeg_start_decompress(). We can't use "new" here
372 // because we are inside the setjmp() block and an error
373 // in the jpeg input stream would cause a memory leak.
374 JSAMPARRAY rows = (info->mem->alloc_sarray)
375 ((j_common_ptr)info, JPOOL_IMAGE,
376 info->output_width * info->output_components, 1);
377
378 (void) jpeg_start_decompress(cinfo: info);
379
380 while (info->output_scanline < info->output_height) {
381 int y = int(info->output_scanline) - clip.y();
382 if (y >= clip.height())
383 break; // We've read the entire clip region, so abort.
384
385 (void) jpeg_read_scanlines(cinfo: info, scanlines: rows, max_lines: 1);
386
387 if (y < 0)
388 continue; // Haven't reached the starting line yet.
389
390 if (info->output_components == 3) {
391 uchar *in = rows[0] + clip.x() * 3;
392 QRgb *out = (QRgb*)outImage->scanLine(y);
393 converter(out, in, clip.width());
394 } else if (info->out_color_space == JCS_CMYK) {
395 // Convert CMYK->RGB.
396 uchar *in = rows[0] + clip.x() * 4;
397 QRgb *out = (QRgb*)outImage->scanLine(y);
398 for (int i = 0; i < clip.width(); ++i) {
399 int k = in[3];
400 *out++ = qRgb(r: k * in[0] / 255, g: k * in[1] / 255,
401 b: k * in[2] / 255);
402 in += 4;
403 }
404 } else if (info->output_components == 1) {
405 // Grayscale.
406 memcpy(dest: outImage->scanLine(y),
407 src: rows[0] + clip.x(), n: clip.width());
408 }
409 }
410 } else {
411 // Load unclipped grayscale data directly into the QImage.
412 (void) jpeg_start_decompress(cinfo: info);
413 while (info->output_scanline < info->output_height) {
414 uchar *row = outImage->scanLine(info->output_scanline);
415 (void) jpeg_read_scanlines(cinfo: info, scanlines: &row, max_lines: 1);
416 }
417 }
418
419 if (info->output_scanline == info->output_height)
420 (void) jpeg_finish_decompress(cinfo: info);
421
422 if (info->density_unit == 1) {
423 outImage->setDotsPerMeterX(int(100. * info->X_density / 2.54));
424 outImage->setDotsPerMeterY(int(100. * info->Y_density / 2.54));
425 } else if (info->density_unit == 2) {
426 outImage->setDotsPerMeterX(int(100. * info->X_density));
427 outImage->setDotsPerMeterY(int(100. * info->Y_density));
428 }
429
430 if (scaledSize.isValid() && scaledSize != clip.size()) {
431 *outImage = outImage->scaled(s: scaledSize, aspectMode: Qt::IgnoreAspectRatio, mode: quality >= HIGH_QUALITY_THRESHOLD ? Qt::SmoothTransformation : Qt::FastTransformation);
432 }
433
434 if (!scaledClipRect.isEmpty())
435 *outImage = outImage->copy(rect: scaledClipRect);
436 return !outImage->isNull();
437 }
438 else
439 return false;
440}
441
442struct my_jpeg_destination_mgr : public jpeg_destination_mgr {
443 // Nothing dynamic - cannot rely on destruction over longjump
444 QIODevice *device;
445 JOCTET buffer[max_buf];
446
447public:
448 my_jpeg_destination_mgr(QIODevice *);
449};
450
451
452extern "C" {
453
454static void qt_init_destination(j_compress_ptr)
455{
456}
457
458static boolean qt_empty_output_buffer(j_compress_ptr cinfo)
459{
460 my_jpeg_destination_mgr* dest = (my_jpeg_destination_mgr*)cinfo->dest;
461
462 int written = dest->device->write(data: (char*)dest->buffer, len: max_buf);
463 if (written == -1)
464 (*cinfo->err->error_exit)((j_common_ptr)cinfo);
465
466 dest->next_output_byte = dest->buffer;
467 dest->free_in_buffer = max_buf;
468
469 return TRUE;
470}
471
472static void qt_term_destination(j_compress_ptr cinfo)
473{
474 my_jpeg_destination_mgr* dest = (my_jpeg_destination_mgr*)cinfo->dest;
475 qint64 n = max_buf - dest->free_in_buffer;
476
477 qint64 written = dest->device->write(data: (char*)dest->buffer, len: n);
478 if (written == -1)
479 (*cinfo->err->error_exit)((j_common_ptr)cinfo);
480}
481
482}
483
484inline my_jpeg_destination_mgr::my_jpeg_destination_mgr(QIODevice *device)
485{
486 jpeg_destination_mgr::init_destination = qt_init_destination;
487 jpeg_destination_mgr::empty_output_buffer = qt_empty_output_buffer;
488 jpeg_destination_mgr::term_destination = qt_term_destination;
489 this->device = device;
490 next_output_byte = buffer;
491 free_in_buffer = max_buf;
492}
493
494static constexpr int maxMarkerSize = 65533;
495
496static inline void set_text(const QImage &image, j_compress_ptr cinfo, const QString &description)
497{
498 const QMap<QString, QString> text = qt_getImageText(image, description);
499 for (auto it = text.begin(), end = text.end(); it != end; ++it) {
500 QByteArray comment = it.key().toUtf8();
501 if (!comment.isEmpty())
502 comment += ": ";
503 comment += it.value().toUtf8();
504 if (comment.length() > maxMarkerSize)
505 comment.truncate(pos: maxMarkerSize);
506 jpeg_write_marker(cinfo, JPEG_COM, dataptr: (const JOCTET *)comment.constData(), datalen: comment.size());
507 }
508}
509
510static inline void write_icc_profile(const QImage &image, j_compress_ptr cinfo)
511{
512 const QByteArray iccProfile = image.colorSpace().iccProfile();
513 if (iccProfile.isEmpty())
514 return;
515
516 const QByteArray iccSignature("ICC_PROFILE", 12);
517 constexpr int maxIccMarkerSize = maxMarkerSize - (12 + 2);
518 int index = 0;
519 const int markers = (iccProfile.size() + (maxIccMarkerSize - 1)) / maxIccMarkerSize;
520 Q_ASSERT(markers < 256);
521 for (int marker = 1; marker <= markers; ++marker) {
522 const int len = std::min(a: iccProfile.size() - index, b: maxIccMarkerSize);
523 const QByteArray block = iccSignature
524 + QByteArray(1, char(marker)) + QByteArray(1, char(markers))
525 + iccProfile.mid(index, len);
526 jpeg_write_marker(cinfo, JPEG_APP0 + 2, dataptr: reinterpret_cast<const JOCTET *>(block.constData()), datalen: block.size());
527 index += len;
528 }
529}
530
531static bool do_write_jpeg_image(struct jpeg_compress_struct &cinfo,
532 JSAMPROW *row_pointer,
533 const QImage &image,
534 QIODevice *device,
535 int sourceQuality,
536 const QString &description,
537 bool optimize,
538 bool progressive)
539{
540 bool success = false;
541 const QVector<QRgb> cmap = image.colorTable();
542
543 if (image.format() == QImage::Format_Invalid || image.format() == QImage::Format_Alpha8)
544 return false;
545
546 struct my_jpeg_destination_mgr *iod_dest = new my_jpeg_destination_mgr(device);
547 struct my_error_mgr jerr;
548
549 cinfo.err = jpeg_std_error(err: &jerr);
550 jerr.error_exit = my_error_exit;
551 jerr.output_message = my_output_message;
552
553 if (!setjmp(jerr.setjmp_buffer)) {
554 // WARNING:
555 // this if loop is inside a setjmp/longjmp branch
556 // do not create C++ temporaries here because the destructor may never be called
557 // if you allocate memory, make sure that you can free it (row_pointer[0])
558 jpeg_create_compress(&cinfo);
559
560 cinfo.dest = iod_dest;
561
562 cinfo.image_width = image.width();
563 cinfo.image_height = image.height();
564
565 bool gray = false;
566 switch (image.format()) {
567 case QImage::Format_Mono:
568 case QImage::Format_MonoLSB:
569 case QImage::Format_Indexed8:
570 gray = true;
571 for (int i = image.colorCount(); gray && i; i--) {
572 gray = gray & qIsGray(rgb: cmap[i-1]);
573 }
574 cinfo.input_components = gray ? 1 : 3;
575 cinfo.in_color_space = gray ? JCS_GRAYSCALE : JCS_RGB;
576 break;
577 case QImage::Format_Grayscale8:
578 gray = true;
579 cinfo.input_components = 1;
580 cinfo.in_color_space = JCS_GRAYSCALE;
581 break;
582 default:
583 cinfo.input_components = 3;
584 cinfo.in_color_space = JCS_RGB;
585 }
586
587 jpeg_set_defaults(cinfo: &cinfo);
588
589 qreal diffInch = qAbs(t: image.dotsPerMeterX()*2.54/100. - qRound(d: image.dotsPerMeterX()*2.54/100.))
590 + qAbs(t: image.dotsPerMeterY()*2.54/100. - qRound(d: image.dotsPerMeterY()*2.54/100.));
591 qreal diffCm = (qAbs(t: image.dotsPerMeterX()/100. - qRound(d: image.dotsPerMeterX()/100.))
592 + qAbs(t: image.dotsPerMeterY()/100. - qRound(d: image.dotsPerMeterY()/100.)))*2.54;
593 if (diffInch < diffCm) {
594 cinfo.density_unit = 1; // dots/inch
595 cinfo.X_density = qRound(d: image.dotsPerMeterX()*2.54/100.);
596 cinfo.Y_density = qRound(d: image.dotsPerMeterY()*2.54/100.);
597 } else {
598 cinfo.density_unit = 2; // dots/cm
599 cinfo.X_density = (image.dotsPerMeterX()+50) / 100;
600 cinfo.Y_density = (image.dotsPerMeterY()+50) / 100;
601 }
602
603 if (optimize)
604 cinfo.optimize_coding = true;
605
606 if (progressive)
607 jpeg_simple_progression(cinfo: &cinfo);
608
609 int quality = sourceQuality >= 0 ? qMin(a: int(sourceQuality),b: 100) : 75;
610 jpeg_set_quality(cinfo: &cinfo, quality, TRUE /* limit to baseline-JPEG values */);
611 jpeg_start_compress(cinfo: &cinfo, TRUE);
612
613 set_text(image, cinfo: &cinfo, description);
614 if (cinfo.in_color_space == JCS_RGB)
615 write_icc_profile(image, cinfo: &cinfo);
616
617 row_pointer[0] = new uchar[cinfo.image_width*cinfo.input_components];
618 int w = cinfo.image_width;
619 while (cinfo.next_scanline < cinfo.image_height) {
620 uchar *row = row_pointer[0];
621 switch (image.format()) {
622 case QImage::Format_Mono:
623 case QImage::Format_MonoLSB:
624 if (gray) {
625 const uchar* data = image.constScanLine(cinfo.next_scanline);
626 if (image.format() == QImage::Format_MonoLSB) {
627 for (int i=0; i<w; i++) {
628 bool bit = !!(*(data + (i >> 3)) & (1 << (i & 7)));
629 row[i] = qRed(rgb: cmap[bit]);
630 }
631 } else {
632 for (int i=0; i<w; i++) {
633 bool bit = !!(*(data + (i >> 3)) & (1 << (7 -(i & 7))));
634 row[i] = qRed(rgb: cmap[bit]);
635 }
636 }
637 } else {
638 const uchar* data = image.constScanLine(cinfo.next_scanline);
639 if (image.format() == QImage::Format_MonoLSB) {
640 for (int i=0; i<w; i++) {
641 bool bit = !!(*(data + (i >> 3)) & (1 << (i & 7)));
642 *row++ = qRed(rgb: cmap[bit]);
643 *row++ = qGreen(rgb: cmap[bit]);
644 *row++ = qBlue(rgb: cmap[bit]);
645 }
646 } else {
647 for (int i=0; i<w; i++) {
648 bool bit = !!(*(data + (i >> 3)) & (1 << (7 -(i & 7))));
649 *row++ = qRed(rgb: cmap[bit]);
650 *row++ = qGreen(rgb: cmap[bit]);
651 *row++ = qBlue(rgb: cmap[bit]);
652 }
653 }
654 }
655 break;
656 case QImage::Format_Indexed8:
657 if (gray) {
658 const uchar* pix = image.constScanLine(cinfo.next_scanline);
659 for (int i=0; i<w; i++) {
660 *row = qRed(rgb: cmap[*pix]);
661 ++row; ++pix;
662 }
663 } else {
664 const uchar* pix = image.constScanLine(cinfo.next_scanline);
665 for (int i=0; i<w; i++) {
666 *row++ = qRed(rgb: cmap[*pix]);
667 *row++ = qGreen(rgb: cmap[*pix]);
668 *row++ = qBlue(rgb: cmap[*pix]);
669 ++pix;
670 }
671 }
672 break;
673 case QImage::Format_Grayscale8:
674 memcpy(dest: row, src: image.constScanLine(cinfo.next_scanline), n: w);
675 break;
676 case QImage::Format_RGB888:
677 memcpy(dest: row, src: image.constScanLine(cinfo.next_scanline), n: w * 3);
678 break;
679 case QImage::Format_RGB32:
680 case QImage::Format_ARGB32:
681 case QImage::Format_ARGB32_Premultiplied:
682 {
683 const QRgb* rgb = (const QRgb*)image.constScanLine(cinfo.next_scanline);
684 for (int i=0; i<w; i++) {
685 *row++ = qRed(rgb: *rgb);
686 *row++ = qGreen(rgb: *rgb);
687 *row++ = qBlue(rgb: *rgb);
688 ++rgb;
689 }
690 }
691 break;
692 default:
693 {
694 // (Testing shows that this way is actually faster than converting to RGB888 + memcpy)
695 QImage rowImg = image.copy(x: 0, y: cinfo.next_scanline, w, h: 1).convertToFormat(f: QImage::Format_RGB32);
696 const QRgb* rgb = (const QRgb*)rowImg.constScanLine(0);
697 for (int i=0; i<w; i++) {
698 *row++ = qRed(rgb: *rgb);
699 *row++ = qGreen(rgb: *rgb);
700 *row++ = qBlue(rgb: *rgb);
701 ++rgb;
702 }
703 }
704 break;
705 }
706 jpeg_write_scanlines(cinfo: &cinfo, scanlines: row_pointer, num_lines: 1);
707 }
708
709 jpeg_finish_compress(cinfo: &cinfo);
710 jpeg_destroy_compress(cinfo: &cinfo);
711 success = true;
712 } else {
713 jpeg_destroy_compress(cinfo: &cinfo);
714 success = false;
715 }
716
717 delete iod_dest;
718 return success;
719}
720
721static bool write_jpeg_image(const QImage &image,
722 QIODevice *device,
723 int sourceQuality,
724 const QString &description,
725 bool optimize,
726 bool progressive)
727{
728 // protect these objects from the setjmp/longjmp pair inside
729 // do_write_jpeg_image (by making them non-local).
730 struct jpeg_compress_struct cinfo;
731 JSAMPROW row_pointer[1];
732 row_pointer[0] = nullptr;
733
734 const bool success = do_write_jpeg_image(cinfo, row_pointer,
735 image, device,
736 sourceQuality, description,
737 optimize, progressive);
738
739 delete [] row_pointer[0];
740 return success;
741}
742
743class QJpegHandlerPrivate
744{
745public:
746 enum State {
747 Ready,
748 ReadHeader,
749 ReadingEnd,
750 Error
751 };
752
753 QJpegHandlerPrivate(QJpegHandler *qq)
754 : quality(75), transformation(QImageIOHandler::TransformationNone), iod_src(nullptr),
755 rgb888ToRgb32ConverterPtr(qt_convert_rgb888_to_rgb32), state(Ready), optimize(false), progressive(false), q(qq)
756 {}
757
758 ~QJpegHandlerPrivate()
759 {
760 if(iod_src)
761 {
762 jpeg_destroy_decompress(cinfo: &info);
763 delete iod_src;
764 iod_src = nullptr;
765 }
766 }
767
768 bool readJpegHeader(QIODevice*);
769 bool read(QImage *image);
770
771 int quality;
772 QImageIOHandler::Transformations transformation;
773 QVariant size;
774 QImage::Format format;
775 QSize scaledSize;
776 QRect scaledClipRect;
777 QRect clipRect;
778 QString description;
779 QStringList readTexts;
780 QByteArray iccProfile;
781
782 struct jpeg_decompress_struct info;
783 struct my_jpeg_source_mgr * iod_src;
784 struct my_error_mgr err;
785
786 Rgb888ToRgb32Converter rgb888ToRgb32ConverterPtr;
787
788 State state;
789
790 bool optimize;
791 bool progressive;
792
793 QJpegHandler *q;
794};
795
796static bool readExifHeader(QDataStream &stream)
797{
798 char prefix[6];
799 if (stream.readRawData(prefix, len: sizeof(prefix)) != sizeof(prefix))
800 return false;
801 static const char exifMagic[6] = {'E', 'x', 'i', 'f', 0, 0};
802 return memcmp(s1: prefix, s2: exifMagic, n: 6) == 0;
803}
804
805/*
806 * Returns -1 on error
807 * Returns 0 if no Exif orientation was found
808 * Returns 1 orientation is horizontal (normal)
809 * Returns 2 mirror horizontal
810 * Returns 3 rotate 180
811 * Returns 4 mirror vertical
812 * Returns 5 mirror horizontal and rotate 270 CCW
813 * Returns 6 rotate 90 CW
814 * Returns 7 mirror horizontal and rotate 90 CW
815 * Returns 8 rotate 270 CW
816 */
817static int getExifOrientation(QByteArray &exifData)
818{
819 // Current EXIF version (2.3) says there can be at most 5 IFDs,
820 // byte we allow for 10 so we're able to deal with future extensions.
821 const int maxIfdCount = 10;
822
823 QDataStream stream(&exifData, QIODevice::ReadOnly);
824
825 if (!readExifHeader(stream))
826 return -1;
827
828 quint16 val;
829 quint32 offset;
830 const qint64 headerStart = 6; // the EXIF header has a constant size
831 Q_ASSERT(headerStart == stream.device()->pos());
832
833 // read byte order marker
834 stream >> val;
835 if (val == 0x4949) // 'II' == Intel
836 stream.setByteOrder(QDataStream::LittleEndian);
837 else if (val == 0x4d4d) // 'MM' == Motorola
838 stream.setByteOrder(QDataStream::BigEndian);
839 else
840 return -1; // unknown byte order
841
842 // confirm byte order
843 stream >> val;
844 if (val != 0x2a)
845 return -1;
846
847 stream >> offset;
848
849 // read IFD
850 for (int n = 0; n < maxIfdCount; ++n) {
851 quint16 numEntries;
852
853 const qint64 bytesToSkip = offset - (stream.device()->pos() - headerStart);
854 if (bytesToSkip < 0 || (offset + headerStart >= exifData.size())) {
855 // disallow going backwards, though it's permitted in the spec
856 return -1;
857 } else if (bytesToSkip != 0) {
858 // seek to the IFD
859 if (!stream.device()->seek(pos: offset + headerStart))
860 return -1;
861 }
862
863 stream >> numEntries;
864
865 for (; numEntries > 0 && stream.status() == QDataStream::Ok; --numEntries) {
866 quint16 tag;
867 quint16 type;
868 quint32 components;
869 quint16 value;
870 quint16 dummy;
871
872 stream >> tag >> type >> components >> value >> dummy;
873 if (tag == 0x0112) { // Tag Exif.Image.Orientation
874 if (components != 1)
875 return -1;
876 if (type != 3) // we are expecting it to be an unsigned short
877 return -1;
878 if (value < 1 || value > 8) // check for valid range
879 return -1;
880
881 // It is possible to include the orientation multiple times.
882 // Right now the first value is returned.
883 return value;
884 }
885 }
886
887 // read offset to next IFD
888 stream >> offset;
889 if (stream.status() != QDataStream::Ok)
890 return -1;
891 if (offset == 0) // this is the last IFD
892 return 0; // No Exif orientation was found
893 }
894
895 // too many IFDs
896 return -1;
897}
898
899static QImageIOHandler::Transformations exif2Qt(int exifOrientation)
900{
901 switch (exifOrientation) {
902 case 1: // normal
903 return QImageIOHandler::TransformationNone;
904 case 2: // mirror horizontal
905 return QImageIOHandler::TransformationMirror;
906 case 3: // rotate 180
907 return QImageIOHandler::TransformationRotate180;
908 case 4: // mirror vertical
909 return QImageIOHandler::TransformationFlip;
910 case 5: // mirror horizontal and rotate 270 CW
911 return QImageIOHandler::TransformationFlipAndRotate90;
912 case 6: // rotate 90 CW
913 return QImageIOHandler::TransformationRotate90;
914 case 7: // mirror horizontal and rotate 90 CW
915 return QImageIOHandler::TransformationMirrorAndRotate90;
916 case 8: // rotate 270 CW
917 return QImageIOHandler::TransformationRotate270;
918 }
919 qWarning(msg: "Invalid EXIF orientation");
920 return QImageIOHandler::TransformationNone;
921}
922
923/*!
924 \internal
925*/
926bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device)
927{
928 if(state == Ready)
929 {
930 state = Error;
931 iod_src = new my_jpeg_source_mgr(device);
932
933 info.err = jpeg_std_error(err: &err);
934 err.error_exit = my_error_exit;
935 err.output_message = my_output_message;
936
937 jpeg_create_decompress(&info);
938 info.src = iod_src;
939
940 if (!setjmp(err.setjmp_buffer)) {
941 jpeg_save_markers(cinfo: &info, JPEG_COM, length_limit: 0xFFFF);
942 jpeg_save_markers(cinfo: &info, JPEG_APP0 + 1, length_limit: 0xFFFF); // Exif uses APP1 marker
943 jpeg_save_markers(cinfo: &info, JPEG_APP0 + 2, length_limit: 0xFFFF); // ICC uses APP2 marker
944
945 (void) jpeg_read_header(cinfo: &info, TRUE);
946
947 int width = 0;
948 int height = 0;
949 read_jpeg_size(w&: width, h&: height, cinfo: &info);
950 size = QSize(width, height);
951
952 format = QImage::Format_Invalid;
953 read_jpeg_format(format, cinfo: &info);
954
955 QByteArray exifData;
956
957 for (jpeg_saved_marker_ptr marker = info.marker_list; marker != nullptr; marker = marker->next) {
958 if (marker->marker == JPEG_COM) {
959#ifndef QT_NO_IMAGEIO_TEXT_LOADING
960 QString key, value;
961 QString s = QString::fromUtf8(str: (const char *)marker->data, size: marker->data_length);
962 int index = s.indexOf(s: QLatin1String(": "));
963 if (index == -1 || s.indexOf(c: QLatin1Char(' ')) < index) {
964 key = QLatin1String("Description");
965 value = s;
966 } else {
967 key = s.left(n: index);
968 value = s.mid(position: index + 2);
969 }
970 if (!description.isEmpty())
971 description += QLatin1String("\n\n");
972 description += key + QLatin1String(": ") + value.simplified();
973 readTexts.append(t: key);
974 readTexts.append(t: value);
975#endif
976 } else if (marker->marker == JPEG_APP0 + 1) {
977 exifData.append(s: (const char*)marker->data, len: marker->data_length);
978 } else if (marker->marker == JPEG_APP0 + 2) {
979 if (marker->data_length > 128 + 4 + 14 && strcmp(s1: (const char *)marker->data, s2: "ICC_PROFILE") == 0) {
980 iccProfile.append(s: (const char*)marker->data + 14, len: marker->data_length - 14);
981 }
982 }
983 }
984
985 if (!exifData.isEmpty()) {
986 // Exif data present
987 int exifOrientation = getExifOrientation(exifData);
988 if (exifOrientation > 0)
989 transformation = exif2Qt(exifOrientation);
990 }
991
992 state = ReadHeader;
993 return true;
994 }
995 else
996 {
997 return false;
998 }
999 }
1000 else if(state == Error)
1001 return false;
1002 return true;
1003}
1004
1005bool QJpegHandlerPrivate::read(QImage *image)
1006{
1007 if(state == Ready)
1008 readJpegHeader(device: q->device());
1009
1010 if(state == ReadHeader)
1011 {
1012 bool success = read_jpeg_image(outImage: image, scaledSize, scaledClipRect, clipRect, quality, converter: rgb888ToRgb32ConverterPtr, info: &info, err: &err);
1013 if (success) {
1014 for (int i = 0; i < readTexts.size()-1; i+=2)
1015 image->setText(key: readTexts.at(i), value: readTexts.at(i: i+1));
1016
1017 if (!iccProfile.isEmpty())
1018 image->setColorSpace(QColorSpace::fromIccProfile(iccProfile));
1019
1020 state = ReadingEnd;
1021 return true;
1022 }
1023
1024 state = Error;
1025 }
1026
1027 return false;
1028}
1029
1030Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_neon(quint32 *dst, const uchar *src, int len);
1031Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_ssse3(quint32 *dst, const uchar *src, int len);
1032extern "C" void qt_convert_rgb888_to_rgb32_mips_dspr2_asm(quint32 *dst, const uchar *src, int len);
1033
1034QJpegHandler::QJpegHandler()
1035 : d(new QJpegHandlerPrivate(this))
1036{
1037#if defined(__ARM_NEON__)
1038 // from qimage_neon.cpp
1039 if (qCpuHasFeature(NEON))
1040 d->rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_neon;
1041#endif
1042
1043#if defined(QT_COMPILER_SUPPORTS_SSSE3)
1044 // from qimage_ssse3.cpps
1045 if (qCpuHasFeature(SSSE3)) {
1046 d->rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_ssse3;
1047 }
1048#endif // QT_COMPILER_SUPPORTS_SSSE3
1049#if defined(QT_COMPILER_SUPPORTS_MIPS_DSPR2)
1050 if (qCpuHasFeature(DSPR2)) {
1051 d->rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_mips_dspr2_asm;
1052 }
1053#endif // QT_COMPILER_SUPPORTS_DSPR2
1054}
1055
1056QJpegHandler::~QJpegHandler()
1057{
1058 delete d;
1059}
1060
1061bool QJpegHandler::canRead() const
1062{
1063 if(d->state == QJpegHandlerPrivate::Ready && !canRead(device: device()))
1064 return false;
1065
1066 if (d->state != QJpegHandlerPrivate::Error && d->state != QJpegHandlerPrivate::ReadingEnd) {
1067 setFormat("jpeg");
1068 return true;
1069 }
1070
1071 return false;
1072}
1073
1074bool QJpegHandler::canRead(QIODevice *device)
1075{
1076 if (!device) {
1077 qWarning(msg: "QJpegHandler::canRead() called with no device");
1078 return false;
1079 }
1080
1081 char buffer[2];
1082 if (device->peek(data: buffer, maxlen: 2) != 2)
1083 return false;
1084 return uchar(buffer[0]) == 0xff && uchar(buffer[1]) == 0xd8;
1085}
1086
1087bool QJpegHandler::read(QImage *image)
1088{
1089 if (!canRead())
1090 return false;
1091 return d->read(image);
1092}
1093
1094extern void qt_imageTransform(QImage &src, QImageIOHandler::Transformations orient);
1095
1096bool QJpegHandler::write(const QImage &image)
1097{
1098 if (d->transformation != QImageIOHandler::TransformationNone) {
1099 // We don't support writing EXIF headers so apply the transform to the data.
1100 QImage img = image;
1101 qt_imageTransform(src&: img, orient: d->transformation);
1102 return write_jpeg_image(image: img, device: device(), sourceQuality: d->quality, description: d->description, optimize: d->optimize, progressive: d->progressive);
1103 }
1104 return write_jpeg_image(image, device: device(), sourceQuality: d->quality, description: d->description, optimize: d->optimize, progressive: d->progressive);
1105}
1106
1107bool QJpegHandler::supportsOption(ImageOption option) const
1108{
1109 return option == Quality
1110 || option == ScaledSize
1111 || option == ScaledClipRect
1112 || option == ClipRect
1113 || option == Description
1114 || option == Size
1115 || option == ImageFormat
1116 || option == OptimizedWrite
1117 || option == ProgressiveScanWrite
1118 || option == ImageTransformation;
1119}
1120
1121QVariant QJpegHandler::option(ImageOption option) const
1122{
1123 switch(option) {
1124 case Quality:
1125 return d->quality;
1126 case ScaledSize:
1127 return d->scaledSize;
1128 case ScaledClipRect:
1129 return d->scaledClipRect;
1130 case ClipRect:
1131 return d->clipRect;
1132 case Description:
1133 d->readJpegHeader(device: device());
1134 return d->description;
1135 case Size:
1136 d->readJpegHeader(device: device());
1137 return d->size;
1138 case ImageFormat:
1139 d->readJpegHeader(device: device());
1140 return d->format;
1141 case OptimizedWrite:
1142 return d->optimize;
1143 case ProgressiveScanWrite:
1144 return d->progressive;
1145 case ImageTransformation:
1146 d->readJpegHeader(device: device());
1147 return int(d->transformation);
1148 default:
1149 break;
1150 }
1151
1152 return QVariant();
1153}
1154
1155void QJpegHandler::setOption(ImageOption option, const QVariant &value)
1156{
1157 switch(option) {
1158 case Quality:
1159 d->quality = value.toInt();
1160 break;
1161 case ScaledSize:
1162 d->scaledSize = value.toSize();
1163 break;
1164 case ScaledClipRect:
1165 d->scaledClipRect = value.toRect();
1166 break;
1167 case ClipRect:
1168 d->clipRect = value.toRect();
1169 break;
1170 case Description:
1171 d->description = value.toString();
1172 break;
1173 case OptimizedWrite:
1174 d->optimize = value.toBool();
1175 break;
1176 case ProgressiveScanWrite:
1177 d->progressive = value.toBool();
1178 break;
1179 case ImageTransformation: {
1180 int transformation = value.toInt();
1181 if (transformation > 0 && transformation < 8)
1182 d->transformation = QImageIOHandler::Transformations(transformation);
1183 }
1184 default:
1185 break;
1186 }
1187}
1188
1189QT_END_NAMESPACE
1190

source code of qtbase/src/plugins/imageformats/jpeg/qjpeghandler.cpp