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

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