File indexing completed on 2024-05-12 15:59:16

0001 /*
0002  *  SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.1-or-later
0005  */
0006 
0007 #include "kis_meta_data_merge_strategy_p.h"
0008 #include <klocalizedstring.h>
0009 
0010 #include "kis_debug.h"
0011 
0012 #include "kis_meta_data_entry.h"
0013 #include "kis_meta_data_schema.h"
0014 #include "kis_meta_data_schema_registry.h"
0015 #include "kis_meta_data_store.h"
0016 #include "kis_meta_data_value.h"
0017 
0018 using namespace KisMetaData;
0019 
0020 //-------------------------------------------//
0021 //------------ DropMergeStrategy ------------//
0022 //-------------------------------------------//
0023 
0024 DropMergeStrategy::DropMergeStrategy()
0025 {
0026 }
0027 
0028 DropMergeStrategy::~DropMergeStrategy()
0029 {
0030 }
0031 
0032 QString DropMergeStrategy::id() const
0033 {
0034     return "Drop";
0035 }
0036 QString DropMergeStrategy::name() const
0037 {
0038     return i18n("Drop");
0039 }
0040 
0041 QString DropMergeStrategy::description() const
0042 {
0043     return i18n("Drop all meta data");
0044 }
0045 
0046 void DropMergeStrategy::merge(Store* dst, QList<const Store*> srcs, QList<double> score) const
0047 {
0048     Q_UNUSED(dst);
0049     Q_UNUSED(srcs);
0050     Q_UNUSED(score);
0051     dbgMetaData << "Drop meta data";
0052 }
0053 
0054 //---------------------------------------//
0055 //---------- DropMergeStrategy ----------//
0056 //---------------------------------------//
0057 
0058 PriorityToFirstMergeStrategy::PriorityToFirstMergeStrategy()
0059 {
0060 }
0061 
0062 PriorityToFirstMergeStrategy::~PriorityToFirstMergeStrategy()
0063 {
0064 }
0065 
0066 QString PriorityToFirstMergeStrategy::id() const
0067 {
0068     return "PriorityToFirst";
0069 }
0070 QString PriorityToFirstMergeStrategy::name() const
0071 {
0072     return i18n("Priority to first meta data");
0073 }
0074 
0075 QString PriorityToFirstMergeStrategy::description() const
0076 {
0077     return i18n("Use in priority the meta data from the layers at the bottom of the stack.");
0078 }
0079 
0080 void PriorityToFirstMergeStrategy::merge(Store* dst, QList<const Store*> srcs, QList<double> score) const
0081 {
0082     Q_UNUSED(score);
0083     dbgMetaData << "Priority to first meta data";
0084 
0085     Q_FOREACH (const Store* store, srcs) {
0086         QList<QString> keys = store->keys();
0087         Q_FOREACH (const QString & key, keys) {
0088             if (!dst->containsEntry(key)) {
0089                 dst->addEntry(store->getEntry(key));
0090             }
0091         }
0092     }
0093 }
0094 //-------------------------------------------//
0095 //------ OnlyIdenticalMergeStrategy ---------//
0096 //-------------------------------------------//
0097 
0098 OnlyIdenticalMergeStrategy::OnlyIdenticalMergeStrategy()
0099 {
0100 }
0101 
0102 OnlyIdenticalMergeStrategy::~OnlyIdenticalMergeStrategy()
0103 {
0104 }
0105 
0106 QString OnlyIdenticalMergeStrategy::id() const
0107 {
0108     return "OnlyIdentical";
0109 }
0110 QString OnlyIdenticalMergeStrategy::name() const
0111 {
0112     return i18n("Only identical");
0113 }
0114 
0115 QString OnlyIdenticalMergeStrategy::description() const
0116 {
0117     return i18n("Keep only meta data that are identical");
0118 }
0119 
0120 void OnlyIdenticalMergeStrategy::merge(Store* dst, QList<const Store*> srcs, QList<double> score) const
0121 {
0122     Q_UNUSED(score);
0123     dbgMetaData << "OnlyIdenticalMergeStrategy";
0124     dbgMetaData << "Priority to first meta data";
0125 
0126     Q_ASSERT(srcs.size() > 0);
0127     QList<QString> keys = srcs[0]->keys();
0128     Q_FOREACH (const QString & key, keys) {
0129         bool keep = true;
0130         const Entry& e = srcs[0]->getEntry(key);
0131         const Value& v = e.value();
0132         Q_FOREACH (const Store* store, srcs) {
0133             if (!(store->containsEntry(key) && e.value() == v)) {
0134                 keep = false;
0135                 break;
0136             }
0137         }
0138         if (keep) {
0139             dst->addEntry(e);
0140         }
0141     }
0142 }
0143 
0144 //-------------------------------------------//
0145 //------------ SmartMergeStrategy -----------//
0146 //-------------------------------------------//
0147 
0148 SmartMergeStrategy::SmartMergeStrategy()
0149 {
0150 }
0151 
0152 SmartMergeStrategy::~SmartMergeStrategy()
0153 {
0154 }
0155 
0156 QString SmartMergeStrategy::id() const
0157 {
0158     return "Smart";
0159 }
0160 QString SmartMergeStrategy::name() const
0161 {
0162     return i18n("Smart");
0163 }
0164 
0165 QString SmartMergeStrategy::description() const
0166 {
0167     return i18n("This merge strategy attempts to find the best solution for merging, "
0168                 "for instance by merging the list of authors together, or keeping "
0169                 "identical photographic information.");
0170 }
0171 
0172 struct ScoreValue {
0173     double score;
0174     Value value;
0175 };
0176 
0177 Value SmartMergeStrategy::election(QList<const Store*> srcs, QList<double> scores, const QString & key) const
0178 {
0179     QList<ScoreValue> scoreValues;
0180     for (int i = 0; i < srcs.size(); i++) {
0181         if (srcs[i]->containsEntry(key)) {
0182             const Value& nv = srcs[i]->getEntry(key).value();
0183             if (nv.type() != Value::Invalid) {
0184                 bool found = false;
0185                 for (int j = 0; j < scoreValues.size(); j++) {
0186                     ScoreValue& sv = scoreValues[j];
0187                     if (sv.value == nv) {
0188                         found = true;
0189                         sv.score += scores[i];
0190                         break;
0191                     }
0192                 }
0193                 if (!found) {
0194                     ScoreValue sv;
0195                     sv.score = scores[i];
0196                     sv.value = nv;
0197                     scoreValues.append(sv);
0198                 }
0199             }
0200         }
0201     }
0202     if (scoreValues.size() < 1) {
0203         warnMetaData << "SmartMergeStrategy::election returned less than 1 score value";
0204         return Value();
0205     }
0206     const ScoreValue* bestSv = 0;
0207     double bestScore = -1.0;
0208     Q_FOREACH (const ScoreValue& sv, scoreValues) {
0209         if (sv.score > bestScore) {
0210             bestScore = sv.score;
0211             bestSv = &sv;
0212         }
0213     }
0214     if (bestSv) {
0215         return bestSv->value;
0216     }
0217     else {
0218         return Value();
0219     }
0220 }
0221 
0222 void SmartMergeStrategy::mergeEntry(Store* dst, QList<const Store*> srcs, const KisMetaData::Schema* schema, const QString & identifier) const
0223 {
0224     bool foundOnce = false;
0225     Value v(QList<Value>(), Value::OrderedArray);
0226     Q_FOREACH (const Store* store, srcs) {
0227         if (store->containsEntry(schema, identifier)) {
0228             v += store->getEntry(schema, identifier).value();
0229             foundOnce = true;
0230         }
0231     }
0232     if (foundOnce) {
0233         dst->getEntry(schema, identifier).value() = v;
0234     }
0235 }
0236 
0237 void SmartMergeStrategy::merge(Store* dst, QList<const Store*> srcs, QList<double> scores) const
0238 {
0239     dbgMetaData << "Smart merging of meta data";
0240     Q_ASSERT(srcs.size() == scores.size());
0241     Q_ASSERT(srcs.size() > 0);
0242     if (srcs.size() == 1) {
0243         dst->copyFrom(srcs[0]);
0244         return;
0245     }
0246     // Initialize some schema
0247     const KisMetaData::Schema* dcSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::DublinCoreSchemaUri);
0248 //     const KisMetaData::Schema* psSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::PhotoshopSchemaUri);
0249     const KisMetaData::Schema* XMPRightsSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::XMPRightsSchemaUri);
0250     const KisMetaData::Schema* XMPSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::XMPSchemaUri);
0251     // Sort the stores and scores
0252     {
0253         QMultiMap<double, const Store*> scores2srcs;
0254         for (int i = 0; i < scores.size(); ++i) {
0255             scores2srcs.insert(scores[i], srcs[i]);
0256         }
0257         srcs = scores2srcs.values();
0258         scores = scores2srcs.keys();
0259     }
0260 
0261     // First attempt to see if one of the store has a higher score than the others
0262     if (scores[0] > 2 * scores[1]) { // One of the store has a higher importance than the other ones
0263         dst->copyFrom(srcs[0]);
0264     } else {
0265         // Merge exif info
0266 
0267 
0268         // Election
0269         Q_FOREACH (const Store* store, srcs) {
0270             QList<QString> keys = store->keys();
0271             Q_FOREACH (const QString & key, keys) {
0272                 if (!dst->containsEntry(key)) {
0273                     Value v = election(srcs, scores, key);
0274                     if (v.type() != Value::Invalid) {
0275                         dst->getEntry(key).value() = v;
0276                     }
0277                 }
0278             }
0279         }
0280 
0281         // Compute rating
0282         double rating = 0.0;
0283         double norm = 0.0;
0284         for (int i = 0; i < srcs.size(); i++) {
0285             const Store* store = srcs[i];
0286             if (store->containsEntry(XMPSchema, "Rating")) {
0287                 double score = scores[i];
0288                 rating += score * store->getEntry(XMPSchema, "Rating").value().asVariant().toDouble();
0289                 norm += score;
0290             }
0291         }
0292         if (norm > 0.01) {
0293             dst->getEntry(XMPSchema, "Rating").value() = QVariant((int)(rating / norm));
0294         }
0295     }
0296     // Merge the list of authors and keywords and other stuff
0297     mergeEntry(dst, srcs, dcSchema, "contributor");
0298     mergeEntry(dst, srcs, dcSchema, "creator");
0299     mergeEntry(dst, srcs, dcSchema, "publisher");
0300     mergeEntry(dst, srcs, dcSchema, "subject");
0301     mergeEntry(dst, srcs, XMPRightsSchema, "Owner");
0302     mergeEntry(dst, srcs, XMPSchema, "Identifier");
0303 }