aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/util/qquickpixmapcache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/util/qquickpixmapcache.cpp')
-rw-r--r--src/quick/util/qquickpixmapcache.cpp1278
1 files changed, 1278 insertions, 0 deletions
diff --git a/src/quick/util/qquickpixmapcache.cpp b/src/quick/util/qquickpixmapcache.cpp
new file mode 100644
index 0000000000..85e155f081
--- /dev/null
+++ b/src/quick/util/qquickpixmapcache.cpp
@@ -0,0 +1,1278 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qquickpixmapcache_p.h"
+#include <qqmlnetworkaccessmanagerfactory.h>
+#include <qqmlimageprovider.h>
+
+#include <qqmlengine.h>
+#include <private/qqmlglobal_p.h>
+#include <private/qqmlengine_p.h>
+
+#include <QtQuick/private/qsgtexture_p.h>
+#include <QtQuick/private/qsgcontext_p.h>
+
+#include <QCoreApplication>
+#include <QImageReader>
+#include <QHash>
+#include <QNetworkReply>
+#include <QPixmapCache>
+#include <QFile>
+#include <QThread>
+#include <QMutex>
+#include <QMutexLocker>
+#include <QWaitCondition>
+#include <QBuffer>
+#include <QWaitCondition>
+#include <QtCore/qdebug.h>
+#include <private/qobject_p.h>
+#include <QSslError>
+
+#define IMAGEREQUEST_MAX_REQUEST_COUNT 8
+#define IMAGEREQUEST_MAX_REDIRECT_RECURSION 16
+#define CACHE_EXPIRE_TIME 30
+#define CACHE_REMOVAL_FRACTION 4
+
+QT_BEGIN_NAMESPACE
+
+// The cache limit describes the maximum "junk" in the cache.
+static int cache_limit = 2048 * 1024; // 2048 KB cache limit for embedded in qpixmapcache.cpp
+
+QSGTexture *QQuickDefaultTextureFactory::createTexture(QQuickCanvas *) const
+{
+ QSGPlainTexture *t = new QSGPlainTexture();
+ t->setImage(im);
+ return t;
+}
+
+class QQuickPixmapReader;
+class QQuickPixmapData;
+class QQuickPixmapReply : public QObject
+{
+ Q_OBJECT
+public:
+ enum ReadError { NoError, Loading, Decoding };
+
+ QQuickPixmapReply(QQuickPixmapData *);
+ ~QQuickPixmapReply();
+
+ QQuickPixmapData *data;
+ QQmlEngine *engineForReader; // always access reader inside readerMutex
+ QSize requestSize;
+ QUrl url;
+
+ bool loading;
+ int redirectCount;
+
+ class Event : public QEvent {
+ public:
+ Event(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory, const QImage &image);
+
+ ReadError error;
+ QString errorString;
+ QSize implicitSize;
+ QImage image;
+ QQuickTextureFactory *textureFactory;
+ };
+ void postReply(ReadError, const QString &, const QSize &, const QImage &image);
+ void postReply(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory, const QImage &image);
+
+
+Q_SIGNALS:
+ void finished();
+ void downloadProgress(qint64, qint64);
+
+protected:
+ bool event(QEvent *event);
+
+private:
+ Q_DISABLE_COPY(QQuickPixmapReply)
+
+public:
+ static int finishedIndex;
+ static int downloadProgressIndex;
+};
+
+class QQuickPixmapReaderThreadObject : public QObject {
+ Q_OBJECT
+public:
+ QQuickPixmapReaderThreadObject(QQuickPixmapReader *);
+ void processJobs();
+ virtual bool event(QEvent *e);
+private slots:
+ void networkRequestDone();
+private:
+ QQuickPixmapReader *reader;
+};
+
+class QQuickPixmapData;
+class QQuickPixmapReader : public QThread
+{
+ Q_OBJECT
+public:
+ QQuickPixmapReader(QQmlEngine *eng);
+ ~QQuickPixmapReader();
+
+ QQuickPixmapReply *getImage(QQuickPixmapData *);
+ void cancel(QQuickPixmapReply *rep);
+
+ static QQuickPixmapReader *instance(QQmlEngine *engine);
+ static QQuickPixmapReader *existingInstance(QQmlEngine *engine);
+
+protected:
+ void run();
+
+private:
+ friend class QQuickPixmapReaderThreadObject;
+ void processJobs();
+ void processJob(QQuickPixmapReply *, const QUrl &, const QSize &);
+ void networkRequestDone(QNetworkReply *);
+
+ QList<QQuickPixmapReply*> jobs;
+ QList<QQuickPixmapReply*> cancelled;
+ QQmlEngine *engine;
+ QObject *eventLoopQuitHack;
+
+ QMutex mutex;
+ QQuickPixmapReaderThreadObject *threadObject;
+ QWaitCondition waitCondition;
+
+ QNetworkAccessManager *networkAccessManager();
+ QNetworkAccessManager *accessManager;
+
+ QHash<QNetworkReply*,QQuickPixmapReply*> replies;
+
+ static int replyDownloadProgress;
+ static int replyFinished;
+ static int downloadProgress;
+ static int threadNetworkRequestDone;
+ static QHash<QQmlEngine *,QQuickPixmapReader*> readers;
+public:
+ static QMutex readerMutex;
+};
+
+class QQuickPixmapData
+{
+public:
+ QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QSize &s, const QString &e)
+ : refCount(1), inCache(false), pixmapStatus(QQuickPixmap::Error),
+ url(u), errorString(e), requestSize(s), textureFactory(0), reply(0), prevUnreferenced(0),
+ prevUnreferencedPtr(0), nextUnreferenced(0)
+ {
+ declarativePixmaps.insert(pixmap);
+ }
+
+ QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QSize &r)
+ : refCount(1), inCache(false), pixmapStatus(QQuickPixmap::Loading),
+ url(u), requestSize(r), textureFactory(0), reply(0), prevUnreferenced(0), prevUnreferencedPtr(0),
+ nextUnreferenced(0)
+ {
+ declarativePixmaps.insert(pixmap);
+ }
+
+ QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QImage &p, const QSize &s, const QSize &r)
+ : refCount(1), inCache(false), privatePixmap(false), pixmapStatus(QQuickPixmap::Ready),
+ url(u), image(p), implicitSize(s), requestSize(r), textureFactory(new QQuickDefaultTextureFactory(p)), reply(0), prevUnreferenced(0),
+ prevUnreferencedPtr(0), nextUnreferenced(0)
+ {
+ declarativePixmaps.insert(pixmap);
+ }
+
+ QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, QQuickTextureFactory *factory, const QImage &p, const QSize &s, const QSize &r)
+ : refCount(1), inCache(false), privatePixmap(false), pixmapStatus(QQuickPixmap::Ready),
+ url(u), image(p), implicitSize(s), requestSize(r), textureFactory(factory), reply(0), prevUnreferenced(0),
+ prevUnreferencedPtr(0), nextUnreferenced(0)
+ {
+ declarativePixmaps.insert(pixmap);
+ }
+
+ QQuickPixmapData(QQuickPixmap *pixmap, const QImage &p)
+ : refCount(1), inCache(false), privatePixmap(true), pixmapStatus(QQuickPixmap::Ready),
+ image(p), implicitSize(p.size()), requestSize(p.size()), textureFactory(new QQuickDefaultTextureFactory(p)), reply(0), prevUnreferenced(0),
+ prevUnreferencedPtr(0), nextUnreferenced(0)
+ {
+ declarativePixmaps.insert(pixmap);
+ }
+
+ ~QQuickPixmapData()
+ {
+ while (!declarativePixmaps.isEmpty()) {
+ QQuickPixmap *referencer = declarativePixmaps.first();
+ declarativePixmaps.remove(referencer);
+ referencer->d = 0;
+ }
+ delete textureFactory;
+ }
+
+ int cost() const;
+ void addref();
+ void release();
+ void addToCache();
+ void removeFromCache();
+
+ uint refCount;
+
+ bool inCache:1;
+ bool privatePixmap:1;
+
+ QQuickPixmap::Status pixmapStatus;
+ QUrl url;
+ QString errorString;
+ QImage image;
+ QSize implicitSize;
+ QSize requestSize;
+
+ QQuickTextureFactory *textureFactory;
+
+ QIntrusiveList<QQuickPixmap, &QQuickPixmap::dataListNode> declarativePixmaps;
+ QQuickPixmapReply *reply;
+
+ QQuickPixmapData *prevUnreferenced;
+ QQuickPixmapData**prevUnreferencedPtr;
+ QQuickPixmapData *nextUnreferenced;
+};
+
+int QQuickPixmapReply::finishedIndex = -1;
+int QQuickPixmapReply::downloadProgressIndex = -1;
+
+// XXX
+QHash<QQmlEngine *,QQuickPixmapReader*> QQuickPixmapReader::readers;
+QMutex QQuickPixmapReader::readerMutex;
+
+int QQuickPixmapReader::replyDownloadProgress = -1;
+int QQuickPixmapReader::replyFinished = -1;
+int QQuickPixmapReader::downloadProgress = -1;
+int QQuickPixmapReader::threadNetworkRequestDone = -1;
+
+
+void QQuickPixmapReply::postReply(ReadError error, const QString &errorString,
+ const QSize &implicitSize, const QImage &image)
+{
+ loading = false;
+ QCoreApplication::postEvent(this, new Event(error, errorString, implicitSize, new QQuickDefaultTextureFactory(image), image));
+}
+
+void QQuickPixmapReply::postReply(ReadError error, const QString &errorString,
+ const QSize &implicitSize, QQuickTextureFactory *factory,
+ const QImage &image)
+{
+ loading = false;
+ QCoreApplication::postEvent(this, new Event(error, errorString, implicitSize, factory, image));
+}
+
+QQuickPixmapReply::Event::Event(ReadError e, const QString &s, const QSize &iSize, QQuickTextureFactory *factory, const QImage &i)
+ : QEvent(QEvent::User), error(e), errorString(s), implicitSize(iSize), image(i), textureFactory(factory)
+{
+}
+
+QNetworkAccessManager *QQuickPixmapReader::networkAccessManager()
+{
+ if (!accessManager) {
+ Q_ASSERT(threadObject);
+ accessManager = QQmlEnginePrivate::get(engine)->createNetworkAccessManager(threadObject);
+ }
+ return accessManager;
+}
+
+static bool readImage(const QUrl& url, QIODevice *dev, QImage *image, QString *errorString, QSize *impsize,
+ const QSize &requestSize)
+{
+ QImageReader imgio(dev);
+
+ bool force_scale = false;
+ if (url.path().endsWith(QLatin1String(".svg"),Qt::CaseInsensitive)) {
+ imgio.setFormat("svg"); // QSvgPlugin::capabilities bug QTBUG-9053
+ force_scale = true;
+ }
+
+ if (requestSize.width() > 0 || requestSize.height() > 0) {
+ QSize s = imgio.size();
+ qreal ratio = 0.0;
+ if (requestSize.width() && (force_scale || requestSize.width() < s.width())) {
+ ratio = qreal(requestSize.width())/s.width();
+ }
+ if (requestSize.height() && (force_scale || requestSize.height() < s.height())) {
+ qreal hr = qreal(requestSize.height())/s.height();
+ if (ratio == 0.0 || hr < ratio)
+ ratio = hr;
+ }
+ if (ratio > 0.0) {
+ s.setHeight(qRound(s.height() * ratio));
+ s.setWidth(qRound(s.width() * ratio));
+ imgio.setScaledSize(s);
+ }
+ }
+
+ if (impsize)
+ *impsize = imgio.size();
+
+ if (imgio.read(image)) {
+ if (impsize && impsize->width() < 0)
+ *impsize = image->size();
+ return true;
+ } else {
+ if (errorString)
+ *errorString = QQuickPixmap::tr("Error decoding: %1: %2").arg(url.toString())
+ .arg(imgio.errorString());
+ return false;
+ }
+}
+
+QQuickPixmapReader::QQuickPixmapReader(QQmlEngine *eng)
+: QThread(eng), engine(eng), threadObject(0), accessManager(0)
+{
+ eventLoopQuitHack = new QObject;
+ eventLoopQuitHack->moveToThread(this);
+ connect(eventLoopQuitHack, SIGNAL(destroyed(QObject*)), SLOT(quit()), Qt::DirectConnection);
+ start(QThread::LowestPriority);
+}
+
+QQuickPixmapReader::~QQuickPixmapReader()
+{
+ readerMutex.lock();
+ readers.remove(engine);
+ readerMutex.unlock();
+
+ mutex.lock();
+ // manually cancel all outstanding jobs.
+ foreach (QQuickPixmapReply *reply, jobs) {
+ delete reply;
+ }
+ jobs.clear();
+ QList<QQuickPixmapReply*> activeJobs = replies.values();
+ foreach (QQuickPixmapReply *reply, activeJobs) {
+ if (reply->loading) {
+ cancelled.append(reply);
+ reply->data = 0;
+ }
+ }
+ if (threadObject) threadObject->processJobs();
+ mutex.unlock();
+
+ eventLoopQuitHack->deleteLater();
+ wait();
+}
+
+void QQuickPixmapReader::networkRequestDone(QNetworkReply *reply)
+{
+ QQuickPixmapReply *job = replies.take(reply);
+
+ if (job) {
+ job->redirectCount++;
+ if (job->redirectCount < IMAGEREQUEST_MAX_REDIRECT_RECURSION) {
+ QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
+ if (redirect.isValid()) {
+ QUrl url = reply->url().resolved(redirect.toUrl());
+ QNetworkRequest req(url);
+ req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
+
+ reply->deleteLater();
+ reply = networkAccessManager()->get(req);
+
+ QMetaObject::connect(reply, replyDownloadProgress, job, downloadProgress);
+ QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
+
+ replies.insert(reply, job);
+ return;
+ }
+ }
+
+ QImage image;
+ QQuickPixmapReply::ReadError error = QQuickPixmapReply::NoError;
+ QString errorString;
+ QSize readSize;
+ if (reply->error()) {
+ error = QQuickPixmapReply::Loading;
+ errorString = reply->errorString();
+ } else {
+ QByteArray all = reply->readAll();
+ QBuffer buff(&all);
+ buff.open(QIODevice::ReadOnly);
+ if (!readImage(reply->url(), &buff, &image, &errorString, &readSize, job->requestSize))
+ error = QQuickPixmapReply::Decoding;
+ }
+ // send completion event to the QQuickPixmapReply
+ mutex.lock();
+ if (!cancelled.contains(job)) {
+ QQuickTextureFactory *factory = QSGContext::createTextureFactoryFromImage(image);
+ if (factory)
+ job->postReply(error, errorString, readSize, factory, image);
+ else
+ job->postReply(error, errorString, readSize, image);
+ }
+ mutex.unlock();
+ }
+ reply->deleteLater();
+
+ // kick off event loop again incase we have dropped below max request count
+ threadObject->processJobs();
+}
+
+QQuickPixmapReaderThreadObject::QQuickPixmapReaderThreadObject(QQuickPixmapReader *i)
+: reader(i)
+{
+}
+
+void QQuickPixmapReaderThreadObject::processJobs()
+{
+ QCoreApplication::postEvent(this, new QEvent(QEvent::User));
+}
+
+bool QQuickPixmapReaderThreadObject::event(QEvent *e)
+{
+ if (e->type() == QEvent::User) {
+ reader->processJobs();
+ return true;
+ } else {
+ return QObject::event(e);
+ }
+}
+
+void QQuickPixmapReaderThreadObject::networkRequestDone()
+{
+ QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
+ reader->networkRequestDone(reply);
+}
+
+void QQuickPixmapReader::processJobs()
+{
+ QMutexLocker locker(&mutex);
+
+ while (true) {
+ if (cancelled.isEmpty() && (jobs.isEmpty() || replies.count() >= IMAGEREQUEST_MAX_REQUEST_COUNT))
+ return; // Nothing else to do
+
+ // Clean cancelled jobs
+ if (cancelled.count()) {
+ for (int i = 0; i < cancelled.count(); ++i) {
+ QQuickPixmapReply *job = cancelled.at(i);
+ QNetworkReply *reply = replies.key(job, 0);
+ if (reply && reply->isRunning()) {
+ // cancel any jobs already started
+ replies.remove(reply);
+ reply->close();
+ }
+ // deleteLater, since not owned by this thread
+ job->deleteLater();
+ }
+ cancelled.clear();
+ }
+
+ if (!jobs.isEmpty() && replies.count() < IMAGEREQUEST_MAX_REQUEST_COUNT) {
+ QQuickPixmapReply *runningJob = jobs.takeLast();
+ runningJob->loading = true;
+
+ QUrl url = runningJob->url;
+ QSize requestSize = runningJob->requestSize;
+ locker.unlock();
+ processJob(runningJob, url, requestSize);
+ locker.relock();
+ }
+ }
+}
+
+void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &url,
+ const QSize &requestSize)
+{
+ // fetch
+ if (url.scheme() == QLatin1String("image")) {
+ // Use QmlImageProvider
+ QSize readSize;
+ QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
+ QQmlImageProvider::ImageType imageType = ep->getImageProviderType(url);
+ if (imageType == QQmlImageProvider::Invalid) {
+ QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::Loading;
+ QString errorStr = QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString());
+ QImage image;
+ mutex.lock();
+ if (!cancelled.contains(runningJob))
+ runningJob->postReply(errorCode, errorStr, readSize, image);
+ mutex.unlock();
+ } else if (imageType == QQmlImageProvider::Image) {
+ QImage image = ep->getImageFromProvider(url, &readSize, requestSize);
+ QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
+ QString errorStr;
+ if (image.isNull()) {
+ errorCode = QQuickPixmapReply::Loading;
+ errorStr = QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString());
+ }
+ mutex.lock();
+ if (!cancelled.contains(runningJob)) {
+ QQuickTextureFactory *factory = QSGContext::createTextureFactoryFromImage(image);
+ if (factory)
+ runningJob->postReply(errorCode, errorStr, readSize, factory, image);
+ else
+ runningJob->postReply(errorCode, errorStr, readSize, image);
+ }
+
+ mutex.unlock();
+ } else {
+ QQuickTextureFactory *t = ep->getTextureFromProvider(url, &readSize, requestSize);
+ QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
+ QString errorStr;
+ if (!t) {
+ errorCode = QQuickPixmapReply::Loading;
+ errorStr = QQuickPixmap::tr("Failed to get texture from provider: %1").arg(url.toString());
+ }
+ mutex.lock();
+ if (!cancelled.contains(runningJob))
+ runningJob->postReply(errorCode, errorStr, readSize, t, QImage());
+ mutex.unlock();
+
+ }
+
+ } else {
+ QString lf = QQmlEnginePrivate::urlToLocalFileOrQrc(url);
+ if (!lf.isEmpty()) {
+ // Image is local - load/decode immediately
+ QImage image;
+ QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
+ QString errorStr;
+ QFile f(lf);
+ QSize readSize;
+ if (f.open(QIODevice::ReadOnly)) {
+ if (!readImage(url, &f, &image, &errorStr, &readSize, requestSize))
+ errorCode = QQuickPixmapReply::Loading;
+ } else {
+ errorStr = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
+ errorCode = QQuickPixmapReply::Loading;
+ }
+ mutex.lock();
+ if (!cancelled.contains(runningJob)) {
+ QQuickTextureFactory *factory = QSGContext::createTextureFactoryFromImage(image);
+ if (factory)
+ runningJob->postReply(errorCode, errorStr, readSize, factory, image);
+ else
+ runningJob->postReply(errorCode, errorStr, readSize, image);
+ }
+ mutex.unlock();
+ } else {
+ // Network resource
+ QNetworkRequest req(url);
+ req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
+ QNetworkReply *reply = networkAccessManager()->get(req);
+
+ QMetaObject::connect(reply, replyDownloadProgress, runningJob, downloadProgress);
+ QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
+
+ replies.insert(reply, runningJob);
+ }
+ }
+}
+
+QQuickPixmapReader *QQuickPixmapReader::instance(QQmlEngine *engine)
+{
+ // XXX NOTE: must be called within readerMutex locking.
+ QQuickPixmapReader *reader = readers.value(engine);
+ if (!reader) {
+ reader = new QQuickPixmapReader(engine);
+ readers.insert(engine, reader);
+ }
+
+ return reader;
+}
+
+QQuickPixmapReader *QQuickPixmapReader::existingInstance(QQmlEngine *engine)
+{
+ // XXX NOTE: must be called within readerMutex locking.
+ return readers.value(engine, 0);
+}
+
+QQuickPixmapReply *QQuickPixmapReader::getImage(QQuickPixmapData *data)
+{
+ mutex.lock();
+ QQuickPixmapReply *reply = new QQuickPixmapReply(data);
+ reply->engineForReader = engine;
+ jobs.append(reply);
+ // XXX
+ if (threadObject) threadObject->processJobs();
+ mutex.unlock();
+ return reply;
+}
+
+void QQuickPixmapReader::cancel(QQuickPixmapReply *reply)
+{
+ mutex.lock();
+ if (reply->loading) {
+ cancelled.append(reply);
+ reply->data = 0;
+ // XXX
+ if (threadObject) threadObject->processJobs();
+ } else {
+ jobs.removeAll(reply);
+ delete reply;
+ }
+ mutex.unlock();
+}
+
+void QQuickPixmapReader::run()
+{
+ if (replyDownloadProgress == -1) {
+ const QMetaObject *nr = &QNetworkReply::staticMetaObject;
+ const QMetaObject *pr = &QQuickPixmapReply::staticMetaObject;
+ const QMetaObject *ir = &QQuickPixmapReaderThreadObject::staticMetaObject;
+ replyDownloadProgress = nr->indexOfSignal("downloadProgress(qint64,qint64)");
+ replyFinished = nr->indexOfSignal("finished()");
+ downloadProgress = pr->indexOfSignal("downloadProgress(qint64,qint64)");
+ threadNetworkRequestDone = ir->indexOfSlot("networkRequestDone()");
+ }
+
+ mutex.lock();
+ threadObject = new QQuickPixmapReaderThreadObject(this);
+ mutex.unlock();
+
+ processJobs();
+ exec();
+
+ delete threadObject;
+ threadObject = 0;
+}
+
+class QQuickPixmapKey
+{
+public:
+ const QUrl *url;
+ const QSize *size;
+};
+
+inline bool operator==(const QQuickPixmapKey &lhs, const QQuickPixmapKey &rhs)
+{
+ return *lhs.size == *rhs.size && *lhs.url == *rhs.url;
+}
+
+inline uint qHash(const QQuickPixmapKey &key)
+{
+ return qHash(*key.url) ^ key.size->width() ^ key.size->height();
+}
+
+class QSGContext;
+
+class QQuickPixmapStore : public QObject
+{
+ Q_OBJECT
+public:
+ QQuickPixmapStore();
+ ~QQuickPixmapStore();
+
+ void unreferencePixmap(QQuickPixmapData *);
+ void referencePixmap(QQuickPixmapData *);
+
+ void purgeCache();
+
+protected:
+ virtual void timerEvent(QTimerEvent *);
+
+public:
+ QHash<QQuickPixmapKey, QQuickPixmapData *> m_cache;
+
+private:
+ void shrinkCache(int remove);
+
+ QQuickPixmapData *m_unreferencedPixmaps;
+ QQuickPixmapData *m_lastUnreferencedPixmap;
+
+ int m_unreferencedCost;
+ int m_timerId;
+ bool m_destroying;
+};
+Q_GLOBAL_STATIC(QQuickPixmapStore, pixmapStore);
+
+
+QQuickPixmapStore::QQuickPixmapStore()
+ : m_unreferencedPixmaps(0), m_lastUnreferencedPixmap(0), m_unreferencedCost(0), m_timerId(-1), m_destroying(false)
+{
+}
+
+QQuickPixmapStore::~QQuickPixmapStore()
+{
+ m_destroying = true;
+
+ int leakedPixmaps = 0;
+ QList<QQuickPixmapData*> cachedData = m_cache.values();
+
+ // Prevent unreferencePixmap() from assuming it needs to kick
+ // off the cache expiry timer, as we're shrinking the cache
+ // manually below after releasing all the pixmaps.
+ m_timerId = -2;
+
+ // unreference all (leaked) pixmaps
+ foreach (QQuickPixmapData* pixmap, cachedData) {
+ int currRefCount = pixmap->refCount;
+ if (currRefCount) {
+ leakedPixmaps++;
+ while (currRefCount > 0) {
+ pixmap->release();
+ currRefCount--;
+ }
+ }
+ }
+
+ // free all unreferenced pixmaps
+ while (m_lastUnreferencedPixmap) {
+ shrinkCache(20);
+ }
+
+ if (leakedPixmaps)
+ qDebug("Number of leaked pixmaps: %i", leakedPixmaps);
+}
+
+void QQuickPixmapStore::unreferencePixmap(QQuickPixmapData *data)
+{
+ Q_ASSERT(data->prevUnreferenced == 0);
+ Q_ASSERT(data->prevUnreferencedPtr == 0);
+ Q_ASSERT(data->nextUnreferenced == 0);
+
+ data->nextUnreferenced = m_unreferencedPixmaps;
+ data->prevUnreferencedPtr = &m_unreferencedPixmaps;
+
+ m_unreferencedPixmaps = data;
+ if (m_unreferencedPixmaps->nextUnreferenced) {
+ m_unreferencedPixmaps->nextUnreferenced->prevUnreferenced = m_unreferencedPixmaps;
+ m_unreferencedPixmaps->nextUnreferenced->prevUnreferencedPtr = &m_unreferencedPixmaps->nextUnreferenced;
+ }
+
+ if (!m_lastUnreferencedPixmap)
+ m_lastUnreferencedPixmap = data;
+
+ m_unreferencedCost += data->cost();
+
+ shrinkCache(-1); // Shrink the cache incase it has become larger than cache_limit
+
+ if (m_timerId == -1 && m_unreferencedPixmaps && !m_destroying)
+ m_timerId = startTimer(CACHE_EXPIRE_TIME * 1000);
+}
+
+void QQuickPixmapStore::referencePixmap(QQuickPixmapData *data)
+{
+ Q_ASSERT(data->prevUnreferencedPtr);
+
+ *data->prevUnreferencedPtr = data->nextUnreferenced;
+ if (data->nextUnreferenced) {
+ data->nextUnreferenced->prevUnreferencedPtr = data->prevUnreferencedPtr;
+ data->nextUnreferenced->prevUnreferenced = data->prevUnreferenced;
+ }
+ if (m_lastUnreferencedPixmap == data)
+ m_lastUnreferencedPixmap = data->prevUnreferenced;
+
+ data->nextUnreferenced = 0;
+ data->prevUnreferencedPtr = 0;
+ data->prevUnreferenced = 0;
+
+ m_unreferencedCost -= data->cost();
+}
+
+void QQuickPixmapStore::shrinkCache(int remove)
+{
+ while ((remove > 0 || m_unreferencedCost > cache_limit) && m_lastUnreferencedPixmap) {
+ QQuickPixmapData *data = m_lastUnreferencedPixmap;
+ Q_ASSERT(data->nextUnreferenced == 0);
+
+ *data->prevUnreferencedPtr = 0;
+ m_lastUnreferencedPixmap = data->prevUnreferenced;
+ data->prevUnreferencedPtr = 0;
+ data->prevUnreferenced = 0;
+
+ remove -= data->cost();
+ m_unreferencedCost -= data->cost();
+ data->removeFromCache();
+ delete data;
+ }
+}
+
+void QQuickPixmapStore::timerEvent(QTimerEvent *)
+{
+ int removalCost = m_unreferencedCost / CACHE_REMOVAL_FRACTION;
+
+ shrinkCache(removalCost);
+
+ if (m_unreferencedPixmaps == 0) {
+ killTimer(m_timerId);
+ m_timerId = -1;
+ }
+}
+
+void QQuickPixmapStore::purgeCache()
+{
+ shrinkCache(m_unreferencedCost);
+}
+
+void QQuickPixmap::purgeCache()
+{
+ pixmapStore()->purgeCache();
+}
+
+QQuickPixmapReply::QQuickPixmapReply(QQuickPixmapData *d)
+: data(d), engineForReader(0), requestSize(d->requestSize), url(d->url), loading(false), redirectCount(0)
+{
+ if (finishedIndex == -1) {
+ finishedIndex = QQuickPixmapReply::staticMetaObject.indexOfSignal("finished()");
+ downloadProgressIndex = QQuickPixmapReply::staticMetaObject.indexOfSignal("downloadProgress(qint64,qint64)");
+ }
+}
+
+QQuickPixmapReply::~QQuickPixmapReply()
+{
+}
+
+bool QQuickPixmapReply::event(QEvent *event)
+{
+ if (event->type() == QEvent::User) {
+
+ if (data) {
+ Event *de = static_cast<Event *>(event);
+ data->pixmapStatus = (de->error == NoError) ? QQuickPixmap::Ready : QQuickPixmap::Error;
+
+ if (data->pixmapStatus == QQuickPixmap::Ready) {
+ if (de->textureFactory) {
+ data->textureFactory = de->textureFactory;
+ }
+ data->image = de->image;
+ data->implicitSize = de->implicitSize;
+ } else {
+ data->errorString = de->errorString;
+ data->removeFromCache(); // We don't continue to cache error'd pixmaps
+ }
+
+ data->reply = 0;
+ emit finished();
+ }
+
+ delete this;
+ return true;
+ } else {
+ return QObject::event(event);
+ }
+}
+
+int QQuickPixmapData::cost() const
+{
+ if (textureFactory)
+ return textureFactory->textureByteCount();
+ return image.byteCount();
+}
+
+void QQuickPixmapData::addref()
+{
+ ++refCount;
+ if (prevUnreferencedPtr)
+ pixmapStore()->referencePixmap(this);
+}
+
+void QQuickPixmapData::release()
+{
+ Q_ASSERT(refCount > 0);
+ --refCount;
+ if (refCount == 0) {
+ if (reply) {
+ QQuickPixmapReply *cancelReply = reply;
+ reply->data = 0;
+ reply = 0;
+ QQuickPixmapReader::readerMutex.lock();
+ QQuickPixmapReader *reader = QQuickPixmapReader::existingInstance(cancelReply->engineForReader);
+ if (reader)
+ reader->cancel(cancelReply);
+ QQuickPixmapReader::readerMutex.unlock();
+ }
+
+ if (pixmapStatus == QQuickPixmap::Ready) {
+ pixmapStore()->unreferencePixmap(this);
+ } else {
+ removeFromCache();
+ delete this;
+ }
+ }
+}
+
+void QQuickPixmapData::addToCache()
+{
+ if (!inCache) {
+ QQuickPixmapKey key = { &url, &requestSize };
+ pixmapStore()->m_cache.insert(key, this);
+ inCache = true;
+ }
+}
+
+void QQuickPixmapData::removeFromCache()
+{
+ if (inCache) {
+ QQuickPixmapKey key = { &url, &requestSize };
+ pixmapStore()->m_cache.remove(key);
+ inCache = false;
+ }
+}
+
+static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, QQmlEngine *engine, const QUrl &url, const QSize &requestSize, bool *ok)
+{
+ if (url.scheme() == QLatin1String("image")) {
+ QSize readSize;
+ QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
+ QQmlImageProvider::ImageType imageType = ep->getImageProviderType(url);
+
+ switch (imageType) {
+ case QQmlImageProvider::Invalid:
+ return new QQuickPixmapData(declarativePixmap, url, requestSize,
+ QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString()));
+ case QQmlImageProvider::Texture:
+ {
+ QQuickTextureFactory *texture = ep->getTextureFromProvider(url, &readSize, requestSize);
+ if (texture) {
+ *ok = true;
+ return new QQuickPixmapData(declarativePixmap, url, texture, QImage(), readSize, requestSize);
+ }
+ }
+
+ case QQmlImageProvider::Image:
+ {
+ QImage image = ep->getImageFromProvider(url, &readSize, requestSize);
+ if (!image.isNull()) {
+ *ok = true;
+ return new QQuickPixmapData(declarativePixmap, url, image, readSize, requestSize);
+ }
+ }
+ case QQmlImageProvider::Pixmap:
+ {
+ QPixmap pixmap = ep->getPixmapFromProvider(url, &readSize, requestSize);
+ if (!pixmap.isNull()) {
+ *ok = true;
+ return new QQuickPixmapData(declarativePixmap, url, pixmap.toImage(), readSize, requestSize);
+ }
+ }
+ }
+
+ // provider has bad image type, or provider returned null image
+ return new QQuickPixmapData(declarativePixmap, url, requestSize,
+ QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString()));
+ }
+
+ QString localFile = QQmlEnginePrivate::urlToLocalFileOrQrc(url);
+ if (localFile.isEmpty())
+ return 0;
+
+ QFile f(localFile);
+ QSize readSize;
+ QString errorString;
+
+ if (f.open(QIODevice::ReadOnly)) {
+ QImage image;
+
+ if (readImage(url, &f, &image, &errorString, &readSize, requestSize)) {
+ *ok = true;
+ return new QQuickPixmapData(declarativePixmap, url, image, readSize, requestSize);
+ }
+ errorString = QQuickPixmap::tr("Invalid image data: %1").arg(url.toString());
+
+ } else {
+ errorString = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
+ }
+ return new QQuickPixmapData(declarativePixmap, url, requestSize, errorString);
+}
+
+
+struct QQuickPixmapNull {
+ QUrl url;
+ QImage image;
+ QSize size;
+};
+Q_GLOBAL_STATIC(QQuickPixmapNull, nullPixmap);
+
+QQuickPixmap::QQuickPixmap()
+: d(0)
+{
+}
+
+QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url)
+: d(0)
+{
+ load(engine, url);
+}
+
+QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QSize &size)
+: d(0)
+{
+ load(engine, url, size);
+}
+
+QQuickPixmap::~QQuickPixmap()
+{
+ if (d) {
+ d->declarativePixmaps.remove(this);
+ d->release();
+ d = 0;
+ }
+}
+
+bool QQuickPixmap::isNull() const
+{
+ return d == 0;
+}
+
+bool QQuickPixmap::isReady() const
+{
+ return status() == Ready;
+}
+
+bool QQuickPixmap::isError() const
+{
+ return status() == Error;
+}
+
+bool QQuickPixmap::isLoading() const
+{
+ return status() == Loading;
+}
+
+QString QQuickPixmap::error() const
+{
+ if (d)
+ return d->errorString;
+ else
+ return QString();
+}
+
+QQuickPixmap::Status QQuickPixmap::status() const
+{
+ if (d)
+ return d->pixmapStatus;
+ else
+ return Null;
+}
+
+const QUrl &QQuickPixmap::url() const
+{
+ if (d)
+ return d->url;
+ else
+ return nullPixmap()->url;
+}
+
+const QSize &QQuickPixmap::implicitSize() const
+{
+ if (d)
+ return d->implicitSize;
+ else
+ return nullPixmap()->size;
+}
+
+const QSize &QQuickPixmap::requestSize() const
+{
+ if (d)
+ return d->requestSize;
+ else
+ return nullPixmap()->size;
+}
+
+QQuickTextureFactory *QQuickPixmap::textureFactory() const
+{
+ if (d)
+ return d->textureFactory;
+
+ return 0;
+}
+
+const QImage &QQuickPixmap::image() const
+{
+ if (d)
+ return d->image;
+ else
+ return nullPixmap()->image;
+}
+
+void QQuickPixmap::setImage(const QImage &p)
+{
+ clear();
+
+ if (!p.isNull())
+ d = new QQuickPixmapData(this, p);
+}
+
+int QQuickPixmap::width() const
+{
+ if (d)
+ return d->textureFactory ? d->textureFactory->textureSize().width() : d->image.width();
+ else
+ return 0;
+}
+
+int QQuickPixmap::height() const
+{
+ if (d)
+ return d->textureFactory ? d->textureFactory->textureSize().height() : d->image.height();
+ else
+ return 0;
+}
+
+QRect QQuickPixmap::rect() const
+{
+ if (d)
+ return d->textureFactory ? QRect(QPoint(), d->textureFactory->textureSize()) : d->image.rect();
+ else
+ return QRect();
+}
+
+void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url)
+{
+ load(engine, url, QSize(), QQuickPixmap::Cache);
+}
+
+void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, QQuickPixmap::Options options)
+{
+ load(engine, url, QSize(), options);
+}
+
+void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &size)
+{
+ load(engine, url, size, QQuickPixmap::Cache);
+}
+
+void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &requestSize, QQuickPixmap::Options options)
+{
+ if (d) {
+ d->declarativePixmaps.remove(this);
+ d->release();
+ d = 0;
+ }
+
+ QQuickPixmapKey key = { &url, &requestSize };
+ QQuickPixmapStore *store = pixmapStore();
+
+ QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.find(key);
+
+ if (iter == store->m_cache.end()) {
+ if (options & QQuickPixmap::Asynchronous) {
+ // pixmaps can only be loaded synchronously
+ if (url.scheme() == QLatin1String("image")
+ && QQmlEnginePrivate::get(engine)->getImageProviderType(url) == QQmlImageProvider::Pixmap) {
+ options &= ~QQuickPixmap::Asynchronous;
+ }
+ }
+
+ if (!(options & QQuickPixmap::Asynchronous)) {
+ bool ok = false;
+ d = createPixmapDataSync(this, engine, url, requestSize, &ok);
+ if (ok) {
+ if (options & QQuickPixmap::Cache)
+ d->addToCache();
+ return;
+ }
+ if (d) // loadable, but encountered error while loading
+ return;
+ }
+
+ if (!engine)
+ return;
+
+ d = new QQuickPixmapData(this, url, requestSize);
+ if (options & QQuickPixmap::Cache)
+ d->addToCache();
+
+ QQuickPixmapReader::readerMutex.lock();
+ d->reply = QQuickPixmapReader::instance(engine)->getImage(d);
+ QQuickPixmapReader::readerMutex.unlock();
+ } else {
+ d = *iter;
+ d->addref();
+ d->declarativePixmaps.insert(this);
+ }
+}
+
+void QQuickPixmap::clear()
+{
+ if (d) {
+ d->declarativePixmaps.remove(this);
+ d->release();
+ d = 0;
+ }
+}
+
+void QQuickPixmap::clear(QObject *obj)
+{
+ if (d) {
+ if (d->reply)
+ QObject::disconnect(d->reply, 0, obj, 0);
+ d->declarativePixmaps.remove(this);
+ d->release();
+ d = 0;
+ }
+}
+
+bool QQuickPixmap::connectFinished(QObject *object, const char *method)
+{
+ if (!d || !d->reply) {
+ qWarning("QQuickPixmap: connectFinished() called when not loading.");
+ return false;
+ }
+
+ return QObject::connect(d->reply, SIGNAL(finished()), object, method);
+}
+
+bool QQuickPixmap::connectFinished(QObject *object, int method)
+{
+ if (!d || !d->reply) {
+ qWarning("QQuickPixmap: connectFinished() called when not loading.");
+ return false;
+ }
+
+ return QMetaObject::connect(d->reply, QQuickPixmapReply::finishedIndex, object, method);
+}
+
+bool QQuickPixmap::connectDownloadProgress(QObject *object, const char *method)
+{
+ if (!d || !d->reply) {
+ qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
+ return false;
+ }
+
+ return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), object, method);
+}
+
+bool QQuickPixmap::connectDownloadProgress(QObject *object, int method)
+{
+ if (!d || !d->reply) {
+ qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
+ return false;
+ }
+
+ return QMetaObject::connect(d->reply, QQuickPixmapReply::downloadProgressIndex, object, method);
+}
+
+QT_END_NAMESPACE
+
+#include <qquickpixmapcache.moc>