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"