Coverage Report

Created: 2026-10-14 08:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/gui/image/qpixmapcache.cpp
Line
Count
Source
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"
9
#include "qpixmapcache_p.h"
10
#include "qthread.h"
11
#include "qcoreapplication.h"
12
13
using namespace std::chrono_literals;
14
15
QT_BEGIN_NAMESPACE
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
61
static const int cache_limit_default = 10240; // 10 MB cache limit
62
63
static inline qsizetype cost(const QPixmap &pixmap)
64
0
{
65
    // make sure to do a 64bit calculation; qsizetype might be smaller
66
0
    const qint64 costKb = static_cast<qint64>(pixmap.width())
67
0
                        * pixmap.height() * pixmap.depth() / (8 * 1024);
68
0
    const qint64 costMax = std::numeric_limits<qsizetype>::max();
69
    // a small pixmap should have at least a cost of 1(kb)
70
0
    return static_cast<qsizetype>(qBound(1LL, costKb, costMax));
71
0
}
72
73
static inline bool qt_pixmapcache_thread_test()
74
0
{
75
0
    if (Q_LIKELY(QThread::isMainThread()))
76
0
        return true;
77
78
0
    return false;
79
0
}
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
*/
95
0
QPixmapCache::Key::Key() : d(nullptr)
96
0
{
97
0
}
98
99
/*!
100
   \internal
101
    Constructs a copy of \a other.
102
*/
103
QPixmapCache::Key::Key(const Key &other)
104
0
{
105
0
    if (other.d)
106
0
        ++(other.d->ref);
107
0
    d = other.d;
108
0
}
109
110
/*!
111
    Destroys the key.
112
*/
113
QPixmapCache::Key::~Key()
114
0
{
115
0
    if (d && --(d->ref) == 0)
116
0
        delete d;
117
0
}
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
*/
125
bool QPixmapCache::Key::operator ==(const Key &key) const
126
0
{
127
0
    return (d == key.d);
128
0
}
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
*/
158
bool QPixmapCache::Key::isValid() const noexcept
159
0
{
160
0
    return d && d->isValid;
161
0
}
162
163
/*!
164
    \internal
165
*/
166
QPixmapCache::Key &QPixmapCache::Key::operator =(const Key &other)
167
0
{
168
0
    if (d != other.d) {
169
0
        if (other.d)
170
0
            ++(other.d->ref);
171
0
        if (d && --(d->ref) == 0)
172
0
            delete d;
173
0
        d = other.d;
174
0
    }
175
0
    return *this;
176
0
}
177
178
class QPMCache : public QObject, public QCache<QPixmapCache::Key, QPixmapCacheEntry>
179
{
180
0
    Q_OBJECT
181
0
public:
182
0
    QPMCache();
183
0
    ~QPMCache();
184
0
185
0
    void timerEvent(QTimerEvent *) override;
186
0
    bool insert(const QString& key, const QPixmap &pixmap, int cost);
187
0
    QPixmapCache::Key insert(const QPixmap &pixmap, int cost);
188
0
    bool remove(const QString &key);
189
0
    bool remove(const QPixmapCache::Key &key);
190
0
191
0
    void resizeKeyArray(int size);
192
0
    QPixmapCache::Key createKey();
193
0
    void releaseKey(const QPixmapCache::Key &key);
194
0
    void clear();
195
0
196
0
    QPixmap *object(const QString &key) const;
197
0
    QPixmap *object(const QPixmapCache::Key &key) const;
198
0
199
0
    static inline QPixmapCache::KeyData *get(const QPixmapCache::Key &key)
200
0
    {return key.d;}
201
202
    static QPixmapCache::KeyData* getKeyData(QPixmapCache::Key *key);
203
204
    bool flushDetachedPixmaps(bool nt);
205
206
private:
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
218
QT_BEGIN_INCLUDE_NAMESPACE
219
#include "qpixmapcache.moc"
220
QT_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
*/
227
size_t QPixmapCache::Key::hash(size_t seed) const noexcept
228
0
{
229
0
    return qHash(this->d ? this->d->key : 0, seed);
230
0
}
231
232
QPMCache::QPMCache()
233
0
    : QObject(nullptr),
234
0
      QCache<QPixmapCache::Key, QPixmapCacheEntry>(cache_limit_default),
235
0
      keyArray(nullptr), ps(0), keyArraySize(0), freeKey(0), t(false)
236
0
{
237
0
}
238
QPMCache::~QPMCache()
239
0
{
240
0
    clear();
241
0
    free(keyArray);
242
0
}
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
*/
258
bool QPMCache::flushDetachedPixmaps(bool nt)
259
0
{
260
0
    auto mc = maxCost();
261
0
    const qsizetype currentTotal = totalCost();
262
0
    const qsizetype oldSize = size();
263
0
    if (currentTotal)
264
0
        setMaxCost(nt ? currentTotal * 3 / 4 : currentTotal - 1);
265
0
    setMaxCost(mc);
266
0
    ps = totalCost();
267
0
    return size() != oldSize;
268
0
}
269
270
void QPMCache::timerEvent(QTimerEvent *)
271
0
{
272
0
    bool nt = totalCost() == ps;
273
0
    if (!flushDetachedPixmaps(nt)) {
274
0
        timer.stop();
275
0
    } else if (nt != t) {
276
0
        timer.start(nt ? soon_time : flush_time, this);
277
0
        t = nt;
278
0
    }
279
0
}
280
281
282
QPixmap *QPMCache::object(const QString &key) const
283
0
{
284
0
    if (const auto it = cacheKeys.find(key); it != cacheKeys.cend())
285
0
        return object(it.value());
286
0
    return nullptr;
287
0
}
288
289
QPixmap *QPMCache::object(const QPixmapCache::Key &key) const
290
0
{
291
0
    Q_ASSERT(key.isValid());
292
0
    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
0
    if (!ptr)
295
0
        const_cast<QPMCache *>(this)->releaseKey(key);
296
0
    return ptr;
297
0
}
298
299
bool QPMCache::insert(const QString& key, const QPixmap &pixmap, int cost)
300
0
{
301
    //If for the same key we add already a pixmap we should delete it
302
0
    remove(key);
303
304
    // this will create a new key; the old one has been removed
305
0
    auto k = insert(pixmap, cost);
306
0
    if (k.isValid()) {
307
0
        k.d->stringKey = key;
308
0
        cacheKeys[key] = std::move(k);
309
0
        return true;
310
0
    }
311
0
    return false;
312
0
}
313
314
QPixmapCache::Key QPMCache::insert(const QPixmap &pixmap, int cost)
315
0
{
316
0
    QPixmapCache::Key cacheKey = createKey(); // invalidated by ~QPixmapCacheEntry on failed insert
317
0
    bool success = QCache<QPixmapCache::Key, QPixmapCacheEntry>::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost);
318
0
    Q_ASSERT(success || !cacheKey.isValid());
319
0
    if (success) {
320
0
        if (!timer.isActive()) {
321
0
            timer.start(flush_time, this);
322
0
            t = false;
323
0
        }
324
0
    }
325
0
    return cacheKey;
326
0
}
327
328
bool QPMCache::remove(const QString &key)
329
0
{
330
0
    const auto cacheKey = cacheKeys.take(key);
331
0
    return cacheKey.isValid() && remove(cacheKey);
332
0
}
333
334
bool QPMCache::remove(const QPixmapCache::Key &key)
335
0
{
336
0
    return QCache<QPixmapCache::Key, QPixmapCacheEntry>::remove(key);
337
0
}
338
339
void QPMCache::resizeKeyArray(int size)
340
0
{
341
0
    if (size <= keyArraySize || size == 0)
342
0
        return;
343
0
    keyArray = q_check_ptr(static_cast<int *>(realloc(keyArray,
344
0
                    size * sizeof(int))));
345
0
    for (int i = keyArraySize; i != size; ++i)
346
0
        keyArray[i] = i + 1;
347
0
    keyArraySize = size;
348
0
}
349
350
QPixmapCache::Key QPMCache::createKey()
351
0
{
352
0
    if (freeKey == keyArraySize)
353
0
        resizeKeyArray(keyArraySize ? keyArraySize << 1 : 2);
354
0
    int id = freeKey;
355
0
    freeKey = keyArray[id];
356
0
    QPixmapCache::Key key;
357
0
    QPixmapCache::KeyData *d = QPMCache::getKeyData(&key);
358
0
    d->key = ++id;
359
0
    return key;
360
0
}
361
362
void QPMCache::releaseKey(const QPixmapCache::Key &key)
363
0
{
364
0
    QPixmapCache::KeyData *keyData = key.d;
365
0
    if (!keyData)
366
0
        return;
367
0
    if (!keyData->stringKey.isNull())
368
0
        cacheKeys.remove(keyData->stringKey);
369
0
    if (keyData->key > keyArraySize || keyData->key <= 0)
370
0
        return;
371
0
    keyData->key--;
372
0
    keyArray[keyData->key] = freeKey;
373
0
    freeKey = keyData->key;
374
0
    keyData->isValid = false;
375
0
    keyData->key = 0;
376
0
}
377
378
void QPMCache::clear()
379
0
{
380
0
    free(keyArray);
381
0
    keyArray = nullptr;
382
0
    freeKey = 0;
383
0
    keyArraySize = 0;
384
    //Mark all keys as invalid
385
0
    const QList<QPixmapCache::Key> keys = QCache<QPixmapCache::Key, QPixmapCacheEntry>::keys();
386
0
    for (const auto &key : keys) {
387
0
        if (key.d)
388
0
            key.d->isValid = false;
389
0
    }
390
0
    QCache<QPixmapCache::Key, QPixmapCacheEntry>::clear();
391
    // Nothing left to flush; stop the timer
392
0
    timer.stop();
393
0
}
394
395
QPixmapCache::KeyData* QPMCache::getKeyData(QPixmapCache::Key *key)
396
0
{
397
0
    if (!key->d)
398
0
        key->d = new QPixmapCache::KeyData;
399
0
    return key->d;
400
0
}
401
402
Q_GLOBAL_STATIC(QPMCache, pm_cache)
403
404
int Q_AUTOTEST_EXPORT q_QPixmapCache_keyHashSize()
405
0
{
406
0
    return pm_cache()->size();
407
0
}
408
409
QPixmapCacheEntry::~QPixmapCacheEntry()
410
0
{
411
0
    pm_cache()->releaseKey(key);
412
0
}
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
423
bool QPixmapCache::find(const QString &key, QPixmap *pixmap)
424
0
{
425
0
    if (key.isEmpty() || !qt_pixmapcache_thread_test())
426
0
        return false;
427
0
    QPixmap *ptr = pm_cache()->object(key);
428
0
    if (ptr && pixmap)
429
0
        *pixmap = *ptr;
430
0
    return ptr != nullptr;
431
0
}
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
*/
440
bool QPixmapCache::find(const Key &key, QPixmap *pixmap)
441
0
{
442
0
    if (!qt_pixmapcache_thread_test())
443
0
        return false;
444
    //The key is not valid anymore, a flush happened before probably
445
0
    if (!key.d || !key.d->isValid)
446
0
        return false;
447
0
    QPixmap *ptr = pm_cache()->object(key);
448
0
    if (ptr && pixmap)
449
0
        *pixmap = *ptr;
450
0
    return ptr != nullptr;
451
0
}
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
473
bool QPixmapCache::insert(const QString &key, const QPixmap &pixmap)
474
0
{
475
0
    if (key.isEmpty() || !qt_pixmapcache_thread_test())
476
0
        return false;
477
0
    return pm_cache()->insert(key, pixmap, cost(pixmap));
478
0
}
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
*/
493
QPixmapCache::Key QPixmapCache::insert(const QPixmap &pixmap)
494
0
{
495
0
    if (!qt_pixmapcache_thread_test())
496
0
        return QPixmapCache::Key();
497
0
    return pm_cache()->insert(pixmap, cost(pixmap));
498
0
}
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
526
int QPixmapCache::cacheLimit()
527
0
{
528
0
    if (!qt_pixmapcache_thread_test())
529
0
        return 0;
530
0
    return pm_cache()->maxCost();
531
0
}
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
541
void QPixmapCache::setCacheLimit(int n)
542
0
{
543
0
    if (!qt_pixmapcache_thread_test())
544
0
        return;
545
0
    pm_cache()->setMaxCost(n);
546
0
}
547
548
/*!
549
  Removes the pixmap associated with \a key from the cache.
550
*/
551
void QPixmapCache::remove(const QString &key)
552
0
{
553
0
    if (key.isEmpty() || !qt_pixmapcache_thread_test())
554
0
        return;
555
0
    pm_cache()->remove(key);
556
0
}
557
558
/*!
559
  Removes the pixmap associated with \a key from the cache and releases
560
  the key for a future insertion.
561
*/
562
void QPixmapCache::remove(const Key &key)
563
0
{
564
0
    if (!qt_pixmapcache_thread_test())
565
0
        return;
566
    //The key is not valid anymore, a flush happened before probably
567
0
    if (!key.d || !key.d->isValid)
568
0
        return;
569
0
    pm_cache()->remove(key);
570
0
}
571
572
/*!
573
    Removes all pixmaps from the cache.
574
*/
575
576
void QPixmapCache::clear()
577
0
{
578
0
    if (!QCoreApplication::closingDown() && !qt_pixmapcache_thread_test())
579
0
        return;
580
0
    QT_TRY {
581
0
        if (pm_cache.exists())
582
0
            pm_cache->clear();
583
0
    } 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
0
    }
587
0
}
588
589
Q_AUTOTEST_EXPORT void qt_qpixmapcache_flush_detached_pixmaps() // for tst_qpixmapcache
590
0
{
591
0
    if (!qt_pixmapcache_thread_test())
592
0
        return;
593
0
    pm_cache()->flushDetachedPixmaps(true);
594
0
}
595
596
Q_AUTOTEST_EXPORT int qt_qpixmapcache_qpixmapcache_total_used() // for tst_qpixmapcache
597
0
{
598
0
    if (!qt_pixmapcache_thread_test())
599
0
        return 0;
600
0
    return (pm_cache()->totalCost()+1023) / 1024;
601
0
}
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
*/
617
QT_END_NAMESPACE