File indexing completed on 2024-05-19 04:29:14
0001 /* 0002 * SPDX-FileCopyrightText: 2023 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 #include "KisLayerThumbnailCache.h" 0007 0008 #include "kis_image.h" 0009 #include "KisIdleTasksManager.h" 0010 #include "kis_layer_utils.h" 0011 0012 #include "KisRunnableStrokeJobUtils.h" 0013 #include "KisRunnableStrokeJobsInterface.h" 0014 0015 0016 namespace { 0017 struct ThumbnailRecord { 0018 QImage image; 0019 int seqNo = -1; 0020 int maxSize = 0; 0021 }; 0022 } // namespace 0023 0024 struct ThumbnailsStroke : KisIdleTaskStrokeStrategy 0025 { 0026 Q_OBJECT 0027 public: 0028 0029 ThumbnailsStroke(KisImageSP image, int maxSize, const QMap<KisNodeWSP, ThumbnailRecord> &cache) 0030 : KisIdleTaskStrokeStrategy(QLatin1String("layer-thumbnails-stroke"), kundo2_i18n("Update layer thumbnails")) 0031 , m_root(image->root()) 0032 , m_maxSize(maxSize) 0033 , m_cache(cache) 0034 { 0035 // thread-safety! 0036 m_cache.detach(); 0037 } 0038 0039 void initStrokeCallback() override 0040 { 0041 KisIdleTaskStrokeStrategy::initStrokeCallback(); 0042 0043 using KisLayerUtils::recursiveApplyNodes; 0044 using KritaUtils::addJobConcurrent; 0045 0046 QVector<KisRunnableStrokeJobData*> jobs; 0047 recursiveApplyNodes(m_root, [&jobs, this] (KisNodeSP node) { 0048 0049 0050 if (!node->parent()) return; 0051 if (node->isFakeNode()) return; 0052 0053 bool shouldRegenerateThumbnail = false; 0054 0055 auto it = m_cache.find(node); 0056 0057 if (it != m_cache.end()) { 0058 ThumbnailRecord rec = *it; 0059 0060 if (rec.seqNo != node->thumbnailSeqNo() || 0061 rec.maxSize != m_maxSize) { 0062 0063 shouldRegenerateThumbnail = true; 0064 } 0065 } else { 0066 shouldRegenerateThumbnail = true; 0067 } 0068 0069 if (shouldRegenerateThumbnail) { 0070 addJobConcurrent(jobs, [node, this] () mutable { 0071 QImage image = node->createThumbnail(m_maxSize, m_maxSize, Qt::KeepAspectRatio); 0072 this->sigThumbnailGenerated(node, node->thumbnailSeqNo(), m_maxSize, image); 0073 }); 0074 } 0075 }); 0076 0077 runnableJobsInterface()->addRunnableJobs(jobs); 0078 } 0079 0080 Q_SIGNALS: 0081 void sigThumbnailGenerated(KisNodeSP node, int maxSize, int seqNo, const QImage &thumb); 0082 private: 0083 0084 KisNodeSP m_root; 0085 int m_maxSize; 0086 QMap<KisNodeWSP, ThumbnailRecord> m_cache; 0087 0088 }; 0089 0090 struct KisLayerThumbnailCache::Private 0091 { 0092 KisImageWSP image; 0093 0094 KisIdleTasksManager::TaskGuard taskGuard; 0095 int maxSize = 32; 0096 QMap<KisNodeWSP, ThumbnailRecord> cache; 0097 0098 void cleanupDeletedNodes(); 0099 }; 0100 0101 0102 KisLayerThumbnailCache::KisLayerThumbnailCache() 0103 : m_d(new Private()) 0104 { 0105 } 0106 0107 KisLayerThumbnailCache::~KisLayerThumbnailCache() = default; 0108 0109 void KisLayerThumbnailCache::setIdleTaskManagerImpl(KisIdleTasksManager *manager) 0110 { 0111 if (manager) { 0112 m_d->taskGuard = manager->addIdleTaskWithGuard([this] (KisImageSP image) { 0113 ThumbnailsStroke *stroke = new ThumbnailsStroke(image, m_d->maxSize, m_d->cache); 0114 connect(stroke, SIGNAL(sigThumbnailGenerated(KisNodeSP, int, int, QImage)), this, SLOT(slotThumbnailGenerated(KisNodeSP, int, int, QImage))); 0115 return stroke; 0116 }); 0117 } else { 0118 m_d->taskGuard = KisIdleTasksManager::TaskGuard(); 0119 } 0120 } 0121 0122 void KisLayerThumbnailCache::setImage(KisImageSP image) 0123 { 0124 m_d->image = image; 0125 m_d->cache.clear(); 0126 0127 if (m_d->image && m_d->taskGuard.isValid()) { 0128 m_d->taskGuard.trigger(); 0129 } 0130 } 0131 0132 void KisLayerThumbnailCache::setIdleTaskManager(KisIdleTasksManager *manager) 0133 { 0134 setIdleTaskManagerImpl(manager); 0135 if (m_d->image && m_d->taskGuard.isValid()) { 0136 m_d->taskGuard.trigger(); 0137 } 0138 } 0139 0140 void KisLayerThumbnailCache::setImage(KisImageSP image, KisIdleTasksManager *manager) 0141 { 0142 setIdleTaskManagerImpl(manager); 0143 setImage(image); 0144 // the update is triggered only by the second call, not the first one! 0145 } 0146 0147 void KisLayerThumbnailCache::setMaxSize(int maxSize) 0148 { 0149 m_d->maxSize = maxSize; 0150 if (m_d->image && m_d->taskGuard.isValid()) { 0151 m_d->taskGuard.trigger(); 0152 } 0153 } 0154 0155 int KisLayerThumbnailCache::maxSize() const 0156 { 0157 return m_d->maxSize; 0158 } 0159 0160 QImage KisLayerThumbnailCache::thumbnail(KisNodeSP node) const 0161 { 0162 QImage image; 0163 0164 auto it = m_d->cache.find(node); 0165 if (it != m_d->cache.end()) { 0166 image = it->image; 0167 0168 if (it->maxSize > m_d->maxSize) { 0169 image = image.scaled(m_d->maxSize, m_d->maxSize, Qt::KeepAspectRatio); 0170 } 0171 } else { 0172 image = QImage(1, 1, QImage::Format_ARGB32); 0173 image.fill(0); 0174 } 0175 0176 return image; 0177 } 0178 0179 void KisLayerThumbnailCache::Private::cleanupDeletedNodes() 0180 { 0181 for (auto it = cache.begin(); it != cache.end();) { 0182 if (!it.key()) { 0183 it = cache.erase(it); 0184 } else { 0185 ++it; 0186 } 0187 } 0188 } 0189 0190 void KisLayerThumbnailCache::notifyNodeRemoved(KisNodeSP node) 0191 { 0192 Q_UNUSED(node); 0193 m_d->cleanupDeletedNodes(); 0194 } 0195 0196 void KisLayerThumbnailCache::notifyNodeAdded(KisNodeSP node) 0197 { 0198 Q_UNUSED(node); 0199 m_d->cleanupDeletedNodes(); 0200 0201 if (m_d->image && m_d->taskGuard.isValid()) { 0202 m_d->taskGuard.trigger(); 0203 } 0204 } 0205 0206 void KisLayerThumbnailCache::clear() 0207 { 0208 m_d->cache.clear(); 0209 } 0210 0211 void KisLayerThumbnailCache::slotThumbnailGenerated(KisNodeSP node, int seqNo, int maxSize, const QImage &thumb) 0212 { 0213 if (node->image() != m_d->image) { 0214 qWarning() << "KisLayerThumbnailCache::slotThumbnailGenerated: node does not belong to the attached image anymore!" << ppVar(node) << ppVar(m_d->image); 0215 return; 0216 } 0217 0218 m_d->cache[node] = {thumb, seqNo, maxSize}; 0219 emit sigLayerThumbnailUpdated(node); 0220 } 0221 0222 #include "KisLayerThumbnailCache.moc"