File indexing completed on 2025-01-19 03:57:55

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2010-09-17
0007  * Description : Managing of face tag region items on a GraphicsDImgView
0008  *
0009  * SPDX-FileCopyrightText: 2010      by Aditya Bhatt <adityabhatt1991 at gmail dot com>
0010  * SPDX-FileCopyrightText: 2010-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0011  * SPDX-FileCopyrightText: 2012-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "facegroup_p.h"
0018 #include "assignnamewidget_p.h"
0019 
0020 namespace Digikam
0021 {
0022 
0023 FaceGroup::FaceGroup(GraphicsDImgView* const view)
0024     : QObject(view),
0025       d      (new Private(this))
0026 {
0027     d->view                 = view;
0028     d->visibilityController = new ItemVisibilityController(this);
0029     d->visibilityController->setShallBeShown(false);
0030 
0031     connect(AlbumManager::instance(), SIGNAL(signalAlbumRenamed(Album*)),
0032             this, SLOT(slotAlbumRenamed(Album*)));
0033 
0034     connect(AlbumManager::instance(), SIGNAL(signalAlbumsUpdated(int)),
0035             this, SLOT(slotAlbumsUpdated(int)));
0036 
0037     connect(view->previewItem(), SIGNAL(stateChanged(int)),
0038             this, SLOT(itemStateChanged(int)));
0039 
0040     d->editPipeline.plugDatabaseEditor();
0041     d->editPipeline.plugTrainer();
0042     d->editPipeline.construct();
0043 }
0044 
0045 FaceGroup::~FaceGroup()
0046 {
0047     delete d;
0048 }
0049 
0050 void FaceGroup::itemStateChanged(int itemState)
0051 {
0052     switch (itemState)
0053     {
0054         case DImgPreviewItem::NoImage:
0055         case DImgPreviewItem::Loading:
0056         case DImgPreviewItem::ImageLoadingFailed:
0057         {
0058             d->visibilityController->hide();
0059             break;
0060         }
0061 
0062         case DImgPreviewItem::ImageLoaded:
0063         {
0064             if (d->state == FacesLoaded)
0065             {
0066                 d->visibilityController->show();
0067             }
0068 
0069             break;
0070         }
0071     }
0072 }
0073 
0074 bool FaceGroup::isVisible() const
0075 {
0076     return d->visibilityController->shallBeShown();
0077 }
0078 
0079 bool FaceGroup::hasVisibleItems() const
0080 {
0081     return d->visibilityController->hasVisibleItems();
0082 }
0083 
0084 ItemInfo FaceGroup::info() const
0085 {
0086     return d->info;
0087 }
0088 
0089 QList<RegionFrameItem*> FaceGroup::items() const
0090 {
0091     QList<RegionFrameItem*> items;
0092 
0093     Q_FOREACH (FaceItem* const item, d->items)
0094     {
0095         items << item;
0096     }
0097 
0098     return items;
0099 }
0100 
0101 void FaceGroup::setAutoSuggest(bool doAutoSuggest)
0102 {
0103     if (d->autoSuggest == doAutoSuggest)
0104     {
0105         return;
0106     }
0107 
0108     d->autoSuggest = doAutoSuggest;
0109 }
0110 
0111 bool FaceGroup::autoSuggest() const
0112 {
0113     return d->autoSuggest;
0114 }
0115 
0116 void FaceGroup::setShowOnHover(bool show)
0117 {
0118     d->showOnHover = show;
0119 }
0120 
0121 bool FaceGroup::showOnHover() const
0122 {
0123     return d->showOnHover;
0124 }
0125 
0126 void FaceGroup::setVisible(bool visible)
0127 {
0128     d->visibilityController->setShallBeShown(visible);
0129     d->applyVisible();
0130 }
0131 
0132 void FaceGroup::setVisibleItem(RegionFrameItem* item)
0133 {
0134     d->visibilityController->setItemThatShallBeShown(item);
0135     d->applyVisible();
0136 }
0137 
0138 void FaceGroup::setInfo(const ItemInfo& info)
0139 {
0140     if ((d->info == info) && (d->state != NoFaces))
0141     {
0142         return;
0143     }
0144 
0145     clear();
0146     d->info = info;
0147 
0148     if (d->visibilityController->shallBeShown())
0149     {
0150         load();
0151     }
0152 }
0153 
0154 void FaceGroup::aboutToSetInfo(const ItemInfo& info)
0155 {
0156     if (d->info == info)
0157     {
0158         return;
0159     }
0160 
0161     applyItemGeometryChanges();
0162     clear();
0163 }
0164 
0165 void FaceGroup::aboutToSetInfoAfterRotate(const ItemInfo& info)
0166 {
0167     if (d->info == info)
0168     {
0169         return;
0170     }
0171 /*
0172     applyItemGeometryChanges();
0173 */
0174     clear();
0175 }
0176 
0177 static QPointF closestPointOfRect(const QPointF& p, const QRectF& r)
0178 {
0179     QPointF cp = p;
0180 
0181     if      (p.x() < r.left())
0182     {
0183         cp.setX(r.left());
0184     }
0185     else if (p.x() > r.right())
0186     {
0187         cp.setX(r.right());
0188     }
0189 
0190     if      (p.y() < r.top())
0191     {
0192         cp.setY(r.top());
0193     }
0194     else if (p.y() > r.bottom())
0195     {
0196         cp.setY(r.bottom());
0197     }
0198 
0199     return cp;
0200 }
0201 
0202 RegionFrameItem* FaceGroup::closestItem(const QPointF& p, qreal* const manhattanLength) const
0203 {
0204     RegionFrameItem* closestItem = nullptr;
0205     qreal minDistance            = 0;
0206     qreal minCenterDistance      = 0;
0207 
0208     Q_FOREACH (RegionFrameItem* const item, d->items)
0209     {
0210         QRectF r       = item->boundingRect().translated(item->pos());
0211         qreal distance = (p - closestPointOfRect(p, r)).manhattanLength();
0212 
0213         if (!closestItem             ||
0214             (distance < minDistance) ||
0215             ((distance == 0) && (p - r.center()).manhattanLength() < minCenterDistance))
0216         {
0217             closestItem = item;
0218             minDistance = distance;
0219 
0220             if (distance == 0)
0221             {
0222                 minCenterDistance = (p - r.center()).manhattanLength();
0223             }
0224         }
0225     }
0226 
0227     if (manhattanLength)
0228     {
0229         *manhattanLength = minDistance;
0230     }
0231 
0232     return closestItem;
0233 }
0234 
0235 bool FaceGroup::acceptsMouseClick(const QPointF& scenePos)
0236 {
0237     return d->hotItems(scenePos).isEmpty();
0238 }
0239 
0240 void FaceGroup::itemHoverMoveEvent(QGraphicsSceneHoverEvent* e)
0241 {
0242     if (d->showOnHover && !isVisible())
0243     {
0244         qreal distance;
0245         RegionFrameItem* const item = closestItem(e->scenePos(), &distance);
0246 
0247         // There's a possible nuisance when the direct mouse way from hovering pos to HUD widget
0248         // is not part of the condition. Maybe, we should add a exemption for this case.
0249 
0250         if (distance < 25)
0251         {
0252             setVisibleItem(item);
0253         }
0254         else
0255         {
0256             // get all items close to pos
0257 
0258             QList<QGraphicsItem*> hItems = d->hotItems(e->scenePos());
0259 
0260             // this will be the one item shown by mouse over
0261 
0262             QList<QObject*> visible      = d->visibilityController->visibleItems(ItemVisibilityController::ExcludeFadingOut);
0263 
0264             Q_FOREACH (QGraphicsItem* const item2, hItems)
0265             {
0266                 Q_FOREACH (QObject* const parent, visible)
0267                 {
0268                     if (static_cast<QGraphicsObject*>(parent)->isAncestorOf(item2))
0269                     {   // cppcheck-suppress useStlAlgorithm
0270                         return;
0271                     }
0272                 }
0273             }
0274 
0275             setVisibleItem(nullptr);
0276         }
0277     }
0278 }
0279 
0280 void FaceGroup::itemHoverLeaveEvent(QGraphicsSceneHoverEvent*)
0281 {
0282 }
0283 
0284 void FaceGroup::itemHoverEnterEvent(QGraphicsSceneHoverEvent*)
0285 {
0286 }
0287 
0288 void FaceGroup::leaveEvent(QEvent*)
0289 {
0290     if (d->showOnHover && !isVisible())
0291     {
0292         setVisibleItem(nullptr);
0293     }
0294 }
0295 
0296 void FaceGroup::enterEvent(QEvent*)
0297 {
0298 }
0299 
0300 bool FaceGroup::hasUnconfirmed()
0301 {
0302     Q_FOREACH (FaceItem* const item, d->items)
0303     {
0304         if (item->face().isUnconfirmedType())
0305         {
0306             return true;
0307         }
0308     }
0309 
0310     return false;
0311 }
0312 
0313 void FaceGroup::load()
0314 {
0315     if (d->state != NoFaces)
0316     {
0317         return;
0318     }
0319 
0320     d->state      = LoadingFaces;
0321     d->exifRotate = (MetaEngineSettings::instance()->settings().exifRotate            ||
0322                      ((d->view->previewItem()->image().detectedFormat() == DImg::RAW) &&
0323                       !d->view->previewItem()->image().attribute(QLatin1String("fromRawEmbeddedPreview")).toBool()));
0324 
0325     if (d->info.isNull())
0326     {
0327         d->state = FacesLoaded;
0328 
0329         return;
0330     }
0331 
0332     QList<FaceTagsIface> faces = FaceTagsEditor().databaseFaces(d->info.id());
0333     d->visibilityController->clear();
0334 
0335     Q_FOREACH (const FaceTagsIface& face, faces)
0336     {
0337         d->addItem(face);
0338     }
0339 
0340     d->state = FacesLoaded;
0341 
0342     if (d->view->previewItem()->isLoaded())
0343     {
0344         d->visibilityController->show();
0345     }
0346 
0347     // See bug 408982
0348 
0349     if (d->visibilityController->hasVisibleItems())
0350     {
0351         d->view->setFocus();
0352     }
0353 }
0354 
0355 void FaceGroup::clear()
0356 {
0357     cancelAddItem();
0358     d->visibilityController->clear();
0359 
0360     Q_FOREACH (RegionFrameItem* const item, d->items)
0361     {
0362         delete item;
0363     }
0364 
0365     d->items.clear();
0366     d->state = NoFaces;
0367 }
0368 
0369 void FaceGroup::rejectAll()
0370 {
0371     FaceUtils().removeAllFaces(d->info.id());
0372     clear();
0373 }
0374 
0375 void FaceGroup::markAllAsIgnored()
0376 {
0377     Q_FOREACH (FaceItem* const item, d->items)
0378     {
0379         if (item->face().isUnknownName()   ||
0380             item->face().isUnconfirmedName())
0381         {
0382             FaceTagsIface face = d->editPipeline.editTag(d->info, item->face(),
0383                                                          FaceTags::ignoredPersonTagId());
0384 
0385             item->setFace(face);
0386             item->switchMode(AssignNameWidget::IgnoredMode);
0387         }
0388     }
0389 }
0390 
0391 void FaceGroup::slotAlbumsUpdated(int type)
0392 {
0393     if (type != Album::TAG)
0394     {
0395         return;
0396     }
0397 
0398     if (d->items.isEmpty())
0399     {
0400         return;
0401     }
0402 
0403     clear();
0404     load();
0405 }
0406 
0407 void FaceGroup::slotAlbumRenamed(Album* album)
0408 {
0409     if (!album || (album->type() != Album::TAG))
0410     {
0411         return;
0412     }
0413 
0414     Q_FOREACH (FaceItem* const item, d->items)
0415     {
0416         if (!item->face().isNull() &&
0417             (item->face().tagId() == album->id()))
0418         {
0419             item->updateCurrentTag();
0420         }
0421     }
0422 }
0423 
0424 void FaceGroup::slotAssigned(const TaggingAction& action, const ItemInfo&, const QVariant& faceIdentifier)
0425 {
0426    QList<QVariant> faceList(faceIdentifier.toList());
0427 
0428     if (faceList.size() != 5)
0429     {
0430         return;
0431     }
0432 
0433     FaceItem* const item    = d->items[faceList[4].toInt()];
0434     FaceTagsIface face      = item->face();
0435     TagRegion currentRegion(item->originalRect());
0436 
0437     if (
0438             !face.isConfirmedName()          ||
0439             (face.region() != currentRegion) ||
0440             action.shallCreateNewTag()       ||
0441             (
0442                  action.shallAssignTag() &&
0443                  (action.tagId() != face.tagId())
0444             )
0445        )
0446     {
0447         int tagId = 0;
0448 
0449         if      (action.shallAssignTag())
0450         {
0451             tagId = action.tagId();
0452         }
0453         else if (action.shallCreateNewTag())
0454         {
0455             QStringList faceNames = action.newTagName().split(QLatin1Char('/'),
0456                                                               QT_SKIP_EMPTY_PARTS);
0457             if (!faceNames.isEmpty())
0458             {
0459                 tagId             = action.parentTagId();
0460 
0461                 Q_FOREACH (const QString& name, faceNames)
0462                 {
0463                     tagId = FaceTags::getOrCreateTagForPerson(name.trimmed(), tagId);
0464                 }
0465             }
0466         }
0467 
0468         if (FaceTags::isTheUnknownPerson(tagId))
0469         {
0470             qCDebug(DIGIKAM_GENERAL_LOG) << "Refusing to assign the unknown person to an image";
0471             return;
0472         }
0473 
0474         if (!tagId)
0475         {
0476             qCDebug(DIGIKAM_GENERAL_LOG) << "Failed to get person tag";
0477             return;
0478         }
0479 
0480         face = d->editPipeline.confirm(d->info, face, d->view->previewItem()->image(), tagId, currentRegion);
0481     }
0482 
0483     item->setFace(face);
0484     item->switchMode(AssignNameWidget::ConfirmedMode);
0485 
0486     focusRandomFace();
0487 }
0488 
0489 void FaceGroup::focusRandomFace()
0490 {
0491     Q_FOREACH (FaceItem* const item, d->items)
0492     {
0493         FaceTagsIface face = item->face();
0494         AddTagsComboBox* comboBox = item->widget()->comboBox();
0495 
0496         if ((comboBox) && (!face.isConfirmedName()))
0497         {
0498             comboBox->lineEdit()->selectAll();
0499             comboBox->lineEdit()->setFocus();
0500 
0501             return;
0502         }
0503     }
0504 }
0505 
0506 void FaceGroup::slotRejected(const ItemInfo&, const QVariant& faceIdentifier)
0507 {
0508     QList<QVariant> faceList(faceIdentifier.toList());
0509 
0510     if (faceList.size() == 5)
0511     {
0512         FaceItem* const item = d->items[faceList[4].toInt()];
0513         d->editPipeline.remove(d->info, item->face());
0514 
0515         item->setFace(FaceTagsIface());
0516         d->visibilityController->hideAndRemoveItem(item);
0517 
0518         focusRandomFace();
0519     }
0520 }
0521 
0522 void FaceGroup::slotIgnored(const ItemInfo&, const QVariant& faceIdentifier)
0523 {
0524     QList<QVariant> faceList(faceIdentifier.toList());
0525 
0526     if (faceList.size() == 5)
0527     {
0528         FaceItem* const item = d->items[faceList[4].toInt()];
0529         QRect faceRect       = item->originalRect();
0530         FaceTagsIface face(item->face());
0531 
0532         if (!d->exifRotate)
0533         {
0534             TagRegion::adjustToOrientation(faceRect,
0535                                            d->info.orientation(),
0536                                            d->info.dimensions());
0537         }
0538 
0539         TagRegion currentRegion(faceRect);
0540 
0541         if (face.region() != currentRegion)
0542         {
0543             DImg preview(d->view->previewItem()->image().copy());
0544 
0545             if (!d->exifRotate)
0546             {
0547                 preview.rotateAndFlip(d->info.orientation());
0548             }
0549 
0550             face = d->editPipeline.editRegion(d->info, preview,
0551                                               face, currentRegion);
0552         }
0553 
0554         face = d->editPipeline.editTag(d->info, face,
0555                                        FaceTags::ignoredPersonTagId());
0556 
0557         item->setFace(face);
0558         item->switchMode(AssignNameWidget::IgnoredMode);
0559 
0560         focusRandomFace();
0561     }
0562 }
0563 
0564 void FaceGroup::slotLabelClicked(const ItemInfo&, const QVariant& faceIdentifier)
0565 {
0566     QList<QVariant> faceList(faceIdentifier.toList());
0567 
0568     if (faceList.size() == 5)
0569     {
0570         FaceItem* const item = d->items[faceList[4].toInt()];
0571         item->switchMode(AssignNameWidget::ConfirmedEditMode);
0572     }
0573 }
0574 
0575 void FaceGroup::slotIgnoredClicked(const ItemInfo&, const QVariant& faceIdentifier)
0576 {
0577     QList<QVariant> faceList(faceIdentifier.toList());
0578 
0579     if (faceList.size() == 5)
0580     {
0581         FaceItem* const item = d->items[faceList[4].toInt()];
0582         item->switchMode(AssignNameWidget::UnconfirmedEditMode);
0583     }
0584 }
0585 
0586 void FaceGroup::startAutoSuggest()
0587 {
0588     if (!d->autoSuggest)
0589     {
0590         return;
0591     }
0592 }
0593 
0594 void FaceGroup::addFace()
0595 {
0596     if (d->manuallyAddWrapItem)
0597     {
0598         return;
0599     }
0600 
0601     d->manuallyAddWrapItem = new ClickDragReleaseItem(d->view->previewItem());
0602     d->manuallyAddWrapItem->setFocus();
0603     d->view->setFocus();
0604 
0605     connect(d->manuallyAddWrapItem, SIGNAL(started(QPointF)),
0606             this, SLOT(slotAddItemStarted(QPointF)));
0607 
0608     connect(d->manuallyAddWrapItem, SIGNAL(moving(QRectF)),
0609             this, SLOT(slotAddItemMoving(QRectF)));
0610 
0611     connect(d->manuallyAddWrapItem, SIGNAL(finished(QRectF)),
0612             this, SLOT(slotAddItemFinished(QRectF)));
0613 
0614     connect(d->manuallyAddWrapItem, SIGNAL(cancelled()),
0615             this, SLOT(cancelAddItem()));
0616 }
0617 
0618 void FaceGroup::slotAddItemStarted(const QPointF& pos)
0619 {
0620     Q_UNUSED(pos);
0621 }
0622 
0623 void FaceGroup::slotAddItemMoving(const QRectF& rect)
0624 {
0625     if (!d->manuallyAddedItem)
0626     {
0627         d->manuallyAddedItem = d->createItem(FaceTagsIface());
0628         d->visibilityController->addItem(d->manuallyAddedItem);
0629         d->visibilityController->showItem(d->manuallyAddedItem);
0630     }
0631 
0632     d->manuallyAddedItem->setRectInSceneCoordinatesAdjusted(rect);
0633 }
0634 
0635 void FaceGroup::slotAddItemFinished(const QRectF& rect)
0636 {
0637     if (d->manuallyAddedItem)
0638     {
0639         d->manuallyAddedItem->setRectInSceneCoordinatesAdjusted(rect);
0640         QRect faceRect = d->manuallyAddedItem->originalRect();
0641         DImg preview(d->view->previewItem()->image().copy());
0642 
0643         if (!d->exifRotate)
0644         {
0645             TagRegion::adjustToOrientation(faceRect,
0646                                            d->info.orientation(),
0647                                            d->info.dimensions());
0648             preview.rotateAndFlip(d->info.orientation());
0649         }
0650 
0651         TagRegion addRegion(faceRect);
0652         FaceTagsIface face   = d->editPipeline.addManually(d->info,
0653                                                            preview,
0654                                                            addRegion);
0655         FaceItem* const item = d->addItem(face);
0656         d->visibilityController->setItemDirectlyVisible(item, true);
0657         item->switchMode(AssignNameWidget::UnconfirmedEditMode);
0658         d->manuallyAddWrapItem->stackBefore(item);
0659     }
0660 
0661     cancelAddItem();
0662 }
0663 
0664 void FaceGroup::cancelAddItem()
0665 {
0666     delete d->manuallyAddedItem;
0667     d->manuallyAddedItem = nullptr;
0668 
0669     if (d->manuallyAddWrapItem)
0670     {
0671         d->view->scene()->removeItem(d->manuallyAddWrapItem);
0672         d->manuallyAddWrapItem->deleteLater();
0673         d->manuallyAddWrapItem = nullptr;
0674     }
0675 }
0676 
0677 void FaceGroup::applyItemGeometryChanges()
0678 {
0679     if (d->items.isEmpty())
0680     {
0681         return;
0682     }
0683 
0684     DImg preview(d->view->previewItem()->image().copy());
0685 
0686     if (!d->exifRotate)
0687     {
0688         preview.rotateAndFlip(d->info.orientation());
0689     }
0690 
0691     Q_FOREACH (FaceItem* const item, d->items)
0692     {
0693         if (item->face().isNull())
0694         {
0695             continue;
0696         }
0697 
0698         QRect faceRect = item->originalRect();
0699 
0700         if (!d->exifRotate)
0701         {
0702             TagRegion::adjustToOrientation(faceRect,
0703                                            d->info.orientation(),
0704                                            d->info.dimensions());
0705         }
0706 
0707         TagRegion currentRegion(faceRect);
0708 
0709         if (item->face().region() != currentRegion)
0710         {
0711             d->editPipeline.editRegion(d->info,
0712                                        preview,
0713                                        item->face(),
0714                                        currentRegion);
0715         }
0716     }
0717 }
0718 
0719 /*
0720 void ItemPreviewView::trainFaces()
0721 {
0722     QList<Face> trainList;
0723 
0724     Q_FOREACH (Face f, d->currentFaces)
0725     {
0726         if (f.name() != "" && !d->faceIface->isFaceTrained(getItemInfo().id(), f.toRect(), f.name()))
0727         {
0728             trainList += f;
0729         }
0730     }
0731 
0732     qCDebug(DIGIKAM_GENERAL_LOG) << "Number of training faces" << trainList.size();
0733 
0734     if (trainList.size()!=0)
0735     {
0736         d->faceIface->trainWithFaces(trainList);
0737         d->faceIface->markFacesAsTrained(getItemInfo().id(), trainList);
0738     }
0739 }
0740 
0741 void ItemPreviewView::suggestFaces()
0742 {
0743     // Assign tentative names to the face list
0744 
0745     QList<Face> recogList;
0746 
0747     Q_FOREACH (Face f, d->currentFaces)
0748     {
0749         if (!d->faceIface->isFaceRecognized(getItemInfo().id(), f.toRect(), f.name()) && f.name().isEmpty())
0750         {
0751             f.setName(d->faceIface->recognizedName(f));
0752             d->faceIface->markFaceAsRecognized(getItemInfo().id(), f.toRect(), f.name());
0753 
0754             // If the face wasn't recognized (too distant) don't suggest anything
0755 
0756             if (f.name().isEmpty())
0757             {
0758                 continue;
0759             }
0760             else
0761             {
0762                 recogList += f;
0763             }
0764         }
0765     }
0766 
0767     qCDebug(DIGIKAM_GENERAL_LOG) << "Number of suggestions = " << recogList.size();
0768     qCDebug(DIGIKAM_GENERAL_LOG) << "Number of faceitems = " << d->faceitems.size();
0769 
0770     // Now find the relevant face items and suggest faces
0771 
0772     for (int i = 0 ; i < recogList.size() ; ++i)
0773     {
0774         for (int j = 0 ; j < d->faceitems.size() ; ++j)
0775         {
0776             if (recogList[i].toRect() == d->faceitems[j]->originalRect())
0777             {
0778                 qCDebug(DIGIKAM_GENERAL_LOG) << "Suggesting a name " << recogList[i].name();
0779                 d->faceitems[j]->suggest(recogList[i].name());
0780                 break;
0781             }
0782         }
0783     }
0784 }
0785 */
0786 
0787 } // namespace Digikam
0788 
0789 #include "moc_facegroup.cpp"