source: trunk/src/gui/image/qpixmapcache.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

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