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