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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtbase/src/gui/image/qpixmapcache.cpp