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"