From 9157087334186ff3ef811f2ec234a3bf5d4a4889 Mon Sep 17 00:00:00 2001 From: Rainer Keller Date: Fri, 24 Oct 2014 15:54:28 +0200 Subject: Rotate images according to Exif orientation Task-number: QTBUG-37946 Change-Id: I181f88acdff0ef76aa02e968dc6afca1ed495f38 Reviewed-by: aavit --- src/gui/image/qjpeghandler.cpp | 150 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 1 deletion(-) (limited to 'src/gui/image') diff --git a/src/gui/image/qjpeghandler.cpp b/src/gui/image/qjpeghandler.cpp index 481101db93..7ca8969798 100644 --- a/src/gui/image/qjpeghandler.cpp +++ b/src/gui/image/qjpeghandler.cpp @@ -726,7 +726,7 @@ public: }; QJpegHandlerPrivate(QJpegHandler *qq) - : quality(75), iod_src(0), state(Ready), q(qq) + : quality(75), exifOrientation(1), iod_src(0), state(Ready), q(qq) {} ~QJpegHandlerPrivate() @@ -741,8 +741,10 @@ public: bool readJpegHeader(QIODevice*); bool read(QImage *image); + void applyExifOrientation(QImage *image); int quality; + int exifOrientation; QVariant size; QImage::Format format; QSize scaledSize; @@ -760,6 +762,97 @@ public: QJpegHandler *q; }; +static bool readExifHeader(QDataStream &stream) +{ + char prefix[6]; + if (stream.readRawData(prefix, sizeof(prefix)) != sizeof(prefix)) + return false; + if (prefix[0] != 'E' || prefix[1] != 'x' || prefix[2] != 'i' || prefix[3] != 'f' || prefix[4] != 0 || prefix[5] != 0) + return false; + return true; +} + +/* + * Returns -1 on error + * Returns 0 if no Exif orientation was found + * Returns 1 orientation is horizontal (normal) + * Returns 2 mirror horizontal + * Returns 3 rotate 180 + * Returns 4 mirror vertical + * Returns 5 mirror horizontal and rotate 270 CCW + * Returns 6 rotate 90 CW + * Returns 7 mirror horizontal and rotate 90 CW + * Returns 8 rotate 270 CW + */ +static int getExifOrientation(QByteArray &exifData) +{ + QDataStream stream(&exifData, QIODevice::ReadOnly); + + if (!readExifHeader(stream)) + return -1; + + quint16 val; + quint32 offset; + + // 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 + + // read size + stream >> val; + if (val != 0x2a) + return -1; + + stream >> offset; + // we have already used 8 bytes of TIFF header + offset -= 8; + + // read IFD + while (!stream.atEnd()) { + quint16 numEntries; + + // skip offset bytes to get the next IFD + if (stream.skipRawData(offset) != (qint32)offset) + return -1; + + stream >> numEntries; + + for (;numEntries > 0; --numEntries) { + quint16 tag; + quint16 type; + quint32 components; + quint32 value; + + stream >> tag >> type >> components >> value; + + 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 (offset == 0) // this is the last IFD + break; + } + + // No Exif orientation was found + return 0; +} /*! \internal */ @@ -779,6 +872,7 @@ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device) if (!setjmp(err.setjmp_buffer)) { jpeg_save_markers(&info, JPEG_COM, 0xFFFF); + jpeg_save_markers(&info, JPEG_APP0+1, 0xFFFF); // Exif uses APP1 marker (void) jpeg_read_header(&info, TRUE); @@ -790,6 +884,8 @@ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device) format = QImage::Format_Invalid; read_jpeg_format(format, &info); + QByteArray exifData; + for (jpeg_saved_marker_ptr marker = info.marker_list; marker != NULL; marker = marker->next) { if (marker->marker == JPEG_COM) { QString key, value; @@ -807,9 +903,18 @@ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device) 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); } } + if (exifData.size()) { + // Exif data present + int orientation = getExifOrientation(exifData); + if (orientation > 0) + exifOrientation = orientation; + } + state = ReadHeader; return true; } @@ -823,6 +928,48 @@ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device) return true; } +void QJpegHandlerPrivate::applyExifOrientation(QImage *image) +{ + // This is not an optimized implementation, but easiest to maintain + QTransform transform; + + switch (exifOrientation) { + case 1: // normal + break; + case 2: // mirror horizontal + *image = image->mirrored(true, false); + break; + case 3: // rotate 180 + transform.rotate(180); + *image = image->transformed(transform); + break; + case 4: // mirror vertical + *image = image->mirrored(false, true); + break; + case 5: // mirror horizontal and rotate 270 CCW + *image = image->mirrored(true, false); + transform.rotate(270); + *image = image->transformed(transform); + break; + case 6: // rotate 90 CW + transform.rotate(90); + *image = image->transformed(transform); + break; + case 7: // mirror horizontal and rotate 90 CW + *image = image->mirrored(true, false); + transform.rotate(90); + *image = image->transformed(transform); + break; + case 8: // rotate 270 CW + transform.rotate(-90); + *image = image->transformed(transform); + break; + default: + qWarning("This should never happen"); + } + exifOrientation = 1; +} + bool QJpegHandlerPrivate::read(QImage *image) { if(state == Ready) @@ -834,6 +981,7 @@ bool QJpegHandlerPrivate::read(QImage *image) if (success) { for (int i = 0; i < readTexts.size()-1; i+=2) image->setText(readTexts.at(i), readTexts.at(i+1)); + applyExifOrientation(image); state = Ready; return true; -- cgit v1.2.3