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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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