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