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