summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/gui/image/qjpeghandler.cpp150
-rw-r--r--tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_1.jpgbin0 -> 910 bytes
-rw-r--r--tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_2.jpgbin0 -> 910 bytes
-rw-r--r--tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_3.jpgbin0 -> 988 bytes
-rw-r--r--tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_4.jpgbin0 -> 995 bytes
-rw-r--r--tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_5.jpgbin0 -> 912 bytes
-rw-r--r--tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_6.jpgbin0 -> 911 bytes
-rw-r--r--tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_7.jpgbin0 -> 987 bytes
-rw-r--r--tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_8.jpgbin0 -> 991 bytes
-rw-r--r--tests/auto/gui/image/qimage/tst_qimage.cpp18
10 files changed, 167 insertions, 1 deletions
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;
diff --git a/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_1.jpg b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_1.jpg
new file mode 100644
index 0000000000..aaa4ac4e10
--- /dev/null
+++ b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_1.jpg
Binary files differ
diff --git a/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_2.jpg b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_2.jpg
new file mode 100644
index 0000000000..a61d2723d7
--- /dev/null
+++ b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_2.jpg
Binary files differ
diff --git a/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_3.jpg b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_3.jpg
new file mode 100644
index 0000000000..43e56dcef7
--- /dev/null
+++ b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_3.jpg
Binary files differ
diff --git a/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_4.jpg b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_4.jpg
new file mode 100644
index 0000000000..d5d06f7409
--- /dev/null
+++ b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_4.jpg
Binary files differ
diff --git a/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_5.jpg b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_5.jpg
new file mode 100644
index 0000000000..1886f3775e
--- /dev/null
+++ b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_5.jpg
Binary files differ
diff --git a/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_6.jpg b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_6.jpg
new file mode 100644
index 0000000000..5cec757354
--- /dev/null
+++ b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_6.jpg
Binary files differ
diff --git a/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_7.jpg b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_7.jpg
new file mode 100644
index 0000000000..b3dcc466a9
--- /dev/null
+++ b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_7.jpg
Binary files differ
diff --git a/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_8.jpg b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_8.jpg
new file mode 100644
index 0000000000..8bc390e2b9
--- /dev/null
+++ b/tests/auto/gui/image/qimage/images/jpeg_exif_orientation_value_8.jpg
Binary files differ
diff --git a/tests/auto/gui/image/qimage/tst_qimage.cpp b/tests/auto/gui/image/qimage/tst_qimage.cpp
index 5f581c9d0f..fcee2884d9 100644
--- a/tests/auto/gui/image/qimage/tst_qimage.cpp
+++ b/tests/auto/gui/image/qimage/tst_qimage.cpp
@@ -173,6 +173,8 @@ private slots:
void invertPixelsRGB_data();
void invertPixelsRGB();
+ void exifOrientation();
+
void cleanupFunctions();
private:
@@ -2624,6 +2626,22 @@ void tst_QImage::invertPixelsRGB()
QCOMPARE(qBlue(pixel) >> 4, (255 - 96) >> 4);
}
+void tst_QImage::exifOrientation()
+{
+ for (unsigned int i = 1; i <= 8; ++i) {
+ QImage img;
+ QRgb px;
+
+ QVERIFY(img.load(m_prefix + QString::fromLatin1("jpeg_exif_orientation_value_%1.jpg").arg(i)));
+
+ px = img.pixel(0, 0);
+ QVERIFY(qRed(px) > 250 && qGreen(px) < 5 && qBlue(px) < 5);
+
+ px = img.pixel(img.width() - 1, 0);
+ QVERIFY(qRed(px) < 5 && qGreen(px) < 5 && qBlue(px) > 250);
+ }
+}
+
static void cleanupFunction(void* info)
{
bool *called = static_cast<bool*>(info);