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 }