mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-05-28 00:30:23 -04:00
Improved EXIF V3 compatibility
EXIF specs V3 added the UTF-8 data type. The MicroExif class now allows serializations to choose whether to support the V2 or V3 format. To maximize compatibility with old readers, even when V3 is set, the ASCII data type is used if possible. The JXR plugin, based on TIFF V6 container, does not allow to use the V3 format (it does not recognize the UTF-8 data type) and therefore V2 has been forced. For all other plugins using MicroExif, it is now possible to save, e.g., descriptions in Japanese. Please note that this patch is also a bugfix: when saving, version 3 was set but the strings were always saved as ASCII.
This commit is contained in:
parent
9c47845f15
commit
ae62ea3dfc
@ -31,7 +31,7 @@
|
||||
},
|
||||
{
|
||||
"key" : "Description",
|
||||
"value" : "TV broadcast test image."
|
||||
"value" : "テレビ放送テスト映像。(TV broadcast test image.)"
|
||||
},
|
||||
{
|
||||
"key" : "Latitude",
|
||||
|
@ -31,7 +31,7 @@
|
||||
},
|
||||
{
|
||||
"key" : "Description",
|
||||
"value" : "TV broadcast test image."
|
||||
"value" : "テレビ放送テスト映像。(TV broadcast test image.)"
|
||||
},
|
||||
{
|
||||
"key" : "Latitude",
|
||||
|
@ -31,7 +31,7 @@
|
||||
},
|
||||
{
|
||||
"key" : "Description",
|
||||
"value" : "TV broadcast test image."
|
||||
"value" : "テレビ放送テスト映像。(TV broadcast test image.)"
|
||||
},
|
||||
{
|
||||
"key" : "Latitude",
|
||||
|
@ -720,11 +720,11 @@ public:
|
||||
|
||||
auto exif = MicroExif::fromImage(image);
|
||||
if (!exif.isEmpty()) {
|
||||
auto exifIfd = exif.exifIfdByteArray(QDataStream::LittleEndian);
|
||||
auto exifIfd = exif.exifIfdByteArray(QDataStream::LittleEndian, MicroExif::V2);
|
||||
if (auto err = PKImageEncode_SetEXIFMetadata_WMP(pEncoder, reinterpret_cast<const quint8 *>(exifIfd.constData()), exifIfd.size())) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting EXIF data:" << err;
|
||||
}
|
||||
auto gpsIfd = exif.gpsIfdByteArray(QDataStream::LittleEndian);
|
||||
auto gpsIfd = exif.gpsIfdByteArray(QDataStream::LittleEndian, MicroExif::V2);
|
||||
if (auto err = PKImageEncode_SetGPSInfoMetadata_WMP(pEncoder, reinterpret_cast<const quint8 *>(gpsIfd.constData()), gpsIfd.size())) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting GPS data:" << err;
|
||||
}
|
||||
|
@ -111,17 +111,17 @@ static const KnownTags staticTagTypes = {
|
||||
TagInfo(TIFF_IMAGEWIDTH, ExifTagType::Long),
|
||||
TagInfo(TIFF_IMAGEHEIGHT, ExifTagType::Long),
|
||||
TagInfo(TIFF_BITSPERSAMPLE, ExifTagType::Short),
|
||||
TagInfo(TIFF_IMAGEDESCRIPTION, ExifTagType::Ascii),
|
||||
TagInfo(TIFF_MAKE, ExifTagType::Ascii),
|
||||
TagInfo(TIFF_MODEL, ExifTagType::Ascii),
|
||||
TagInfo(TIFF_IMAGEDESCRIPTION, ExifTagType::Utf8),
|
||||
TagInfo(TIFF_MAKE, ExifTagType::Utf8),
|
||||
TagInfo(TIFF_MODEL, ExifTagType::Utf8),
|
||||
TagInfo(TIFF_ORIENT, ExifTagType::Short),
|
||||
TagInfo(TIFF_XRES, ExifTagType::Rational),
|
||||
TagInfo(TIFF_YRES, ExifTagType::Rational),
|
||||
TagInfo(TIFF_URES, ExifTagType::Short),
|
||||
TagInfo(TIFF_SOFTWARE, ExifTagType::Ascii),
|
||||
TagInfo(TIFF_ARTIST, ExifTagType::Ascii),
|
||||
TagInfo(TIFF_SOFTWARE, ExifTagType::Utf8),
|
||||
TagInfo(TIFF_ARTIST, ExifTagType::Utf8),
|
||||
TagInfo(TIFF_DATETIME, ExifTagType::Ascii),
|
||||
TagInfo(TIFF_COPYRIGHT, ExifTagType::Ascii),
|
||||
TagInfo(TIFF_COPYRIGHT, ExifTagType::Utf8),
|
||||
TagInfo(EXIF_EXIFIFD, ExifTagType::Long),
|
||||
TagInfo(EXIF_GPSIFD, ExifTagType::Long),
|
||||
TagInfo(EXIF_DATETIMEORIGINAL, ExifTagType::Ascii),
|
||||
@ -134,10 +134,10 @@ static const KnownTags staticTagTypes = {
|
||||
TagInfo(EXIF_PIXELYDIM, ExifTagType::Long),
|
||||
TagInfo(EXIF_IMAGEUNIQUEID, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_BODYSERIALNUMBER, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_LENSMAKE, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_LENSMODEL, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_LENSMAKE, ExifTagType::Utf8),
|
||||
TagInfo(EXIF_LENSMODEL, ExifTagType::Utf8),
|
||||
TagInfo(EXIF_LENSSERIALNUMBER, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_IMAGETITLE, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_IMAGETITLE, ExifTagType::Utf8),
|
||||
TagInfo(EXIF_EXIFVERSION, ExifTagType::Undefined)
|
||||
};
|
||||
// clang-format on
|
||||
@ -367,6 +367,30 @@ static void writeData(QDataStream &ds, const QVariant &value, const ExifTagType&
|
||||
}
|
||||
}
|
||||
|
||||
static ExifTagType updateDataType(const ExifTagType &dataType, const QVariant &value, const MicroExif::Version &ver)
|
||||
{
|
||||
if (dataType != ExifTagType::Utf8)
|
||||
return dataType;
|
||||
|
||||
if (ver == MicroExif::V2)
|
||||
return ExifTagType::Ascii;
|
||||
|
||||
// Note that in EXIF specs, UTF-8 is backward compatible with ASCII: all UTF-8 tags can also be ASCII.
|
||||
// To maximize compatibility, I check if the string can be encoded in ASCII.
|
||||
auto txt = value.toString();
|
||||
|
||||
// I try to implement a rudimentary check without going through Qt's string conversion classes:
|
||||
// a UTF-8 string if it uses only characters in the first 127 of the ASCII table is encoded as
|
||||
// a Latin 1. Each character above 128, whether it is an extended ASCII character or a character
|
||||
// of another nature, uses 2 or more bytes. Since the EXIF specifications state that the ASCII
|
||||
// type must use only the first 127 characters, I only need to do the size comparison to understand
|
||||
// which type to use.
|
||||
if (txt.toLatin1().size() == txt.toUtf8().size())
|
||||
return ExifTagType::Ascii;
|
||||
|
||||
return dataType;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief writeIfd
|
||||
* \param ds The stream.
|
||||
@ -375,7 +399,12 @@ static void writeData(QDataStream &ds, const QVariant &value, const ExifTagType&
|
||||
* \param knownTags List of known and supported tags.
|
||||
* \return True on success, otherwise false.
|
||||
*/
|
||||
static bool writeIfd(QDataStream &ds, const MicroExif::Tags &tags, TagPos &positions, quint32 pos = 0, const KnownTags &knownTags = staticTagTypes)
|
||||
static bool writeIfd(QDataStream &ds,
|
||||
const MicroExif::Version &ver,
|
||||
const MicroExif::Tags &tags,
|
||||
TagPos &positions,
|
||||
quint32 pos = 0,
|
||||
const KnownTags &knownTags = staticTagTypes)
|
||||
{
|
||||
if (tags.isEmpty())
|
||||
return true;
|
||||
@ -390,7 +419,7 @@ static bool writeIfd(QDataStream &ds, const MicroExif::Tags &tags, TagPos &posit
|
||||
continue;
|
||||
}
|
||||
auto value = tags.value(key);
|
||||
auto dataType = knownTags.value(key);
|
||||
auto dataType = updateDataType(knownTags.value(key), value, ver);
|
||||
auto count = countBytes(dataType, value);
|
||||
|
||||
ds << quint16(key);
|
||||
@ -412,9 +441,8 @@ static bool writeIfd(QDataStream &ds, const MicroExif::Tags &tags, TagPos &posit
|
||||
if (!knownTags.contains(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto value = tags.value(key);
|
||||
auto dataType = knownTags.value(key);
|
||||
auto dataType = updateDataType(knownTags.value(key), value, ver);
|
||||
auto count = countBytes(dataType, value);
|
||||
auto valueSize = count * EXIF_TAG_SIZEOF(dataType);
|
||||
if (valueSize <= 4)
|
||||
@ -1032,7 +1060,7 @@ void MicroExif::setImageDirection(double degree, bool isMagnetic)
|
||||
m_gpsTags.insert(GPS_IMGDIRECTION, degree);
|
||||
}
|
||||
|
||||
QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder) const
|
||||
QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder, const Version &version) const
|
||||
{
|
||||
QByteArray ba;
|
||||
{
|
||||
@ -1043,16 +1071,16 @@ QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder) const
|
||||
return ba;
|
||||
}
|
||||
|
||||
QByteArray MicroExif::exifIfdByteArray(const QDataStream::ByteOrder &byteOrder) const
|
||||
QByteArray MicroExif::exifIfdByteArray(const QDataStream::ByteOrder &byteOrder, const Version &version) const
|
||||
{
|
||||
QByteArray ba;
|
||||
{
|
||||
QDataStream ds(&ba, QIODevice::WriteOnly);
|
||||
ds.setByteOrder(byteOrder);
|
||||
auto exifTags = m_exifTags;
|
||||
exifTags.insert(EXIF_EXIFVERSION, QByteArray("0300"));
|
||||
exifTags.insert(EXIF_EXIFVERSION, version == Version::V3 ? QByteArray("0300") : QByteArray("0232"));
|
||||
TagPos positions;
|
||||
if (!writeIfd(ds, exifTags, positions))
|
||||
if (!writeIfd(ds, version, exifTags, positions))
|
||||
return {};
|
||||
}
|
||||
return ba;
|
||||
@ -1065,7 +1093,7 @@ bool MicroExif::setExifIfdByteArray(const QByteArray &ba, const QDataStream::Byt
|
||||
return readIfd(ds, m_exifTags, 0, staticTagTypes);
|
||||
}
|
||||
|
||||
QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder) const
|
||||
QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder, const Version &version) const
|
||||
{
|
||||
QByteArray ba;
|
||||
{
|
||||
@ -1074,7 +1102,7 @@ QByteArray MicroExif::gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder) c
|
||||
auto gpsTags = m_gpsTags;
|
||||
gpsTags.insert(GPS_GPSVERSION, QByteArray("2400"));
|
||||
TagPos positions;
|
||||
if (!writeIfd(ds, gpsTags, positions, 0, staticGpsTagTypes))
|
||||
if (!writeIfd(ds, version, gpsTags, positions, 0, staticGpsTagTypes))
|
||||
return {};
|
||||
return ba;
|
||||
}
|
||||
@ -1087,7 +1115,7 @@ bool MicroExif::setGpsIfdByteArray(const QByteArray &ba, const QDataStream::Byte
|
||||
return readIfd(ds, m_gpsTags, 0, staticGpsTagTypes);
|
||||
}
|
||||
|
||||
bool MicroExif::write(QIODevice *device, const QDataStream::ByteOrder &byteOrder) const
|
||||
bool MicroExif::write(QIODevice *device, const QDataStream::ByteOrder &byteOrder, const Version &version) const
|
||||
{
|
||||
if (device == nullptr || device->isSequential() || isEmpty())
|
||||
return false;
|
||||
@ -1096,7 +1124,7 @@ bool MicroExif::write(QIODevice *device, const QDataStream::ByteOrder &byteOrder
|
||||
ds.setByteOrder(byteOrder);
|
||||
if (!writeHeader(ds))
|
||||
return false;
|
||||
if (!writeIfds(ds))
|
||||
if (!writeIfds(ds, version))
|
||||
return false;
|
||||
}
|
||||
device->close();
|
||||
@ -1327,30 +1355,30 @@ bool MicroExif::writeHeader(QDataStream &ds) const
|
||||
return ds.status() == QDataStream::Ok;
|
||||
}
|
||||
|
||||
bool MicroExif::writeIfds(QDataStream &ds) const
|
||||
bool MicroExif::writeIfds(QDataStream &ds, const Version &version) const
|
||||
{
|
||||
auto tiffTags = m_tiffTags;
|
||||
auto exifTags = m_exifTags;
|
||||
auto gpsTags = m_gpsTags;
|
||||
updateTags(tiffTags, exifTags, gpsTags);
|
||||
updateTags(tiffTags, exifTags, gpsTags, version);
|
||||
|
||||
TagPos positions;
|
||||
if (!writeIfd(ds, tiffTags, positions))
|
||||
if (!writeIfd(ds, version, tiffTags, positions))
|
||||
return false;
|
||||
if (!writeIfd(ds, exifTags, positions, positions.value(EXIF_EXIFIFD)))
|
||||
if (!writeIfd(ds, version, exifTags, positions, positions.value(EXIF_EXIFIFD)))
|
||||
return false;
|
||||
if (!writeIfd(ds, gpsTags, positions, positions.value(EXIF_GPSIFD), staticGpsTagTypes))
|
||||
if (!writeIfd(ds, version, gpsTags, positions, positions.value(EXIF_GPSIFD), staticGpsTagTypes))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void MicroExif::updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags) const
|
||||
void MicroExif::updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags, const Version &version) const
|
||||
{
|
||||
if (exifTags.isEmpty()) {
|
||||
tiffTags.remove(EXIF_EXIFIFD);
|
||||
} else {
|
||||
tiffTags.insert(EXIF_EXIFIFD, quint32());
|
||||
exifTags.insert(EXIF_EXIFVERSION, QByteArray("0300"));
|
||||
exifTags.insert(EXIF_EXIFVERSION, version == Version::V3 ? QByteArray("0300") : QByteArray("0232"));
|
||||
}
|
||||
if (gpsTags.isEmpty()) {
|
||||
tiffTags.remove(EXIF_GPSIFD);
|
||||
|
@ -37,6 +37,15 @@ class MicroExif
|
||||
public:
|
||||
using Tags = QMap<quint16, QVariant>;
|
||||
|
||||
/*!
|
||||
* \brief The Version enum
|
||||
* Exif specs version used when writing.
|
||||
*/
|
||||
enum Version {
|
||||
V2, // V2.xx
|
||||
V3 // V3.xx, use of UTF-8 data type (default)
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief MicroExif
|
||||
* Constructs an empty class.
|
||||
@ -265,18 +274,20 @@ public:
|
||||
* - EXIF IFD
|
||||
* - GPS IFD
|
||||
* \param byteOrder Sets the serialization byte order for EXIF data.
|
||||
* \param version The EXIF specs version to use.
|
||||
* \return A byte array containing the serialized data.
|
||||
* \sa write
|
||||
*/
|
||||
QByteArray toByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
|
||||
QByteArray toByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const;
|
||||
|
||||
/*!
|
||||
* \brief exifIfdByteArray
|
||||
* Convert the EXIF IFD only to RAW data. Useful when you want to add EXIF data to an existing TIFF container.
|
||||
* \param byteOrder Sets the serialization byte order for the data.
|
||||
* \param version The EXIF specs version to use.
|
||||
* \return A byte array containing the serialized data.
|
||||
*/
|
||||
QByteArray exifIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
|
||||
QByteArray exifIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const;
|
||||
/*!
|
||||
* \brief setExifIfdByteArray
|
||||
* \param ba The RAW data of EXIF IFD.
|
||||
@ -289,9 +300,10 @@ public:
|
||||
* \brief gpsIfdByteArray
|
||||
* Convert the GPS IFD only to RAW data. Useful when you want to add GPS data to an existing TIFF container.
|
||||
* \param byteOrder Sets the serialization byte order for the data.
|
||||
* \param version The EXIF specs version to use.
|
||||
* \return A byte array containing the serialized data.
|
||||
*/
|
||||
QByteArray gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
|
||||
QByteArray gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const;
|
||||
/*!
|
||||
* \brief setGpsIfdByteArray
|
||||
* \param ba The RAW data of GPS IFD.
|
||||
@ -309,10 +321,11 @@ public:
|
||||
* - GPS IFD
|
||||
* \param device A random access device.
|
||||
* \param byteOrder Sets the serialization byte order for EXIF data.
|
||||
* \param version The EXIF specs version to use.
|
||||
* \return True on success, otherwise false.
|
||||
* \sa toByteArray
|
||||
*/
|
||||
bool write(QIODevice *device, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER) const;
|
||||
bool write(QIODevice *device, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const;
|
||||
|
||||
/*!
|
||||
* \brief updateImageMetadata
|
||||
@ -373,8 +386,8 @@ private:
|
||||
void setGpsString(quint16 tagId, const QString& s);
|
||||
QString gpsString(quint16 tagId) const;
|
||||
bool writeHeader(QDataStream &ds) const;
|
||||
bool writeIfds(QDataStream &ds) const;
|
||||
void updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags) const;
|
||||
bool writeIfds(QDataStream &ds, const Version &version) const;
|
||||
void updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags, const Version &version) const;
|
||||
|
||||
static void setString(Tags &tags, quint16 tagId, const QString &s);
|
||||
static QString string(const Tags &tags, quint16 tagId);
|
||||
|
Loading…
Reference in New Issue
Block a user