Krita Source Code Documentation
Loading...
Searching...
No Matches
KisLayerThumbnailCache.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2023 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
7
8#include "kis_config.h"
9#include "kis_image.h"
10#include "KisIdleTasksManager.h"
11#include "kis_layer_utils.h"
12
15
16
17namespace {
18struct ThumbnailRecord {
19 QImage image;
20 int seqNo = -1;
21 int maxSize = 0;
22};
23} // namespace
24
26{
27 Q_OBJECT
28public:
29
30 ThumbnailsStroke(KisImageSP image, int maxSize, const QMap<KisNodeWSP, ThumbnailRecord> &cache)
31 : KisIdleTaskStrokeStrategy(QLatin1String("layer-thumbnails-stroke"), kundo2_i18n("Update layer thumbnails"))
32 , m_root(image->root())
33 , m_maxSize(maxSize)
34 , m_cache(cache)
35 {
36 // thread-safety!
37 m_cache.detach();
38 }
39
40 void initStrokeCallback() override
41 {
43
46
48 recursiveApplyNodes(m_root, [&jobs, this] (KisNodeSP node) {
49
50
51 if (!node->parent()) return;
52 if (node->isFakeNode()) return;
53
54 bool shouldRegenerateThumbnail = false;
55
56 auto it = m_cache.find(node);
57
58 if (it != m_cache.end()) {
59 ThumbnailRecord rec = *it;
60
61 if (rec.seqNo != node->thumbnailSeqNo() ||
62 rec.maxSize != m_maxSize) {
63
64 shouldRegenerateThumbnail = true;
65 }
66 } else {
67 shouldRegenerateThumbnail = true;
68 }
69
70 if (shouldRegenerateThumbnail) {
71 const int timeout = KisConfig(true).layerThumbnailGenerationTimeout();
72
73 addJobConcurrent(jobs, [node, timeout, this] () mutable {
74 QElapsedTimer timer;
75 timer.start();
76 QImage image = node->createPreferredThumbnail(m_maxSize, m_maxSize, Qt::KeepAspectRatio);
77 const int measuredTime = timer.elapsed();
78
80 measuredTime > timeout) {
81 warnUI << "WARNING: thumbnail generation for" << node->name() << "took longer than expected:" << measuredTime << "(timeout:" << timeout << ")";
82 warnUI << " This layer's thumbnail will be rendered in imprecise mode from now on";
83 node->setPreferredThumbnailBoundsMode(KisThumbnailBoundsMode::Coarse);
84 }
85
86 this->sigThumbnailGenerated(node, node->thumbnailSeqNo(), m_maxSize, image);
87 });
88 }
89 });
90
92 }
93
94Q_SIGNALS:
95 void sigThumbnailGenerated(KisNodeSP node, int maxSize, int seqNo, const QImage &thumb);
96private:
97
100 QMap<KisNodeWSP, ThumbnailRecord> m_cache;
101
102};
103
105{
107
109 int maxSize = 32;
110 QMap<KisNodeWSP, ThumbnailRecord> cache;
111
112 void cleanupDeletedNodes();
113};
114
115
120
122
124{
125 if (manager) {
126 m_d->taskGuard = manager->addIdleTaskWithGuard([this] (KisImageSP image) {
127 ThumbnailsStroke *stroke = new ThumbnailsStroke(image, m_d->maxSize, m_d->cache);
128 connect(stroke, SIGNAL(sigThumbnailGenerated(KisNodeSP, int, int, QImage)), this, SLOT(slotThumbnailGenerated(KisNodeSP, int, int, QImage)));
129 return stroke;
130 });
131 } else {
132 m_d->taskGuard = KisIdleTasksManager::TaskGuard();
133 }
134}
135
137{
138 m_d->image = image;
139 m_d->cache.clear();
140
141 if (m_d->image && m_d->taskGuard.isValid()) {
142 m_d->taskGuard.trigger();
143 }
144}
145
147{
148 setIdleTaskManagerImpl(manager);
149 if (m_d->image && m_d->taskGuard.isValid()) {
150 m_d->taskGuard.trigger();
151 }
152}
153
155{
156 setIdleTaskManagerImpl(manager);
157 setImage(image);
158 // the update is triggered only by the second call, not the first one!
159}
160
162{
163 m_d->maxSize = maxSize;
164 if (m_d->image && m_d->taskGuard.isValid()) {
165 m_d->taskGuard.trigger();
166 }
167}
168
170{
171 return m_d->maxSize;
172}
173
175{
176 QImage image;
177
178 auto it = m_d->cache.find(node);
179 if (it != m_d->cache.end()) {
180 image = it->image;
181
182 if (it->maxSize > m_d->maxSize) {
183 image = image.scaled(m_d->maxSize, m_d->maxSize, Qt::KeepAspectRatio);
184 }
185 } else {
186 image = QImage(1, 1, QImage::Format_ARGB32);
187 image.fill(0);
188 }
189
190 return image;
191}
192
194{
195 for (auto it = cache.begin(); it != cache.end();) {
196 if (!it.key()) {
197 it = cache.erase(it);
198 } else {
199 ++it;
200 }
201 }
202}
203
205{
206 Q_UNUSED(node);
207 m_d->cleanupDeletedNodes();
208}
209
211{
212 Q_UNUSED(node);
213 m_d->cleanupDeletedNodes();
214
215 if (m_d->image && m_d->taskGuard.isValid()) {
216 m_d->taskGuard.trigger();
217 }
218}
219
221{
222 m_d->cache.clear();
223}
224
225void KisLayerThumbnailCache::slotThumbnailGenerated(KisNodeSP node, int seqNo, int maxSize, const QImage &thumb)
226{
227 if (node->image() != m_d->image) {
228 qWarning() << "KisLayerThumbnailCache::slotThumbnailGenerated: node does not belong to the attached image anymore!" << ppVar(node) << ppVar(m_d->image);
229 return;
230 }
231
232 m_d->cache[node] = {thumb, seqNo, maxSize};
233 Q_EMIT sigLayerThumbnailUpdated(node);
234}
235
236#include "KisLayerThumbnailCache.moc"
int layerThumbnailGenerationTimeout(bool defaultValue=false) const
TaskGuard addIdleTaskWithGuard(KisIdleTaskStrokeStrategyFactory factory)
Registers the factory for the idle task.
void setImage(KisImageSP image)
QImage thumbnail(KisNodeSP node) const
void notifyNodeAdded(KisNodeSP node)
void sigLayerThumbnailUpdated(KisNodeSP node)
void setIdleTaskManagerImpl(KisIdleTasksManager *manager)
QScopedPointer< Private > m_d
void setIdleTaskManager(KisIdleTasksManager *manager)
void slotThumbnailGenerated(KisNodeSP node, int seqNo, int maxSize, const QImage &thumb)
void notifyNodeRemoved(KisNodeSP node)
KisRunnableStrokeJobsInterface * runnableJobsInterface() const
virtual void addRunnableJobs(const QVector< KisRunnableStrokeJobDataBase * > &list)=0
#define ppVar(var)
Definition kis_debug.h:155
KUndo2MagicString kundo2_i18n(const char *text)
void recursiveApplyNodes(NodePointer node, Functor func)
void addJobConcurrent(QVector< Job * > &jobs, Func func)
QImage createPreferredThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode=Qt::IgnoreAspectRatio)
KisThumbnailBoundsMode preferredThumbnailBoundsMode() const
virtual int thumbnailSeqNo() const
KisImageWSP image
virtual bool isFakeNode() const
KisIdleTasksManager::TaskGuard taskGuard
QMap< KisNodeWSP, ThumbnailRecord > cache
KisNodeWSP parent
Definition kis_node.cpp:86
void initStrokeCallback() override
QMap< KisNodeWSP, ThumbnailRecord > m_cache
ThumbnailsStroke(KisImageSP image, int maxSize, const QMap< KisNodeWSP, ThumbnailRecord > &cache)
void sigThumbnailGenerated(KisNodeSP node, int maxSize, int seqNo, const QImage &thumb)