mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-01-17 11:28:07 +00:00
1775 lines
48 KiB
C++
1775 lines
48 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
|
** All rights reserved.
|
|
** Contact: Nokia Corporation (qt-info@nokia.com)
|
|
**
|
|
** This file is part of the Qt scene graph research project.
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
|
** No Commercial Usage
|
|
** This file contains pre-release code and may not be distributed.
|
|
** You may use this file in accordance with the terms and conditions
|
|
** contained in the Technology Preview License Agreement accompanying
|
|
** this package.
|
|
**
|
|
** GNU Lesser General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
** General Public License version 2.1 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.LGPL included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU Lesser General Public License version 2.1 requirements
|
|
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
**
|
|
** In addition, as a special exception, Nokia gives you certain additional
|
|
** rights. These rights are described in the Nokia Qt LGPL Exception
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
**
|
|
** If you have questions regarding the use of this file, please contact
|
|
** Nokia at qt-info@nokia.com.
|
|
**
|
|
**
|
|
**
|
|
**
|
|
**
|
|
**
|
|
**
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
// This file was copied from Qt Extended 4.5
|
|
|
|
#include <QBuffer>
|
|
#include <QDataStream>
|
|
#include <QDateTime>
|
|
#include <QFile>
|
|
#include <QImage>
|
|
#include <QStringConverter>
|
|
#include <QtDebug>
|
|
|
|
#include "Utils.hpp"
|
|
|
|
#include "QExifImageHeader.hpp"
|
|
|
|
/*!
|
|
\typedef QExifSRational
|
|
|
|
A synonym for \c QPair<qint32,qint32> representing a signed rational number as stored in EXIF
|
|
headers. The first integer in the pair is the numerator and the second the denominator.
|
|
*/
|
|
|
|
/*!
|
|
\typedef QExifURational
|
|
|
|
A synonym for \c QPair<qint32,qint32> representing an unsigned rational number as stored in
|
|
EXIF headers. The first integer in the pair is the numerator and the second the denominator.
|
|
*/
|
|
|
|
struct ExifIfdHeader {
|
|
quint16 tag;
|
|
quint16 type;
|
|
quint32 count;
|
|
union {
|
|
quint32 offset;
|
|
quint8 offsetBytes[4];
|
|
char offsetAscii[4];
|
|
quint16 offsetShorts[2];
|
|
};
|
|
};
|
|
|
|
QDataStream &operator>>(QDataStream &stream, ExifIfdHeader &header) {
|
|
stream >> header.tag;
|
|
stream >> header.type;
|
|
stream >> header.count;
|
|
|
|
if (header.type == QExifValue::Byte && header.count <= 4) {
|
|
stream.readRawData(header.offsetAscii, 4);
|
|
} else if (header.type == QExifValue::Ascii && header.count <= 4) {
|
|
stream.readRawData(header.offsetAscii, 4);
|
|
} else if (header.type == QExifValue::Short && header.count <= 2) {
|
|
stream >> header.offsetShorts[0];
|
|
stream >> header.offsetShorts[1];
|
|
} else {
|
|
stream >> header.offset;
|
|
}
|
|
|
|
return stream;
|
|
}
|
|
|
|
class QExifValuePrivate : public QSharedData {
|
|
public:
|
|
QExifValuePrivate(quint16 t, int c) : type(t), count(c) {
|
|
}
|
|
|
|
virtual ~QExifValuePrivate() {
|
|
}
|
|
|
|
quint16 type;
|
|
int count;
|
|
};
|
|
|
|
class QExifByteValuePrivate : public QExifValuePrivate {
|
|
public:
|
|
QExifByteValuePrivate() : QExifValuePrivate(QExifValue::Byte, 0) {
|
|
ref.ref();
|
|
}
|
|
|
|
QExifByteValuePrivate(const QVector<quint8> &v) : QExifValuePrivate(QExifValue::Byte, v.size()), value(v) {
|
|
}
|
|
~QExifByteValuePrivate() {
|
|
}
|
|
QVector<quint8> value;
|
|
};
|
|
|
|
class QExifUndefinedValuePrivate : public QExifValuePrivate {
|
|
public:
|
|
QExifUndefinedValuePrivate(const QByteArray &v) : QExifValuePrivate(QExifValue::Undefined, v.size()), value(v) {
|
|
}
|
|
|
|
QByteArray value;
|
|
};
|
|
|
|
class QExifAsciiValuePrivate : public QExifValuePrivate {
|
|
public:
|
|
QExifAsciiValuePrivate(const QString &v) : QExifValuePrivate(QExifValue::Ascii, v.size() + 1), value(v) {
|
|
}
|
|
|
|
QString value;
|
|
};
|
|
|
|
class QExifShortValuePrivate : public QExifValuePrivate {
|
|
public:
|
|
QExifShortValuePrivate(const QVector<quint16> &v) : QExifValuePrivate(QExifValue::Short, v.size()), value(v) {
|
|
}
|
|
|
|
QVector<quint16> value;
|
|
};
|
|
|
|
class QExifLongValuePrivate : public QExifValuePrivate {
|
|
public:
|
|
QExifLongValuePrivate(const QVector<quint32> &v) : QExifValuePrivate(QExifValue::Long, v.size()), value(v) {
|
|
}
|
|
|
|
QVector<quint32> value;
|
|
};
|
|
|
|
class QExifSignedLongValuePrivate : public QExifValuePrivate {
|
|
public:
|
|
QExifSignedLongValuePrivate(const QVector<qint32> &v)
|
|
: QExifValuePrivate(QExifValue::SignedLong, v.size()), value(v) {
|
|
}
|
|
|
|
QVector<qint32> value;
|
|
};
|
|
|
|
class QExifRationalValuePrivate : public QExifValuePrivate {
|
|
public:
|
|
QExifRationalValuePrivate(const QVector<QExifURational> &v)
|
|
: QExifValuePrivate(QExifValue::Rational, v.size()), value(v) {
|
|
}
|
|
|
|
QVector<QExifURational> value;
|
|
};
|
|
|
|
class QExifSignedRationalValuePrivate : public QExifValuePrivate {
|
|
public:
|
|
QExifSignedRationalValuePrivate(const QVector<QExifSRational> &v)
|
|
: QExifValuePrivate(QExifValue::SignedRational, v.size()), value(v) {
|
|
}
|
|
|
|
QVector<QExifSRational> value;
|
|
};
|
|
|
|
Q_GLOBAL_STATIC(QExifByteValuePrivate, qExifValuePrivateSharedNull)
|
|
|
|
/*!
|
|
\class QExifValue
|
|
\inpublicgroup QtBaseModule
|
|
\brief The QExifValue class represents data types found in EXIF image headers.
|
|
|
|
Tag values in EXIF headers are stored as arrays of a limited number of data types. QExifValue
|
|
encapsulates a union of these types and provides conversions to and from appropriate Qt types.
|
|
|
|
\section1 String encoding
|
|
|
|
Most tags with string values in EXIF headers are ASCII encoded and have the Ascii value type,
|
|
but some tags allow other encodings. In this case the value type is Undefined and the encoding
|
|
of the text is given by the encoding function().
|
|
|
|
\section1 Date-time values
|
|
|
|
Date-time values in EXIF headers are stored in ASCII encoded strings of the form
|
|
\c {yyyy:MM:dd HH:mm:ss}. Constructing a QExifValue from a QDateTime will perform this
|
|
conversion and likewise an appropriately formed QExifValue can be converted to a QDateTime
|
|
using the toDateTime() function.
|
|
|
|
\sa QExifImageHeader
|
|
|
|
\preliminary
|
|
*/
|
|
|
|
/*!
|
|
\enum QExifValue::Type
|
|
|
|
Enumerates the possible types of EXIF values.
|
|
|
|
\value Byte An unsigned 8 bit integer.
|
|
\value Ascii A null terminated ascii string.
|
|
\value Short An unsigned 16 bit integer.
|
|
\value Long An unsigned 32 bit integer.
|
|
\value Rational Two unsigned 32 bit integers, representing a the numerator and denominator of an unsigned rational
|
|
number. \value Undefined An array of 8 bit integers. \value SignedLong A signed 32 bit integer. \value SignedRational
|
|
Two signed 32 bit integers representing the numerator and denominator of a signed rational number.
|
|
*/
|
|
|
|
/*!
|
|
\enum QExifValue::TextEncoding
|
|
|
|
Enumerates the encodings of text strings in EXIF values of Undefined type.
|
|
|
|
\value NoEncoding An ASCII string of Ascii type.
|
|
\value AsciiEncoding An ASCII string of Undefined type.
|
|
\value JisEncoding A JIS X208-1990 string of Undefined type.
|
|
\value UnicodeEncoding A Unicode string of Undefined type.
|
|
\value UndefinedEncoding An unspecified string encoding of Undefined type. Assumed to be the local 8-bit
|
|
encoding.
|
|
*/
|
|
|
|
/*!
|
|
Constructs a null QExifValue.
|
|
*/
|
|
QExifValue::QExifValue() : d(qExifValuePrivateSharedNull()) {
|
|
}
|
|
|
|
/*!
|
|
Constructs a QExifValue with a \a value of type Byte.
|
|
*/
|
|
QExifValue::QExifValue(quint8 value) : d(new QExifByteValuePrivate(QVector<quint8>(1, value))) {
|
|
}
|
|
|
|
/*!
|
|
Constructs a QExifValue with an array of \a values of type Byte.
|
|
*/
|
|
QExifValue::QExifValue(const QVector<quint8> &values) : d(new QExifByteValuePrivate(values)) {
|
|
}
|
|
|
|
/*!
|
|
Constructs a QExifValue with a \a value of type Ascii or Undefined.
|
|
|
|
If the \a encoding is NoEncoding the value will be of type Ascii, otherwise it will be Undefined and the string
|
|
encoded using the given \a encoding.
|
|
*/
|
|
QExifValue::QExifValue(const QString &value, TextEncoding encoding) : d(qExifValuePrivateSharedNull()) {
|
|
switch (encoding) {
|
|
case AsciiEncoding:
|
|
d = new QExifUndefinedValuePrivate(QByteArray::fromRawData("ASCII\0\0\0", 8) + value.toUtf8());
|
|
break;
|
|
case JisEncoding: {
|
|
auto encoding = QStringConverter::encodingForName("JIS X 0208");
|
|
if (encoding.has_value()) {
|
|
QStringEncoder encoder(encoding.value());
|
|
auto encoded = encoder.encode(value);
|
|
d = new QExifUndefinedValuePrivate(QByteArray::fromRawData("JIS\0\0\0\0\0", 8) + encoded);
|
|
}
|
|
} break;
|
|
case UnicodeEncoding: {
|
|
auto encoding = QStringConverter::encodingForName("UTF-16");
|
|
if (encoding.has_value()) {
|
|
QStringEncoder encoder(encoding.value());
|
|
auto encoded = encoder.encode(value);
|
|
d = new QExifUndefinedValuePrivate(QByteArray::fromRawData("UNICODE\0", 8) + encoded);
|
|
}
|
|
} break;
|
|
case UndefinedEncoding:
|
|
d = new QExifUndefinedValuePrivate(QByteArray::fromRawData("\0\0\0\0\0\0\0\\0", 8) + value.toLocal8Bit());
|
|
break;
|
|
default:
|
|
d = new QExifAsciiValuePrivate(value);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Constructs a QExifValue with a \a value of type Short.
|
|
*/
|
|
QExifValue::QExifValue(quint16 value) : d(new QExifShortValuePrivate(QVector<quint16>(1, value))) {
|
|
}
|
|
|
|
/*!
|
|
Constructs a QExifValue with an array of \a values of type Short.
|
|
*/
|
|
QExifValue::QExifValue(const QVector<quint16> &values) : d(new QExifShortValuePrivate(values)) {
|
|
}
|
|
|
|
/*!
|
|
Constructs a QExifValue with a \a value of type Long.
|
|
*/
|
|
QExifValue::QExifValue(quint32 value) : d(new QExifLongValuePrivate(QVector<quint32>(1, value))) {
|
|
}
|
|
|
|
/*!
|
|
Constructs a QExifValue with an array of \a values of type Long.
|
|
*/
|
|
QExifValue::QExifValue(const QVector<quint32> &values) : d(new QExifLongValuePrivate(values)) {
|
|
}
|
|
|
|
/*!
|
|
Constructs a QExifValue with a \a value of type Rational.
|
|
*/
|
|
QExifValue::QExifValue(const QExifURational &value)
|
|
: d(new QExifRationalValuePrivate(QVector<QExifURational>(1, value))) {
|
|
}
|
|
|
|
/*!
|
|
Constructs a QExifValue with an array of \a values of type Rational.
|
|
*/
|
|
QExifValue::QExifValue(const QVector<QExifURational> &values) : d(new QExifRationalValuePrivate(values)) {
|
|
}
|
|
|
|
/*!
|
|
Constructs a QExifValue with a \a value of type Undefined.
|
|
*/
|
|
QExifValue::QExifValue(const QByteArray &value) : d(new QExifUndefinedValuePrivate(value)) {
|
|
}
|
|
|
|
/*!
|
|
Constructs a QExifValue with a \a value of type SignedLong.
|
|
*/
|
|
QExifValue::QExifValue(qint32 value) : d(new QExifSignedLongValuePrivate(QVector<qint32>(1, value))) {
|
|
}
|
|
|
|
/*!
|
|
Constructs a QExifValue with an array of \a values of type SignedLong.
|
|
*/
|
|
QExifValue::QExifValue(const QVector<qint32> &values) : d(new QExifSignedLongValuePrivate(values)) {
|
|
}
|
|
|
|
/*!
|
|
Constructs a QExifValue with a \a value of type SignedRational.
|
|
*/
|
|
QExifValue::QExifValue(const QExifSRational &value)
|
|
: d(new QExifSignedRationalValuePrivate(QVector<QExifSRational>(1, value))) {
|
|
}
|
|
|
|
/*!
|
|
Constructs a QExifValue with an array of \a values of type SignedRational.
|
|
*/
|
|
QExifValue::QExifValue(const QVector<QExifSRational> &values) : d(new QExifSignedRationalValuePrivate(values)) {
|
|
}
|
|
|
|
/*!
|
|
Constructs a QExifValue of type Ascii with an ascii string formatted from a date-time \a value.
|
|
|
|
Date-times are stored as strings in the format \c {yyyy:MM:dd HH:mm:ss}.
|
|
*/
|
|
QExifValue::QExifValue(const QDateTime &value)
|
|
: d(new QExifAsciiValuePrivate(value.toString(QLatin1String("yyyy:MM:dd HH:mm:ss")))) {
|
|
}
|
|
|
|
/*!
|
|
Constructs a copy of the QExifValue \a other.
|
|
*/
|
|
QExifValue::QExifValue(const QExifValue &other) : d(other.d) {
|
|
}
|
|
|
|
/*!
|
|
Assigns the value of \a other to a QExifValue.
|
|
*/
|
|
QExifValue &QExifValue::operator=(const QExifValue &other) {
|
|
d = other.d;
|
|
|
|
return *this;
|
|
}
|
|
|
|
/*!
|
|
Destroys a QExifValue.
|
|
*/
|
|
QExifValue::~QExifValue() {
|
|
}
|
|
|
|
/*!
|
|
Compares a QExifValue to \a other. Returns true if they are the same value and false otherwise.
|
|
*/
|
|
bool QExifValue::operator==(const QExifValue &other) const {
|
|
return d == other.d;
|
|
}
|
|
|
|
/*!
|
|
Returns true if a QExifValue has a null value and false otherwise.
|
|
*/
|
|
bool QExifValue::isNull() const {
|
|
return d == qExifValuePrivateSharedNull();
|
|
}
|
|
|
|
/*!
|
|
Returns the type of a QExifValue.
|
|
*/
|
|
int QExifValue::type() const {
|
|
return d->type;
|
|
}
|
|
|
|
/*!
|
|
Returns the number of elements in a QExifValue. For ascii strings this is the length of the string
|
|
including the terminating null.
|
|
*/
|
|
int QExifValue::count() const {
|
|
return d->count;
|
|
}
|
|
|
|
/*!
|
|
Returns the encoding of strings stored in Undefined values.
|
|
*/
|
|
QExifValue::TextEncoding QExifValue::encoding() const {
|
|
if (d->type == Undefined && d->count > 8) {
|
|
QByteArray value = static_cast<const QExifUndefinedValuePrivate *>(d.constData())->value;
|
|
|
|
if (value.startsWith(QByteArray::fromRawData("ASCII\0\0\0", 8))) return AsciiEncoding;
|
|
else if (value.startsWith(QByteArray::fromRawData("JIS\0\0\0\0\0", 8))) return JisEncoding;
|
|
else if (value.startsWith(QByteArray::fromRawData("UNICODE\0", 8))) return UnicodeEncoding;
|
|
else if (value.startsWith(QByteArray::fromRawData("\0\0\0\0\0\0\0\0", 8))) return UndefinedEncoding;
|
|
}
|
|
return NoEncoding;
|
|
}
|
|
|
|
/*!
|
|
Returns the value of a single element QExifValue of type Byte.
|
|
*/
|
|
quint8 QExifValue::toByte() const {
|
|
return d->type == Byte && d->count == 1 ? static_cast<const QExifByteValuePrivate *>(d.constData())->value.at(0)
|
|
: 0;
|
|
}
|
|
|
|
/*!
|
|
Returns the value of a multiple element QExifValue of type Byte.
|
|
*/
|
|
QVector<quint8> QExifValue::toByteVector() const {
|
|
return d->type == Byte ? static_cast<const QExifByteValuePrivate *>(d.constData())->value : QVector<quint8>();
|
|
}
|
|
|
|
/*!
|
|
Returns the value of a QExifValue of type Ascii.
|
|
*/
|
|
QString QExifValue::toString() const {
|
|
switch (d->type) {
|
|
case Ascii:
|
|
return static_cast<const QExifAsciiValuePrivate *>(d.constData())->value;
|
|
case Undefined: {
|
|
QByteArray string = static_cast<const QExifUndefinedValuePrivate *>(d.constData())->value.mid(8);
|
|
|
|
switch (encoding()) {
|
|
case AsciiEncoding:
|
|
return QString::fromUtf8(string.constData(), string.length());
|
|
case JisEncoding: {
|
|
auto encoding = QStringConverter::encodingForName("JIS X 0208");
|
|
if (encoding.has_value()) {
|
|
QStringDecoder decoder(encoding.value());
|
|
return decoder.decode(string);
|
|
}
|
|
} break;
|
|
case UnicodeEncoding: {
|
|
auto encoding = QStringConverter::encodingForName("UTF-16");
|
|
if (encoding.has_value()) {
|
|
QStringDecoder decoder(encoding.value());
|
|
return decoder.decode(string);
|
|
}
|
|
return QString::fromLocal8Bit(string.constData(), string.length());
|
|
}
|
|
case UndefinedEncoding:
|
|
return QString::fromLocal8Bit(string.constData(), string.length());
|
|
default:
|
|
break;
|
|
}
|
|
return QString();
|
|
}
|
|
default:
|
|
return QString();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Returns the value of a single element QExifValue of type Byte or Short.
|
|
*/
|
|
quint16 QExifValue::toShort() const {
|
|
if (d->count == 1) {
|
|
switch (d->type) {
|
|
case Byte:
|
|
return static_cast<const QExifByteValuePrivate *>(d.constData())->value.at(0);
|
|
case Short:
|
|
return static_cast<const QExifShortValuePrivate *>(d.constData())->value.at(0);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*!
|
|
Returns the value of a single element QExifValue of type Short.
|
|
*/
|
|
QVector<quint16> QExifValue::toShortVector() const {
|
|
return d->type == Short ? static_cast<const QExifShortValuePrivate *>(d.constData())->value : QVector<quint16>();
|
|
}
|
|
|
|
/*!
|
|
Returns the value of a single element QExifValue of type Byte, Short, Long, or SignedLong.
|
|
*/
|
|
quint32 QExifValue::toLong() const {
|
|
if (d->count == 1) {
|
|
switch (d->type) {
|
|
case Byte:
|
|
return static_cast<const QExifByteValuePrivate *>(d.constData())->value.at(0);
|
|
case Short:
|
|
return static_cast<const QExifShortValuePrivate *>(d.constData())->value.at(0);
|
|
case Long:
|
|
return static_cast<const QExifLongValuePrivate *>(d.constData())->value.at(0);
|
|
case SignedLong:
|
|
return quint32(static_cast<const QExifSignedLongValuePrivate *>(d.constData())->value.at(0));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*!
|
|
Returns the value of a multiple element QExifValue of type Long.
|
|
*/
|
|
QVector<quint32> QExifValue::toLongVector() const {
|
|
return d->type == Long ? static_cast<const QExifLongValuePrivate *>(d.constData())->value : QVector<quint32>();
|
|
}
|
|
|
|
/*!
|
|
Returns the value of a multiple element QExifValue of type Rational.
|
|
*/
|
|
QExifURational QExifValue::toRational() const {
|
|
return d->type == Rational && d->count == 1
|
|
? static_cast<const QExifRationalValuePrivate *>(d.constData())->value.at(0)
|
|
: QExifURational();
|
|
}
|
|
|
|
/*!
|
|
Returns the value of a multiple element QExifValue of type Rational.
|
|
*/
|
|
QVector<QExifURational> QExifValue::toRationalVector() const {
|
|
return d->type == Rational ? static_cast<const QExifRationalValuePrivate *>(d.constData())->value
|
|
: QVector<QExifURational>();
|
|
}
|
|
|
|
/*!
|
|
Returns the value of a QExifValue of type Undefined.
|
|
*/
|
|
QByteArray QExifValue::toByteArray() const {
|
|
switch (d->type) {
|
|
case Ascii:
|
|
return static_cast<const QExifAsciiValuePrivate *>(d.constData())->value.toUtf8();
|
|
case Undefined:
|
|
return static_cast<const QExifUndefinedValuePrivate *>(d.constData())->value;
|
|
default:
|
|
return QByteArray();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Returns the value of a single element QExifValue of type Byte, Short, Long, or SignedLong.
|
|
*/
|
|
qint32 QExifValue::toSignedLong() const {
|
|
if (d->count == 1) {
|
|
switch (d->type) {
|
|
case Byte:
|
|
return static_cast<const QExifByteValuePrivate *>(d.constData())->value.at(0);
|
|
case Short:
|
|
return static_cast<const QExifShortValuePrivate *>(d.constData())->value.at(0);
|
|
case Long:
|
|
return qint32(static_cast<const QExifLongValuePrivate *>(d.constData())->value.at(0));
|
|
case SignedLong:
|
|
return static_cast<const QExifSignedLongValuePrivate *>(d.constData())->value.at(0);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*!
|
|
Returns the value of a multiple element QExifValue of type SignedLong.
|
|
*/
|
|
QVector<qint32> QExifValue::toSignedLongVector() const {
|
|
return d->type == SignedLong ? static_cast<const QExifSignedLongValuePrivate *>(d.constData())->value
|
|
: QVector<qint32>();
|
|
}
|
|
|
|
/*!
|
|
Returns the value of a single element QExifValue of type SignedRational.
|
|
*/
|
|
QExifSRational QExifValue::toSignedRational() const {
|
|
return d->type == SignedRational && d->count == 1
|
|
? static_cast<const QExifSignedRationalValuePrivate *>(d.constData())->value.at(0)
|
|
: QExifSRational();
|
|
}
|
|
|
|
/*!
|
|
Returns the value of a multiple element QExifValue of type SignedRational.
|
|
*/
|
|
QVector<QExifSRational> QExifValue::toSignedRationalVector() const {
|
|
return d->type == SignedRational ? static_cast<const QExifSignedRationalValuePrivate *>(d.constData())->value
|
|
: QVector<QExifSRational>();
|
|
}
|
|
|
|
/*!
|
|
Returns the value of QExifValue storing a date-time.
|
|
|
|
Date-times are stored as ascii strings in the format \c {yyyy:MM:dd HH:mm:ss}.
|
|
*/
|
|
QDateTime QExifValue::toDateTime() const {
|
|
return d->type == Ascii && d->count == 20
|
|
? QDateTime::fromString(static_cast<const QExifAsciiValuePrivate *>(d.constData())->value,
|
|
QLatin1String("yyyy:MM:dd HH:mm:ss"))
|
|
: QDateTime();
|
|
}
|
|
|
|
class QExifImageHeaderPrivate {
|
|
public:
|
|
QSysInfo::Endian byteOrder;
|
|
mutable qint64 size;
|
|
QMap<QExifImageHeader::ImageTag, QExifValue> imageIfdValues;
|
|
QMap<QExifImageHeader::ExifExtendedTag, QExifValue> exifIfdValues;
|
|
QMap<QExifImageHeader::GpsTag, QExifValue> gpsIfdValues;
|
|
|
|
QSize thumbnailSize;
|
|
QByteArray thumbnailData;
|
|
QExifValue thumbnailXResolution;
|
|
QExifValue thumbnailYResolution;
|
|
QExifValue thumbnailResolutionUnit;
|
|
QExifValue thumbnailOrientation;
|
|
};
|
|
|
|
/*!
|
|
\class QExifImageHeader
|
|
\inpublicgroup QtBaseModule
|
|
\brief The QExifImageHeader class provides functionality for reading and writing EXIF image headers.
|
|
|
|
EXIF headers are a collection of properties that describe the image they're embedded in.
|
|
Each property is identified by a tag of which there are three kinds. \l {ImageTag}{Image tags}
|
|
which mostly describe the format (dimensions, resolution, orientation) but also include some
|
|
descriptive information (description, camera make and model, artist). \l {ExifExtendedTag}
|
|
{EXIF extended tags} which elaborate on some of the image tags and record the camera settings at
|
|
time of capture among other things. Finally there are \l {GpsTag}{GPS tags} which record the
|
|
location the image was captured.
|
|
|
|
EXIF tags are typically found in JPEG images but may be found in other image formats. To read
|
|
headers from a JPEG image QExifImageHeader provides the loadFromJpeg() function, and the
|
|
complementary saveToJpeg() function for writing. To allow reading and writing arbitrary
|
|
formats QExifImageHeader provides the read() and write() functions which work with just the
|
|
EXIF header data itself.
|
|
|
|
\preliminary
|
|
*/
|
|
|
|
/*!
|
|
\enum QExifImageHeader::ImageTag
|
|
Enumerates the TIFF image tag IDs defined in the EXIF specification.
|
|
|
|
\value ImageWidth
|
|
\value ImageLength
|
|
\value BitsPerSample
|
|
\value Compression
|
|
\value PhotometricInterpretation
|
|
\value Orientation
|
|
\value SamplesPerPixel
|
|
\value PlanarConfiguration
|
|
\value YCbCrSubSampling
|
|
\value XResolution
|
|
\value YResolution
|
|
\value ResolutionUnit
|
|
\value StripOffsets
|
|
\value RowsPerStrip
|
|
\value StripByteCounts
|
|
\value TransferFunction
|
|
\value WhitePoint
|
|
\value PrimaryChromaciticies
|
|
\value YCbCrCoefficients
|
|
\value ReferenceBlackWhite
|
|
\value DateTime
|
|
\value ImageDescription
|
|
\value Make
|
|
\value Model
|
|
\value Software
|
|
\value Artist
|
|
\value Copyright
|
|
*/
|
|
|
|
/*!
|
|
\enum QExifImageHeader::ExifExtendedTag
|
|
Enumerates the extended EXIF tag IDs defined in the EXIF specification.
|
|
|
|
\value ExifVersion
|
|
\value FlashPixVersion
|
|
\value ColorSpace
|
|
\value ComponentsConfiguration
|
|
\value CompressedBitsPerPixel
|
|
\value PixelXDimension
|
|
\value PixelYDimension
|
|
\value MakerNote
|
|
\value UserComment
|
|
\value RelatedSoundFile
|
|
\value DateTimeOriginal
|
|
\value DateTimeDigitized
|
|
\value SubSecTime
|
|
\value SubSecTimeOriginal
|
|
\value SubSecTimeDigitized
|
|
\value ImageUniqueId
|
|
\value ExposureTime
|
|
\value FNumber
|
|
\value ExposureProgram
|
|
\value SpectralSensitivity
|
|
\value ISOSpeedRatings
|
|
\value Oecf
|
|
\value ShutterSpeedValue
|
|
\value ApertureValue
|
|
\value BrightnessValue
|
|
\value ExposureBiasValue
|
|
\value MaxApertureValue
|
|
\value SubjectDistance
|
|
\value MeteringMode
|
|
\value LightSource
|
|
\value Flash
|
|
\value FocalLength
|
|
\value SubjectArea
|
|
\value FlashEnergy
|
|
\value SpatialFrequencyResponse
|
|
\value FocalPlaneXResolution
|
|
\value FocalPlaneYResolution
|
|
\value FocalPlaneResolutionUnit
|
|
\value SubjectLocation
|
|
\value ExposureIndex
|
|
\value SensingMethod
|
|
\value FileSource
|
|
\value SceneType
|
|
\value CfaPattern
|
|
\value CustomRendered
|
|
\value ExposureMode
|
|
\value WhiteBalance
|
|
\value DigitalZoomRatio
|
|
\value FocalLengthIn35mmFilm
|
|
\value SceneCaptureType
|
|
\value GainControl
|
|
\value Contrast
|
|
\value Saturation
|
|
\value Sharpness
|
|
\value DeviceSettingDescription
|
|
\value SubjectDistanceRange
|
|
*/
|
|
|
|
/*!
|
|
\enum QExifImageHeader::GpsTag
|
|
Enumerates the GPS tag IDs from the EXIF specification.
|
|
|
|
\value GpsVersionId
|
|
\value GpsLatitudeRef
|
|
\value GpsLatitude
|
|
\value GpsLongitudeRef
|
|
\value GpsLongitude
|
|
\value GpsAltitudeRef
|
|
\value GpsAltitude
|
|
\value GpsTimeStamp
|
|
\value GpsSatellites
|
|
\value GpsStatus
|
|
\value GpsMeasureMode
|
|
\value GpsDop
|
|
\value GpsSpeedRef
|
|
\value GpsSpeed
|
|
\value GpsTrackRef
|
|
\value GpsTrack
|
|
\value GpsImageDirectionRef
|
|
\value GpsImageDirection
|
|
\value GpsMapDatum
|
|
\value GpsDestLatitudeRef
|
|
\value GpsDestLatitude
|
|
\value GpsDestLongitudeRef
|
|
\value GpsDestLongitude
|
|
\value GpsDestBearingRef
|
|
\value GpsDestBearing
|
|
\value GpsDestDistanceRef
|
|
\value GpsDestDistance
|
|
\value GpsProcessingMethod
|
|
\value GpsAreaInformation
|
|
\value GpsDateStamp
|
|
\value GpsDifferential
|
|
*/
|
|
|
|
/*!
|
|
Constructs a new EXIF image data editor.
|
|
*/
|
|
QExifImageHeader::QExifImageHeader() : d(new QExifImageHeaderPrivate) {
|
|
d->byteOrder = QSysInfo::ByteOrder;
|
|
d->size = -1;
|
|
}
|
|
|
|
/*!
|
|
Constructs a new EXIF image data editor and reads the meta-data from a JPEG image with the given \a fileName.
|
|
*/
|
|
QExifImageHeader::QExifImageHeader(const QString &fileName) : d(new QExifImageHeaderPrivate) {
|
|
d->byteOrder = QSysInfo::ByteOrder;
|
|
d->size = -1;
|
|
|
|
loadFromJpeg(fileName);
|
|
}
|
|
|
|
/*!
|
|
Destroys an EXIF image data editor.
|
|
*/
|
|
QExifImageHeader::~QExifImageHeader() {
|
|
clear();
|
|
|
|
delete d;
|
|
}
|
|
|
|
/*!
|
|
Reads meta-data from a JPEG image with the given \a fileName.
|
|
|
|
Returns true if the data was successfully parsed and false otherwise.
|
|
*/
|
|
bool QExifImageHeader::loadFromJpeg(const QString &fileName) {
|
|
QFile file(fileName);
|
|
|
|
if (file.open(QIODevice::ReadOnly)) return loadFromJpeg(&file);
|
|
else return false;
|
|
}
|
|
|
|
/*!
|
|
Reads meta-data from an I/O \a device containing a JPEG image.
|
|
|
|
Returns true if the data was successfully parsed and false otherwise.
|
|
*/
|
|
bool QExifImageHeader::loadFromJpeg(QIODevice *device) {
|
|
clear();
|
|
|
|
QByteArray exifData = extractExif(device);
|
|
|
|
if (!exifData.isEmpty()) {
|
|
QBuffer buffer(&exifData);
|
|
|
|
return buffer.open(QIODevice::ReadOnly) && read(&buffer);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*!
|
|
Saves meta-data to a JPEG image with the given \a fileName.
|
|
|
|
Returns true if the data was successfully written.
|
|
*/
|
|
bool QExifImageHeader::saveToJpeg(const QString &fileName) const {
|
|
QFile file(fileName);
|
|
|
|
if (file.open(QIODevice::ReadWrite)) return saveToJpeg(&file);
|
|
else return false;
|
|
}
|
|
|
|
/*!
|
|
Save meta-data to the given I/O \a device.
|
|
|
|
The device must be non-sequential and already contain a valid JPEG image.
|
|
|
|
Returns true if the data was successfully written.
|
|
*/
|
|
bool QExifImageHeader::saveToJpeg(QIODevice *device) const {
|
|
if (device->isSequential()) return false;
|
|
|
|
QByteArray exif;
|
|
|
|
{
|
|
QBuffer buffer(&exif);
|
|
|
|
if (!buffer.open(QIODevice::WriteOnly)) return false;
|
|
|
|
write(&buffer);
|
|
|
|
buffer.close();
|
|
|
|
exif = QByteArray::fromRawData("Exif\0\0", 6) + exif;
|
|
}
|
|
|
|
QDataStream stream(device);
|
|
|
|
stream.setByteOrder(QDataStream::BigEndian);
|
|
|
|
if (device->read(2) != "\xFF\xD8") // Not a valid JPEG image.
|
|
return false;
|
|
|
|
quint16 segmentId;
|
|
quint16 segmentLength;
|
|
|
|
stream >> segmentId;
|
|
stream >> segmentLength;
|
|
|
|
if (segmentId == 0xFFE0) {
|
|
QByteArray jfif = device->read(segmentLength - 2);
|
|
|
|
if (!jfif.startsWith("JFIF")) return false;
|
|
|
|
stream >> segmentId;
|
|
stream >> segmentLength;
|
|
|
|
if (segmentId == 0xFFE1) {
|
|
QByteArray oldExif = device->read(segmentLength - 2);
|
|
|
|
if (!oldExif.startsWith("Exif")) return false;
|
|
|
|
int dSize = oldExif.size() - exif.size();
|
|
|
|
if (dSize > 0) exif += QByteArray(dSize, '\0');
|
|
|
|
QByteArray remainder = device->readAll();
|
|
|
|
device->seek(0);
|
|
|
|
stream << quint16(0xFFD8); // SOI
|
|
stream << quint16(0xFFE0); // APP0
|
|
stream << quint16(jfif.size() + 2);
|
|
device->write(jfif);
|
|
stream << quint16(0xFFE1); // APP1
|
|
stream << quint16(exif.size() + 2);
|
|
device->write(exif);
|
|
device->write(remainder);
|
|
} else {
|
|
QByteArray remainder = device->readAll();
|
|
|
|
device->seek(0);
|
|
|
|
stream << quint16(0xFFD8); // SOI
|
|
stream << quint16(0xFFE0); // APP0
|
|
stream << quint16(jfif.size() + 2);
|
|
device->write(jfif);
|
|
stream << quint16(0xFFE1); // APP1
|
|
stream << quint16(exif.size() + 2);
|
|
device->write(exif);
|
|
stream << quint16(0xFFE0); // APP0
|
|
stream << segmentId;
|
|
stream << segmentLength;
|
|
device->write(remainder);
|
|
}
|
|
} else if (segmentId == 0xFFE1) {
|
|
QByteArray oldExif = device->read(segmentLength - 2);
|
|
|
|
if (!oldExif.startsWith("Exif")) return false;
|
|
|
|
int dSize = oldExif.size() - exif.size();
|
|
|
|
if (dSize > 0) exif += QByteArray(dSize, '\0');
|
|
|
|
QByteArray remainder = device->readAll();
|
|
|
|
device->seek(0);
|
|
|
|
stream << quint16(0xFFD8); // SOI
|
|
stream << quint16(0xFFE1); // APP1
|
|
stream << quint16(exif.size() + 2);
|
|
device->write(exif);
|
|
device->write(remainder);
|
|
} else {
|
|
QByteArray remainder = device->readAll();
|
|
|
|
device->seek(0);
|
|
|
|
stream << quint16(0xFFD8); // SOI
|
|
stream << quint16(0xFFE1); // APP1
|
|
stream << quint16(exif.size() + 2);
|
|
device->write(exif);
|
|
stream << segmentId;
|
|
stream << segmentLength;
|
|
device->write(remainder);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*!
|
|
Returns the byte order of EXIF file.
|
|
*/
|
|
QSysInfo::Endian QExifImageHeader::byteOrder() const {
|
|
return d->byteOrder;
|
|
}
|
|
|
|
quint32 QExifImageHeader::sizeOf(const QExifValue &value) const {
|
|
switch (value.type()) {
|
|
case QExifValue::Byte:
|
|
case QExifValue::Undefined:
|
|
return quint32(value.count() > 4 ? 12 + value.count() : 12);
|
|
case QExifValue::Ascii:
|
|
return quint32(value.count() > 4 ? 12 + value.count() : 12);
|
|
case QExifValue::Short:
|
|
return value.count() > 2 ? quint32(12 + quint32(value.count()) * sizeof(quint16)) : 12;
|
|
case QExifValue::Long:
|
|
case QExifValue::SignedLong:
|
|
return value.count() > 1 ? quint32(12 + quint32(value.count()) * sizeof(quint32)) : 12;
|
|
case QExifValue::Rational:
|
|
case QExifValue::SignedRational:
|
|
return value.count() > 0 ? quint32(12 + quint32(value.count()) * sizeof(quint32) * 2) : 12;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
quint32 QExifImageHeader::calculateSize(const QMap<T, QExifValue> &values) const {
|
|
quint32 size = sizeof(quint16);
|
|
|
|
foreach (const QExifValue &value, values)
|
|
size += sizeOf(value);
|
|
|
|
return size;
|
|
}
|
|
|
|
/*!
|
|
Returns the size of EXIF data in bytes.
|
|
*/
|
|
qint64 QExifImageHeader::size() const {
|
|
if (d->size == -1) {
|
|
d->size = 2 + // Byte Order
|
|
2 + // Marker
|
|
4 + // Image Ifd offset
|
|
12 + // ExifIfdPointer Ifd
|
|
4 + // Thumbnail Ifd offset
|
|
calculateSize(d->imageIfdValues) + // Image headers and values.
|
|
calculateSize(d->exifIfdValues); // Exif headers and values.
|
|
|
|
if (!d->gpsIfdValues.isEmpty()) {
|
|
d->size += 12 + // GpsInfoIfdPointer Ifd
|
|
calculateSize(d->gpsIfdValues); // Gps headers and values.
|
|
}
|
|
|
|
if (!d->thumbnailData.isEmpty()) {
|
|
d->size += 2 + // Thumbnail Ifd count
|
|
12 + // Compression Ifd
|
|
20 + // XResolution Ifd
|
|
20 + // YResolution Ifd
|
|
12 + // ResolutionUnit Ifd
|
|
12 + // JpegInterchangeFormat Ifd
|
|
12 + // JpegInterchangeFormatLength Ifd
|
|
d->thumbnailData.size(); // Thumbnail data size.
|
|
}
|
|
}
|
|
|
|
return d->size;
|
|
}
|
|
|
|
/*!
|
|
Clears all image meta-data.
|
|
*/
|
|
void QExifImageHeader::clear() {
|
|
d->imageIfdValues.clear();
|
|
d->exifIfdValues.clear();
|
|
d->gpsIfdValues.clear();
|
|
d->thumbnailData.clear();
|
|
|
|
d->size = -1;
|
|
}
|
|
|
|
/*!
|
|
Returns a list of all image tags in an EXIF header.
|
|
*/
|
|
QList<QExifImageHeader::ImageTag> QExifImageHeader::imageTags() const {
|
|
return d->imageIfdValues.keys();
|
|
}
|
|
|
|
/*!
|
|
Returns a list of all extended EXIF tags in a header.
|
|
*/
|
|
QList<QExifImageHeader::ExifExtendedTag> QExifImageHeader::extendedTags() const {
|
|
return d->exifIfdValues.keys();
|
|
}
|
|
|
|
/*!
|
|
Returns a list of all GPS tags in an EXIF header.
|
|
*/
|
|
QList<QExifImageHeader::GpsTag> QExifImageHeader::gpsTags() const {
|
|
return d->gpsIfdValues.keys();
|
|
}
|
|
|
|
/*!
|
|
Returns true if an EXIf header contains a value for an image \a tag and false otherwise.
|
|
*/
|
|
bool QExifImageHeader::contains(ImageTag tag) const {
|
|
return d->imageIfdValues.contains(tag);
|
|
}
|
|
|
|
/*!
|
|
Returns true if a header contains a a value for an extended EXIF \a tag and false otherwise.
|
|
*/
|
|
bool QExifImageHeader::contains(ExifExtendedTag tag) const {
|
|
return d->exifIfdValues.contains(tag);
|
|
}
|
|
|
|
/*!
|
|
Returns true if an EXIf header contains a value for a GPS \a tag and false otherwise.
|
|
*/
|
|
bool QExifImageHeader::contains(GpsTag tag) const {
|
|
return d->gpsIfdValues.contains(tag);
|
|
}
|
|
|
|
/*!
|
|
Removes the value for an image \a tag.
|
|
*/
|
|
void QExifImageHeader::remove(ImageTag tag) {
|
|
d->imageIfdValues.remove(tag);
|
|
|
|
d->size = -1;
|
|
}
|
|
|
|
/*!
|
|
Removes the value for an extended EXIF \a tag.
|
|
*/
|
|
void QExifImageHeader::remove(ExifExtendedTag tag) {
|
|
d->exifIfdValues.remove(tag);
|
|
|
|
d->size = -1;
|
|
}
|
|
|
|
/*!
|
|
Removes the value for a GPS \a tag.
|
|
*/
|
|
void QExifImageHeader::remove(GpsTag tag) {
|
|
d->gpsIfdValues.remove(tag);
|
|
|
|
d->size = -1;
|
|
}
|
|
|
|
/*!
|
|
Returns the value for an image \a tag.
|
|
*/
|
|
QExifValue QExifImageHeader::value(ImageTag tag) const {
|
|
return d->imageIfdValues.value(tag);
|
|
}
|
|
|
|
/*!
|
|
Returns the value for an extended EXIF \a tag.
|
|
*/
|
|
QExifValue QExifImageHeader::value(ExifExtendedTag tag) const {
|
|
return d->exifIfdValues.value(tag);
|
|
}
|
|
|
|
/*!
|
|
Returns the value for a GPS tag.
|
|
*/
|
|
QExifValue QExifImageHeader::value(GpsTag tag) const {
|
|
return d->gpsIfdValues.value(tag);
|
|
}
|
|
|
|
/*!
|
|
Sets the \a value for an image \a tag.
|
|
*/
|
|
void QExifImageHeader::setValue(ImageTag tag, const QExifValue &value) {
|
|
d->imageIfdValues[tag] = value;
|
|
|
|
d->size = -1;
|
|
}
|
|
|
|
/*!
|
|
Sets the \a value for an extended EXIF \a tag.
|
|
*/
|
|
void QExifImageHeader::setValue(ExifExtendedTag tag, const QExifValue &value) {
|
|
d->exifIfdValues[tag] = value;
|
|
|
|
d->size = -1;
|
|
}
|
|
|
|
/*!
|
|
Sets the \a value for an GPS \a tag.
|
|
*/
|
|
void QExifImageHeader::setValue(GpsTag tag, const QExifValue &value) {
|
|
d->gpsIfdValues[tag] = value;
|
|
|
|
d->size = -1;
|
|
}
|
|
|
|
/*!
|
|
Returns the image thumbnail.
|
|
*/
|
|
QImage QExifImageHeader::thumbnail() const {
|
|
QImage image;
|
|
|
|
image.loadFromData(d->thumbnailData, "JPG");
|
|
|
|
if (!d->thumbnailOrientation.isNull()) {
|
|
switch (d->thumbnailOrientation.toShort()) {
|
|
case 1:
|
|
return image;
|
|
case 2:
|
|
return image.transformed(QTransform().rotate(180, Qt::YAxis));
|
|
case 3:
|
|
return image.transformed(QTransform().rotate(180, Qt::ZAxis));
|
|
case 4:
|
|
return image.transformed(QTransform().rotate(180, Qt::XAxis));
|
|
case 5:
|
|
return image.transformed(QTransform().rotate(180, Qt::YAxis).rotate(90, Qt::ZAxis));
|
|
case 6:
|
|
return image.transformed(QTransform().rotate(90, Qt::ZAxis));
|
|
case 7:
|
|
return image.transformed(QTransform().rotate(180, Qt::XAxis).rotate(90, Qt::ZAxis));
|
|
case 8:
|
|
return image.transformed(QTransform().rotate(270, Qt::ZAxis));
|
|
}
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
/*!
|
|
Sets the image \a thumbnail.
|
|
*/
|
|
void QExifImageHeader::setThumbnail(const QImage &thumbnail) {
|
|
if (!thumbnail.isNull()) {
|
|
QBuffer buffer;
|
|
|
|
if (buffer.open(QIODevice::WriteOnly) && thumbnail.save(&buffer, "JPG")) {
|
|
buffer.close();
|
|
|
|
d->thumbnailSize = thumbnail.size();
|
|
d->thumbnailData = buffer.data();
|
|
d->thumbnailOrientation = QExifValue();
|
|
}
|
|
} else {
|
|
d->thumbnailSize = QSize();
|
|
d->thumbnailData = QByteArray();
|
|
}
|
|
|
|
d->size = -1;
|
|
}
|
|
|
|
QByteArray QExifImageHeader::extractExif(QIODevice *device) const {
|
|
QDataStream stream(device);
|
|
|
|
stream.setByteOrder(QDataStream::BigEndian);
|
|
|
|
if (device->read(2) != "\xFF\xD8") return QByteArray();
|
|
|
|
while (device->read(2) != "\xFF\xE1") {
|
|
if (device->atEnd()) return QByteArray();
|
|
|
|
quint16 length;
|
|
|
|
stream >> length;
|
|
|
|
device->seek(device->pos() + length - 2);
|
|
}
|
|
|
|
quint16 length;
|
|
|
|
stream >> length;
|
|
|
|
if (device->read(4) != "Exif") return QByteArray();
|
|
|
|
device->read(2);
|
|
|
|
return device->read(length - 8);
|
|
}
|
|
|
|
QList<ExifIfdHeader> QExifImageHeader::readIfdHeaders(QDataStream &stream) const {
|
|
QList<ExifIfdHeader> headers;
|
|
|
|
quint16 count;
|
|
|
|
stream >> count;
|
|
|
|
for (quint16 i = 0; i < count; i++) {
|
|
ExifIfdHeader header;
|
|
|
|
stream >> header;
|
|
|
|
headers.append(header);
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
|
|
QExifValue QExifImageHeader::readIfdValue(QDataStream &stream, int startPos, const ExifIfdHeader &header) const {
|
|
switch (header.type) {
|
|
case QExifValue::Byte: {
|
|
QVector<quint8> value(int(header.count));
|
|
|
|
if (header.count > 4) {
|
|
stream.device()->seek(startPos + qint64(header.offset));
|
|
|
|
for (quint32 i = 0; i < header.count; i++)
|
|
stream >> value[int(i)];
|
|
} else {
|
|
for (quint32 i = 0; i < header.count; i++)
|
|
value[int(i)] = header.offsetBytes[i];
|
|
}
|
|
return QExifValue(value);
|
|
}
|
|
case QExifValue::Undefined:
|
|
if (header.count > 4) {
|
|
stream.device()->seek(startPos + qint64(header.offset));
|
|
|
|
return QExifValue(stream.device()->read(header.count));
|
|
} else {
|
|
return QExifValue(QByteArray::fromRawData(header.offsetAscii, int(header.count)));
|
|
}
|
|
case QExifValue::Ascii:
|
|
if (header.count > 4) {
|
|
stream.device()->seek(startPos + qint64(header.offset));
|
|
|
|
QByteArray ascii = stream.device()->read(header.count);
|
|
|
|
return QExifValue(QString::fromUtf8(ascii.constData(), ascii.size() - 1));
|
|
} else {
|
|
return QExifValue(QString::fromUtf8(header.offsetAscii, int(header.count) - 1));
|
|
}
|
|
case QExifValue::Short: {
|
|
QVector<quint16> value(int(header.count));
|
|
|
|
if (header.count > 2) {
|
|
stream.device()->seek(startPos + qint64(header.offset));
|
|
|
|
for (quint32 i = 0; i < header.count; i++)
|
|
stream >> value[int(i)];
|
|
} else {
|
|
for (quint32 i = 0; i < header.count; i++)
|
|
value[int(i)] = header.offsetShorts[i];
|
|
}
|
|
return QExifValue(value);
|
|
}
|
|
case QExifValue::Long: {
|
|
QVector<quint32> value(int(header.count));
|
|
|
|
if (header.count > 1) {
|
|
stream.device()->seek(startPos + qint64(header.offset));
|
|
|
|
for (quint32 i = 0; i < header.count; i++)
|
|
stream >> value[int(i)];
|
|
} else if (header.count == 1) {
|
|
value[0] = header.offset;
|
|
}
|
|
return QExifValue(value);
|
|
}
|
|
case QExifValue::SignedLong: {
|
|
QVector<qint32> value(int(header.count));
|
|
|
|
if (header.count > 1) {
|
|
stream.device()->seek(startPos + qint64(header.offset));
|
|
|
|
for (quint32 i = 0; i < header.count; i++)
|
|
stream >> value[int(i)];
|
|
} else if (header.count == 1) {
|
|
value[0] = int(header.offset);
|
|
}
|
|
return QExifValue(value);
|
|
}
|
|
case QExifValue::Rational: {
|
|
QVector<QExifURational> value(int(header.count));
|
|
|
|
stream.device()->seek(startPos + qint64(header.offset));
|
|
|
|
for (quint32 i = 0; i < header.count; i++)
|
|
stream >> value[int(i)];
|
|
|
|
return QExifValue(value);
|
|
}
|
|
case QExifValue::SignedRational: {
|
|
QVector<QExifSRational> value(int(header.count));
|
|
|
|
stream.device()->seek(startPos + qint64(header.offset));
|
|
|
|
for (quint32 i = 0; i < header.count; i++)
|
|
stream >> value[int(i)];
|
|
|
|
return QExifValue(value);
|
|
}
|
|
default:
|
|
qWarning() << "Invalid Ifd Type" << header.type;
|
|
|
|
return QExifValue();
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
QMap<T, QExifValue>
|
|
QExifImageHeader::readIfdValues(QDataStream &stream, int startPos, const QList<ExifIfdHeader> &headers) const {
|
|
QMap<T, QExifValue> values;
|
|
|
|
// This needs to be non-const so it works with gcc3
|
|
QList<ExifIfdHeader> headers_ = headers;
|
|
foreach (const ExifIfdHeader &header, headers_)
|
|
values[T(header.tag)] = readIfdValue(stream, startPos, header);
|
|
|
|
return values;
|
|
}
|
|
|
|
template <typename T>
|
|
QMap<T, QExifValue>
|
|
QExifImageHeader::readIfdValues(QDataStream &stream, int startPos, const QExifValue &pointer) const {
|
|
if (pointer.type() == QExifValue::Long && pointer.count() == 1) {
|
|
stream.device()->seek(qint64(startPos) + pointer.toLong());
|
|
|
|
QList<ExifIfdHeader> headers = readIfdHeaders(stream);
|
|
|
|
return readIfdValues<T>(stream, startPos, headers);
|
|
} else {
|
|
return QMap<T, QExifValue>();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Reads the contents of an EXIF header from an I/O \a device.
|
|
|
|
Returns true if the header was read and false otherwise.
|
|
|
|
\sa loadFromJpeg(), write()
|
|
*/
|
|
bool QExifImageHeader::read(QIODevice *device) {
|
|
clear();
|
|
|
|
int startPos = int(device->pos());
|
|
|
|
QDataStream stream(device);
|
|
|
|
QByteArray byteOrder = device->read(2);
|
|
|
|
if (byteOrder == "II") {
|
|
d->byteOrder = QSysInfo::LittleEndian;
|
|
|
|
stream.setByteOrder(QDataStream::LittleEndian);
|
|
} else if (byteOrder == "MM") {
|
|
d->byteOrder = QSysInfo::BigEndian;
|
|
|
|
stream.setByteOrder(QDataStream::BigEndian);
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
quint16 id;
|
|
quint32 offset;
|
|
|
|
stream >> id;
|
|
stream >> offset;
|
|
|
|
if (id != 0x002A) return false;
|
|
|
|
device->seek(startPos + qint64(offset));
|
|
|
|
QList<ExifIfdHeader> headers = readIfdHeaders(stream);
|
|
|
|
stream >> offset;
|
|
|
|
d->imageIfdValues = readIfdValues<ImageTag>(stream, startPos, headers);
|
|
|
|
QExifValue exifIfdPointer = d->imageIfdValues.take(ImageTag(ExifIfdPointer));
|
|
QExifValue gpsIfdPointer = d->imageIfdValues.take(ImageTag(GpsInfoIfdPointer));
|
|
|
|
d->exifIfdValues = readIfdValues<ExifExtendedTag>(stream, startPos, exifIfdPointer);
|
|
d->gpsIfdValues = readIfdValues<GpsTag>(stream, startPos, gpsIfdPointer);
|
|
|
|
d->exifIfdValues.remove(ExifExtendedTag(InteroperabilityIfdPointer));
|
|
|
|
if (offset) {
|
|
device->seek(startPos + qint64(offset));
|
|
|
|
QMap<quint16, QExifValue> thumbnailIfdValues = readIfdValues<quint16>(stream, startPos, readIfdHeaders(stream));
|
|
|
|
QExifValue jpegOffset = thumbnailIfdValues.value(JpegInterchangeFormat);
|
|
QExifValue jpegLength = thumbnailIfdValues.value(JpegInterchangeFormatLength);
|
|
|
|
if (jpegOffset.type() == QExifValue::Long && jpegOffset.count() == 1 && jpegLength.type() == QExifValue::Long &&
|
|
jpegLength.count() == 1) {
|
|
device->seek(startPos + qint64(jpegOffset.toLong()));
|
|
|
|
d->thumbnailData = device->read(jpegLength.toLong());
|
|
|
|
d->thumbnailXResolution = thumbnailIfdValues.value(XResolution);
|
|
d->thumbnailYResolution = thumbnailIfdValues.value(YResolution);
|
|
d->thumbnailResolutionUnit = thumbnailIfdValues.value(ResolutionUnit);
|
|
d->thumbnailOrientation = thumbnailIfdValues.value(Orientation);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
quint32
|
|
QExifImageHeader::writeExifHeader(QDataStream &stream, quint16 tag, const QExifValue &value, quint32 offset) const {
|
|
stream << tag;
|
|
stream << quint16(value.type());
|
|
stream << quint32(value.count());
|
|
|
|
switch (value.type()) {
|
|
case QExifValue::Byte:
|
|
if (value.count() <= 4) {
|
|
foreach (quint8 byte, value.toByteVector())
|
|
stream << byte;
|
|
for (int j = value.count(); j < 4; j++)
|
|
stream << quint8(0);
|
|
} else {
|
|
stream << offset;
|
|
|
|
offset += quint32(value.count());
|
|
}
|
|
break;
|
|
case QExifValue::Undefined:
|
|
if (value.count() <= 4) {
|
|
stream.device()->write(value.toByteArray());
|
|
|
|
if (value.count() < 4) stream.writeRawData("\0\0\0\0", 4 - value.count());
|
|
} else {
|
|
stream << offset;
|
|
|
|
offset += quint32(value.count());
|
|
}
|
|
break;
|
|
case QExifValue::Ascii:
|
|
if (value.count() <= 4) {
|
|
QByteArray bytes = value.toByteArray();
|
|
|
|
stream.writeRawData(bytes.constData(), value.count());
|
|
if (value.count() < 4) stream.writeRawData("\0\0\0\0", 4 - value.count());
|
|
} else {
|
|
stream << offset;
|
|
|
|
offset += quint32(value.count());
|
|
}
|
|
break;
|
|
case QExifValue::Short:
|
|
if (value.count() <= 2) {
|
|
foreach (quint16 shrt, value.toShortVector())
|
|
stream << shrt;
|
|
for (int j = value.count(); j < 2; j++)
|
|
stream << quint16(0);
|
|
} else {
|
|
stream << offset;
|
|
|
|
offset += quint32(value.count()) * quint32(sizeof(quint16));
|
|
}
|
|
break;
|
|
case QExifValue::Long:
|
|
if (value.count() == 0) {
|
|
stream << quint32(0);
|
|
} else if (value.count() == 1) {
|
|
stream << value.toLong();
|
|
} else {
|
|
stream << offset;
|
|
|
|
offset += quint32(value.count()) * quint32(sizeof(quint32));
|
|
}
|
|
break;
|
|
case QExifValue::SignedLong:
|
|
if (value.count() == 0) {
|
|
stream << quint32(0);
|
|
} else if (value.count() == 1) {
|
|
stream << value.toSignedLong();
|
|
} else {
|
|
stream << offset;
|
|
|
|
offset += quint32(value.count()) * quint32(sizeof(quint32));
|
|
}
|
|
break;
|
|
case QExifValue::Rational:
|
|
if (value.count() == 0) {
|
|
stream << quint32(0);
|
|
} else {
|
|
stream << offset;
|
|
|
|
offset += quint32(value.count()) * quint32(sizeof(quint32)) * 2;
|
|
}
|
|
break;
|
|
case QExifValue::SignedRational:
|
|
if (value.count() == 0) {
|
|
stream << quint32(0);
|
|
} else {
|
|
stream << offset;
|
|
|
|
offset += quint32(value.count()) * quint32(sizeof(quint32)) * 2;
|
|
}
|
|
break;
|
|
default:
|
|
qWarning() << "Invalid Ifd Type" << value.type();
|
|
stream << quint32(0);
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
void QExifImageHeader::writeExifValue(QDataStream &stream, const QExifValue &value) const {
|
|
switch (value.type()) {
|
|
case QExifValue::Byte:
|
|
if (value.count() > 4)
|
|
foreach (quint8 byte, value.toByteVector())
|
|
stream << byte;
|
|
break;
|
|
case QExifValue::Undefined:
|
|
if (value.count() > 4) stream.device()->write(value.toByteArray());
|
|
break;
|
|
case QExifValue::Ascii:
|
|
if (value.count() > 4) {
|
|
QByteArray bytes = value.toByteArray();
|
|
|
|
stream.writeRawData(bytes.constData(), bytes.size() + 1);
|
|
}
|
|
break;
|
|
case QExifValue::Short:
|
|
if (value.count() > 2)
|
|
foreach (quint16 shrt, value.toShortVector())
|
|
stream << shrt;
|
|
break;
|
|
case QExifValue::Long:
|
|
if (value.count() > 1)
|
|
foreach (quint32 lng, value.toLongVector())
|
|
stream << lng;
|
|
break;
|
|
case QExifValue::SignedLong:
|
|
if (value.count() > 1)
|
|
foreach (qint32 lng, value.toSignedLongVector())
|
|
stream << lng;
|
|
break;
|
|
case QExifValue::Rational:
|
|
if (value.count() > 0)
|
|
foreach (QExifURational rational, value.toRationalVector())
|
|
stream << rational;
|
|
break;
|
|
case QExifValue::SignedRational:
|
|
if (value.count() > 0)
|
|
foreach (QExifSRational rational, value.toSignedRationalVector())
|
|
stream << rational;
|
|
break;
|
|
default:
|
|
qWarning() << "Invalid Ifd Type" << value.type();
|
|
break;
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
quint32
|
|
QExifImageHeader::writeExifHeaders(QDataStream &stream, const QMap<T, QExifValue> &values, quint32 offset) const {
|
|
offset += quint32(values.count() * 12);
|
|
|
|
for (typename QMap<T, QExifValue>::const_iterator i = values.constBegin(); i != values.constEnd(); i++)
|
|
offset = writeExifHeader(stream, i.key(), i.value(), offset);
|
|
|
|
return offset;
|
|
}
|
|
|
|
template <typename T>
|
|
void QExifImageHeader::writeExifValues(QDataStream &stream, const QMap<T, QExifValue> &values) const {
|
|
for (typename QMap<T, QExifValue>::const_iterator i = values.constBegin(); i != values.constEnd(); i++)
|
|
writeExifValue(stream, i.value());
|
|
}
|
|
|
|
/*!
|
|
Writes an EXIF header to an I/O \a device.
|
|
|
|
Returns the total number of bytes written.
|
|
*/
|
|
qint64 QExifImageHeader::write(QIODevice *device) const {
|
|
// #ifndef QT_NO_DEBUG
|
|
qint64 startPos = device->pos();
|
|
// #endif
|
|
|
|
QDataStream stream(device);
|
|
|
|
if (d->byteOrder == QSysInfo::LittleEndian) {
|
|
stream.setByteOrder(QDataStream::LittleEndian);
|
|
|
|
device->write("II", 2);
|
|
device->write("\x2A\x00", 2);
|
|
device->write("\x08\x00\x00\x00", 4);
|
|
} else if (d->byteOrder == QSysInfo::BigEndian) {
|
|
stream.setByteOrder(QDataStream::BigEndian);
|
|
|
|
device->write("MM", 2);
|
|
device->write("\x00\x2A", 2);
|
|
device->write("\x00\x00\x00\x08", 4);
|
|
}
|
|
|
|
quint16 count = quint16(d->imageIfdValues.count() + 1);
|
|
quint32 offset = 26;
|
|
|
|
if (!d->gpsIfdValues.isEmpty()) {
|
|
count++;
|
|
offset += 12;
|
|
}
|
|
|
|
stream << count;
|
|
|
|
offset = writeExifHeaders(stream, d->imageIfdValues, offset);
|
|
|
|
quint32 exifIfdOffset = offset;
|
|
|
|
stream << quint16(ExifIfdPointer);
|
|
stream << quint16(QExifValue::Long);
|
|
stream << quint32(1);
|
|
stream << exifIfdOffset;
|
|
offset += calculateSize(d->exifIfdValues);
|
|
|
|
quint32 gpsIfdOffset = offset;
|
|
|
|
if (!d->gpsIfdValues.isEmpty()) {
|
|
stream << quint16(GpsInfoIfdPointer);
|
|
stream << quint16(QExifValue::Long);
|
|
stream << quint32(1);
|
|
stream << gpsIfdOffset;
|
|
|
|
d->imageIfdValues.insert(ImageTag(GpsInfoIfdPointer), QExifValue(offset));
|
|
|
|
offset += calculateSize(d->gpsIfdValues);
|
|
}
|
|
|
|
if (!d->thumbnailData.isEmpty()) stream << offset; // Write offset to thumbnail Ifd.
|
|
else stream << quint32(0);
|
|
|
|
writeExifValues(stream, d->imageIfdValues);
|
|
|
|
Q_ASSERT(startPos + exifIfdOffset == device->pos());
|
|
|
|
stream << quint16(d->exifIfdValues.count());
|
|
|
|
writeExifHeaders(stream, d->exifIfdValues, exifIfdOffset);
|
|
writeExifValues(stream, d->exifIfdValues);
|
|
|
|
Q_ASSERT(startPos + gpsIfdOffset == device->pos());
|
|
|
|
if (!d->gpsIfdValues.isEmpty()) {
|
|
stream << quint16(d->gpsIfdValues.count());
|
|
|
|
writeExifHeaders(stream, d->gpsIfdValues, gpsIfdOffset);
|
|
writeExifValues(stream, d->gpsIfdValues);
|
|
}
|
|
|
|
Q_ASSERT(startPos + offset == device->pos());
|
|
|
|
if (!d->thumbnailData.isEmpty()) {
|
|
offset += 86;
|
|
|
|
stream << quint16(7);
|
|
|
|
QExifValue xResolution =
|
|
d->thumbnailXResolution.isNull() ? QExifValue(QExifURational(72, 1)) : d->thumbnailXResolution;
|
|
|
|
QExifValue yResolution =
|
|
d->thumbnailYResolution.isNull() ? QExifValue(QExifURational(72, 1)) : d->thumbnailYResolution;
|
|
|
|
QExifValue resolutionUnit =
|
|
d->thumbnailResolutionUnit.isNull() ? QExifValue(quint16(2)) : d->thumbnailResolutionUnit;
|
|
|
|
QExifValue orientation = d->thumbnailOrientation.isNull() ? QExifValue(quint16(0)) : d->thumbnailOrientation;
|
|
|
|
writeExifHeader(stream, Compression, QExifValue(quint16(6)), offset);
|
|
|
|
offset = writeExifHeader(stream, XResolution, xResolution, offset);
|
|
offset = writeExifHeader(stream, YResolution, yResolution, offset);
|
|
|
|
writeExifHeader(stream, ResolutionUnit, resolutionUnit, offset);
|
|
writeExifHeader(stream, Orientation, orientation, offset);
|
|
writeExifHeader(stream, JpegInterchangeFormat, QExifValue(offset), offset);
|
|
writeExifHeader(stream, JpegInterchangeFormatLength, QExifValue(quint32(d->thumbnailData.size())), offset);
|
|
|
|
writeExifValue(stream, xResolution);
|
|
writeExifValue(stream, yResolution);
|
|
|
|
Q_ASSERT(startPos + offset == device->pos());
|
|
|
|
device->write(d->thumbnailData);
|
|
|
|
offset += quint32(d->thumbnailData.size());
|
|
}
|
|
|
|
Q_ASSERT(startPos + offset == device->pos());
|
|
|
|
d->size = offset;
|
|
|
|
return offset;
|
|
}
|