/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qjpeghandler_p.h" #include #include #include #include #include #include #include #include #include #include #include // for qt_getImageText #include // jpeglib needs this to be pre-included #include #ifdef FAR #undef FAR #endif // including jpeglib.h seems to be a little messy extern "C" { // jpeglib.h->jmorecfg.h tries to typedef int boolean; but this conflicts with // some Windows headers that may or may not have been included #ifdef HAVE_BOOLEAN # undef HAVE_BOOLEAN #endif #define boolean jboolean #define XMD_H // shut JPEGlib up #include #ifdef const # undef const // remove crazy C hackery in jconfig.h #endif } QT_BEGIN_NAMESPACE QT_WARNING_DISABLE_GCC("-Wclobbered") Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32(quint32 *dst, const uchar *src, int len); typedef void (QT_FASTCALL *Rgb888ToRgb32Converter)(quint32 *dst, const uchar *src, int len); struct my_error_mgr : public jpeg_error_mgr { jmp_buf setjmp_buffer; }; extern "C" { static void my_error_exit (j_common_ptr cinfo) { my_error_mgr* myerr = (my_error_mgr*) cinfo->err; char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message)(cinfo, buffer); qWarning("%s", buffer); longjmp(myerr->setjmp_buffer, 1); } static void my_output_message(j_common_ptr cinfo) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message)(cinfo, buffer); qWarning("%s", buffer); } } static const int max_buf = 4096; struct my_jpeg_source_mgr : public jpeg_source_mgr { // Nothing dynamic - cannot rely on destruction over longjump QIODevice *device; JOCTET buffer[max_buf]; const QBuffer *memDevice; public: my_jpeg_source_mgr(QIODevice *device); }; extern "C" { static void qt_init_source(j_decompress_ptr) { } static boolean qt_fill_input_buffer(j_decompress_ptr cinfo) { my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src; qint64 num_read = 0; if (src->memDevice) { src->next_input_byte = (const JOCTET *)(src->memDevice->data().constData() + src->memDevice->pos()); num_read = src->memDevice->data().size() - src->memDevice->pos(); src->device->seek(src->memDevice->data().size()); } else { src->next_input_byte = src->buffer; num_read = src->device->read((char*)src->buffer, max_buf); } if (num_read <= 0) { // Insert a fake EOI marker - as per jpeglib recommendation src->next_input_byte = src->buffer; src->buffer[0] = (JOCTET) 0xFF; src->buffer[1] = (JOCTET) JPEG_EOI; src->bytes_in_buffer = 2; } else { src->bytes_in_buffer = num_read; } return TRUE; } static void qt_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src; // `dumb' implementation from jpeglib /* Just a dumb implementation for now. Could use fseek() except * it doesn't work on pipes. Not clear that being smart is worth * any trouble anyway --- large skips are infrequent. */ if (num_bytes > 0) { while (num_bytes > (long) src->bytes_in_buffer) { // Should not happen in case of memDevice num_bytes -= (long) src->bytes_in_buffer; (void) qt_fill_input_buffer(cinfo); /* note we assume that qt_fill_input_buffer will never return false, * so suspension need not be handled. */ } src->next_input_byte += (size_t) num_bytes; src->bytes_in_buffer -= (size_t) num_bytes; } } static void qt_term_source(j_decompress_ptr cinfo) { my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src; if (!src->device->isSequential()) src->device->seek(src->device->pos() - src->bytes_in_buffer); } } inline my_jpeg_source_mgr::my_jpeg_source_mgr(QIODevice *device) { jpeg_source_mgr::init_source = qt_init_source; jpeg_source_mgr::fill_input_buffer = qt_fill_input_buffer; jpeg_source_mgr::skip_input_data = qt_skip_input_data; jpeg_source_mgr::resync_to_restart = jpeg_resync_to_restart; jpeg_source_mgr::term_source = qt_term_source; this->device = device; memDevice = qobject_cast(device); bytes_in_buffer = 0; next_input_byte = buffer; } inline static bool read_jpeg_size(int &w, int &h, j_decompress_ptr cinfo) { (void) jpeg_calc_output_dimensions(cinfo); w = cinfo->output_width; h = cinfo->output_height; return true; } #define HIGH_QUALITY_THRESHOLD 50 inline static bool read_jpeg_format(QImage::Format &format, j_decompress_ptr cinfo) { bool result = true; switch (cinfo->output_components) { case 1: format = QImage::Format_Grayscale8; break; case 3: case 4: format = QImage::Format_RGB32; break; default: result = false; break; } cinfo->output_scanline = cinfo->output_height; return result; } static bool ensureValidImage(QImage *dest, struct jpeg_decompress_struct *info, const QSize& size) { QImage::Format format; switch (info->output_components) { case 1: format = QImage::Format_Grayscale8; break; case 3: case 4: format = QImage::Format_RGB32; break; default: return false; // unsupported format } if (dest->size() != size || dest->format() != format) *dest = QImage(size, format); return !dest->isNull(); } 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 ) { if (!setjmp(err->setjmp_buffer)) { // -1 means default quality. if (quality < 0) quality = 75; // If possible, merge the scaledClipRect into either scaledSize // or clipRect to avoid doing a separate scaled clipping pass. // Best results are achieved by clipping before scaling, not after. if (!scaledClipRect.isEmpty()) { if (scaledSize.isEmpty() && clipRect.isEmpty()) { // No clipping or scaling before final clip. clipRect = scaledClipRect; scaledClipRect = QRect(); } else if (scaledSize.isEmpty()) { // Clipping, but no scaling: combine the clip regions. scaledClipRect.translate(clipRect.topLeft()); clipRect = scaledClipRect.intersected(clipRect); scaledClipRect = QRect(); } else if (clipRect.isEmpty()) { // No clipping, but scaling: if we can map back to an // integer pixel boundary, then clip before scaling. if ((info->image_width % scaledSize.width()) == 0 && (info->image_height % scaledSize.height()) == 0) { int x = scaledClipRect.x() * info->image_width / scaledSize.width(); int y = scaledClipRect.y() * info->image_height / scaledSize.height(); int width = (scaledClipRect.right() + 1) * info->image_width / scaledSize.width() - x; int height = (scaledClipRect.bottom() + 1) * info->image_height / scaledSize.height() - y; clipRect = QRect(x, y, width, height); scaledSize = scaledClipRect.size(); scaledClipRect = QRect(); } } else { // Clipping and scaling: too difficult to figure out, // and not a likely use case, so do it the long way. } } // Determine the scale factor to pass to libjpeg for quick downscaling. if (!scaledSize.isEmpty() && info->image_width && info->image_height) { if (clipRect.isEmpty()) { double f = qMin(double(info->image_width) / scaledSize.width(), double(info->image_height) / scaledSize.height()); // libjpeg supports M/8 scaling with M=[1,16]. All downscaling factors // are a speed improvement, but upscaling during decode is slower. info->scale_num = qBound(1, qCeil(8/f), 8); info->scale_denom = 8; } else { info->scale_denom = qMin(clipRect.width() / scaledSize.width(), clipRect.height() / scaledSize.height()); // Only scale by powers of two when clipping so we can // keep the exact pixel boundaries if (info->scale_denom < 2) info->scale_denom = 1; else if (info->scale_denom < 4) info->scale_denom = 2; else if (info->scale_denom < 8) info->scale_denom = 4; else info->scale_denom = 8; info->scale_num = 1; // Correct the scale factor so that we clip accurately. // It is recommended that the clip rectangle be aligned // on an 8-pixel boundary for best performance. while (info->scale_denom > 1 && ((clipRect.x() % info->scale_denom) != 0 || (clipRect.y() % info->scale_denom) != 0 || (clipRect.width() % info->scale_denom) != 0 || (clipRect.height() % info->scale_denom) != 0)) { info->scale_denom /= 2; } } } // If high quality not required, use fast decompression if( quality < HIGH_QUALITY_THRESHOLD ) { info->dct_method = JDCT_IFAST; info->do_fancy_upsampling = FALSE; } (void) jpeg_calc_output_dimensions(info); // Determine the clip region to extract. QRect imageRect(0, 0, info->output_width, info->output_height); QRect clip; if (clipRect.isEmpty()) { clip = imageRect; } else if (info->scale_denom == info->scale_num) { clip = clipRect.intersected(imageRect); } else { // The scale factor was corrected above to ensure that // we don't miss pixels when we scale the clip rectangle. clip = QRect(clipRect.x() / int(info->scale_denom), clipRect.y() / int(info->scale_denom), clipRect.width() / int(info->scale_denom), clipRect.height() / int(info->scale_denom)); clip = clip.intersected(imageRect); } // Allocate memory for the clipped QImage. if (!ensureValidImage(outImage, info, clip.size())) longjmp(err->setjmp_buffer, 1); // Avoid memcpy() overhead if grayscale with no clipping. bool quickGray = (info->output_components == 1 && clip == imageRect); if (!quickGray) { // Ask the jpeg library to allocate a temporary row. // The library will automatically delete it for us later. // The libjpeg docs say we should do this before calling // jpeg_start_decompress(). We can't use "new" here // because we are inside the setjmp() block and an error // in the jpeg input stream would cause a memory leak. JSAMPARRAY rows = (info->mem->alloc_sarray) ((j_common_ptr)info, JPOOL_IMAGE, info->output_width * info->output_components, 1); (void) jpeg_start_decompress(info); while (info->output_scanline < info->output_height) { int y = int(info->output_scanline) - clip.y(); if (y >= clip.height()) break; // We've read the entire clip region, so abort. (void) jpeg_read_scanlines(info, rows, 1); if (y < 0) continue; // Haven't reached the starting line yet. if (info->output_components == 3) { uchar *in = rows[0] + clip.x() * 3; QRgb *out = (QRgb*)outImage->scanLine(y); converter(out, in, clip.width()); } else if (info->out_color_space == JCS_CMYK) { // Convert CMYK->RGB. uchar *in = rows[0] + clip.x() * 4; QRgb *out = (QRgb*)outImage->scanLine(y); for (int i = 0; i < clip.width(); ++i) { int k = in[3]; *out++ = qRgb(k * in[0] / 255, k * in[1] / 255, k * in[2] / 255); in += 4; } } else if (info->output_components == 1) { // Grayscale. memcpy(outImage->scanLine(y), rows[0] + clip.x(), clip.width()); } } } else { // Load unclipped grayscale data directly into the QImage. (void) jpeg_start_decompress(info); while (info->output_scanline < info->output_height) { uchar *row = outImage->scanLine(info->output_scanline); (void) jpeg_read_scanlines(info, &row, 1); } } if (info->output_scanline == info->output_height) (void) jpeg_finish_decompress(info); if (info->density_unit == 1) { outImage->setDotsPerMeterX(int(100. * info->X_density / 2.54)); outImage->setDotsPerMeterY(int(100. * info->Y_density / 2.54)); } else if (info->density_unit == 2) { outImage->setDotsPerMeterX(int(100. * info->X_density)); outImage->setDotsPerMeterY(int(100. * info->Y_density)); } if (scaledSize.isValid() && scaledSize != clip.size()) { *outImage = outImage->scaled(scaledSize, Qt::IgnoreAspectRatio, quality >= HIGH_QUALITY_THRESHOLD ? Qt::SmoothTransformation : Qt::FastTransformation); } if (!scaledClipRect.isEmpty()) *outImage = outImage->copy(scaledClipRect); return !outImage->isNull(); } else return false; } struct my_jpeg_destination_mgr : public jpeg_destination_mgr { // Nothing dynamic - cannot rely on destruction over longjump QIODevice *device; JOCTET buffer[max_buf]; public: my_jpeg_destination_mgr(QIODevice *); }; extern "C" { static void qt_init_destination(j_compress_ptr) { } static boolean qt_empty_output_buffer(j_compress_ptr cinfo) { my_jpeg_destination_mgr* dest = (my_jpeg_destination_mgr*)cinfo->dest; int written = dest->device->write((char*)dest->buffer, max_buf); if (written == -1) (*cinfo->err->error_exit)((j_common_ptr)cinfo); dest->next_output_byte = dest->buffer; dest->free_in_buffer = max_buf; return TRUE; } static void qt_term_destination(j_compress_ptr cinfo) { my_jpeg_destination_mgr* dest = (my_jpeg_destination_mgr*)cinfo->dest; qint64 n = max_buf - dest->free_in_buffer; qint64 written = dest->device->write((char*)dest->buffer, n); if (written == -1) (*cinfo->err->error_exit)((j_common_ptr)cinfo); } } inline my_jpeg_destination_mgr::my_jpeg_destination_mgr(QIODevice *device) { jpeg_destination_mgr::init_destination = qt_init_destination; jpeg_destination_mgr::empty_output_buffer = qt_empty_output_buffer; jpeg_destination_mgr::term_destination = qt_term_destination; this->device = device; next_output_byte = buffer; free_in_buffer = max_buf; } static constexpr int maxMarkerSize = 65533; static inline void set_text(const QImage &image, j_compress_ptr cinfo, const QString &description) { const QMap text = qt_getImageText(image, description); for (auto it = text.begin(), end = text.end(); it != end; ++it) { QByteArray comment = it.key().toUtf8(); if (!comment.isEmpty()) comment += ": "; comment += it.value().toUtf8(); if (comment.length() > maxMarkerSize) comment.truncate(maxMarkerSize); jpeg_write_marker(cinfo, JPEG_COM, (const JOCTET *)comment.constData(), comment.size()); } } static inline void write_icc_profile(const QImage &image, j_compress_ptr cinfo) { const QByteArray iccProfile = image.colorSpace().iccProfile(); if (iccProfile.isEmpty()) return; const QByteArray iccSignature("ICC_PROFILE", 12); constexpr int maxIccMarkerSize = maxMarkerSize - (12 + 2); int index = 0; const int markers = (iccProfile.size() + (maxIccMarkerSize - 1)) / maxIccMarkerSize; Q_ASSERT(markers < 256); for (int marker = 1; marker <= markers; ++marker) { const int len = std::min(iccProfile.size() - index, maxIccMarkerSize); const QByteArray block = iccSignature + QByteArray(1, char(marker)) + QByteArray(1, char(markers)) + iccProfile.mid(index, len); jpeg_write_marker(cinfo, JPEG_APP0 + 2, reinterpret_cast(block.constData()), block.size()); index += len; } } static bool do_write_jpeg_image(struct jpeg_compress_struct &cinfo, JSAMPROW *row_pointer, const QImage &image, QIODevice *device, int sourceQuality, const QString &description, bool optimize, bool progressive) { bool success = false; const QVector cmap = image.colorTable(); if (image.format() == QImage::Format_Invalid || image.format() == QImage::Format_Alpha8) return false; struct my_jpeg_destination_mgr *iod_dest = new my_jpeg_destination_mgr(device); struct my_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jerr.error_exit = my_error_exit; jerr.output_message = my_output_message; if (!setjmp(jerr.setjmp_buffer)) { // WARNING: // this if loop is inside a setjmp/longjmp branch // do not create C++ temporaries here because the destructor may never be called // if you allocate memory, make sure that you can free it (row_pointer[0]) jpeg_create_compress(&cinfo); cinfo.dest = iod_dest; cinfo.image_width = image.width(); cinfo.image_height = image.height(); bool gray = false; switch (image.format()) { case QImage::Format_Mono: case QImage::Format_MonoLSB: case QImage::Format_Indexed8: gray = true; for (int i = image.colorCount(); gray && i; i--) { gray = gray & qIsGray(cmap[i-1]); } cinfo.input_components = gray ? 1 : 3; cinfo.in_color_space = gray ? JCS_GRAYSCALE : JCS_RGB; break; case QImage::Format_Grayscale8: gray = true; cinfo.input_components = 1; cinfo.in_color_space = JCS_GRAYSCALE; break; default: cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; } jpeg_set_defaults(&cinfo); qreal diffInch = qAbs(image.dotsPerMeterX()*2.54/100. - qRound(image.dotsPerMeterX()*2.54/100.)) + qAbs(image.dotsPerMeterY()*2.54/100. - qRound(image.dotsPerMeterY()*2.54/100.)); qreal diffCm = (qAbs(image.dotsPerMeterX()/100. - qRound(image.dotsPerMeterX()/100.)) + qAbs(image.dotsPerMeterY()/100. - qRound(image.dotsPerMeterY()/100.)))*2.54; if (diffInch < diffCm) { cinfo.density_unit = 1; // dots/inch cinfo.X_density = qRound(image.dotsPerMeterX()*2.54/100.); cinfo.Y_density = qRound(image.dotsPerMeterY()*2.54/100.); } else { cinfo.density_unit = 2; // dots/cm cinfo.X_density = (image.dotsPerMeterX()+50) / 100; cinfo.Y_density = (image.dotsPerMeterY()+50) / 100; } if (optimize) cinfo.optimize_coding = true; if (progressive) jpeg_simple_progression(&cinfo); int quality = sourceQuality >= 0 ? qMin(int(sourceQuality),100) : 75; jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); jpeg_start_compress(&cinfo, TRUE); set_text(image, &cinfo, description); if (cinfo.in_color_space == JCS_RGB) write_icc_profile(image, &cinfo); row_pointer[0] = new uchar[cinfo.image_width*cinfo.input_components]; int w = cinfo.image_width; while (cinfo.next_scanline < cinfo.image_height) { uchar *row = row_pointer[0]; switch (image.format()) { case QImage::Format_Mono: case QImage::Format_MonoLSB: if (gray) { const uchar* data = image.constScanLine(cinfo.next_scanline); if (image.format() == QImage::Format_MonoLSB) { for (int i=0; i> 3)) & (1 << (i & 7))); row[i] = qRed(cmap[bit]); } } else { for (int i=0; i> 3)) & (1 << (7 -(i & 7)))); row[i] = qRed(cmap[bit]); } } } else { const uchar* data = image.constScanLine(cinfo.next_scanline); if (image.format() == QImage::Format_MonoLSB) { for (int i=0; i> 3)) & (1 << (i & 7))); *row++ = qRed(cmap[bit]); *row++ = qGreen(cmap[bit]); *row++ = qBlue(cmap[bit]); } } else { for (int i=0; i> 3)) & (1 << (7 -(i & 7)))); *row++ = qRed(cmap[bit]); *row++ = qGreen(cmap[bit]); *row++ = qBlue(cmap[bit]); } } } break; case QImage::Format_Indexed8: if (gray) { const uchar* pix = image.constScanLine(cinfo.next_scanline); for (int i=0; ipos()); // read byte order marker stream >> val; if (val == 0x4949) // 'II' == Intel stream.setByteOrder(QDataStream::LittleEndian); else if (val == 0x4d4d) // 'MM' == Motorola stream.setByteOrder(QDataStream::BigEndian); else return -1; // unknown byte order // confirm byte order stream >> val; if (val != 0x2a) return -1; stream >> offset; // read IFD for (int n = 0; n < maxIfdCount; ++n) { quint16 numEntries; const qint64 bytesToSkip = offset - (stream.device()->pos() - headerStart); if (bytesToSkip < 0 || (offset + headerStart >= exifData.size())) { // disallow going backwards, though it's permitted in the spec return -1; } else if (bytesToSkip != 0) { // seek to the IFD if (!stream.device()->seek(offset + headerStart)) return -1; } stream >> numEntries; for (; numEntries > 0 && stream.status() == QDataStream::Ok; --numEntries) { quint16 tag; quint16 type; quint32 components; quint16 value; quint16 dummy; stream >> tag >> type >> components >> value >> dummy; if (tag == 0x0112) { // Tag Exif.Image.Orientation if (components != 1) return -1; if (type != 3) // we are expecting it to be an unsigned short return -1; if (value < 1 || value > 8) // check for valid range return -1; // It is possible to include the orientation multiple times. // Right now the first value is returned. return value; } } // read offset to next IFD stream >> offset; if (stream.status() != QDataStream::Ok) return -1; if (offset == 0) // this is the last IFD return 0; // No Exif orientation was found } // too many IFDs return -1; } static QImageIOHandler::Transformations exif2Qt(int exifOrientation) { switch (exifOrientation) { case 1: // normal return QImageIOHandler::TransformationNone; case 2: // mirror horizontal return QImageIOHandler::TransformationMirror; case 3: // rotate 180 return QImageIOHandler::TransformationRotate180; case 4: // mirror vertical return QImageIOHandler::TransformationFlip; case 5: // mirror horizontal and rotate 270 CW return QImageIOHandler::TransformationFlipAndRotate90; case 6: // rotate 90 CW return QImageIOHandler::TransformationRotate90; case 7: // mirror horizontal and rotate 90 CW return QImageIOHandler::TransformationMirrorAndRotate90; case 8: // rotate 270 CW return QImageIOHandler::TransformationRotate270; } qWarning("Invalid EXIF orientation"); return QImageIOHandler::TransformationNone; } /*! \internal */ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device) { if(state == Ready) { state = Error; iod_src = new my_jpeg_source_mgr(device); info.err = jpeg_std_error(&err); err.error_exit = my_error_exit; err.output_message = my_output_message; jpeg_create_decompress(&info); info.src = iod_src; if (!setjmp(err.setjmp_buffer)) { jpeg_save_markers(&info, JPEG_COM, 0xFFFF); jpeg_save_markers(&info, JPEG_APP0 + 1, 0xFFFF); // Exif uses APP1 marker jpeg_save_markers(&info, JPEG_APP0 + 2, 0xFFFF); // ICC uses APP2 marker (void) jpeg_read_header(&info, TRUE); int width = 0; int height = 0; read_jpeg_size(width, height, &info); size = QSize(width, height); format = QImage::Format_Invalid; read_jpeg_format(format, &info); QByteArray exifData; for (jpeg_saved_marker_ptr marker = info.marker_list; marker != nullptr; marker = marker->next) { if (marker->marker == JPEG_COM) { QString key, value; QString s = QString::fromUtf8((const char *)marker->data, marker->data_length); int index = s.indexOf(QLatin1String(": ")); if (index == -1 || s.indexOf(QLatin1Char(' ')) < index) { key = QLatin1String("Description"); value = s; } else { key = s.left(index); value = s.mid(index + 2); } if (!description.isEmpty()) description += QLatin1String("\n\n"); description += key + QLatin1String(": ") + value.simplified(); readTexts.append(key); readTexts.append(value); } else if (marker->marker == JPEG_APP0 + 1) { exifData.append((const char*)marker->data, marker->data_length); } else if (marker->marker == JPEG_APP0 + 2) { if (marker->data_length > 128 + 4 + 14 && strcmp((const char *)marker->data, "ICC_PROFILE") == 0) { iccProfile.append((const char*)marker->data + 14, marker->data_length - 14); } } } if (!exifData.isEmpty()) { // Exif data present int exifOrientation = getExifOrientation(exifData); if (exifOrientation > 0) transformation = exif2Qt(exifOrientation); } state = ReadHeader; return true; } else { return false; } } else if(state == Error) return false; return true; } bool QJpegHandlerPrivate::read(QImage *image) { if(state == Ready) readJpegHeader(q->device()); if(state == ReadHeader) { bool success = read_jpeg_image(image, scaledSize, scaledClipRect, clipRect, quality, rgb888ToRgb32ConverterPtr, &info, &err); if (success) { for (int i = 0; i < readTexts.size()-1; i+=2) image->setText(readTexts.at(i), readTexts.at(i+1)); if (!iccProfile.isEmpty()) image->setColorSpace(QColorSpace::fromIccProfile(iccProfile)); state = ReadingEnd; return true; } state = Error; } return false; } Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_neon(quint32 *dst, const uchar *src, int len); Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_ssse3(quint32 *dst, const uchar *src, int len); extern "C" void qt_convert_rgb888_to_rgb32_mips_dspr2_asm(quint32 *dst, const uchar *src, int len); QJpegHandler::QJpegHandler() : d(new QJpegHandlerPrivate(this)) { #if defined(__ARM_NEON__) // from qimage_neon.cpp if (qCpuHasFeature(NEON)) d->rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_neon; #endif #if defined(QT_COMPILER_SUPPORTS_SSSE3) // from qimage_ssse3.cpps if (qCpuHasFeature(SSSE3)) { d->rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_ssse3; } #endif // QT_COMPILER_SUPPORTS_SSSE3 #if defined(QT_COMPILER_SUPPORTS_MIPS_DSPR2) if (qCpuHasFeature(DSPR2)) { d->rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_mips_dspr2_asm; } #endif // QT_COMPILER_SUPPORTS_DSPR2 } QJpegHandler::~QJpegHandler() { delete d; } bool QJpegHandler::canRead() const { if(d->state == QJpegHandlerPrivate::Ready && !canRead(device())) return false; if (d->state != QJpegHandlerPrivate::Error && d->state != QJpegHandlerPrivate::ReadingEnd) { setFormat("jpeg"); return true; } return false; } bool QJpegHandler::canRead(QIODevice *device) { if (!device) { qWarning("QJpegHandler::canRead() called with no device"); return false; } char buffer[2]; if (device->peek(buffer, 2) != 2) return false; return uchar(buffer[0]) == 0xff && uchar(buffer[1]) == 0xd8; } bool QJpegHandler::read(QImage *image) { if (!canRead()) return false; return d->read(image); } extern void qt_imageTransform(QImage &src, QImageIOHandler::Transformations orient); bool QJpegHandler::write(const QImage &image) { if (d->transformation != QImageIOHandler::TransformationNone) { // We don't support writing EXIF headers so apply the transform to the data. QImage img = image; qt_imageTransform(img, d->transformation); return write_jpeg_image(img, device(), d->quality, d->description, d->optimize, d->progressive); } return write_jpeg_image(image, device(), d->quality, d->description, d->optimize, d->progressive); } bool QJpegHandler::supportsOption(ImageOption option) const { return option == Quality || option == ScaledSize || option == ScaledClipRect || option == ClipRect || option == Description || option == Size || option == ImageFormat || option == OptimizedWrite || option == ProgressiveScanWrite || option == ImageTransformation; } QVariant QJpegHandler::option(ImageOption option) const { switch(option) { case Quality: return d->quality; case ScaledSize: return d->scaledSize; case ScaledClipRect: return d->scaledClipRect; case ClipRect: return d->clipRect; case Description: d->readJpegHeader(device()); return d->description; case Size: d->readJpegHeader(device()); return d->size; case ImageFormat: d->readJpegHeader(device()); return d->format; case OptimizedWrite: return d->optimize; case ProgressiveScanWrite: return d->progressive; case ImageTransformation: d->readJpegHeader(device()); return int(d->transformation); default: break; } return QVariant(); } void QJpegHandler::setOption(ImageOption option, const QVariant &value) { switch(option) { case Quality: d->quality = value.toInt(); break; case ScaledSize: d->scaledSize = value.toSize(); break; case ScaledClipRect: d->scaledClipRect = value.toRect(); break; case ClipRect: d->clipRect = value.toRect(); break; case Description: d->description = value.toString(); break; case OptimizedWrite: d->optimize = value.toBool(); break; case ProgressiveScanWrite: d->progressive = value.toBool(); break; case ImageTransformation: { int transformation = value.toInt(); if (transformation > 0 && transformation < 8) d->transformation = QImageIOHandler::Transformations(transformation); } default: break; } } QT_END_NAMESPACE