1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qquickpixmapcache_p.h"
43 #include <qqmlnetworkaccessmanagerfactory.h>
44 #include <qquickimageprovider.h>
46 #include <qqmlengine.h>
47 #include <private/qqmlglobal_p.h>
48 #include <private/qqmlengine_p.h>
50 #include <QtQuick/private/qsgtexture_p.h>
51 #include <QtQuick/private/qsgcontext_p.h>
53 #include <QCoreApplication>
54 #include <QImageReader>
56 #include <QNetworkReply>
57 #include <QPixmapCache>
61 #include <QMutexLocker>
62 #include <QWaitCondition>
64 #include <QWaitCondition>
65 #include <QtCore/qdebug.h>
66 #include <private/qobject_p.h>
69 #define IMAGEREQUEST_MAX_REQUEST_COUNT 8
70 #define IMAGEREQUEST_MAX_REDIRECT_RECURSION 16
71 #define CACHE_EXPIRE_TIME 30
72 #define CACHE_REMOVAL_FRACTION 4
76 // The cache limit describes the maximum "junk" in the cache.
77 static int cache_limit = 2048 * 1024; // 2048 KB cache limit for embedded in qpixmapcache.cpp
79 static inline QString imageProviderId(const QUrl &url)
84 static inline QString imageId(const QUrl &url)
86 return url.toString(QUrl::RemoveScheme | QUrl::RemoveAuthority).mid(1);
89 QSGTexture *QQuickDefaultTextureFactory::createTexture(QQuickCanvas *) const
91 QSGPlainTexture *t = new QSGPlainTexture();
96 static QQuickTextureFactory *textureFactoryForImage(const QImage &image)
100 QQuickTextureFactory *texture = QSGContext::createTextureFactoryFromImage(image);
103 return new QQuickDefaultTextureFactory(image);
106 class QQuickPixmapReader;
107 class QQuickPixmapData;
108 class QQuickPixmapReply : public QObject
112 enum ReadError { NoError, Loading, Decoding };
114 QQuickPixmapReply(QQuickPixmapData *);
115 ~QQuickPixmapReply();
117 QQuickPixmapData *data;
118 QQmlEngine *engineForReader; // always access reader inside readerMutex
125 class Event : public QEvent {
127 Event(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory);
132 QQuickTextureFactory *textureFactory;
134 void postReply(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory);
139 void downloadProgress(qint64, qint64);
142 bool event(QEvent *event);
145 Q_DISABLE_COPY(QQuickPixmapReply)
148 static int finishedIndex;
149 static int downloadProgressIndex;
152 class QQuickPixmapReaderThreadObject : public QObject {
155 QQuickPixmapReaderThreadObject(QQuickPixmapReader *);
157 virtual bool event(QEvent *e);
159 void networkRequestDone();
161 QQuickPixmapReader *reader;
164 class QQuickPixmapData;
165 class QQuickPixmapReader : public QThread
169 QQuickPixmapReader(QQmlEngine *eng);
170 ~QQuickPixmapReader();
172 QQuickPixmapReply *getImage(QQuickPixmapData *);
173 void cancel(QQuickPixmapReply *rep);
175 static QQuickPixmapReader *instance(QQmlEngine *engine);
176 static QQuickPixmapReader *existingInstance(QQmlEngine *engine);
182 friend class QQuickPixmapReaderThreadObject;
184 void processJob(QQuickPixmapReply *, const QUrl &, const QSize &);
185 void networkRequestDone(QNetworkReply *);
187 QList<QQuickPixmapReply*> jobs;
188 QList<QQuickPixmapReply*> cancelled;
190 QObject *eventLoopQuitHack;
193 QQuickPixmapReaderThreadObject *threadObject;
194 QWaitCondition waitCondition;
196 QNetworkAccessManager *networkAccessManager();
197 QNetworkAccessManager *accessManager;
199 QHash<QNetworkReply*,QQuickPixmapReply*> replies;
201 static int replyDownloadProgress;
202 static int replyFinished;
203 static int downloadProgress;
204 static int threadNetworkRequestDone;
205 static QHash<QQmlEngine *,QQuickPixmapReader*> readers;
207 static QMutex readerMutex;
210 class QQuickPixmapData
213 QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QSize &s, const QString &e)
214 : refCount(1), inCache(false), pixmapStatus(QQuickPixmap::Error),
215 url(u), errorString(e), requestSize(s), textureFactory(0), reply(0), prevUnreferenced(0),
216 prevUnreferencedPtr(0), nextUnreferenced(0)
218 declarativePixmaps.insert(pixmap);
221 QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QSize &r)
222 : refCount(1), inCache(false), pixmapStatus(QQuickPixmap::Loading),
223 url(u), requestSize(r), textureFactory(0), reply(0), prevUnreferenced(0), prevUnreferencedPtr(0),
226 declarativePixmaps.insert(pixmap);
229 QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, QQuickTextureFactory *texture, const QSize &s, const QSize &r)
230 : refCount(1), inCache(false), privatePixmap(false), pixmapStatus(QQuickPixmap::Ready),
231 url(u), implicitSize(s), requestSize(r), textureFactory(texture), reply(0), prevUnreferenced(0),
232 prevUnreferencedPtr(0), nextUnreferenced(0)
234 declarativePixmaps.insert(pixmap);
237 QQuickPixmapData(QQuickPixmap *pixmap, QQuickTextureFactory *texture)
238 : refCount(1), inCache(false), privatePixmap(true), pixmapStatus(QQuickPixmap::Ready),
239 textureFactory(texture), reply(0), prevUnreferenced(0),
240 prevUnreferencedPtr(0), nextUnreferenced(0)
243 requestSize = implicitSize = texture->textureSize();
244 declarativePixmaps.insert(pixmap);
249 while (!declarativePixmaps.isEmpty()) {
250 QQuickPixmap *referencer = declarativePixmaps.first();
251 declarativePixmaps.remove(referencer);
254 delete textureFactory;
261 void removeFromCache();
266 bool privatePixmap:1;
268 QQuickPixmap::Status pixmapStatus;
274 QQuickTextureFactory *textureFactory;
276 QIntrusiveList<QQuickPixmap, &QQuickPixmap::dataListNode> declarativePixmaps;
277 QQuickPixmapReply *reply;
279 QQuickPixmapData *prevUnreferenced;
280 QQuickPixmapData**prevUnreferencedPtr;
281 QQuickPixmapData *nextUnreferenced;
284 int QQuickPixmapReply::finishedIndex = -1;
285 int QQuickPixmapReply::downloadProgressIndex = -1;
288 QHash<QQmlEngine *,QQuickPixmapReader*> QQuickPixmapReader::readers;
289 QMutex QQuickPixmapReader::readerMutex;
291 int QQuickPixmapReader::replyDownloadProgress = -1;
292 int QQuickPixmapReader::replyFinished = -1;
293 int QQuickPixmapReader::downloadProgress = -1;
294 int QQuickPixmapReader::threadNetworkRequestDone = -1;
297 void QQuickPixmapReply::postReply(ReadError error, const QString &errorString,
298 const QSize &implicitSize, QQuickTextureFactory *factory)
301 QCoreApplication::postEvent(this, new Event(error, errorString, implicitSize, factory));
304 QQuickPixmapReply::Event::Event(ReadError e, const QString &s, const QSize &iSize, QQuickTextureFactory *factory)
305 : QEvent(QEvent::User), error(e), errorString(s), implicitSize(iSize), textureFactory(factory)
309 QNetworkAccessManager *QQuickPixmapReader::networkAccessManager()
311 if (!accessManager) {
312 Q_ASSERT(threadObject);
313 accessManager = QQmlEnginePrivate::get(engine)->createNetworkAccessManager(threadObject);
315 return accessManager;
318 static bool readImage(const QUrl& url, QIODevice *dev, QImage *image, QString *errorString, QSize *impsize,
319 const QSize &requestSize)
321 QImageReader imgio(dev);
323 bool force_scale = false;
324 if (url.path().endsWith(QLatin1String(".svg"),Qt::CaseInsensitive)) {
325 imgio.setFormat("svg"); // QSvgPlugin::capabilities bug QTBUG-9053
329 if (requestSize.width() > 0 || requestSize.height() > 0) {
330 QSize s = imgio.size();
332 if (requestSize.width() && (force_scale || requestSize.width() < s.width())) {
333 ratio = qreal(requestSize.width())/s.width();
335 if (requestSize.height() && (force_scale || requestSize.height() < s.height())) {
336 qreal hr = qreal(requestSize.height())/s.height();
337 if (ratio == 0.0 || hr < ratio)
341 s.setHeight(qRound(s.height() * ratio));
342 s.setWidth(qRound(s.width() * ratio));
343 imgio.setScaledSize(s);
348 *impsize = imgio.size();
350 if (imgio.read(image)) {
351 if (impsize && impsize->width() < 0)
352 *impsize = image->size();
356 *errorString = QQuickPixmap::tr("Error decoding: %1: %2").arg(url.toString())
357 .arg(imgio.errorString());
362 QQuickPixmapReader::QQuickPixmapReader(QQmlEngine *eng)
363 : QThread(eng), engine(eng), threadObject(0), accessManager(0)
365 eventLoopQuitHack = new QObject;
366 eventLoopQuitHack->moveToThread(this);
367 connect(eventLoopQuitHack, SIGNAL(destroyed(QObject*)), SLOT(quit()), Qt::DirectConnection);
368 start(QThread::LowestPriority);
371 QQuickPixmapReader::~QQuickPixmapReader()
374 readers.remove(engine);
375 readerMutex.unlock();
378 // manually cancel all outstanding jobs.
379 foreach (QQuickPixmapReply *reply, jobs) {
383 QList<QQuickPixmapReply*> activeJobs = replies.values();
384 foreach (QQuickPixmapReply *reply, activeJobs) {
385 if (reply->loading) {
386 cancelled.append(reply);
390 if (threadObject) threadObject->processJobs();
393 eventLoopQuitHack->deleteLater();
397 void QQuickPixmapReader::networkRequestDone(QNetworkReply *reply)
399 QQuickPixmapReply *job = replies.take(reply);
402 job->redirectCount++;
403 if (job->redirectCount < IMAGEREQUEST_MAX_REDIRECT_RECURSION) {
404 QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
405 if (redirect.isValid()) {
406 QUrl url = reply->url().resolved(redirect.toUrl());
407 QNetworkRequest req(url);
408 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
410 reply->deleteLater();
411 reply = networkAccessManager()->get(req);
413 QMetaObject::connect(reply, replyDownloadProgress, job, downloadProgress);
414 QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
416 replies.insert(reply, job);
422 QQuickPixmapReply::ReadError error = QQuickPixmapReply::NoError;
425 if (reply->error()) {
426 error = QQuickPixmapReply::Loading;
427 errorString = reply->errorString();
429 QByteArray all = reply->readAll();
431 buff.open(QIODevice::ReadOnly);
432 if (!readImage(reply->url(), &buff, &image, &errorString, &readSize, job->requestSize))
433 error = QQuickPixmapReply::Decoding;
435 // send completion event to the QQuickPixmapReply
437 if (!cancelled.contains(job))
438 job->postReply(error, errorString, readSize, textureFactoryForImage(image));
441 reply->deleteLater();
443 // kick off event loop again incase we have dropped below max request count
444 threadObject->processJobs();
447 QQuickPixmapReaderThreadObject::QQuickPixmapReaderThreadObject(QQuickPixmapReader *i)
452 void QQuickPixmapReaderThreadObject::processJobs()
454 QCoreApplication::postEvent(this, new QEvent(QEvent::User));
457 bool QQuickPixmapReaderThreadObject::event(QEvent *e)
459 if (e->type() == QEvent::User) {
460 reader->processJobs();
463 return QObject::event(e);
467 void QQuickPixmapReaderThreadObject::networkRequestDone()
469 QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
470 reader->networkRequestDone(reply);
473 void QQuickPixmapReader::processJobs()
475 QMutexLocker locker(&mutex);
478 if (cancelled.isEmpty() && (jobs.isEmpty() || replies.count() >= IMAGEREQUEST_MAX_REQUEST_COUNT))
479 return; // Nothing else to do
481 // Clean cancelled jobs
482 if (cancelled.count()) {
483 for (int i = 0; i < cancelled.count(); ++i) {
484 QQuickPixmapReply *job = cancelled.at(i);
485 QNetworkReply *reply = replies.key(job, 0);
486 if (reply && reply->isRunning()) {
487 // cancel any jobs already started
488 replies.remove(reply);
491 // deleteLater, since not owned by this thread
497 if (!jobs.isEmpty() && replies.count() < IMAGEREQUEST_MAX_REQUEST_COUNT) {
498 QQuickPixmapReply *runningJob = jobs.takeLast();
499 runningJob->loading = true;
501 QUrl url = runningJob->url;
502 QSize requestSize = runningJob->requestSize;
504 processJob(runningJob, url, requestSize);
510 void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &url,
511 const QSize &requestSize)
514 if (url.scheme() == QLatin1String("image")) {
515 // Use QQuickImageProvider
518 QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
519 QQuickImageProvider *provider = static_cast<QQuickImageProvider *>(engine->imageProvider(imageProviderId(url)));
521 imageType = provider->imageType();
523 if (imageType == QQuickImageProvider::Invalid) {
524 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::Loading;
525 QString errorStr = QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString());
528 if (!cancelled.contains(runningJob))
529 runningJob->postReply(errorCode, errorStr, readSize, textureFactoryForImage(image));
531 } else if (imageType == QQuickImageProvider::Image) {
532 QImage image = provider->requestImage(imageId(url), &readSize, requestSize);
533 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
535 if (image.isNull()) {
536 errorCode = QQuickPixmapReply::Loading;
537 errorStr = QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString());
540 if (!cancelled.contains(runningJob))
541 runningJob->postReply(errorCode, errorStr, readSize, textureFactoryForImage(image));
544 QQuickTextureFactory *t = provider->requestTexture(imageId(url), &readSize, requestSize);
545 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
548 errorCode = QQuickPixmapReply::Loading;
549 errorStr = QQuickPixmap::tr("Failed to get texture from provider: %1").arg(url.toString());
552 if (!cancelled.contains(runningJob))
553 runningJob->postReply(errorCode, errorStr, readSize, t);
559 QString lf = QQmlEnginePrivate::urlToLocalFileOrQrc(url);
561 // Image is local - load/decode immediately
563 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
567 if (f.open(QIODevice::ReadOnly)) {
568 if (!readImage(url, &f, &image, &errorStr, &readSize, requestSize))
569 errorCode = QQuickPixmapReply::Loading;
571 errorStr = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
572 errorCode = QQuickPixmapReply::Loading;
575 if (!cancelled.contains(runningJob))
576 runningJob->postReply(errorCode, errorStr, readSize, textureFactoryForImage(image));
580 QNetworkRequest req(url);
581 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
582 QNetworkReply *reply = networkAccessManager()->get(req);
584 QMetaObject::connect(reply, replyDownloadProgress, runningJob, downloadProgress);
585 QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
587 replies.insert(reply, runningJob);
592 QQuickPixmapReader *QQuickPixmapReader::instance(QQmlEngine *engine)
594 // XXX NOTE: must be called within readerMutex locking.
595 QQuickPixmapReader *reader = readers.value(engine);
597 reader = new QQuickPixmapReader(engine);
598 readers.insert(engine, reader);
604 QQuickPixmapReader *QQuickPixmapReader::existingInstance(QQmlEngine *engine)
606 // XXX NOTE: must be called within readerMutex locking.
607 return readers.value(engine, 0);
610 QQuickPixmapReply *QQuickPixmapReader::getImage(QQuickPixmapData *data)
613 QQuickPixmapReply *reply = new QQuickPixmapReply(data);
614 reply->engineForReader = engine;
617 if (threadObject) threadObject->processJobs();
622 void QQuickPixmapReader::cancel(QQuickPixmapReply *reply)
625 if (reply->loading) {
626 cancelled.append(reply);
629 if (threadObject) threadObject->processJobs();
631 jobs.removeAll(reply);
637 void QQuickPixmapReader::run()
639 if (replyDownloadProgress == -1) {
640 const QMetaObject *nr = &QNetworkReply::staticMetaObject;
641 const QMetaObject *pr = &QQuickPixmapReply::staticMetaObject;
642 const QMetaObject *ir = &QQuickPixmapReaderThreadObject::staticMetaObject;
643 replyDownloadProgress = nr->indexOfSignal("downloadProgress(qint64,qint64)");
644 replyFinished = nr->indexOfSignal("finished()");
645 downloadProgress = pr->indexOfSignal("downloadProgress(qint64,qint64)");
646 threadNetworkRequestDone = ir->indexOfSlot("networkRequestDone()");
650 threadObject = new QQuickPixmapReaderThreadObject(this);
660 class QQuickPixmapKey
667 inline bool operator==(const QQuickPixmapKey &lhs, const QQuickPixmapKey &rhs)
669 return *lhs.size == *rhs.size && *lhs.url == *rhs.url;
672 inline uint qHash(const QQuickPixmapKey &key)
674 return qHash(*key.url) ^ key.size->width() ^ key.size->height();
679 class QQuickPixmapStore : public QObject
684 ~QQuickPixmapStore();
686 void unreferencePixmap(QQuickPixmapData *);
687 void referencePixmap(QQuickPixmapData *);
692 virtual void timerEvent(QTimerEvent *);
695 QHash<QQuickPixmapKey, QQuickPixmapData *> m_cache;
698 void shrinkCache(int remove);
700 QQuickPixmapData *m_unreferencedPixmaps;
701 QQuickPixmapData *m_lastUnreferencedPixmap;
703 int m_unreferencedCost;
707 Q_GLOBAL_STATIC(QQuickPixmapStore, pixmapStore);
710 QQuickPixmapStore::QQuickPixmapStore()
711 : m_unreferencedPixmaps(0), m_lastUnreferencedPixmap(0), m_unreferencedCost(0), m_timerId(-1), m_destroying(false)
715 QQuickPixmapStore::~QQuickPixmapStore()
719 int leakedPixmaps = 0;
720 QList<QQuickPixmapData*> cachedData = m_cache.values();
722 // Prevent unreferencePixmap() from assuming it needs to kick
723 // off the cache expiry timer, as we're shrinking the cache
724 // manually below after releasing all the pixmaps.
727 // unreference all (leaked) pixmaps
728 foreach (QQuickPixmapData* pixmap, cachedData) {
729 int currRefCount = pixmap->refCount;
732 while (currRefCount > 0) {
739 // free all unreferenced pixmaps
740 while (m_lastUnreferencedPixmap) {
745 qDebug("Number of leaked pixmaps: %i", leakedPixmaps);
748 void QQuickPixmapStore::unreferencePixmap(QQuickPixmapData *data)
750 Q_ASSERT(data->prevUnreferenced == 0);
751 Q_ASSERT(data->prevUnreferencedPtr == 0);
752 Q_ASSERT(data->nextUnreferenced == 0);
754 data->nextUnreferenced = m_unreferencedPixmaps;
755 data->prevUnreferencedPtr = &m_unreferencedPixmaps;
756 if (!m_destroying) // the texture factories may have been cleaned up already.
757 m_unreferencedCost += data->cost();
759 m_unreferencedPixmaps = data;
760 if (m_unreferencedPixmaps->nextUnreferenced) {
761 m_unreferencedPixmaps->nextUnreferenced->prevUnreferenced = m_unreferencedPixmaps;
762 m_unreferencedPixmaps->nextUnreferenced->prevUnreferencedPtr = &m_unreferencedPixmaps->nextUnreferenced;
765 if (!m_lastUnreferencedPixmap)
766 m_lastUnreferencedPixmap = data;
768 shrinkCache(-1); // Shrink the cache incase it has become larger than cache_limit
770 if (m_timerId == -1 && m_unreferencedPixmaps && !m_destroying)
771 m_timerId = startTimer(CACHE_EXPIRE_TIME * 1000);
774 void QQuickPixmapStore::referencePixmap(QQuickPixmapData *data)
776 Q_ASSERT(data->prevUnreferencedPtr);
778 *data->prevUnreferencedPtr = data->nextUnreferenced;
779 if (data->nextUnreferenced) {
780 data->nextUnreferenced->prevUnreferencedPtr = data->prevUnreferencedPtr;
781 data->nextUnreferenced->prevUnreferenced = data->prevUnreferenced;
783 if (m_lastUnreferencedPixmap == data)
784 m_lastUnreferencedPixmap = data->prevUnreferenced;
786 data->nextUnreferenced = 0;
787 data->prevUnreferencedPtr = 0;
788 data->prevUnreferenced = 0;
790 m_unreferencedCost -= data->cost();
793 void QQuickPixmapStore::shrinkCache(int remove)
795 while ((remove > 0 || m_unreferencedCost > cache_limit) && m_lastUnreferencedPixmap) {
796 QQuickPixmapData *data = m_lastUnreferencedPixmap;
797 Q_ASSERT(data->nextUnreferenced == 0);
799 *data->prevUnreferencedPtr = 0;
800 m_lastUnreferencedPixmap = data->prevUnreferenced;
801 data->prevUnreferencedPtr = 0;
802 data->prevUnreferenced = 0;
805 remove -= data->cost();
806 m_unreferencedCost -= data->cost();
808 data->removeFromCache();
813 void QQuickPixmapStore::timerEvent(QTimerEvent *)
815 int removalCost = m_unreferencedCost / CACHE_REMOVAL_FRACTION;
817 shrinkCache(removalCost);
819 if (m_unreferencedPixmaps == 0) {
820 killTimer(m_timerId);
825 void QQuickPixmapStore::purgeCache()
827 shrinkCache(m_unreferencedCost);
830 void QQuickPixmap::purgeCache()
832 pixmapStore()->purgeCache();
835 QQuickPixmapReply::QQuickPixmapReply(QQuickPixmapData *d)
836 : data(d), engineForReader(0), requestSize(d->requestSize), url(d->url), loading(false), redirectCount(0)
838 if (finishedIndex == -1) {
839 finishedIndex = QQuickPixmapReply::staticMetaObject.indexOfSignal("finished()");
840 downloadProgressIndex = QQuickPixmapReply::staticMetaObject.indexOfSignal("downloadProgress(qint64,qint64)");
844 QQuickPixmapReply::~QQuickPixmapReply()
848 bool QQuickPixmapReply::event(QEvent *event)
850 if (event->type() == QEvent::User) {
853 Event *de = static_cast<Event *>(event);
854 data->pixmapStatus = (de->error == NoError) ? QQuickPixmap::Ready : QQuickPixmap::Error;
856 if (data->pixmapStatus == QQuickPixmap::Ready) {
857 data->textureFactory = de->textureFactory;
858 data->implicitSize = de->implicitSize;
860 data->errorString = de->errorString;
861 data->removeFromCache(); // We don't continue to cache error'd pixmaps
871 return QObject::event(event);
875 int QQuickPixmapData::cost() const
878 return textureFactory->textureByteCount();
882 void QQuickPixmapData::addref()
885 if (prevUnreferencedPtr)
886 pixmapStore()->referencePixmap(this);
889 void QQuickPixmapData::release()
891 Q_ASSERT(refCount > 0);
895 QQuickPixmapReply *cancelReply = reply;
898 QQuickPixmapReader::readerMutex.lock();
899 QQuickPixmapReader *reader = QQuickPixmapReader::existingInstance(cancelReply->engineForReader);
901 reader->cancel(cancelReply);
902 QQuickPixmapReader::readerMutex.unlock();
905 if (pixmapStatus == QQuickPixmap::Ready) {
906 pixmapStore()->unreferencePixmap(this);
914 void QQuickPixmapData::addToCache()
917 QQuickPixmapKey key = { &url, &requestSize };
918 pixmapStore()->m_cache.insert(key, this);
923 void QQuickPixmapData::removeFromCache()
926 QQuickPixmapKey key = { &url, &requestSize };
927 pixmapStore()->m_cache.remove(key);
932 static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, QQmlEngine *engine, const QUrl &url, const QSize &requestSize, bool *ok)
934 if (url.scheme() == QLatin1String("image")) {
937 QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
938 QQuickImageProvider *provider = static_cast<QQuickImageProvider *>(engine->imageProvider(imageProviderId(url)));
940 imageType = provider->imageType();
943 case QQuickImageProvider::Invalid:
944 return new QQuickPixmapData(declarativePixmap, url, requestSize,
945 QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString()));
946 case QQuickImageProvider::Texture:
948 QQuickTextureFactory *texture = provider->requestTexture(imageId(url), &readSize, requestSize);
951 return new QQuickPixmapData(declarativePixmap, url, texture, readSize, requestSize);
955 case QQuickImageProvider::Image:
957 QImage image = provider->requestImage(imageId(url), &readSize, requestSize);
958 if (!image.isNull()) {
960 return new QQuickPixmapData(declarativePixmap, url, textureFactoryForImage(image), readSize, requestSize);
963 case QQuickImageProvider::Pixmap:
965 QPixmap pixmap = provider->requestPixmap(imageId(url), &readSize, requestSize);
966 if (!pixmap.isNull()) {
968 return new QQuickPixmapData(declarativePixmap, url, textureFactoryForImage(pixmap.toImage()), readSize, requestSize);
973 // provider has bad image type, or provider returned null image
974 return new QQuickPixmapData(declarativePixmap, url, requestSize,
975 QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString()));
978 QString localFile = QQmlEnginePrivate::urlToLocalFileOrQrc(url);
979 if (localFile.isEmpty())
986 if (f.open(QIODevice::ReadOnly)) {
989 if (readImage(url, &f, &image, &errorString, &readSize, requestSize)) {
991 return new QQuickPixmapData(declarativePixmap, url, textureFactoryForImage(image), readSize, requestSize);
993 errorString = QQuickPixmap::tr("Invalid image data: %1").arg(url.toString());
996 errorString = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
998 return new QQuickPixmapData(declarativePixmap, url, requestSize, errorString);
1002 struct QQuickPixmapNull {
1006 Q_GLOBAL_STATIC(QQuickPixmapNull, nullPixmap);
1008 QQuickPixmap::QQuickPixmap()
1013 QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url)
1019 QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QSize &size)
1022 load(engine, url, size);
1025 QQuickPixmap::~QQuickPixmap()
1028 d->declarativePixmaps.remove(this);
1034 bool QQuickPixmap::isNull() const
1039 bool QQuickPixmap::isReady() const
1041 return status() == Ready;
1044 bool QQuickPixmap::isError() const
1046 return status() == Error;
1049 bool QQuickPixmap::isLoading() const
1051 return status() == Loading;
1054 QString QQuickPixmap::error() const
1057 return d->errorString;
1062 QQuickPixmap::Status QQuickPixmap::status() const
1065 return d->pixmapStatus;
1070 const QUrl &QQuickPixmap::url() const
1075 return nullPixmap()->url;
1078 const QSize &QQuickPixmap::implicitSize() const
1081 return d->implicitSize;
1083 return nullPixmap()->size;
1086 const QSize &QQuickPixmap::requestSize() const
1089 return d->requestSize;
1091 return nullPixmap()->size;
1094 QQuickTextureFactory *QQuickPixmap::textureFactory() const
1097 return d->textureFactory;
1102 QImage QQuickPixmap::image() const
1104 if (d && d->textureFactory)
1105 return d->textureFactory->image();
1109 void QQuickPixmap::setImage(const QImage &p)
1114 d = new QQuickPixmapData(this, textureFactoryForImage(p));
1117 int QQuickPixmap::width() const
1119 if (d && d->textureFactory)
1120 return d->textureFactory->textureSize().width();
1125 int QQuickPixmap::height() const
1127 if (d && d->textureFactory)
1128 return d->textureFactory->textureSize().height();
1133 QRect QQuickPixmap::rect() const
1135 if (d && d->textureFactory)
1136 return QRect(QPoint(), d->textureFactory->textureSize());
1141 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url)
1143 load(engine, url, QSize(), QQuickPixmap::Cache);
1146 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, QQuickPixmap::Options options)
1148 load(engine, url, QSize(), options);
1151 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &size)
1153 load(engine, url, size, QQuickPixmap::Cache);
1156 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &requestSize, QQuickPixmap::Options options)
1159 d->declarativePixmaps.remove(this);
1164 QQuickPixmapKey key = { &url, &requestSize };
1165 QQuickPixmapStore *store = pixmapStore();
1167 QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.find(key);
1169 if (iter == store->m_cache.end()) {
1170 if (options & QQuickPixmap::Asynchronous) {
1171 // pixmaps can only be loaded synchronously
1172 if (url.scheme() == QLatin1String("image")) {
1173 QQuickImageProvider *provider = static_cast<QQuickImageProvider *>(engine->imageProvider(imageProviderId(url)));
1174 if (provider && provider->imageType() == QQuickImageProvider::Pixmap) {
1175 options &= ~QQuickPixmap::Asynchronous;
1180 if (!(options & QQuickPixmap::Asynchronous)) {
1182 d = createPixmapDataSync(this, engine, url, requestSize, &ok);
1184 if (options & QQuickPixmap::Cache)
1188 if (d) // loadable, but encountered error while loading
1195 d = new QQuickPixmapData(this, url, requestSize);
1196 if (options & QQuickPixmap::Cache)
1199 QQuickPixmapReader::readerMutex.lock();
1200 d->reply = QQuickPixmapReader::instance(engine)->getImage(d);
1201 QQuickPixmapReader::readerMutex.unlock();
1205 d->declarativePixmaps.insert(this);
1209 void QQuickPixmap::clear()
1212 d->declarativePixmaps.remove(this);
1218 void QQuickPixmap::clear(QObject *obj)
1222 QObject::disconnect(d->reply, 0, obj, 0);
1223 d->declarativePixmaps.remove(this);
1229 bool QQuickPixmap::connectFinished(QObject *object, const char *method)
1231 if (!d || !d->reply) {
1232 qWarning("QQuickPixmap: connectFinished() called when not loading.");
1236 return QObject::connect(d->reply, SIGNAL(finished()), object, method);
1239 bool QQuickPixmap::connectFinished(QObject *object, int method)
1241 if (!d || !d->reply) {
1242 qWarning("QQuickPixmap: connectFinished() called when not loading.");
1246 return QMetaObject::connect(d->reply, QQuickPixmapReply::finishedIndex, object, method);
1249 bool QQuickPixmap::connectDownloadProgress(QObject *object, const char *method)
1251 if (!d || !d->reply) {
1252 qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
1256 return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), object, method);
1259 bool QQuickPixmap::connectDownloadProgress(QObject *object, int method)
1261 if (!d || !d->reply) {
1262 qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
1266 return QMetaObject::connect(d->reply, QQuickPixmapReply::downloadProgressIndex, object, method);
1271 #include <qquickpixmapcache.moc>