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