File indexing completed on 2025-01-05 03:53:48
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2006-04-14 0007 * Description : Load and cache tag thumbnails 0008 * 0009 * SPDX-FileCopyrightText: 2006-2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0010 * SPDX-FileCopyrightText: 2015 by Mohamed_Anwer <m_dot_anwer at gmx dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "albumthumbnailloader.h" 0017 0018 // C++ includes 0019 0020 #include <cmath> 0021 0022 // Qt includes 0023 0024 #include <QList> 0025 #include <QMap> 0026 #include <QCache> 0027 #include <QPair> 0028 0029 // Local includes 0030 0031 #include "digikam_debug.h" 0032 #include "album.h" 0033 #include "albummanager.h" 0034 #include "applicationsettings.h" 0035 #include "coredb.h" 0036 #include "coredbaccess.h" 0037 #include "iteminfo.h" 0038 #include "faceutils.h" 0039 #include "metaenginesettings.h" 0040 #include "thumbnailloadthread.h" 0041 #include "thumbnailsize.h" 0042 #include "thumbnailcreator.h" 0043 #include "dio.h" 0044 0045 namespace Digikam 0046 { 0047 0048 typedef QMap<QPair<qlonglong, QString>, QList<int> > IdAlbumMap; 0049 typedef QMap<int, QPixmap> AlbumThumbnailMap; 0050 0051 class Q_DECL_HIDDEN AlbumThumbnailLoaderCreator 0052 { 0053 public: 0054 0055 AlbumThumbnailLoader object; 0056 }; 0057 0058 Q_GLOBAL_STATIC(AlbumThumbnailLoaderCreator, creator) 0059 0060 // --------------------------------------------------------------------------------------------- 0061 0062 class Q_DECL_HIDDEN AlbumThumbnailLoader::Private 0063 { 0064 public: 0065 0066 explicit Private() 0067 : iconSize (ApplicationSettings::instance()->getTreeViewIconSize()), 0068 faceSize (ApplicationSettings::instance()->getTreeViewFaceSize()), 0069 minBlendSize (20), 0070 iconTagThumbThread (nullptr), 0071 iconFaceThumbThread (nullptr), 0072 iconAlbumThumbThread(nullptr), 0073 fallBackIcon (QIcon::fromTheme(QLatin1String("dialog-cancel"))) 0074 { 0075 } 0076 0077 int iconSize; 0078 int faceSize; 0079 int minBlendSize; 0080 0081 ThumbnailLoadThread* iconTagThumbThread; 0082 ThumbnailLoadThread* iconFaceThumbThread; 0083 ThumbnailLoadThread* iconAlbumThumbThread; 0084 0085 QIcon fallBackIcon; 0086 0087 IdAlbumMap idAlbumMap; 0088 0089 AlbumThumbnailMap thumbnailMap; 0090 0091 QCache<QPair<QString, int>, QPixmap> iconCache; 0092 }; 0093 0094 bool operator<(const ThumbnailIdentifier& a, const ThumbnailIdentifier& b) 0095 { 0096 if (a.id || b.id) 0097 { 0098 return (a.id < b.id); 0099 } 0100 else 0101 { 0102 return (a.filePath < b.filePath); 0103 } 0104 } 0105 0106 AlbumThumbnailLoader* AlbumThumbnailLoader::instance() 0107 { 0108 return &creator->object; 0109 } 0110 0111 AlbumThumbnailLoader::AlbumThumbnailLoader() 0112 : d(new Private) 0113 { 0114 connect(this, SIGNAL(signalDispatchThumbnailInternal(int,QPixmap)), 0115 this, SLOT(slotDispatchThumbnailInternal(int,QPixmap))); 0116 0117 connect(AlbumManager::instance(), SIGNAL(signalAlbumIconChanged(Album*)), 0118 this, SLOT(slotIconChanged(Album*))); 0119 0120 connect(AlbumManager::instance(), SIGNAL(signalAlbumDeleted(Album*)), 0121 this, SLOT(slotIconChanged(Album*))); 0122 } 0123 0124 AlbumThumbnailLoader::~AlbumThumbnailLoader() 0125 { 0126 delete d->iconAlbumThumbThread; 0127 delete d->iconFaceThumbThread; 0128 delete d->iconTagThumbThread; 0129 delete d; 0130 } 0131 0132 void AlbumThumbnailLoader::cleanUp() 0133 { 0134 if (d->iconAlbumThumbThread) 0135 { 0136 d->iconAlbumThumbThread->stopAllTasks(); 0137 d->iconAlbumThumbThread->wait(); 0138 } 0139 0140 if (d->iconFaceThumbThread) 0141 { 0142 d->iconFaceThumbThread->stopAllTasks(); 0143 d->iconFaceThumbThread->wait(); 0144 } 0145 0146 if (d->iconTagThumbThread) 0147 { 0148 d->iconTagThumbThread->stopAllTasks(); 0149 d->iconTagThumbThread->wait(); 0150 } 0151 } 0152 0153 QPixmap AlbumThumbnailLoader::getStandardTagIcon(RelativeSize relativeSize) 0154 { 0155 return loadIcon(QLatin1String("tag"), computeIconSize(relativeSize)); 0156 } 0157 0158 QPixmap AlbumThumbnailLoader::getStandardTagRootIcon(RelativeSize relativeSize) 0159 { 0160 return loadIcon(QLatin1String("document-open"), computeIconSize(relativeSize)); 0161 } 0162 0163 QPixmap AlbumThumbnailLoader::getStandardTagIcon(TAlbum* const album, RelativeSize relativeSize) 0164 { 0165 if (album->isRoot()) 0166 { 0167 return getStandardTagRootIcon(relativeSize); 0168 } 0169 else 0170 { 0171 return getStandardTagIcon(relativeSize); 0172 } 0173 } 0174 0175 QPixmap AlbumThumbnailLoader::getStandardAlbumIcon(PAlbum* const album, RelativeSize relativeSize) 0176 { 0177 if (album->isRoot() || album->isAlbumRoot()) 0178 { 0179 return getStandardAlbumRootIcon(relativeSize); 0180 } 0181 else if (album->isTrashAlbum()) 0182 { 0183 PAlbum* const palbum = static_cast<PAlbum*>(album->parent()); 0184 0185 if (palbum) 0186 { 0187 if (DIO::getTrashCounter(palbum->folderPath()) > 0) 0188 { 0189 return getStandardFullTrashIcon(relativeSize); 0190 } 0191 } 0192 0193 return getStandardEmptyTrashIcon(relativeSize); 0194 } 0195 0196 return getStandardAlbumIcon(relativeSize); 0197 } 0198 0199 QPixmap AlbumThumbnailLoader::getStandardFaceIcon(TAlbum* const /*album*/, RelativeSize relativeSize) 0200 { 0201 return loadIcon(QLatin1String("smiley"), computeFaceSize(relativeSize)); 0202 } 0203 0204 QPixmap AlbumThumbnailLoader::getNewTagIcon(RelativeSize relativeSize) 0205 { 0206 return loadIcon(QLatin1String("tag-new"), computeIconSize(relativeSize)); 0207 } 0208 0209 QPixmap AlbumThumbnailLoader::getStandardEmptyTrashIcon(RelativeSize relativeSize) 0210 { 0211 return loadIcon(QLatin1String("user-trash"), computeIconSize(relativeSize)); 0212 } 0213 0214 QPixmap AlbumThumbnailLoader::getStandardFullTrashIcon(RelativeSize relativeSize) 0215 { 0216 return loadIcon(QLatin1String("user-trash-full"), computeIconSize(relativeSize)); 0217 } 0218 0219 QPixmap AlbumThumbnailLoader::getStandardAlbumRootIcon(RelativeSize relativeSize) 0220 { 0221 return loadIcon(QLatin1String("folder-pictures"), computeIconSize(relativeSize)); 0222 } 0223 0224 QPixmap AlbumThumbnailLoader::getStandardAlbumIcon(RelativeSize relativeSize) 0225 { 0226 return loadIcon(QLatin1String("folder"), computeIconSize(relativeSize)); 0227 } 0228 0229 int AlbumThumbnailLoader::computeIconSize(RelativeSize relativeSize) const 0230 { 0231 if (relativeSize == SmallerSize) 0232 { 0233 // when size was 32 smaller was 20. Scale. 0234 0235 return lround(20.0 / 32.0 * (double)d->iconSize); 0236 } 0237 0238 return d->iconSize; 0239 } 0240 0241 int AlbumThumbnailLoader::computeFaceSize(RelativeSize relativeSize) const 0242 { 0243 if (relativeSize == SmallerSize) 0244 { 0245 // when size was 32 smaller was 20. Scale. 0246 0247 return lround(20.0 / 32.0 * (double)d->faceSize); 0248 } 0249 0250 return d->faceSize; 0251 } 0252 0253 QPixmap AlbumThumbnailLoader::loadIcon(const QString& name, int size) const 0254 { 0255 QPixmap* cachePix = d->iconCache[qMakePair(name, size)]; 0256 0257 if (!cachePix) 0258 { 0259 QPixmap pix; 0260 0261 // We check for a slash to see if it's a path to a file. 0262 // This is significantly faster than check with QFileInfo. 0263 0264 if (name.contains(QLatin1Char('/'))) 0265 { 0266 if (pix.load(name)) 0267 { 0268 pix = pix.scaled(size, 0269 size, 0270 Qt::KeepAspectRatio, 0271 Qt::SmoothTransformation); 0272 } 0273 else 0274 { 0275 pix = d->fallBackIcon.pixmap(size); 0276 } 0277 } 0278 else 0279 { 0280 pix = QIcon::fromTheme(name, d->fallBackIcon).pixmap(size); 0281 } 0282 0283 d->iconCache.insert(qMakePair(name, size), new QPixmap(pix)); 0284 0285 return pix; 0286 } 0287 0288 return (*cachePix); // ownership of the pointer is kept by the icon cache. 0289 } 0290 0291 bool AlbumThumbnailLoader::getTagThumbnail(TAlbum* const album, QPixmap& icon) 0292 { 0293 if (album->iconId() && (d->iconSize > d->minBlendSize)) 0294 { 0295 addUrl(album, album->iconId()); 0296 icon = QPixmap(); 0297 0298 return true; 0299 } 0300 else if (!album->icon().isEmpty()) 0301 { 0302 icon = loadIcon(album->icon(), d->iconSize); 0303 0304 return false; 0305 } 0306 0307 icon = QPixmap(); 0308 0309 return false; 0310 } 0311 0312 QPixmap AlbumThumbnailLoader::getTagThumbnailDirectly(TAlbum* const album) 0313 { 0314 if (album->iconId() && (d->iconSize > d->minBlendSize)) 0315 { 0316 // icon cached? 0317 AlbumThumbnailMap::const_iterator it = d->thumbnailMap.constFind(album->globalID()); 0318 0319 if (it != d->thumbnailMap.constEnd()) 0320 { 0321 return *it; 0322 } 0323 0324 addUrl(album, album->iconId()); 0325 } 0326 else if (!album->icon().isEmpty()) 0327 { 0328 QPixmap pixmap = loadIcon(album->icon(), d->iconSize); 0329 0330 return pixmap; 0331 } 0332 0333 return getStandardTagIcon(album); 0334 } 0335 0336 QPixmap AlbumThumbnailLoader::getFaceThumbnailDirectly(TAlbum* const album) 0337 { 0338 if (album->iconId() && (d->faceSize > d->minBlendSize)) 0339 { 0340 // icon cached? 0341 AlbumThumbnailMap::const_iterator it = d->thumbnailMap.constFind(album->globalID()); 0342 0343 if (it != d->thumbnailMap.constEnd()) 0344 { 0345 return *it; 0346 } 0347 0348 addUrl(album, album->iconId()); 0349 } 0350 else if (!album->icon().isEmpty()) 0351 { 0352 QPixmap pixmap = loadIcon(album->icon(), d->faceSize); 0353 0354 return pixmap; 0355 } 0356 else if (!album->iconId() && !FaceTags::isSystemPersonTagId(album->id())) 0357 { 0358 qlonglong firstItemId = CoreDbAccess().db()->getFirstItemWithFaceTag(album->id()); 0359 0360 if (firstItemId > 0) 0361 { 0362 QString err; 0363 0364 if (!AlbumManager::instance()->updateTAlbumIcon(album, QString(), 0365 firstItemId, err)) 0366 { 0367 qCDebug(DIGIKAM_GENERAL_LOG) << err; 0368 } 0369 else 0370 { 0371 addUrl(album, firstItemId); 0372 } 0373 } 0374 } 0375 0376 return getStandardFaceIcon(album); 0377 } 0378 0379 bool AlbumThumbnailLoader::getAlbumThumbnail(PAlbum* const album) 0380 { 0381 if (album->iconId() && (d->iconSize > d->minBlendSize)) 0382 { 0383 addUrl(album, album->iconId()); 0384 } 0385 else 0386 { 0387 return false; 0388 } 0389 0390 return true; 0391 } 0392 0393 QPixmap AlbumThumbnailLoader::getAlbumThumbnailDirectly(PAlbum* const album) 0394 { 0395 if (album->iconId() && !album->isTrashAlbum() && (d->iconSize > d->minBlendSize)) 0396 { 0397 // icon cached? 0398 0399 AlbumThumbnailMap::const_iterator it = d->thumbnailMap.constFind(album->globalID()); 0400 0401 if (it != d->thumbnailMap.constEnd()) 0402 { 0403 return *it; 0404 } 0405 0406 // schedule for loading 0407 0408 addUrl(album, album->iconId()); 0409 } 0410 0411 return getStandardAlbumIcon(album); 0412 } 0413 0414 void AlbumThumbnailLoader::addUrl(Album* const album, qlonglong id) 0415 { 0416 // First check cached thumbnails. 0417 // We use a private cache which is actually a map to be sure to cache _all_ album thumbnails. 0418 // At startup, this is not relevant, as the views will add their requests in a row. 0419 // This is to speed up context menu and IE imagedescedit 0420 0421 AlbumThumbnailMap::const_iterator ttit = d->thumbnailMap.constFind(album->globalID()); 0422 0423 if (ttit != d->thumbnailMap.constEnd()) 0424 { 0425 // It is not necessary to return cached icon asynchronously - they could be 0426 // returned by getTagThumbnail already - but this would make the API 0427 // less elegant, it feels much better this way. 0428 0429 Q_EMIT signalDispatchThumbnailInternal(album->globalID(), *ttit); 0430 0431 return; 0432 } 0433 0434 // Finding face rect to identify correct tag 0435 0436 QRect faceRect = QRect(); 0437 0438 if ((album->type() == Album::TAG) && static_cast<TAlbum*>(album)->hasProperty(TagPropertyName::person())) 0439 { 0440 QList<FaceTagsIface> faces = FaceTagsEditor().databaseFaces(id); 0441 0442 Q_FOREACH (const FaceTagsIface& face, faces) 0443 { 0444 if (face.tagId() == album->id()) 0445 { 0446 faceRect = FaceUtils::faceRectToDisplayRect(face.region().toRect()); 0447 break; 0448 } 0449 } 0450 } 0451 0452 // Simple way to put QRect into QMap 0453 0454 QString faceRectStr = QString(QLatin1String("%1%2%3%4")) 0455 .arg(faceRect.x()).arg(faceRect.y()).arg(faceRect.right()).arg(faceRect.bottom()); 0456 0457 // Check if the URL has already been added 0458 0459 IdAlbumMap::iterator it = d->idAlbumMap.find(QPair<qlonglong, QString>(id, faceRectStr)); 0460 0461 if (it == d->idAlbumMap.end()) 0462 { 0463 // use two threads so that tag and album thumbnails are loaded 0464 // in parallel and not first album, then tag thumbnails 0465 0466 if (album->type() == Album::TAG) 0467 { 0468 if (!d->iconTagThumbThread) 0469 { 0470 d->iconTagThumbThread = new ThumbnailLoadThread(); 0471 d->iconTagThumbThread->setThumbnailSize(d->iconSize); 0472 d->iconTagThumbThread->setSendSurrogatePixmap(false); 0473 0474 connect(d->iconTagThumbThread, 0475 SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), 0476 SLOT(slotGotThumbnailFromIcon(LoadingDescription,QPixmap)), 0477 Qt::QueuedConnection); 0478 } 0479 0480 if (!d->iconFaceThumbThread) 0481 { 0482 d->iconFaceThumbThread = new ThumbnailLoadThread(); 0483 d->iconFaceThumbThread->setThumbnailSize(d->faceSize); 0484 d->iconFaceThumbThread->setSendSurrogatePixmap(false); 0485 0486 connect(d->iconFaceThumbThread, 0487 SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), 0488 SLOT(slotGotThumbnailFromIcon(LoadingDescription,QPixmap)), 0489 Qt::QueuedConnection); 0490 } 0491 0492 if (static_cast<TAlbum*>(album)->hasProperty(TagPropertyName::person())) 0493 { 0494 d->iconFaceThumbThread->find(ItemInfo::thumbnailIdentifier(id), faceRect); 0495 } 0496 else 0497 { 0498 d->iconTagThumbThread->find(ItemInfo::thumbnailIdentifier(id)); 0499 } 0500 } 0501 else 0502 { 0503 if (!d->iconAlbumThumbThread) 0504 { 0505 d->iconAlbumThumbThread = new ThumbnailLoadThread(); 0506 d->iconAlbumThumbThread->setThumbnailSize(d->iconSize); 0507 d->iconAlbumThumbThread->setSendSurrogatePixmap(false); 0508 0509 connect(d->iconAlbumThumbThread, 0510 SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), 0511 SLOT(slotGotThumbnailFromIcon(LoadingDescription,QPixmap)), 0512 Qt::QueuedConnection); 0513 } 0514 0515 d->iconAlbumThumbThread->find(ItemInfo::thumbnailIdentifier(id)); 0516 } 0517 0518 // insert new entry to map, add album globalID 0519 0520 QList<int>& list = d->idAlbumMap[QPair<qlonglong, QString>(id, faceRectStr)]; 0521 list.removeAll(album->globalID()); 0522 list.append(album->globalID()); 0523 } 0524 else 0525 { 0526 // only add album global ID to list which is already inserted in map 0527 0528 (*it).removeAll(album->globalID()); 0529 (*it).append(album->globalID()); 0530 } 0531 } 0532 0533 void AlbumThumbnailLoader::setThumbnailSize(int size, int face) 0534 { 0535 if ((d->iconSize == size) && (d->faceSize == face)) 0536 { 0537 return; 0538 } 0539 0540 d->iconSize = size; 0541 d->faceSize = face; 0542 0543 // clear task list 0544 0545 d->idAlbumMap.clear(); 0546 0547 // clear cached thumbnails 0548 0549 d->thumbnailMap.clear(); 0550 0551 if (d->iconAlbumThumbThread) 0552 { 0553 d->iconAlbumThumbThread->stopLoading(); 0554 d->iconAlbumThumbThread->setThumbnailSize(size); 0555 } 0556 0557 if (d->iconFaceThumbThread) 0558 { 0559 d->iconFaceThumbThread->stopLoading(); 0560 d->iconFaceThumbThread->setThumbnailSize(face); 0561 } 0562 0563 if (d->iconTagThumbThread) 0564 { 0565 d->iconTagThumbThread->stopLoading(); 0566 d->iconTagThumbThread->setThumbnailSize(size); 0567 } 0568 0569 Q_EMIT signalReloadThumbnails(); 0570 } 0571 0572 int AlbumThumbnailLoader::thumbnailSize() const 0573 { 0574 return d->iconSize; 0575 } 0576 0577 void AlbumThumbnailLoader::slotGotThumbnailFromIcon(const LoadingDescription& loadingDescription, const QPixmap& thumbnail) 0578 { 0579 // We need to find all albums for which the given url has been requested, 0580 // and Q_EMIT a signal for each album. 0581 0582 QRect faceRect = QRect(); 0583 0584 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0585 0586 if (loadingDescription.previewParameters.extraParameter.typeId() == QVariant::Rect) 0587 0588 #else 0589 0590 if (loadingDescription.previewParameters.extraParameter.type() == QVariant::Rect) 0591 0592 #endif 0593 0594 { 0595 faceRect = loadingDescription.previewParameters.extraParameter.toRect(); 0596 } 0597 0598 // Simple way to put QRect into QMap 0599 0600 QString faceRectStr = QString(QLatin1String("%1%2%3%4")) 0601 .arg(faceRect.x()).arg(faceRect.y()).arg(faceRect.right()).arg(faceRect.bottom()); 0602 0603 ThumbnailIdentifier id = loadingDescription.thumbnailIdentifier(); 0604 IdAlbumMap::iterator it = d->idAlbumMap.find(QPair<qlonglong, QString>(id.id, faceRectStr)); 0605 0606 if (it != d->idAlbumMap.end()) 0607 { 0608 AlbumManager* const manager = AlbumManager::instance(); 0609 0610 if (thumbnail.isNull()) 0611 { 0612 // Loading failed 0613 0614 for (QList<int>::const_iterator vit = (*it).constBegin() ; vit != (*it).constEnd() ; ++vit) 0615 { 0616 Album* const album = manager->findAlbum(*vit); 0617 0618 if (album) 0619 { 0620 Q_EMIT signalFailed(album); 0621 } 0622 } 0623 } 0624 else 0625 { 0626 // Loading succeeded 0627 0628 for (QList<int>::const_iterator vit = (*it).constBegin() ; vit != (*it).constEnd() ; ++vit) 0629 { 0630 // look up with global id 0631 0632 Album* const album = manager->findAlbum(*vit); 0633 0634 if (album) 0635 { 0636 d->thumbnailMap.insert(album->globalID(), thumbnail); 0637 Q_EMIT signalThumbnail(album, thumbnail); 0638 } 0639 } 0640 } 0641 0642 d->idAlbumMap.erase(it); 0643 } 0644 } 0645 0646 void AlbumThumbnailLoader::slotDispatchThumbnailInternal(int albumID, const QPixmap& thumbnail) 0647 { 0648 // for cached thumbnails 0649 0650 AlbumManager* const manager = AlbumManager::instance(); 0651 Album* const album = manager->findAlbum(albumID); 0652 0653 if (album) 0654 { 0655 if (thumbnail.isNull()) 0656 { 0657 Q_EMIT signalFailed(album); 0658 } 0659 else 0660 { 0661 Q_EMIT signalThumbnail(album, thumbnail); 0662 } 0663 } 0664 } 0665 0666 void AlbumThumbnailLoader::slotIconChanged(Album* album) 0667 { 0668 if (!album || ((album->type() != Album::TAG) && (album->type() != Album::PHYSICAL))) 0669 { 0670 return; 0671 } 0672 0673 d->thumbnailMap.remove(album->globalID()); 0674 } 0675 0676 /* 0677 * This code is maximally inefficient 0678 QImage AlbumThumbnailLoader::getAlbumPreviewDirectly(PAlbum* const album, int size) 0679 { 0680 if (album->iconId()) 0681 { 0682 ThumbnailLoadThread* const thread = new ThumbnailLoadThread; 0683 thread->setPixmapRequested(false); 0684 thread->setThumbnailSize(size); 0685 ThumbnailImageCatcher* const catcher = new ThumbnailImageCatcher(thread); 0686 catcher->setActive(true); 0687 catcher->thread()->find(ThumbnailIdentifier(album->iconId()); 0688 catcher->enqueue(); 0689 QList<QImage> images = catcher->waitForThumbnails(); 0690 catcher->setActive(false); 0691 delete thread; 0692 delete catcher; 0693 0694 if (!images.isEmpty()) 0695 { 0696 return images[0]; 0697 } 0698 } 0699 0700 return loadIcon("folder", size).toImage(); 0701 } 0702 */ 0703 0704 } // namespace Digikam 0705 0706 #include "moc_albumthumbnailloader.cpp"