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"