File indexing completed on 2025-01-19 03:55:56
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2006-02-23 0007 * Description : item metadata interface - faces helpers 0008 * 0009 * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * SPDX-FileCopyrightText: 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0011 * SPDX-FileCopyrightText: 2013 by Veaceslav Munteanu <veaceslav dot munteanu90 at gmail dot com> 0012 * SPDX-FileCopyrightText: 2011 by Leif Huhn <leif at dkstat dot com> 0013 * 0014 * SPDX-License-Identifier: GPL-2.0-or-later 0015 * 0016 * ============================================================ */ 0017 0018 #include "dmetadata.h" 0019 0020 // Qt includes 0021 0022 #include <QLocale> 0023 0024 // Local includes 0025 0026 #include "metaenginesettings.h" 0027 #include "digikam_version.h" 0028 #include "digikam_globals.h" 0029 #include "digikam_debug.h" 0030 0031 namespace Digikam 0032 { 0033 0034 bool DMetadata::getItemFacesMap(QMultiMap<QString, QVariant>& faces) const 0035 { 0036 faces.clear(); 0037 0038 // The example code for Exiv2 says: 0039 // > There are no specialized values for structures, qualifiers and nested 0040 // > types. However, these can be added by using an XmpTextValue and a path as 0041 // > the key. 0042 0043 // I think that means I have to iterate over the WLPG face tags in the clunky 0044 // way below (guess numbers and look them up as strings). (Leif) 0045 0046 QString winQxmpTagName = QLatin1String("Xmp.MP.RegionInfo/MPRI:Regions"); 0047 QString winRectTagKey = winQxmpTagName + QLatin1String("[%1]/MPReg:Rectangle"); 0048 QString winNameTagKey = winQxmpTagName + QLatin1String("[%1]/MPReg:PersonDisplayName"); 0049 0050 for (int i = 1 ; ; ++i) 0051 { 0052 QString person = getXmpTagString(winNameTagKey.arg(i).toLatin1().constData(), false); 0053 QString rectString = getXmpTagString(winRectTagKey.arg(i).toLatin1().constData(), false); 0054 0055 person.replace(QLatin1Char('/'), QLatin1Char('\\')); 0056 0057 if (rectString.isEmpty() && person.isEmpty()) 0058 { 0059 break; 0060 } 0061 0062 // The WLPG tags have the format X.XX, Y.YY, W.WW, H.HH 0063 // That is, four decimal numbers ranging from 0-1. 0064 // The top left position is indicated by X.XX, Y.YY (as a 0065 // percentage of the width/height of the entire image). 0066 // Similarly the width and height of the face's box are 0067 // indicated by W.WW and H.HH. 0068 0069 QStringList list = rectString.split(QLatin1Char(',')); 0070 0071 if (list.size() < 4) 0072 { 0073 qCDebug(DIGIKAM_METAENGINE_LOG) << "Cannot parse WLPG rectangle string" << rectString; 0074 0075 faces.insert(person, QRectF()); 0076 0077 continue; 0078 } 0079 0080 QRectF rect(list.at(0).toFloat(), 0081 list.at(1).toFloat(), 0082 list.at(2).toFloat(), 0083 list.at(3).toFloat()); 0084 0085 faces.insert(person, rect); 0086 } 0087 0088 /** 0089 * Read face tags only if Exiv2 can write them, otherwise 0090 * garbage tags will be generated on image transformation 0091 */ 0092 0093 // Read face tags as saved by Picasa 0094 // https://www.exiv2.org/tags-xmp-mwg-rs.html 0095 0096 QString qxmpTagName = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList"); 0097 QString nameTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Name"); 0098 QString areaxTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:x"); 0099 QString areayTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:y"); 0100 QString areawTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:w"); 0101 QString areahTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:h"); 0102 0103 for (int i = 1 ; ; ++i) 0104 { 0105 QString person = getXmpTagString(nameTagKey.arg(i).toLatin1().constData(), false); 0106 0107 person.replace(QLatin1Char('/'), QLatin1Char('\\')); 0108 0109 // x and y is the center point 0110 0111 float x = getXmpTagString(areaxTagKey.arg(i).toLatin1().constData(), false).toFloat(); 0112 float y = getXmpTagString(areayTagKey.arg(i).toLatin1().constData(), false).toFloat(); 0113 float w = getXmpTagString(areawTagKey.arg(i).toLatin1().constData(), false).toFloat(); 0114 float h = getXmpTagString(areahTagKey.arg(i).toLatin1().constData(), false).toFloat(); 0115 0116 QRectF rect(x - w / 2, y - h / 2, w, h); 0117 0118 if (person.isEmpty() && !rect.isValid()) 0119 { 0120 break; 0121 } 0122 0123 // Ignore the full size face region. 0124 // See bug 437708 (Lumia 930 Windows Phone) 0125 0126 if (rect != QRectF(0.0, 0.0, 1.0, 1.0)) 0127 { 0128 faces.insert(person, rect); 0129 0130 qCDebug(DIGIKAM_METAENGINE_LOG) << "Found new rect:" << person << rect; 0131 } 0132 } 0133 0134 return !faces.isEmpty(); 0135 } 0136 0137 bool DMetadata::setItemFacesMap(const QMultiMap<QString, QVariant>& facesPath, bool write, const QSize& size) const 0138 { 0139 QString qxmpTagName = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList"); 0140 QString nameTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Name"); 0141 QString typeTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Type"); 0142 QString areaTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area"); 0143 QString areaxTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:x"); 0144 QString areayTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:y"); 0145 QString areawTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:w"); 0146 QString areahTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:h"); 0147 QString areanormTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:unit"); 0148 0149 QString adimTagName = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:AppliedToDimensions"); 0150 QString adimhTagKey = adimTagName + QLatin1String("/stDim:h"); 0151 QString adimwTagKey = adimTagName + QLatin1String("/stDim:w"); 0152 QString adimpixTagKey = adimTagName + QLatin1String("/stDim:unit"); 0153 0154 QString winQxmpTagName = QLatin1String("Xmp.MP.RegionInfo/MPRI:Regions"); 0155 QString winRectTagKey = winQxmpTagName + QLatin1String("[%1]/MPReg:Rectangle"); 0156 QString winNameTagKey = winQxmpTagName + QLatin1String("[%1]/MPReg:PersonDisplayName"); 0157 0158 if (!write) 0159 { 0160 QString check = getXmpTagString(nameTagKey.arg(1).toLatin1().constData()); 0161 0162 if (check.isEmpty()) 0163 { 0164 return true; 0165 } 0166 } 0167 0168 // Remove face metadata before writing new ones to prevent problems (bug 436286). 0169 0170 removeItemFacesMap(); 0171 0172 if (facesPath.isEmpty()) 0173 { 0174 return true; 0175 } 0176 0177 setXmpTagString(winQxmpTagName.toLatin1().constData(), 0178 QString(), 0179 MetaEngine::ArrayBagTag); 0180 0181 QMultiMap<QString, QVariant>::const_iterator it = facesPath.constBegin(); 0182 int i = 1; 0183 int j = 1; 0184 bool ok = true; 0185 bool validFaces = false; 0186 0187 while (it != facesPath.constEnd()) 0188 { 0189 // Set tag name 0190 0191 setXmpTagString(winNameTagKey.arg(i).toLatin1().constData(), 0192 it.key(), 0193 MetaEngine::NormalTag); 0194 0195 if (!it.value().toRectF().isValid()) 0196 { 0197 ++it; 0198 ++i; 0199 0200 continue; 0201 } 0202 0203 if (!validFaces) 0204 { 0205 if (!size.isNull()) 0206 { 0207 // Set tag AppliedToDimens, with xmp type struct 0208 0209 setXmpTagString(adimTagName.toLatin1().constData(), 0210 QString(), 0211 MetaEngine::StructureTag); 0212 0213 // Set stDim:w inside AppliedToDimens structure 0214 0215 setXmpTagString(adimwTagKey.toLatin1().constData(), 0216 QString::number(size.width()), 0217 MetaEngine::NormalTag); 0218 0219 // Set stDim:h inside AppliedToDimens structure 0220 0221 setXmpTagString(adimhTagKey.toLatin1().constData(), 0222 QString::number(size.height()), 0223 MetaEngine::NormalTag); 0224 0225 // Set stDim:unit inside AppliedToDimens structure as pixel 0226 0227 setXmpTagString(adimpixTagKey.toLatin1().constData(), 0228 QLatin1String("pixel"), 0229 MetaEngine::NormalTag); 0230 } 0231 0232 setXmpTagString(qxmpTagName.toLatin1().constData(), 0233 QString(), 0234 MetaEngine::ArrayBagTag); 0235 0236 validFaces = true; 0237 } 0238 0239 qreal x, y, w, h; 0240 it.value().toRectF().getRect(&x, &y, &w, &h); 0241 qCDebug(DIGIKAM_METAENGINE_LOG) << "Set face region:" << x << y << w << h; 0242 0243 // Write face tags in Windows Live Photo format 0244 0245 QString rectString; 0246 0247 rectString.append(QString::number(x) + QLatin1String(", ")); 0248 rectString.append(QString::number(y) + QLatin1String(", ")); 0249 rectString.append(QString::number(w) + QLatin1String(", ")); 0250 rectString.append(QString::number(h)); 0251 0252 // Set tag rect 0253 0254 setXmpTagString(winRectTagKey.arg(i).toLatin1().constData(), 0255 rectString, 0256 MetaEngine::NormalTag); 0257 0258 // Writing rectangle in Metadata Group format 0259 0260 x += w / 2; 0261 y += h / 2; 0262 0263 // Set tag name 0264 0265 ok &= setXmpTagString(nameTagKey.arg(j).toLatin1().constData(), 0266 it.key(), 0267 MetaEngine::NormalTag); 0268 qCDebug(DIGIKAM_METAENGINE_LOG) << " => set tag name:" << ok; 0269 0270 // Set tag type as Face 0271 0272 ok &= setXmpTagString(typeTagKey.arg(j).toLatin1().constData(), 0273 QLatin1String("Face"), 0274 MetaEngine::NormalTag); 0275 qCDebug(DIGIKAM_METAENGINE_LOG) << " => set tag type:" << ok; 0276 0277 // Set tag Area, with xmp type struct 0278 0279 ok &= setXmpTagString(areaTagKey.arg(j).toLatin1().constData(), 0280 QString(), 0281 MetaEngine::StructureTag); 0282 qCDebug(DIGIKAM_METAENGINE_LOG) << " => set area struct:" << ok; 0283 0284 // Set stArea:x inside Area structure 0285 0286 ok &= setXmpTagString(areaxTagKey.arg(j).toLatin1().constData(), 0287 QString::number(x), 0288 MetaEngine::NormalTag); 0289 qCDebug(DIGIKAM_METAENGINE_LOG) << " => set xpos:" << ok; 0290 0291 // Set stArea:y inside Area structure 0292 0293 ok &= setXmpTagString(areayTagKey.arg(j).toLatin1().constData(), 0294 QString::number(y), 0295 MetaEngine::NormalTag); 0296 qCDebug(DIGIKAM_METAENGINE_LOG) << " => set ypos:" << ok; 0297 0298 // Set stArea:w inside Area structure 0299 0300 ok &= setXmpTagString(areawTagKey.arg(j).toLatin1().constData(), 0301 QString::number(w), 0302 MetaEngine::NormalTag); 0303 qCDebug(DIGIKAM_METAENGINE_LOG) << " => set width:" << ok; 0304 0305 // Set stArea:h inside Area structure 0306 0307 ok &= setXmpTagString(areahTagKey.arg(j).toLatin1().constData(), 0308 QString::number(h), 0309 MetaEngine::NormalTag); 0310 qCDebug(DIGIKAM_METAENGINE_LOG) << " => set height:" << ok; 0311 0312 // Set stArea:unit inside Area structure as normalized 0313 0314 ok &= setXmpTagString(areanormTagKey.arg(j).toLatin1().constData(), 0315 QLatin1String("normalized"), 0316 MetaEngine::NormalTag); 0317 qCDebug(DIGIKAM_METAENGINE_LOG) << " => set unit:" << ok; 0318 0319 ++it; 0320 ++i; 0321 ++j; 0322 } 0323 0324 return ok; 0325 } 0326 0327 bool DMetadata::removeItemFacesMap() const 0328 { 0329 QString qxmpStructName = QLatin1String("Xmp.mwg-rs.Regions"); 0330 QString winQxmpStructName = QLatin1String("Xmp.MP.RegionInfo"); 0331 0332 // Remove mwg-rs tags 0333 0334 removeXmpTag(qxmpStructName.toLatin1().constData(), true); 0335 0336 // Remove MP tags 0337 0338 removeXmpTag(winQxmpStructName.toLatin1().constData(), true); 0339 0340 return true; 0341 } 0342 0343 } // namespace Digikam