Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qpixmapcache.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "qpixmapcache.h"
6#include "qbasictimer.h"
7#include "qobject.h"
8#include "qdebug.h"
10#include "qthread.h"
12
13using namespace std::chrono_literals;
14
16
17/*!
18 \class QPixmapCache
19 \inmodule QtGui
20
21 \brief The QPixmapCache class provides an application-wide cache for pixmaps.
22
23 This class is a tool for optimized drawing with QPixmap. You can
24 use it to store temporary pixmaps that are expensive to generate
25 without using more storage space than cacheLimit(). Use insert()
26 to insert pixmaps, find() to find them, and clear() to empty the
27 cache.
28
29 QPixmapCache contains no member data, only static functions to
30 access the global pixmap cache. It creates an internal QCache
31 object for caching the pixmaps.
32
33 The cache associates a pixmap with a user-provided string as a key,
34 or with a QPixmapCache::Key that the cache generates.
35 Using QPixmapCache::Key for keys is faster than using strings. The string API is
36 very convenient for complex keys but the QPixmapCache::Key API will be very
37 efficient and convenient for a one-to-one object-to-pixmap mapping - in
38 this case, you can store the keys as members of an object.
39
40 If two pixmaps are inserted into the cache using equal keys then the
41 last pixmap will replace the first pixmap in the cache. This follows the
42 behavior of the QHash and QCache classes.
43
44 The cache becomes full when the total size of all pixmaps in the
45 cache exceeds cacheLimit(). The initial cache limit is 10240 KB (10 MB);
46 you can change this by calling setCacheLimit() with the required value.
47 A pixmap takes roughly (\e{width} * \e{height} * \e{depth})/8 bytes of
48 memory.
49
50 The \e{Qt Quarterly} article
51 \l{http://doc.qt.io/archives/qq/qq12-qpixmapcache.html}{Optimizing
52 with QPixmapCache} explains how to use QPixmapCache to speed up
53 applications by caching the results of painting.
54
55 \note QPixmapCache is only usable from the application's main thread.
56 Access from other threads will be ignored and return failure.
57
58 \sa QCache, QPixmap
59*/
60
61static const int cache_limit_default = 10240; // 10 MB cache limit
62
63static inline qsizetype cost(const QPixmap &pixmap)
64{
65 // make sure to do a 64bit calculation; qsizetype might be smaller
66 const qint64 costKb = static_cast<qint64>(pixmap.width())
67 * pixmap.height() * pixmap.depth() / (8 * 1024);
68 const qint64 costMax = std::numeric_limits<qsizetype>::max();
69 // a small pixmap should have at least a cost of 1(kb)
70 return static_cast<qsizetype>(qBound(1LL, costKb, costMax));
71}
72
73static inline bool qt_pixmapcache_thread_test()
74{
75 if (Q_LIKELY(QThread::isMainThread()))
76 return true;
77
78 return false;
79}
80
81/*!
82 \class QPixmapCache::Key
83 \brief The QPixmapCache::Key class can be used for efficient access
84 to the QPixmapCache.
85 \inmodule QtGui
86
87 Use QPixmapCache::insert() to receive an instance of Key generated
88 by the pixmap cache. You can store the key in your own objects for
89 a very efficient one-to-one object-to-pixmap mapping.
90*/
91
92/*!
93 Constructs an empty Key object.
94*/
95QPixmapCache::Key::Key() : d(nullptr)
96{
97}
98
99/*!
100 \internal
101 Constructs a copy of \a other.
102*/
103QPixmapCache::Key::Key(const Key &other)
104{
105 if (other.d)
106 ++(other.d->ref);
107 d = other.d;
108}
109
110/*!
111 Destroys the key.
112*/
113QPixmapCache::Key::~Key()
114{
115 if (d && --(d->ref) == 0)
116 delete d;
117}
118
119/*!
120 \internal
121
122 Returns \c true if this key is the same as the given \a key; otherwise returns
123 false.
124*/
125bool QPixmapCache::Key::operator ==(const Key &key) const
126{
127 return (d == key.d);
128}
129
130/*!
131 \fn bool QPixmapCache::Key::operator !=(const Key &key) const
132 \internal
133*/
134
135/*!
136 \fn QPixmapCache::Key::Key(Key &&)
137 \internal
138 \since 5.6
139*/
140
141/*!
142 \fn QPixmapCache::Key &QPixmapCache::Key::operator=(Key &&)
143 \internal
144 \since 5.6
145*/
146
147/*!
148 \fn void QPixmapCache::Key::swap(Key &)
149 \since 5.6
150 \memberswap{key}
151*/
152
153/*!
154 Returns \c true if there is a cached pixmap associated with this key.
155 Otherwise, if pixmap was flushed, the key is no longer valid.
156 \since 5.7
157*/
158bool QPixmapCache::Key::isValid() const noexcept
159{
160 return d && d->isValid;
161}
162
163/*!
164 \internal
165*/
166QPixmapCache::Key &QPixmapCache::Key::operator =(const Key &other)
167{
168 if (d != other.d) {
169 if (other.d)
170 ++(other.d->ref);
171 if (d && --(d->ref) == 0)
172 delete d;
173 d = other.d;
174 }
175 return *this;
176}
177
179{
181public:
184
185 void timerEvent(QTimerEvent *) override;
186 bool insert(const QString& key, const QPixmap &pixmap, int cost);
187 QPixmapCache::Key insert(const QPixmap &pixmap, int cost);
188 bool remove(const QString &key);
189 bool remove(const QPixmapCache::Key &key);
190
191 void resizeKeyArray(int size);
193 void releaseKey(const QPixmapCache::Key &key);
194 void clear();
195
196 QPixmap *object(const QString &key) const;
197 QPixmap *object(const QPixmapCache::Key &key) const;
198
199 static inline QPixmapCache::KeyData *get(const QPixmapCache::Key &key)
200 {return key.d;}
201
202 static QPixmapCache::KeyData* getKeyData(QPixmapCache::Key *key);
203
204 bool flushDetachedPixmaps(bool nt);
205
206private:
207 static constexpr auto soon_time = 10s;
208 static constexpr auto flush_time = 30s;
209 int *keyArray;
210 QBasicTimer timer;
211 int ps;
212 int keyArraySize;
213 int freeKey;
214 QHash<QString, QPixmapCache::Key> cacheKeys;
215 bool t;
216};
217
218QT_BEGIN_INCLUDE_NAMESPACE
219#include "qpixmapcache.moc"
220QT_END_INCLUDE_NAMESPACE
221
222/*!
223 \fn size_t QPixmapCache::Key::qHash(const Key &key, size_t seed)
224 \since 6.6
225 \qhash{QPixmapCache::Key}
226*/
227size_t QPixmapCache::Key::hash(size_t seed) const noexcept
228{
229 return qHash(this->d ? this->d->key : 0, seed);
230}
231
232QPMCache::QPMCache()
233 : QObject(nullptr),
234 QCache<QPixmapCache::Key, QPixmapCacheEntry>(cache_limit_default),
235 keyArray(nullptr), ps(0), keyArraySize(0), freeKey(0), t(false)
236{
237}
239{
240 clear();
241 free(keyArray);
242}
243
244/*
245 This is supposed to cut the cache size down by about 25% in a
246 minute once the application becomes idle, to let any inserted pixmap
247 remain in the cache for some time before it becomes a candidate for
248 cleaning-up, and to not cut down the size of the cache while the
249 cache is in active use.
250
251 When the last detached pixmap has been deleted from the cache, kill the
252 timer so Qt won't keep the CPU from going into sleep mode. Currently
253 the timer is not restarted when the pixmap becomes unused, but it does
254 restart once something else is added (i.e. the cache space is actually needed).
255
256 Returns \c true if any were removed.
257*/
259{
260 auto mc = maxCost();
261 const qsizetype currentTotal = totalCost();
262 const qsizetype oldSize = size();
263 if (currentTotal)
264 setMaxCost(nt ? currentTotal * 3 / 4 : currentTotal - 1);
265 setMaxCost(mc);
266 ps = totalCost();
267 return size() != oldSize;
268}
269
270void QPMCache::timerEvent(QTimerEvent *)
271{
272 bool nt = totalCost() == ps;
273 if (!flushDetachedPixmaps(nt)) {
274 timer.stop();
275 } else if (nt != t) {
276 timer.start(nt ? soon_time : flush_time, this);
277 t = nt;
278 }
279}
280
281
282QPixmap *QPMCache::object(const QString &key) const
283{
284 if (const auto it = cacheKeys.find(key); it != cacheKeys.cend())
285 return object(it.value());
286 return nullptr;
287}
288
289QPixmap *QPMCache::object(const QPixmapCache::Key &key) const
290{
291 Q_ASSERT(key.isValid());
292 QPixmap *ptr = QCache<QPixmapCache::Key, QPixmapCacheEntry>::object(key);
293 //We didn't find the pixmap in the cache, the key is not valid anymore
294 if (!ptr)
295 const_cast<QPMCache *>(this)->releaseKey(key);
296 return ptr;
297}
298
299bool QPMCache::insert(const QString& key, const QPixmap &pixmap, int cost)
300{
301 //If for the same key we add already a pixmap we should delete it
302 remove(key);
303
304 // this will create a new key; the old one has been removed
305 auto k = insert(pixmap, cost);
306 if (k.isValid()) {
307 k.d->stringKey = key;
308 cacheKeys[key] = std::move(k);
309 return true;
310 }
311 return false;
312}
313
314QPixmapCache::Key QPMCache::insert(const QPixmap &pixmap, int cost)
315{
316 QPixmapCache::Key cacheKey = createKey(); // invalidated by ~QPixmapCacheEntry on failed insert
317 bool success = QCache<QPixmapCache::Key, QPixmapCacheEntry>::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost);
318 Q_ASSERT(success || !cacheKey.isValid());
319 if (success) {
320 if (!timer.isActive()) {
321 timer.start(flush_time, this);
322 t = false;
323 }
324 }
325 return cacheKey;
326}
327
328bool QPMCache::remove(const QString &key)
329{
330 const auto cacheKey = cacheKeys.take(key);
331 return cacheKey.isValid() && remove(cacheKey);
332}
333
334bool QPMCache::remove(const QPixmapCache::Key &key)
335{
336 return QCache<QPixmapCache::Key, QPixmapCacheEntry>::remove(key);
337}
338
340{
341 if (size <= keyArraySize || size == 0)
342 return;
343 keyArray = q_check_ptr(static_cast<int *>(realloc(keyArray,
344 size * sizeof(int))));
345 for (int i = keyArraySize; i != size; ++i)
346 keyArray[i] = i + 1;
347 keyArraySize = size;
348}
349
351{
352 if (freeKey == keyArraySize)
353 resizeKeyArray(keyArraySize ? keyArraySize << 1 : 2);
354 int id = freeKey;
355 freeKey = keyArray[id];
356 QPixmapCache::Key key;
357 QPixmapCache::KeyData *d = QPMCache::getKeyData(&key);
358 d->key = ++id;
359 return key;
360}
361
362void QPMCache::releaseKey(const QPixmapCache::Key &key)
363{
364 QPixmapCache::KeyData *keyData = key.d;
365 if (!keyData)
366 return;
367 if (!keyData->stringKey.isNull())
368 cacheKeys.remove(keyData->stringKey);
369 if (keyData->key > keyArraySize || keyData->key <= 0)
370 return;
371 keyData->key--;
372 keyArray[keyData->key] = freeKey;
373 freeKey = keyData->key;
374 keyData->isValid = false;
375 keyData->key = 0;
376}
377
379{
380 free(keyArray);
381 keyArray = nullptr;
382 freeKey = 0;
383 keyArraySize = 0;
384 //Mark all keys as invalid
385 const QList<QPixmapCache::Key> keys = QCache<QPixmapCache::Key, QPixmapCacheEntry>::keys();
386 for (const auto &key : keys) {
387 if (key.d)
388 key.d->isValid = false;
389 }
390 QCache<QPixmapCache::Key, QPixmapCacheEntry>::clear();
391 // Nothing left to flush; stop the timer
392 timer.stop();
393}
394
395QPixmapCache::KeyData* QPMCache::getKeyData(QPixmapCache::Key *key)
396{
397 if (!key->d)
398 key->d = new QPixmapCache::KeyData;
399 return key->d;
400}
401
402Q_GLOBAL_STATIC(QPMCache, pm_cache)
403
404int Q_AUTOTEST_EXPORT q_QPixmapCache_keyHashSize()
405{
406 return pm_cache()->size();
407}
408
410{
411 pm_cache()->releaseKey(key);
412}
413
414/*!
415 Looks for a cached pixmap associated with the given \a key in the cache.
416 If the pixmap is found, the function sets \a pixmap to that pixmap and
417 returns \c true; otherwise it leaves \a pixmap alone and returns \c false.
418
419 Example:
420 \snippet code/src_gui_image_qpixmapcache.cpp 1
421*/
422
423bool QPixmapCache::find(const QString &key, QPixmap *pixmap)
424{
425 if (key.isEmpty() || !qt_pixmapcache_thread_test())
426 return false;
427 QPixmap *ptr = pm_cache()->object(key);
428 if (ptr && pixmap)
429 *pixmap = *ptr;
430 return ptr != nullptr;
431}
432
433/*!
434 Looks for a cached pixmap associated with the given \a key in the cache.
435 If the pixmap is found, the function sets \a pixmap to that pixmap and
436 returns \c true; otherwise it leaves \a pixmap alone and returns \c false. If
437 the pixmap is not found, it means that the \a key is no longer valid,
438 so it will be released for the next insertion.
439*/
440bool QPixmapCache::find(const Key &key, QPixmap *pixmap)
441{
443 return false;
444 //The key is not valid anymore, a flush happened before probably
445 if (!key.d || !key.d->isValid)
446 return false;
447 QPixmap *ptr = pm_cache()->object(key);
448 if (ptr && pixmap)
449 *pixmap = *ptr;
450 return ptr != nullptr;
451}
452
453/*!
454 Inserts a copy of the pixmap \a pixmap associated with the \a key into
455 the cache.
456
457 All pixmaps inserted by the Qt library have a key starting with
458 "$qt", so your own pixmap keys should never begin "$qt".
459
460 When a pixmap is inserted and the cache is about to exceed its
461 limit, it removes pixmaps until there is enough room for the
462 pixmap to be inserted.
463
464 The oldest pixmaps (least recently accessed in the cache) are
465 deleted when more space is needed.
466
467 The function returns \c true if the object was inserted into the
468 cache; otherwise it returns \c false.
469
470 \sa setCacheLimit()
471*/
472
473bool QPixmapCache::insert(const QString &key, const QPixmap &pixmap)
474{
475 if (key.isEmpty() || !qt_pixmapcache_thread_test())
476 return false;
477 return pm_cache()->insert(key, pixmap, cost(pixmap));
478}
479
480/*!
481 Inserts a copy of the given \a pixmap into the cache and returns a key
482 that can be used to retrieve it.
483
484 When a pixmap is inserted and the cache is about to exceed its
485 limit, it removes pixmaps until there is enough room for the
486 pixmap to be inserted.
487
488 The oldest pixmaps (least recently accessed in the cache) are
489 deleted when more space is needed.
490
491 \sa setCacheLimit()
492*/
493QPixmapCache::Key QPixmapCache::insert(const QPixmap &pixmap)
494{
495 if (!qt_pixmapcache_thread_test())
496 return QPixmapCache::Key();
497 return pm_cache()->insert(pixmap, cost(pixmap));
498}
499
500#if QT_DEPRECATED_SINCE(6, 6)
501/*!
502 \fn bool QPixmapCache::replace(const Key &key, const QPixmap &pixmap)
503
504 \deprecated [6.6] Use \c{remove(key); key = insert(pixmap);} instead.
505
506 Replaces the pixmap associated with the given \a key with the \a pixmap
507 specified. Returns \c true if the \a pixmap has been correctly inserted into
508 the cache; otherwise returns \c false.
509
510 The passed \a key is updated to reference \a pixmap now. Other copies of \a
511 key, if any, still refer to the old pixmap, which is, however, removed from
512 the cache by this function.
513
514 \sa setCacheLimit(), insert()
515*/
516#endif // QT_DEPRECATED_SINCE(6, 6)
517
518/*!
519 Returns the cache limit (in kilobytes).
520
521 The default cache limit is 10240 KB.
522
523 \sa setCacheLimit()
524*/
525
526int QPixmapCache::cacheLimit()
527{
529 return 0;
530 return pm_cache()->maxCost();
531}
532
533/*!
534 Sets the cache limit to \a n kilobytes.
535
536 The default setting is 10240 KB.
537
538 \sa cacheLimit()
539*/
540
541void QPixmapCache::setCacheLimit(int n)
542{
544 return;
545 pm_cache()->setMaxCost(n);
546}
547
548/*!
549 Removes the pixmap associated with \a key from the cache.
550*/
551void QPixmapCache::remove(const QString &key)
552{
553 if (key.isEmpty() || !qt_pixmapcache_thread_test())
554 return;
555 pm_cache()->remove(key);
556}
557
558/*!
559 Removes the pixmap associated with \a key from the cache and releases
560 the key for a future insertion.
561*/
562void QPixmapCache::remove(const Key &key)
563{
565 return;
566 //The key is not valid anymore, a flush happened before probably
567 if (!key.d || !key.d->isValid)
568 return;
569 pm_cache()->remove(key);
570}
571
572/*!
573 Removes all pixmaps from the cache.
574*/
575
576void QPixmapCache::clear()
577{
578 if (!QCoreApplication::closingDown() && !qt_pixmapcache_thread_test())
579 return;
580 QT_TRY {
581 if (pm_cache.exists())
582 pm_cache->clear();
583 } QT_CATCH(const std::bad_alloc &) {
584 // if we ran out of memory during pm_cache(), it's no leak,
585 // so just ignore it.
586 }
587}
588
590{
592 return;
593 pm_cache()->flushDetachedPixmaps(true);
594}
595
597{
599 return 0;
600 return (pm_cache()->totalCost()+1023) / 1024;
601}
602
603/*!
604 \fn QPixmapCache::KeyData::KeyData()
605
606 \internal
607*/
608/*!
609 \fn QPixmapCache::KeyData::KeyData(const KeyData &other)
610 \internal
611*/
612/*!
613 \fn QPixmapCache::KeyData::~KeyData()
614
615 \internal
616*/
617QT_END_NAMESPACE
bool remove(const QString &key)
bool insert(const QString &key, const QPixmap &pixmap, int cost)
void releaseKey(const QPixmapCache::Key &key)
static QPixmapCache::KeyData * getKeyData(QPixmapCache::Key *key)
bool flushDetachedPixmaps(bool nt)
QPixmap * object(const QString &key) const
static QPixmapCache::KeyData * get(const QPixmapCache::Key &key)
void resizeKeyArray(int size)
QPixmapCache::Key createKey()
QPixmapCache::Key insert(const QPixmap &pixmap, int cost)
void timerEvent(QTimerEvent *) override
This event handler can be reimplemented in a subclass to receive timer events for the object.
static const int cache_limit_default
static bool qt_pixmapcache_thread_test()
static qsizetype cost(const QPixmap &pixmap)
Q_AUTOTEST_EXPORT int qt_qpixmapcache_qpixmapcache_total_used()
Q_AUTOTEST_EXPORT void qt_qpixmapcache_flush_detached_pixmaps()