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(QThread::isMainThread()))
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 \since 5.6
148 \memberswap{key}
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 \qhash{QPixmapCache::Key}
224*/
225size_t QPixmapCache::Key::hash(size_t seed) const noexcept
226{
227 return qHash(key: this->d ? this->d->key : 0, seed);
228}
229
230QPMCache::QPMCache()
231 : QObject(nullptr),
232 QCache<QPixmapCache::Key, QPixmapCacheEntry>(cache_limit_default),
233 keyArray(nullptr), theid(0), ps(0), keyArraySize(0), freeKey(0), t(false)
234{
235}
236QPMCache::~QPMCache()
237{
238 clear();
239 free(ptr: keyArray);
240}
241
242/*
243 This is supposed to cut the cache size down by about 25% in a
244 minute once the application becomes idle, to let any inserted pixmap
245 remain in the cache for some time before it becomes a candidate for
246 cleaning-up, and to not cut down the size of the cache while the
247 cache is in active use.
248
249 When the last detached pixmap has been deleted from the cache, kill the
250 timer so Qt won't keep the CPU from going into sleep mode. Currently
251 the timer is not restarted when the pixmap becomes unused, but it does
252 restart once something else is added (i.e. the cache space is actually needed).
253
254 Returns \c true if any were removed.
255*/
256bool QPMCache::flushDetachedPixmaps(bool nt)
257{
258 auto mc = maxCost();
259 const qsizetype currentTotal = totalCost();
260 const qsizetype oldSize = size();
261 if (currentTotal)
262 setMaxCost(nt ? currentTotal * 3 / 4 : currentTotal - 1);
263 setMaxCost(mc);
264 ps = totalCost();
265 return size() != oldSize;
266}
267
268void QPMCache::timerEvent(QTimerEvent *)
269{
270 bool nt = totalCost() == ps;
271 if (!flushDetachedPixmaps(nt)) {
272 killTimer(id: theid);
273 theid = 0;
274 } else if (nt != t) {
275 killTimer(id: theid);
276 theid = startTimer(time: nt ? soon_time : flush_time);
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(key: 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(key: cacheKey, object: new QPixmapCacheEntry(cacheKey, pixmap), cost);
318 Q_ASSERT(success || !cacheKey.isValid());
319 if (success) {
320 if (!theid) {
321 theid = startTimer(time: flush_time);
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(key: cacheKey);
332}
333
334bool QPMCache::remove(const QPixmapCache::Key &key)
335{
336 return QCache<QPixmapCache::Key, QPixmapCacheEntry>::remove(key);
337}
338
339void QPMCache::resizeKeyArray(int size)
340{
341 if (size <= keyArraySize || size == 0)
342 return;
343 keyArray = q_check_ptr(p: static_cast<int *>(realloc(ptr: keyArray,
344 size: size * sizeof(int))));
345 for (int i = keyArraySize; i != size; ++i)
346 keyArray[i] = i + 1;
347 keyArraySize = size;
348}
349
350QPixmapCache::Key QPMCache::createKey()
351{
352 if (freeKey == keyArraySize)
353 resizeKeyArray(size: keyArraySize ? keyArraySize << 1 : 2);
354 int id = freeKey;
355 freeKey = keyArray[id];
356 QPixmapCache::Key key;
357 QPixmapCache::KeyData *d = QPMCache::getKeyData(key: &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(key: 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
378void QPMCache::clear()
379{
380 free(ptr: 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 if (theid) {
393 killTimer(id: theid);
394 theid = 0;
395 }
396}
397
398QPixmapCache::KeyData* QPMCache::getKeyData(QPixmapCache::Key *key)
399{
400 if (!key->d)
401 key->d = new QPixmapCache::KeyData;
402 return key->d;
403}
404
405Q_GLOBAL_STATIC(QPMCache, pm_cache)
406
407int Q_AUTOTEST_EXPORT q_QPixmapCache_keyHashSize()
408{
409 return pm_cache()->size();
410}
411
412QPixmapCacheEntry::~QPixmapCacheEntry()
413{
414 pm_cache()->releaseKey(key);
415}
416
417/*!
418 Looks for a cached pixmap associated with the given \a key in the cache.
419 If the pixmap is found, the function sets \a pixmap to that pixmap and
420 returns \c true; otherwise it leaves \a pixmap alone and returns \c false.
421
422 Example:
423 \snippet code/src_gui_image_qpixmapcache.cpp 1
424*/
425
426bool QPixmapCache::find(const QString &key, QPixmap *pixmap)
427{
428 if (key.isEmpty() || !qt_pixmapcache_thread_test())
429 return false;
430 QPixmap *ptr = pm_cache()->object(key);
431 if (ptr && pixmap)
432 *pixmap = *ptr;
433 return ptr != nullptr;
434}
435
436/*!
437 Looks for a cached pixmap associated with the given \a key in the cache.
438 If the pixmap is found, the function sets \a pixmap to that pixmap and
439 returns \c true; otherwise it leaves \a pixmap alone and returns \c false. If
440 the pixmap is not found, it means that the \a key is no longer valid,
441 so it will be released for the next insertion.
442*/
443bool QPixmapCache::find(const Key &key, QPixmap *pixmap)
444{
445 if (!qt_pixmapcache_thread_test())
446 return false;
447 //The key is not valid anymore, a flush happened before probably
448 if (!key.d || !key.d->isValid)
449 return false;
450 QPixmap *ptr = pm_cache()->object(key);
451 if (ptr && pixmap)
452 *pixmap = *ptr;
453 return ptr != nullptr;
454}
455
456/*!
457 Inserts a copy of the pixmap \a pixmap associated with the \a key into
458 the cache.
459
460 All pixmaps inserted by the Qt library have a key starting with
461 "$qt", so your own pixmap keys should never begin "$qt".
462
463 When a pixmap is inserted and the cache is about to exceed its
464 limit, it removes pixmaps until there is enough room for the
465 pixmap to be inserted.
466
467 The oldest pixmaps (least recently accessed in the cache) are
468 deleted when more space is needed.
469
470 The function returns \c true if the object was inserted into the
471 cache; otherwise it returns \c false.
472
473 \sa setCacheLimit()
474*/
475
476bool QPixmapCache::insert(const QString &key, const QPixmap &pixmap)
477{
478 if (key.isEmpty() || !qt_pixmapcache_thread_test())
479 return false;
480 return pm_cache()->insert(key, pixmap, cost: cost(pixmap));
481}
482
483/*!
484 Inserts a copy of the given \a pixmap into the cache and returns a key
485 that can be used to retrieve it.
486
487 When a pixmap is inserted and the cache is about to exceed its
488 limit, it removes pixmaps until there is enough room for the
489 pixmap to be inserted.
490
491 The oldest pixmaps (least recently accessed in the cache) are
492 deleted when more space is needed.
493
494 \sa setCacheLimit(), replace()
495*/
496QPixmapCache::Key QPixmapCache::insert(const QPixmap &pixmap)
497{
498 if (!qt_pixmapcache_thread_test())
499 return QPixmapCache::Key();
500 return pm_cache()->insert(pixmap, cost: cost(pixmap));
501}
502
503#if QT_DEPRECATED_SINCE(6, 6)
504/*!
505 \fn bool QPixmapCache::replace(const Key &key, const QPixmap &pixmap)
506
507 \deprecated [6.6] Use \c{remove(key); key = insert(pixmap);} instead.
508
509 Replaces the pixmap associated with the given \a key with the \a pixmap
510 specified. Returns \c true if the \a pixmap has been correctly inserted into
511 the cache; otherwise returns \c false.
512
513 The passed \a key is updated to reference \a pixmap now. Other copies of \a
514 key, if any, still refer to the old pixmap, which is, however, removed from
515 the cache by this function.
516
517 \sa setCacheLimit(), insert()
518*/
519#endif // QT_DEPRECATED_SINCE(6, 6)
520
521/*!
522 Returns the cache limit (in kilobytes).
523
524 The default cache limit is 10240 KB.
525
526 \sa setCacheLimit()
527*/
528
529int QPixmapCache::cacheLimit()
530{
531 if (!qt_pixmapcache_thread_test())
532 return 0;
533 return pm_cache()->maxCost();
534}
535
536/*!
537 Sets the cache limit to \a n kilobytes.
538
539 The default setting is 10240 KB.
540
541 \sa cacheLimit()
542*/
543
544void QPixmapCache::setCacheLimit(int n)
545{
546 if (!qt_pixmapcache_thread_test())
547 return;
548 pm_cache()->setMaxCost(n);
549}
550
551/*!
552 Removes the pixmap associated with \a key from the cache.
553*/
554void QPixmapCache::remove(const QString &key)
555{
556 if (key.isEmpty() || !qt_pixmapcache_thread_test())
557 return;
558 pm_cache()->remove(key);
559}
560
561/*!
562 Removes the pixmap associated with \a key from the cache and releases
563 the key for a future insertion.
564*/
565void QPixmapCache::remove(const Key &key)
566{
567 if (!qt_pixmapcache_thread_test())
568 return;
569 //The key is not valid anymore, a flush happened before probably
570 if (!key.d || !key.d->isValid)
571 return;
572 pm_cache()->remove(key);
573}
574
575/*!
576 Removes all pixmaps from the cache.
577*/
578
579void QPixmapCache::clear()
580{
581 if (!QCoreApplication::closingDown() && !qt_pixmapcache_thread_test())
582 return;
583 QT_TRY {
584 if (pm_cache.exists())
585 pm_cache->clear();
586 } QT_CATCH(const std::bad_alloc &) {
587 // if we ran out of memory during pm_cache(), it's no leak,
588 // so just ignore it.
589 }
590}
591
592Q_AUTOTEST_EXPORT void qt_qpixmapcache_flush_detached_pixmaps() // for tst_qpixmapcache
593{
594 if (!qt_pixmapcache_thread_test())
595 return;
596 pm_cache()->flushDetachedPixmaps(nt: true);
597}
598
599Q_AUTOTEST_EXPORT int qt_qpixmapcache_qpixmapcache_total_used() // for tst_qpixmapcache
600{
601 if (!qt_pixmapcache_thread_test())
602 return 0;
603 return (pm_cache()->totalCost()+1023) / 1024;
604}
605
606/*!
607 \fn QPixmapCache::KeyData::KeyData()
608
609 \internal
610*/
611/*!
612 \fn QPixmapCache::KeyData::KeyData(const KeyData &other)
613 \internal
614*/
615/*!
616 \fn QPixmapCache::KeyData::~KeyData()
617
618 \internal
619*/
620QT_END_NAMESPACE
621

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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