File indexing completed on 2024-12-22 04:16:01
0001 /* 0002 * SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net> 0003 * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.1-or-later 0006 */ 0007 0008 #ifndef _KIS_EXIV2_COMMON_H_ 0009 #define _KIS_EXIV2_COMMON_H_ 0010 0011 #include <exiv2/exiv2.hpp> 0012 0013 #include <QDateTime> 0014 #include <QScopedPointer> 0015 0016 #include <kis_debug.h> 0017 #include <kis_meta_data_value.h> 0018 0019 // ---- Generic conversion functions ---- // 0020 0021 // Convert an exiv value to a KisMetaData value 0022 inline KisMetaData::Value 0023 #if EXIV2_TEST_VERSION(0,28,0) 0024 exivValueToKMDValue(const Exiv2::Value::UniquePtr &value, bool forceSeq, KisMetaData::Value::ValueType arrayType = KisMetaData::Value::UnorderedArray) 0025 #else 0026 exivValueToKMDValue(const Exiv2::Value::AutoPtr &value, bool forceSeq, KisMetaData::Value::ValueType arrayType = KisMetaData::Value::UnorderedArray) 0027 #endif 0028 { 0029 switch (value->typeId()) { 0030 case Exiv2::signedByte: 0031 case Exiv2::invalidTypeId: 0032 case Exiv2::lastTypeId: 0033 case Exiv2::directory: 0034 dbgMetaData << "Invalid value :" << value->typeId() << " value =" << value->toString().c_str(); 0035 return {}; 0036 case Exiv2::undefined: { 0037 dbgMetaData << "Undefined value :" << value->typeId() << " value =" << value->toString().c_str(); 0038 QByteArray array(value->count(), 0); 0039 value->copy(reinterpret_cast<Exiv2::byte *>(array.data()), Exiv2::invalidByteOrder); 0040 return {QString(array.toBase64())}; 0041 } 0042 case Exiv2::unsignedByte: 0043 case Exiv2::unsignedShort: 0044 case Exiv2::unsignedLong: 0045 case Exiv2::signedShort: 0046 case Exiv2::signedLong: { 0047 if (value->count() == 1 && !forceSeq) { 0048 #if EXIV2_TEST_VERSION(0,28,0) 0049 return {static_cast<int>(value->toUint32())}; 0050 #else 0051 return {static_cast<int>(value->toLong())}; 0052 #endif 0053 } else { 0054 QList<KisMetaData::Value> array; 0055 for (int i = 0; i < value->count(); i++) 0056 #if EXIV2_TEST_VERSION(0,28,0) 0057 array.push_back({static_cast<int>(value->toUint32(i))}); 0058 #else 0059 array.push_back({static_cast<int>(value->toLong(i))}); 0060 #endif 0061 return {array, arrayType}; 0062 } 0063 } 0064 case Exiv2::asciiString: 0065 case Exiv2::string: 0066 case Exiv2::comment: // look at kexiv2 for the problem about decoding correctly that tag 0067 return {QString::fromStdString(value->toString())}; 0068 case Exiv2::unsignedRational: 0069 if (value->count() == 1 && !forceSeq) { 0070 if (value->size() < 2) { 0071 dbgMetaData << "Invalid size :" << value->size() << " value =" << value->toString().c_str(); 0072 return {}; 0073 } 0074 return {KisMetaData::Rational(value->toRational().first, value->toRational().second)}; 0075 } else { 0076 QList<KisMetaData::Value> array; 0077 #if EXIV2_TEST_VERSION(0,28,0) 0078 for (size_t i = 0; i < value->count(); i++) { 0079 #else 0080 for (long i = 0; i < value->count(); i++) { 0081 #endif 0082 array.push_back(KisMetaData::Rational(value->toRational(i).first, value->toRational(i).second)); 0083 } 0084 return {array, arrayType}; 0085 } 0086 case Exiv2::signedRational: 0087 if (value->count() == 1 && !forceSeq) { 0088 if (value->size() < 2) { 0089 dbgMetaData << "Invalid size :" << value->size() << " value =" << value->toString().c_str(); 0090 return {}; 0091 } 0092 return {KisMetaData::Rational(value->toRational().first, value->toRational().second)}; 0093 } else { 0094 QList<KisMetaData::Value> array; 0095 #if EXIV2_TEST_VERSION(0,28,0) 0096 for (size_t i = 0; i < value->count(); i++) { 0097 #else 0098 for (long i = 0; i < value->count(); i++) { 0099 #endif 0100 array.push_back(KisMetaData::Rational(value->toRational(i).first, value->toRational(i).second)); 0101 } 0102 return {array, arrayType}; 0103 } 0104 case Exiv2::date: 0105 case Exiv2::time: 0106 return {QDateTime::fromString(QString::fromStdString(value->toString()), Qt::ISODate)}; 0107 case Exiv2::xmpText: 0108 case Exiv2::xmpAlt: 0109 case Exiv2::xmpBag: 0110 case Exiv2::xmpSeq: 0111 case Exiv2::langAlt: 0112 default: { 0113 dbgMetaData << "Unknown type id :" << value->typeId() << " value =" << value->toString().c_str(); 0114 // Q_ASSERT(false); // This point must never be reached ! 0115 return {}; 0116 } 0117 } 0118 dbgMetaData << "Unknown type id :" << value->typeId() << " value =" << value->toString().c_str(); 0119 // Q_ASSERT(false); // This point must never be reached ! 0120 return {}; 0121 } 0122 0123 // Convert a QtVariant to an Exiv value 0124 inline Exiv2::Value *variantToExivValue(const QVariant &variant, Exiv2::TypeId type) 0125 { 0126 switch (type) { 0127 case Exiv2::undefined: { 0128 const QByteArray arr = QByteArray::fromBase64(variant.toString().toLatin1()); 0129 return new Exiv2::DataValue(reinterpret_cast<const Exiv2::byte *>(arr.data()), arr.size()); 0130 } 0131 case Exiv2::unsignedByte: 0132 return new Exiv2::ValueType<uint16_t>((uint16_t)variant.toUInt()); 0133 case Exiv2::unsignedShort: 0134 return new Exiv2::ValueType<uint16_t>((uint16_t)variant.toUInt()); 0135 case Exiv2::unsignedLong: 0136 return new Exiv2::ValueType<uint32_t>((uint32_t)variant.toUInt()); 0137 case Exiv2::signedShort: 0138 return new Exiv2::ValueType<int16_t>((int16_t)variant.toInt()); 0139 case Exiv2::signedLong: 0140 return new Exiv2::ValueType<int32_t>((int32_t)variant.toInt()); 0141 case Exiv2::date: { 0142 QDate date = variant.toDate(); 0143 return new Exiv2::DateValue(date.year(), date.month(), date.day()); 0144 } 0145 case Exiv2::asciiString: 0146 if (variant.type() == QVariant::DateTime) { 0147 return new Exiv2::AsciiValue( 0148 qPrintable(QLocale::c().toString(variant.toDateTime(), QStringLiteral("yyyy:MM:dd hh:mm:ss")))); 0149 } else 0150 return new Exiv2::AsciiValue(qPrintable(variant.toString())); 0151 case Exiv2::string: { 0152 if (variant.type() == QVariant::DateTime) { 0153 return new Exiv2::StringValue( 0154 qPrintable(QLocale::c().toString(variant.toDateTime(), QStringLiteral("yyyy:MM:dd hh:mm:ss")))); 0155 } else 0156 return new Exiv2::StringValue(qPrintable(variant.toString())); 0157 } 0158 case Exiv2::comment: 0159 return new Exiv2::CommentValue(qPrintable(variant.toString())); 0160 default: 0161 dbgMetaData << "Unhandled type:" << type; 0162 // Q_ASSERT(false); 0163 return nullptr; 0164 } 0165 } 0166 0167 template<typename T> 0168 Exiv2::Value *arrayToExivValue(const KisMetaData::Value &value) 0169 { 0170 Exiv2::ValueType<T> *exivValue = new Exiv2::ValueType<T>(); 0171 const QList<KisMetaData::Value> array = value.asArray(); 0172 Q_FOREACH (const KisMetaData::Value &item, array) { 0173 exivValue->value_.push_back(qvariant_cast<T>(item.asVariant())); 0174 } 0175 return exivValue; 0176 } 0177 0178 /// Convert a KisMetaData to an Exiv value 0179 inline Exiv2::Value *kmdValueToExivValue(const KisMetaData::Value &value, Exiv2::TypeId type) 0180 { 0181 switch (value.type()) { 0182 case KisMetaData::Value::Invalid: 0183 return Exiv2::Value::create(Exiv2::invalidTypeId).release(); 0184 case KisMetaData::Value::Variant: { 0185 return variantToExivValue(value.asVariant(), type); 0186 } 0187 case KisMetaData::Value::Rational: 0188 // Q_ASSERT(type == Exiv2::signedRational || type == Exiv2::unsignedRational); 0189 if (type == Exiv2::signedRational) { 0190 return new Exiv2::RationalValue({value.asRational().numerator, value.asRational().denominator}); 0191 } else { 0192 return new Exiv2::URationalValue({value.asRational().numerator, value.asRational().denominator}); 0193 } 0194 case KisMetaData::Value::OrderedArray: 0195 Q_FALLTHROUGH(); 0196 case KisMetaData::Value::UnorderedArray: 0197 Q_FALLTHROUGH(); 0198 case KisMetaData::Value::LangArray: 0199 Q_FALLTHROUGH(); 0200 case KisMetaData::Value::AlternativeArray: { 0201 switch (type) { 0202 case Exiv2::unsignedByte: 0203 Q_FALLTHROUGH(); 0204 case Exiv2::unsignedShort: 0205 return arrayToExivValue<uint16_t>(value); 0206 case Exiv2::unsignedLong: 0207 return arrayToExivValue<uint32_t>(value); 0208 case Exiv2::signedShort: 0209 return arrayToExivValue<int16_t>(value); 0210 case Exiv2::signedLong: 0211 return arrayToExivValue<int32_t>(value); 0212 case Exiv2::asciiString: 0213 Q_FALLTHROUGH(); 0214 case Exiv2::string: { 0215 // Using toLatin1 here is not lossy for asciiString, 0216 // but definitely is for string. IPTC allows UTF-8 0217 // encoding which supersets ASCII. See: 0218 // https://www.iptc.org/std/photometadata/specification/IPTC-PhotoMetadata#iim-properties 0219 // https://doc.qt.io/qt-5/qstring.html#toLatin1 0220 const QList<KisMetaData::Value> list = value.asArray(); 0221 QStringList result; 0222 Q_FOREACH (const KisMetaData::Value &v, list) { 0223 result << v.asVariant().value<QString>(); 0224 } 0225 return new Exiv2::StringValue(result.join(',').toStdString()); 0226 } 0227 case Exiv2::signedRational: { 0228 Exiv2::RationalValue *exivValue = new Exiv2::RationalValue(); 0229 const QList<KisMetaData::Value> array = value.asArray(); 0230 exivValue->value_.reserve(static_cast<size_t>(array.size())); 0231 Q_FOREACH (const KisMetaData::Value &item, array) { 0232 const KisMetaData::Rational value = item.asRational(); 0233 exivValue->value_.emplace_back(value.numerator, value.denominator); 0234 } 0235 return exivValue; 0236 } 0237 case Exiv2::unsignedRational: { 0238 Exiv2::URationalValue *exivValue = new Exiv2::URationalValue(); 0239 const QList<KisMetaData::Value> array = value.asArray(); 0240 exivValue->value_.reserve(static_cast<size_t>(array.size())); 0241 Q_FOREACH (const KisMetaData::Value &item, array) { 0242 const KisMetaData::Rational value = item.asRational(); 0243 exivValue->value_.emplace_back(static_cast<uint32_t>(value.numerator), static_cast<uint32_t>(value.denominator)); 0244 } 0245 return exivValue; 0246 } 0247 default: 0248 warnMetaData << type << " " << value.type() << value; 0249 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(0 && "Unknown alternative array type", nullptr); 0250 break; 0251 } 0252 break; 0253 } break; 0254 default: 0255 warnMetaData << type << " " << value.type() << value; 0256 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(false && "Unknown array type", nullptr); 0257 break; 0258 } 0259 return nullptr; 0260 } 0261 0262 /// Convert a KisMetaData to an Exiv value, without knowing the targeted Exiv2::TypeId 0263 /// This function should be used for saving to XMP. 0264 inline Exiv2::Value *kmdValueToExivXmpValue(const KisMetaData::Value &value) 0265 { 0266 // Q_ASSERT(value.type() != KisMetaData::Value::Structure); 0267 switch (value.type()) { 0268 case KisMetaData::Value::Invalid: 0269 return new Exiv2::DataValue(Exiv2::invalidTypeId); 0270 case KisMetaData::Value::Variant: { 0271 QVariant var = value.asVariant(); 0272 if (var.type() == QVariant::Bool) { 0273 if (var.toBool()) { 0274 return new Exiv2::XmpTextValue("True"); 0275 } else { 0276 return new Exiv2::XmpTextValue("False"); 0277 } 0278 } else { 0279 // Q_ASSERT(var.canConvert(QVariant::String)); 0280 return new Exiv2::XmpTextValue(var.toString().toLatin1().constData()); 0281 } 0282 } 0283 case KisMetaData::Value::Rational: { 0284 QString rat = "%1 / %2"; 0285 rat = rat.arg(value.asRational().numerator); 0286 rat = rat.arg(value.asRational().denominator); 0287 return new Exiv2::XmpTextValue(rat.toLatin1().constData()); 0288 } 0289 case KisMetaData::Value::AlternativeArray: 0290 case KisMetaData::Value::OrderedArray: 0291 case KisMetaData::Value::UnorderedArray: { 0292 Exiv2::XmpArrayValue *arrV = new Exiv2::XmpArrayValue(); 0293 switch (value.type()) { 0294 case KisMetaData::Value::OrderedArray: 0295 arrV->setXmpArrayType(Exiv2::XmpValue::xaSeq); 0296 break; 0297 case KisMetaData::Value::UnorderedArray: 0298 arrV->setXmpArrayType(Exiv2::XmpValue::xaBag); 0299 break; 0300 case KisMetaData::Value::AlternativeArray: 0301 arrV->setXmpArrayType(Exiv2::XmpValue::xaAlt); 0302 break; 0303 default: 0304 // Cannot happen 0305 ; 0306 } 0307 Q_FOREACH (const KisMetaData::Value &value, value.asArray()) { 0308 QScopedPointer<Exiv2::Value> exivValue(kmdValueToExivXmpValue(value)); 0309 if (exivValue) { 0310 arrV->read(exivValue->toString()); 0311 } 0312 } 0313 return arrV; 0314 } 0315 case KisMetaData::Value::LangArray: { 0316 Exiv2::Value *arrV = new Exiv2::LangAltValue; 0317 QMap<QString, KisMetaData::Value> langArray = value.asLangArray(); 0318 for (auto it = langArray.begin(); it != langArray.end(); ++it) { 0319 QString exivVal; 0320 if (it.key() != "x-default") { 0321 exivVal = "lang=" + it.key() + ' '; 0322 } 0323 // Q_ASSERT(it.value().type() == KisMetaData::Value::Variant); 0324 QVariant var = it.value().asVariant(); 0325 // Q_ASSERT(var.type() == QVariant::String); 0326 exivVal += var.toString(); 0327 arrV->read(exivVal.toLatin1().constData()); 0328 } 0329 return arrV; 0330 } 0331 case KisMetaData::Value::Structure: 0332 default: { 0333 warnKrita << "KisExiv2: Unhandled value type"; 0334 return nullptr; 0335 } 0336 } 0337 warnKrita << "KisExiv2: Unhandled value type"; 0338 return nullptr; 0339 } 0340 #endif