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

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