Created
August 21, 2018 23:20
-
-
Save Theverat/34aa4510ae3f98bb36975c88c045af3a to your computer and use it in GitHub Desktop.
My custom EXIF parser for use in Qt projects. Currently it only reads the image orientation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "exifparser.h" | |
#include <QFile> | |
#include <QDataStream> | |
#include <QtEndian> | |
#include <QDebug> | |
#include <vector> | |
ExifParser::ExifParser(const QString &filepath) { | |
QByteArray buffer; | |
if (!readHeaderBytes(filepath, buffer)) { | |
qDebug() << "EXIF read fail: Could not read file header"; | |
return; | |
} | |
if (!areBytesEqual(buffer, MARKER_JPEG_START, 0)) { | |
qDebug() << "EXIF read fail: Not a jpeg image"; | |
return; | |
} | |
const int exifStartPos = buffer.indexOf(MARKER_EXIF_START); | |
if (exifStartPos == -1) { | |
qDebug() << "EXIF read fail: No exif start marker"; | |
return; | |
} | |
const int exifLengthPos = exifStartPos + MARKER_EXIF_START.size(); | |
// const unsigned short exifLength = readUnsignedShort(buffer, | |
// exifLengthPos); | |
const int exifCodePos = exifLengthPos + UNSIGNED_SHORT_SIZE; | |
if (!areBytesEqual(buffer, EXIF_CODE, exifCodePos)) { | |
qDebug() << "EXIF read fail: Header does not contain EXIF data"; | |
return; | |
} | |
// now we are in the TIFF header (8 bytes) | |
const int tiffHeaderPos = exifCodePos + EXIF_CODE.size(); | |
bool isBigEndian; | |
if (areBytesEqual(buffer, BIG_ENDIAN_CODE, tiffHeaderPos)) { | |
isBigEndian = true; | |
} else if (areBytesEqual(buffer, LITTLE_ENDIAN_CODE, tiffHeaderPos)) { | |
isBigEndian = false; | |
} else { | |
qDebug() << "EXIF read fail: Incorrect endian information in TIFF header"; | |
return; | |
} | |
// last 6 bytes of TIFF header contain always the same data, skip them | |
// now we are in the first image file directory (IDF) | |
// get amount of EXIF tags (2 bytes) | |
const int tagAmountPos = tiffHeaderPos + TIFF_HEADER_SIZE; | |
unsigned short tagAmount = readUnsignedShort(buffer, tagAmountPos, | |
isBigEndian); | |
// read tags | |
for (int i = 0; i < tagAmount; ++i) { | |
const int tagPosition = tagAmountPos + UNSIGNED_SHORT_SIZE + (TAG_SIZE * i); | |
if (readOrientationTag(buffer, tagPosition, isBigEndian)) { | |
// Since we are only interested in the orientation, | |
// we can skip all other tags once we found it | |
break; | |
} | |
} | |
valid = true; | |
} | |
bool ExifParser::isValid() const { | |
return valid; | |
} | |
unsigned short ExifParser::getOrientation() const { | |
return orientation; | |
} | |
//------------------------------------------------------------------ | |
// private | |
bool ExifParser::readHeaderBytes(const QString &filepath, QByteArray &buffer) { | |
QFile file(filepath); | |
if (!file.open(QIODevice::ReadOnly)) { | |
return false; | |
} | |
QDataStream stream(&file); | |
buffer.resize(JPEG_HEADER_MAX_SIZE); | |
const int r = stream.readRawData(buffer.data(), JPEG_HEADER_MAX_SIZE); | |
if (r == -1 || buffer.size() == 0) { | |
return false; | |
} | |
return true; | |
} | |
bool ExifParser::areBytesEqual(const QByteArray &source, | |
const QByteArray &comp, | |
int startIndex) { | |
if (startIndex + comp.size() > source.size()) | |
return false; | |
for (int i = 0; i < comp.size(); ++i) | |
if (source.at(startIndex + i) != comp.at(i)) | |
return false; | |
return true; | |
} | |
unsigned short ExifParser::readUnsignedShort(const QByteArray &source, | |
int startIndex, | |
bool isBigEndian) { | |
Q_ASSERT(startIndex + UNSIGNED_SHORT_SIZE <= source.size()); | |
unsigned short raw[UNSIGNED_SHORT_SIZE]; | |
for(int i = 0; i < UNSIGNED_SHORT_SIZE; ++i) | |
raw[i] = static_cast<unsigned short>(source.at(startIndex + i)); | |
unsigned short combined = (raw[1] << 8) | raw[0]; | |
if (isBigEndian) | |
return combined; | |
else | |
return qFromLittleEndian(combined); | |
} | |
unsigned long ExifParser::readUnsignedLong(const QByteArray &source, | |
int startIndex, | |
bool isBigEndian) { | |
Q_ASSERT(startIndex + UNSIGNED_LONG_SIZE <= source.size()); | |
unsigned long raw[UNSIGNED_LONG_SIZE]; | |
for(int i = 0; i < UNSIGNED_LONG_SIZE; ++i) | |
raw[i] = static_cast<unsigned long>(source.at(startIndex + i)); | |
unsigned long combined = (raw[3] << (8 * 3)) | |
| (raw[2] << (8 * 2)) | |
| (raw[1] << 8) | |
| raw[0]; | |
if (isBigEndian) | |
return combined; | |
else | |
return qFromLittleEndian(combined); | |
} | |
bool ExifParser::readOrientationTag(const QByteArray &source, int startIndex, | |
bool isBigEndian) { | |
const unsigned short tagType = readUnsignedShort(source, startIndex, isBigEndian); | |
// 4 bytes: length of data (as 32 bit number) | |
// const unsigned long dataLength = readUnsignedLong(source, startIndex + 4, | |
// isBigEndian); | |
// 4 bytes: data in the tag or offset to the data if more than 4 bytes | |
if (tagType == TAG_TYPE_ORIENTATION) { | |
this->orientation = readUnsignedShort(source, startIndex + 8, | |
isBigEndian); | |
return true; | |
} | |
return false; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef EXIFPARSER_H | |
#define EXIFPARSER_H | |
#include <QString> | |
#include <QByteArray> | |
class ExifParser | |
{ | |
public: | |
ExifParser(const QString &filepath); | |
bool isValid() const; | |
unsigned short getOrientation() const; | |
private: | |
bool valid = false; | |
unsigned short orientation = 1; | |
const QByteArray MARKER_JPEG_START = QByteArray::fromHex("FFD8"); | |
const QByteArray MARKER_EXIF_START = QByteArray::fromHex("FFE1"); | |
const QByteArray EXIF_CODE = QByteArray::fromHex("457869660000"); | |
const QByteArray LITTLE_ENDIAN_CODE = QByteArray::fromHex("4949"); | |
const QByteArray BIG_ENDIAN_CODE = QByteArray::fromHex("4D4D"); | |
// All sizes in bytes | |
const int JPEG_HEADER_MAX_SIZE = 65536; | |
const int TIFF_HEADER_SIZE = 8; | |
const int UNSIGNED_SHORT_SIZE = 2; | |
const int UNSIGNED_LONG_SIZE = 4; | |
const int TAG_SIZE = 12; | |
// Tag types | |
const unsigned short TAG_TYPE_ORIENTATION = 0x112; | |
bool readHeaderBytes(const QString &filepath, QByteArray &buffer); | |
bool areBytesEqual(const QByteArray &source, const QByteArray &comp, | |
int startIndex); | |
unsigned short readUnsignedShort(const QByteArray &source, int startIndex, | |
bool isBigEndian=true); | |
unsigned long readUnsignedLong(const QByteArray &source, int startIndex, | |
bool isBigEndian=true); | |
bool readOrientationTag(const QByteArray &source, int startIndex, bool isBigEndian); | |
}; | |
#endif // EXIFPARSER_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment