diff options
Diffstat (limited to 'src/quick/util/qquickpixmapcache.cpp')
-rw-r--r-- | src/quick/util/qquickpixmapcache.cpp | 1278 |
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> |