Qt 6.x
The Qt SDK
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
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
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 {
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(myerr->setjmp_buffer, 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
72 JOCTET buffer[max_buf];
74
75public:
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{
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(src->memDevice->data().size());
93 } else {
94 src->next_input_byte = src->buffer;
95 num_read = src->device->read((char*)src->buffer, 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{
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{
135 if (!src->device->isSequential())
136 src->device->seek(src->device->pos() - src->bytes_in_buffer);
137}
138
139}
140
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 *>(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:
173 break;
174 case 3:
175 case 4:
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{
190 switch (info->output_components) {
191 case 1:
193 break;
194 case 3:
195 case 4:
197 break;
198 default:
199 return false; // unsupported format
200 }
201
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(clipRect.topLeft());
227 clipRect = scaledClipRect.intersected(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(double(info->image_width) / scaledSize.width(),
256 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(1, qCeil(8/f), 8);
261 info->scale_denom = 8;
262 } else {
263 info->scale_denom = qMin(clipRect.width() / scaledSize.width(),
264 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(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(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(imageRect);
314 }
315
316 // Allocate memory for the clipped QImage.
317 if (!ensureValidImage(outImage, info, 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(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(info, rows, 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(k * in[0] / 255, k * in[1] / 255,
357 k * in[2] / 255);
358 in += 4;
359 }
360 } else if (info->output_components == 1) {
361 // Grayscale.
362 memcpy(outImage->scanLine(y),
363 rows[0] + clip.x(), clip.width());
364 }
365 }
366 } else {
367 // Load unclipped grayscale data directly into the QImage.
368 (void) jpeg_start_decompress(info);
369 while (info->output_scanline < info->output_height) {
370 uchar *row = outImage->scanLine(info->output_scanline);
371 (void) jpeg_read_scanlines(info, &row, 1);
372 }
373 }
374
375 if (info->output_scanline == info->output_height)
376 (void) jpeg_finish_decompress(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()) {
388 }
389
390 if (!scaledClipRect.isEmpty())
391 *outImage = outImage->copy(scaledClipRect);
392 return !outImage->isNull();
393 }
394 else {
395 my_output_message(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
404
405public:
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{
419
420 int written = dest->device->write((char*)dest->buffer, 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{
433 qint64 n = max_buf - dest->free_in_buffer;
434
435 qint64 written = dest->device->write((char*)dest->buffer, n);
436 if (written == -1)
437 (*cinfo->err->error_exit)((j_common_ptr)cinfo);
438}
439
440}
441
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(maxMarkerSize);
464 jpeg_write_marker(cinfo, JPEG_COM, (const JOCTET *)comment.constData(), 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(iccProfile.size() - index, 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, reinterpret_cast<const JOCTET *>(block.constData()), 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,
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
505 struct my_error_mgr jerr;
506
507 cinfo.err = jpeg_std_error(&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()) {
528 gray = true;
529 for (int i = image.colorCount(); gray && i; i--) {
530 gray = gray & qIsGray(cmap[i-1]);
531 }
532 cinfo.input_components = gray ? 1 : 3;
533 cinfo.in_color_space = gray ? JCS_GRAYSCALE : JCS_RGB;
534 break;
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);
547
548 qreal diffInch = qAbs(image.dotsPerMeterX()*2.54/100. - qRound(image.dotsPerMeterX()*2.54/100.))
549 + qAbs(image.dotsPerMeterY()*2.54/100. - qRound(image.dotsPerMeterY()*2.54/100.));
550 qreal diffCm = (qAbs(image.dotsPerMeterX()/100. - qRound(image.dotsPerMeterX()/100.))
551 + qAbs(image.dotsPerMeterY()/100. - qRound(image.dotsPerMeterY()/100.)))*2.54;
552 if (diffInch < diffCm) {
553 cinfo.density_unit = 1; // dots/inch
554 cinfo.X_density = qRound(image.dotsPerMeterX()*2.54/100.);
555 cinfo.Y_density = qRound(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);
567
568 int quality = sourceQuality >= 0 ? qMin(int(sourceQuality),100) : 75;
569 jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
570 jpeg_start_compress(&cinfo, TRUE);
571
572 set_text(image, &cinfo, description);
573 if (cinfo.in_color_space == JCS_RGB)
574 write_icc_profile(image, &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()) {
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(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(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(cmap[bit]);
602 *row++ = qGreen(cmap[bit]);
603 *row++ = qBlue(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(cmap[bit]);
609 *row++ = qGreen(cmap[bit]);
610 *row++ = qBlue(cmap[bit]);
611 }
612 }
613 }
614 break;
616 if (gray) {
617 const uchar* pix = image.constScanLine(cinfo.next_scanline);
618 for (int i=0; i<w; i++) {
619 *row = qRed(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(cmap[*pix]);
626 *row++ = qGreen(cmap[*pix]);
627 *row++ = qBlue(cmap[*pix]);
628 ++pix;
629 }
630 }
631 break;
633 memcpy(row, image.constScanLine(cinfo.next_scanline), w);
634 break;
636 {
637 QImage rowImg = image.copy(0, cinfo.next_scanline, w, 1).convertToFormat(QImage::Format_Grayscale8);
638 memcpy(row, rowImg.constScanLine(0), w);
639 }
640 break;
642 memcpy(row, image.constScanLine(cinfo.next_scanline), w * 3);
643 break;
647 {
648 const QRgb* rgb = (const QRgb*)image.constScanLine(cinfo.next_scanline);
649 for (int i=0; i<w; i++) {
650 *row++ = qRed(*rgb);
651 *row++ = qGreen(*rgb);
652 *row++ = qBlue(*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(0, cinfo.next_scanline, w, 1).convertToFormat(QImage::Format_RGB32);
661 const QRgb* rgb = (const QRgb*)rowImg.constScanLine(0);
662 for (int i=0; i<w; i++) {
663 *row++ = qRed(*rgb);
664 *row++ = qGreen(*rgb);
665 *row++ = qBlue(*rgb);
666 ++rgb;
667 }
668 }
669 break;
670 }
671 jpeg_write_scanlines(&cinfo, row_pointer, 1);
672 }
673
674 jpeg_finish_compress(&cinfo);
675 jpeg_destroy_compress(&cinfo);
676 success = true;
677 } else {
678 my_output_message(j_common_ptr(&cinfo));
679 jpeg_destroy_compress(&cinfo);
680 success = false;
681 }
682
683 delete iod_dest;
684 return success;
685}
686
687static bool write_jpeg_image(const QImage &image,
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
710{
711public:
712 enum State {
716 Error
717 };
718
720 : quality(75), transformation(QImageIOHandler::TransformationNone), iod_src(nullptr),
722 {}
723
725 {
726 if (iod_src)
727 {
728 jpeg_destroy_decompress(&info);
729 delete iod_src;
730 iod_src = nullptr;
731 }
732 }
733
735 bool read(QImage *image);
736
738 QImageIOHandler::Transformations transformation;
747
748 struct jpeg_decompress_struct info;
751
753
755
758
760};
761
763{
764 char prefix[6];
765 if (stream.readRawData(prefix, sizeof(prefix)) != sizeof(prefix))
766 return false;
767 static const char exifMagic[6] = {'E', 'x', 'i', 'f', 0, 0};
768 return memcmp(prefix, exifMagic, 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
790
792 return -1;
793
794 quint16 val;
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(offset + headerStart))
826 return -1;
827 }
828
829 stream >> numEntries;
830
831 for (; numEntries > 0 && stream.status() == QDataStream::Ok; --numEntries) {
832 quint16 tag;
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
870 case 2: // mirror horizontal
872 case 3: // rotate 180
874 case 4: // mirror vertical
876 case 5: // mirror horizontal and rotate 270 CW
878 case 6: // rotate 90 CW
880 case 7: // mirror horizontal and rotate 90 CW
882 case 8: // rotate 270 CW
884 }
885 qCWarning(lcJpeg, "Invalid EXIF orientation");
887}
888
893{
894 if (state == Ready)
895 {
896 state = Error;
898
899 info.err = jpeg_std_error(&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(&info, JPEG_COM, 0xFFFF);
908 jpeg_save_markers(&info, JPEG_APP0 + 1, 0xFFFF); // Exif uses APP1 marker
909 jpeg_save_markers(&info, JPEG_APP0 + 2, 0xFFFF); // ICC uses APP2 marker
910
911 (void) jpeg_read_header(&info, TRUE);
912
913 int width = 0;
914 int height = 0;
917
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
927 QString s = QString::fromUtf8((const char *)marker->data, marker->data_length);
928 int index = s.indexOf(QLatin1String(": "));
929 if (index == -1 || s.indexOf(QLatin1Char(' ')) < index) {
930 key = QLatin1String("Description");
931 value = s;
932 } else {
933 key = s.left(index);
934 value = s.mid(index + 2);
935 }
936 if (!description.isEmpty())
937 description += QLatin1String("\n\n");
938 description += key + QLatin1String(": ") + value.simplified();
939 readTexts.append(key);
940 readTexts.append(value);
941#endif
942 } else if (marker->marker == JPEG_APP0 + 1) {
943 exifData.append((const char*)marker->data, marker->data_length);
944 } else if (marker->marker == JPEG_APP0 + 2) {
945 if (marker->data_length > 128 + 4 + 14 && strcmp((const char *)marker->data, "ICC_PROFILE") == 0) {
946 iccProfile.append((const char*)marker->data + 14, 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
959 return true;
960 }
961 else {
962 my_output_message(j_common_ptr(&info));
963 return false;
964 }
965 }
966 else if (state == Error)
967 return false;
968 return true;
969}
970
972{
973 if (state == Ready)
974 readJpegHeader(q->device());
975
976 if (state == ReadHeader)
977 {
979 if (success) {
980 for (int i = 0; i < readTexts.size()-1; i+=2)
981 image->setText(readTexts.at(i), readTexts.at(i+1));
982
983 if (!iccProfile.isEmpty())
985
987 return true;
988 }
989
990 state = Error;
991 }
992
993 return false;
994}
995
999
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
1023{
1024 delete d;
1025}
1026
1028{
1029 if (d->state == QJpegHandlerPrivate::Ready && !canRead(device()))
1030 return false;
1031
1033 setFormat("jpeg");
1034 return true;
1035 }
1036
1037 return false;
1038}
1039
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(buffer, 2) != 2)
1049 return false;
1050 return uchar(buffer[0]) == 0xff && uchar(buffer[1]) == 0xd8;
1051}
1052
1054{
1055 if (!canRead())
1056 return false;
1057 return d->read(image);
1058}
1059
1060extern void qt_imageTransform(QImage &src, QImageIOHandler::Transformations orient);
1061
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(img, d->transformation);
1068 return write_jpeg_image(img, device(), d->quality, d->description, d->optimize, d->progressive);
1069 }
1070 return write_jpeg_image(image, device(), d->quality, d->description, d->optimize, d->progressive);
1071}
1072
1074{
1075 return option == Quality
1076 || option == ScaledSize
1078 || option == ClipRect
1079 || option == Description
1080 || option == Size
1081 || option == ImageFormat
1085}
1086
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());
1100 return d->description;
1101 case Size:
1102 d->readJpegHeader(device());
1103 return d->size;
1104 case ImageFormat:
1105 d->readJpegHeader(device());
1106 return d->format;
1107 case OptimizedWrite:
1108 return d->optimize;
1110 return d->progressive;
1112 d->readJpegHeader(device());
1113 return int(d->transformation);
1114 default:
1115 break;
1116 }
1117
1118 return QVariant();
1119}
1120
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;
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
IOBluetoothDevice * device
\inmodule QtCore \reentrant
Definition qbuffer.h:16
\inmodule QtCore
Definition qbytearray.h:57
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:474
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
void truncate(qsizetype pos)
Truncates the byte array at index position pos.
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:106
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
QByteArray mid(qsizetype index, qsizetype len=-1) const
Returns a byte array containing len bytes from this byte array, starting at position pos.
static QColorSpace fromIccProfile(const QByteArray &iccProfile)
Creates a QColorSpace from ICC profile iccProfile.
\inmodule QtCore\reentrant
Definition qdatastream.h:30
\inmodule QtCore \reentrant
Definition qiodevice.h:34
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
qint64 peek(char *data, qint64 maxlen)
The QImageIOHandler class defines the common image I/O interface for all image formats in Qt.
ImageOption
This enum describes the different options supported by QImageIOHandler.
static bool allocateImage(QSize size, QImage::Format format, QImage *image)
QIODevice * device() const
Returns the device currently assigned to the QImageIOHandler.
void setFormat(const QByteArray &format)
Sets the format of the QImageIOHandler to format.
\inmodule QtGui
Definition qimage.h:37
void setDotsPerMeterY(int)
Sets the number of pixels that fit vertically in a physical meter, to y.
Definition qimage.cpp:4120
QImage scaled(int w, int h, Qt::AspectRatioMode aspectMode=Qt::IgnoreAspectRatio, Qt::TransformationMode mode=Qt::FastTransformation) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qimage.h:208
uchar * scanLine(int)
Returns a pointer to the pixel data at the scanline with index i.
Definition qimage.cpp:1615
QImage copy(const QRect &rect=QRect()) const
Returns a sub-area of the image as a new image.
bool isNull() const
Returns true if it is a null image, otherwise returns false.
Definition qimage.cpp:1197
Format
The following image formats are available in Qt.
Definition qimage.h:41
@ Format_Grayscale16
Definition qimage.h:70
@ Format_Alpha8
Definition qimage.h:65
@ Format_RGB888
Definition qimage.h:55
@ Format_RGB32
Definition qimage.h:46
@ Format_Invalid
Definition qimage.h:42
@ Format_MonoLSB
Definition qimage.h:44
@ Format_Mono
Definition qimage.h:43
@ Format_Indexed8
Definition qimage.h:45
@ Format_ARGB32_Premultiplied
Definition qimage.h:48
@ Format_ARGB32
Definition qimage.h:47
@ Format_Grayscale8
Definition qimage.h:66
const uchar * constScanLine(int) const
Returns a pointer to the pixel data at the scanline with index i.
Definition qimage.cpp:1657
void setDotsPerMeterX(int)
Sets the number of pixels that fit horizontally in a physical meter, to x.
Definition qimage.cpp:4098
QImage::Format format
bool readJpegHeader(QIODevice *)
struct my_error_mgr err
struct jpeg_decompress_struct info
Rgb888ToRgb32Converter rgb888ToRgb32ConverterPtr
bool read(QImage *image)
QJpegHandlerPrivate(QJpegHandler *qq)
struct my_jpeg_source_mgr * iod_src
QImageIOHandler::Transformations transformation
bool supportsOption(ImageOption option) const override
Returns true if the QImageIOHandler supports the option option; otherwise returns false.
bool read(QImage *image) override
Read an image from the device, and stores it in image.
void setOption(ImageOption option, const QVariant &value) override
Sets the option option with the value value.
QVariant option(ImageOption option) const override
Returns the value assigned to option as a QVariant.
bool write(const QImage &image) override
Writes the image image to the assigned device.
bool canRead() const override
Returns true if an image can be read from the device (i.e., the image format is supported,...
Definition qlist.h:74
Definition qmap.h:186
\inmodule QtCore\reentrant
Definition qrect.h:30
constexpr bool isEmpty() const noexcept
Returns true if the rectangle is empty, otherwise returns false.
Definition qrect.h:166
constexpr int height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:238
QRect intersected(const QRect &other) const noexcept
Definition qrect.h:414
constexpr int bottom() const noexcept
Returns the y-coordinate of the rectangle's bottom edge.
Definition qrect.h:181
constexpr QPoint topLeft() const noexcept
Returns the position of the rectangle's top-left corner.
Definition qrect.h:220
constexpr int x() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:184
constexpr QSize size() const noexcept
Returns the size of the rectangle.
Definition qrect.h:241
constexpr void translate(int dx, int dy) noexcept
Moves the rectangle dx along the x axis and dy along the y axis, relative to the current position.
Definition qrect.h:244
constexpr int width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:235
constexpr int y() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:187
constexpr int right() const noexcept
Returns the x-coordinate of the rectangle's right edge.
Definition qrect.h:178
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:132
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:129
constexpr bool isEmpty() const noexcept
Returns true if either of the width and height is less than or equal to 0; otherwise returns false.
Definition qsize.h:123
constexpr bool isValid() const noexcept
Returns true if both the width and height is equal to or greater than 0; otherwise returns false.
Definition qsize.h:126
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
iterator begin()
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first character in the string.
Definition qstring.h:1197
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5857
iterator end()
Returns an \l{STL-style iterators}{STL-style iterator} pointing just after the last character in the ...
Definition qstring.h:1205
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
\inmodule QtCore
Definition qvariant.h:64
#define this
Definition dialogs.cpp:9
QString text
QSet< QString >::iterator it
QPixmap pix
Combined button and popup list for selecting options.
@ FastTransformation
@ SmoothTransformation
@ IgnoreAspectRatio
Definition image.cpp:4
#define rgb(r, g, b)
Definition qcolor.cpp:124
#define QT_FASTCALL
#define QT_WARNING_DISABLE_GCC(text)
AudioChannelLayoutTag tag
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
EGLStreamKHR stream
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:281
Q_GUI_EXPORT void qt_imageTransform(QImage &src, QImageIOHandler::Transformations orient)
Definition qimage.cpp:5739
QMap< QString, QString > qt_getImageText(const QImage &image, const QString &description)
Definition qimage.cpp:5753
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 bool write_jpeg_image(const QImage &image, QIODevice *device, int sourceQuality, const QString &description, bool optimize, bool progressive)
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)
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 readExifHeader(QDataStream &stream)
void(QT_FASTCALL * Rgb888ToRgb32Converter)(quint32 *dst, const uchar *src, int len)
static bool ensureValidImage(QImage *dest, struct jpeg_decompress_struct *info, const QSize &size)
static const int max_buf
static constexpr int maxMarkerSize
static bool read_jpeg_format(QImage::Format &format, j_decompress_ptr cinfo)
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:5739
static void qt_term_source(j_decompress_ptr cinfo)
static bool read_jpeg_size(int &w, int &h, j_decompress_ptr cinfo)
static int getExifOrientation(QByteArray &exifData)
Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_ssse3(quint32 *dst, const uchar *src, int len)
Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_neon(quint32 *dst, const uchar *src, int len)
void qt_convert_rgb888_to_rgb32_mips_dspr2_asm(quint32 *dst, const uchar *src, int len)
static boolean qt_empty_output_buffer(j_compress_ptr cinfo)
static boolean qt_fill_input_buffer(j_decompress_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)
QT_BEGIN_NAMESPACE Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32(quint32 *dst, const uchar *src, int len)
static QImageIOHandler::Transformations exif2Qt(int exifOrientation)
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
int qCeil(T v)
Definition qmath.h:36
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
GLint GLint GLint GLint GLint x
[0]
GLuint64 key
GLfloat GLfloat GLfloat w
[0]
GLint GLsizei GLsizei height
GLint GLenum GLint components
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLuint GLuint end
GLfloat GLfloat f
GLenum src
GLenum GLuint buffer
GLint GLsizei width
GLenum type
GLenum GLenum dst
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint GLintptr offset
const GLchar * marker
GLfloat n
GLint GLsizei GLsizei GLenum format
GLint y
GLfloat GLfloat GLfloat GLfloat h
GLuint GLfloat * val
GLint void * img
Definition qopenglext.h:233
GLenum GLsizei len
GLuint in
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLenum GLenum GLsizei void * row
GLuint64EXT * result
[6]
GLdouble s
[6]
Definition qopenglext.h:235
GLuint GLenum option
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
constexpr bool qIsGray(QRgb rgb)
Definition qrgb.h:42
QT_BEGIN_NAMESPACE typedef unsigned int QRgb
Definition qrgb.h:13
constexpr QRgb qRgb(int r, int g, int b)
Definition qrgb.h:30
constexpr int qRed(QRgb rgb)
Definition qrgb.h:18
constexpr int qGreen(QRgb rgb)
Definition qrgb.h:21
constexpr int qBlue(QRgb rgb)
Definition qrgb.h:24
#define qCpuHasFeature(feature)
Definition qsimd_p.h:378
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
unsigned int quint32
Definition qtypes.h:45
unsigned char uchar
Definition qtypes.h:27
unsigned short quint16
Definition qtypes.h:43
long long qint64
Definition qtypes.h:55
double qreal
Definition qtypes.h:92
QFileInfo info(fileName)
[8]
QTextStream out(stdout)
[7]
QObject::connect nullptr
\inmodule QtCore \reentrant
Definition qchar.h:17
jmp_buf setjmp_buffer
my_jpeg_destination_mgr(QIODevice *)
my_jpeg_source_mgr(QIODevice *device)
const QBuffer * memDevice
JOCTET buffer[max_buf]