File indexing completed on 2024-05-19 04:27:40

0001 /*
0002  * SPDX-FileCopyrightText: 2023 Sharaf Zaman <shzam@sdf.org>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "KisResourceThumbnailCache.h"
0008 
0009 #include <QMap>
0010 #include <QModelIndex>
0011 #include <QSize>
0012 
0013 #include <KisResourceLocator.h>
0014 #include <KisResourceModel.h>
0015 
0016 #include <kis_global.h>
0017 
0018 Q_GLOBAL_STATIC(KisResourceThumbnailCache, s_instance);
0019 
0020 struct ImageScalingParameters {
0021     QSize size;
0022     Qt::AspectRatioMode aspectRatioMode;
0023     Qt::TransformationMode transformationMode;
0024 
0025     bool operator<(const ImageScalingParameters &other) const
0026     {
0027         if (size != other.size) {
0028             if (size.width() != other.size.width()) {
0029                 return size.width() < other.size.width();
0030             } else {
0031                 return size.height() < other.size.height();
0032             }
0033         } else if (aspectRatioMode != other.aspectRatioMode) {
0034             return aspectRatioMode < other.aspectRatioMode;
0035         } else if (transformationMode != other.transformationMode) {
0036             return transformationMode < other.transformationMode;
0037         } else {
0038             // they're the same
0039             return false;
0040         }
0041     }
0042 };
0043 
0044 namespace
0045 {
0046 using ResourceKey = QPair<QString, QString>;
0047 using ThumbnailCacheT = QMap<ImageScalingParameters, QImage>;
0048 } // namespace
0049 
0050 struct KisResourceThumbnailCache::Private {
0051     QMap<ResourceKey, ThumbnailCacheT> scaledThumbnailCache;
0052     QMap<ResourceKey, QImage> originalImageCache;
0053 
0054     QImage getExactMatch(const ResourceKey &key, ImageScalingParameters param) const;
0055     QImage getOriginal(const ResourceKey &key) const;
0056     void insertOriginal(const ResourceKey &key, const QImage &image);
0057     bool containsOriginal(const ResourceKey &key) const;
0058 
0059     ResourceKey
0060     key(const QString &storageLocation, const QString &resourceType, const QString &filename) const;
0061 };
0062 
0063 QImage KisResourceThumbnailCache::Private::getExactMatch(const ResourceKey &key,
0064                                                          ImageScalingParameters param) const
0065 {
0066     const auto thumbnailEntries = scaledThumbnailCache.find(key);
0067     if (thumbnailEntries != scaledThumbnailCache.end()) {
0068         const auto scaledThumbnail = thumbnailEntries->find(param);
0069         if (scaledThumbnail != thumbnailEntries->end()) {
0070             return *scaledThumbnail;
0071         }
0072     }
0073 
0074     const auto originalImage = originalImageCache.find(key);
0075     if (originalImage != originalImageCache.end() && originalImage->size() == param.size) {
0076         return *originalImage;
0077     }
0078 
0079     return QImage();
0080 }
0081 
0082 QImage KisResourceThumbnailCache::Private::getOriginal(const ResourceKey &key) const
0083 {
0084     return originalImageCache[key];
0085 }
0086 
0087 void KisResourceThumbnailCache::Private::insertOriginal(const ResourceKey &key, const QImage &image)
0088 {
0089     // Someone else has added the image to this cache, when the only path to here is from a method which
0090     // checks whether this cache contains it or not.
0091     KIS_ASSERT(!originalImageCache.contains(key));
0092     originalImageCache.insert(key, image);
0093 }
0094 
0095 bool KisResourceThumbnailCache::Private::containsOriginal(const ResourceKey &key) const
0096 {
0097     return originalImageCache.contains(key);
0098 }
0099 
0100 ResourceKey KisResourceThumbnailCache::Private::key(const QString &storageLocation,
0101                                                     const QString &resourceType,
0102                                                     const QString &filename) const
0103 {
0104     return {storageLocation, resourceType + "/" + filename};
0105 }
0106 
0107 KisResourceThumbnailCache *KisResourceThumbnailCache::instance()
0108 {
0109     return s_instance;
0110 }
0111 
0112 KisResourceThumbnailCache::KisResourceThumbnailCache()
0113     : m_d(new Private)
0114 {
0115 }
0116 
0117 KisResourceThumbnailCache::~KisResourceThumbnailCache()
0118 {
0119 }
0120 
0121 QImage KisResourceThumbnailCache::originalImage(const QString &storageLocation,
0122                                                 const QString &resourceType,
0123                                                 const QString &filename) const
0124 {
0125     const ResourceKey key = m_d->key(storageLocation, resourceType, filename);
0126     return m_d->containsOriginal(key) ? m_d->getOriginal(key) : QImage();
0127 }
0128 
0129 void KisResourceThumbnailCache::insert(const QString &storageLocation,
0130                                        const QString &resourceType,
0131                                        const QString &filename,
0132                                        const QImage &image)
0133 {
0134     if (image.isNull()) {
0135         return;
0136     }
0137     insert(m_d->key(storageLocation, resourceType, filename), image);
0138 }
0139 
0140 void KisResourceThumbnailCache::insert(const QPair<QString, QString> &key, const QImage &image)
0141 {
0142     m_d->insertOriginal(key, image);
0143 }
0144 
0145 void KisResourceThumbnailCache::remove(const QString &storageLocation,
0146                                        const QString &resourceType,
0147                                        const QString &filename)
0148 {
0149     remove(m_d->key(storageLocation, resourceType, filename));
0150 }
0151 
0152 void KisResourceThumbnailCache::remove(const QPair<QString, QString> &key)
0153 {
0154     if (m_d->originalImageCache.contains(key)) {
0155         m_d->originalImageCache.remove(key);
0156 
0157         if (m_d->scaledThumbnailCache.contains(key)) {
0158             m_d->scaledThumbnailCache.remove(key);
0159         }
0160     } else {
0161         // Something must have gone wrong for thumbnail to exist in scaledThumbnailCache but not be in
0162         // original.
0163         KIS_ASSERT(!m_d->scaledThumbnailCache.contains(key));
0164     }
0165 }
0166 
0167 QImage KisResourceThumbnailCache::getImage(const QModelIndex &index,
0168                                            const QSize size,
0169                                            Qt::AspectRatioMode aspectMode,
0170                                            Qt::TransformationMode transformMode)
0171 {
0172     const QString storageLocation = KisResourceLocator::instance()->makeStorageLocationAbsolute(
0173         index.data(Qt::UserRole + KisAbstractResourceModel::Location).value<QString>());
0174     const QString resourceType =
0175         index.data(Qt::UserRole + KisAbstractResourceModel::ResourceType).value<QString>();
0176     const QString filename = index.data(Qt::UserRole + KisAbstractResourceModel::Filename).value<QString>();
0177 
0178     const ImageScalingParameters param = {size, aspectMode, transformMode};
0179 
0180     ResourceKey key = m_d->key(storageLocation, resourceType, filename);
0181 
0182     QImage result = m_d->getExactMatch(key, param);
0183     if (!result.isNull()) {
0184         return result;
0185     } else if (m_d->containsOriginal(key)) {
0186         result = m_d->getOriginal(key);
0187     } else {
0188         result = index.data(Qt::UserRole + KisAbstractResourceModel::Thumbnail).value<QImage>();
0189         // KisResourceQueryMapper should have inserted the image, so we don't have to.
0190         // Why there? Because most of the API usage for Thumbnail is going to be from index.data(), so we just
0191         // remove the dependency that our user has to know this class for just accessing the cached original
0192         // thumbnail.
0193         KIS_SAFE_ASSERT_RECOVER_NOOP(result.isNull() || m_d->containsOriginal(key));
0194     }
0195     // if the size that the has been demanded, we will then cache the size and then pass it.
0196     if (!result.isNull() && param.size.isValid()) {
0197         const QImage scaledImage = result.scaled(param.size, param.aspectRatioMode, param.transformationMode);
0198         if (m_d->scaledThumbnailCache.contains(key)) {
0199             m_d->scaledThumbnailCache[key].insert(param, scaledImage);
0200         } else {
0201             ThumbnailCacheT scaledCacheMap;
0202             scaledCacheMap.insert(param, scaledImage);
0203             m_d->scaledThumbnailCache.insert(key, scaledCacheMap);
0204         }
0205         return scaledImage;
0206     } else {
0207         return result;
0208     }
0209 }