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>
70 #define IMAGEREQUEST_MAX_REQUEST_COUNT 8
71 #define IMAGEREQUEST_MAX_REDIRECT_RECURSION 16
72 #define CACHE_EXPIRE_TIME 30
73 #define CACHE_REMOVAL_FRACTION 4
77 // The cache limit describes the maximum "junk" in the cache.
78 static int cache_limit = 2048 * 1024; // 2048 KB cache limit for embedded in qpixmapcache.cpp
80 static inline QString imageProviderId(const QUrl &url)
85 static inline QString imageId(const QUrl &url)
87 return url.toString(QUrl::RemoveScheme | QUrl::RemoveAuthority).mid(1);
90 QSGTexture *QQuickDefaultTextureFactory::createTexture(QQuickCanvas *) const
92 QSGPlainTexture *t = new QSGPlainTexture();
97 static QQuickTextureFactory *textureFactoryForImage(const QImage &image)
101 QQuickTextureFactory *texture = QSGContext::createTextureFactoryFromImage(image);
104 return new QQuickDefaultTextureFactory(image);
107 class QQuickPixmapReader;
108 class QQuickPixmapData;
109 class QQuickPixmapReply : public QObject
113 enum ReadError { NoError, Loading, Decoding };
115 QQuickPixmapReply(QQuickPixmapData *);
116 ~QQuickPixmapReply();
118 QQuickPixmapData *data;
119 QQmlEngine *engineForReader; // always access reader inside readerMutex
126 class Event : public QEvent {
128 Event(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory);
133 QQuickTextureFactory *textureFactory;
135 void postReply(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory);
140 void downloadProgress(qint64, qint64);
143 bool event(QEvent *event);
146 Q_DISABLE_COPY(QQuickPixmapReply)
149 static int finishedIndex;
150 static int downloadProgressIndex;
153 class QQuickPixmapReaderThreadObject : public QObject {
156 QQuickPixmapReaderThreadObject(QQuickPixmapReader *);
158 virtual bool event(QEvent *e);
160 void networkRequestDone();
162 QQuickPixmapReader *reader;
165 class QQuickPixmapData;
166 class QQuickPixmapReader : public QThread
170 QQuickPixmapReader(QQmlEngine *eng);
171 ~QQuickPixmapReader();
173 QQuickPixmapReply *getImage(QQuickPixmapData *);
174 void cancel(QQuickPixmapReply *rep);
176 static QQuickPixmapReader *instance(QQmlEngine *engine);
177 static QQuickPixmapReader *existingInstance(QQmlEngine *engine);
183 friend class QQuickPixmapReaderThreadObject;
185 void processJob(QQuickPixmapReply *, const QUrl &, const QSize &);
186 void networkRequestDone(QNetworkReply *);
188 QList<QQuickPixmapReply*> jobs;
189 QList<QQuickPixmapReply*> cancelled;
191 QObject *eventLoopQuitHack;
194 QQuickPixmapReaderThreadObject *threadObject;
195 QWaitCondition waitCondition;
197 QNetworkAccessManager *networkAccessManager();
198 QNetworkAccessManager *accessManager;
200 QHash<QNetworkReply*,QQuickPixmapReply*> replies;
202 static int replyDownloadProgress;
203 static int replyFinished;
204 static int downloadProgress;
205 static int threadNetworkRequestDone;
206 static QHash<QQmlEngine *,QQuickPixmapReader*> readers;
208 static QMutex readerMutex;
211 class QQuickPixmapData
214 QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QSize &s, const QString &e)
215 : refCount(1), inCache(false), pixmapStatus(QQuickPixmap::Error),
216 url(u), errorString(e), requestSize(s), textureFactory(0), reply(0), prevUnreferenced(0),
217 prevUnreferencedPtr(0), nextUnreferenced(0)
219 declarativePixmaps.insert(pixmap);
222 QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QSize &r)
223 : refCount(1), inCache(false), pixmapStatus(QQuickPixmap::Loading),
224 url(u), requestSize(r), textureFactory(0), reply(0), prevUnreferenced(0), prevUnreferencedPtr(0),
227 declarativePixmaps.insert(pixmap);
230 QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, QQuickTextureFactory *texture, const QSize &s, const QSize &r)
231 : refCount(1), inCache(false), privatePixmap(false), pixmapStatus(QQuickPixmap::Ready),
232 url(u), implicitSize(s), requestSize(r), textureFactory(texture), reply(0), prevUnreferenced(0),
233 prevUnreferencedPtr(0), nextUnreferenced(0)
235 declarativePixmaps.insert(pixmap);
238 QQuickPixmapData(QQuickPixmap *pixmap, QQuickTextureFactory *texture)
239 : refCount(1), inCache(false), privatePixmap(true), pixmapStatus(QQuickPixmap::Ready),
240 textureFactory(texture), reply(0), prevUnreferenced(0),
241 prevUnreferencedPtr(0), nextUnreferenced(0)
244 requestSize = implicitSize = texture->textureSize();
245 declarativePixmaps.insert(pixmap);
250 while (!declarativePixmaps.isEmpty()) {
251 QQuickPixmap *referencer = declarativePixmaps.first();
252 declarativePixmaps.remove(referencer);
255 delete textureFactory;
262 void removeFromCache();
267 bool privatePixmap:1;
269 QQuickPixmap::Status pixmapStatus;
275 QQuickTextureFactory *textureFactory;
277 QIntrusiveList<QQuickPixmap, &QQuickPixmap::dataListNode> declarativePixmaps;
278 QQuickPixmapReply *reply;
280 QQuickPixmapData *prevUnreferenced;
281 QQuickPixmapData**prevUnreferencedPtr;
282 QQuickPixmapData *nextUnreferenced;
285 int QQuickPixmapReply::finishedIndex = -1;
286 int QQuickPixmapReply::downloadProgressIndex = -1;
289 QHash<QQmlEngine *,QQuickPixmapReader*> QQuickPixmapReader::readers;
290 QMutex QQuickPixmapReader::readerMutex;
292 int QQuickPixmapReader::replyDownloadProgress = -1;
293 int QQuickPixmapReader::replyFinished = -1;
294 int QQuickPixmapReader::downloadProgress = -1;
295 int QQuickPixmapReader::threadNetworkRequestDone = -1;
298 void QQuickPixmapReply::postReply(ReadError error, const QString &errorString,
299 const QSize &implicitSize, QQuickTextureFactory *factory)
302 QCoreApplication::postEvent(this, new Event(error, errorString, implicitSize, factory));
305 QQuickPixmapReply::Event::Event(ReadError e, const QString &s, const QSize &iSize, QQuickTextureFactory *factory)
306 : QEvent(QEvent::User), error(e), errorString(s), implicitSize(iSize), textureFactory(factory)
310 QNetworkAccessManager *QQuickPixmapReader::networkAccessManager()
312 if (!accessManager) {
313 Q_ASSERT(threadObject);
314 accessManager = QQmlEnginePrivate::get(engine)->createNetworkAccessManager(threadObject);
316 return accessManager;
319 static bool readImage(const QUrl& url, QIODevice *dev, QImage *image, QString *errorString, QSize *impsize,
320 const QSize &requestSize)
322 QImageReader imgio(dev);
324 bool force_scale = false;
325 if (url.path().endsWith(QLatin1String(".svg"),Qt::CaseInsensitive)) {
326 imgio.setFormat("svg"); // QSvgPlugin::capabilities bug QTBUG-9053
330 if (requestSize.width() > 0 || requestSize.height() > 0) {
331 QSize s = imgio.size();
333 if (requestSize.width() && (force_scale || requestSize.width() < s.width())) {
334 ratio = qreal(requestSize.width())/s.width();
336 if (requestSize.height() && (force_scale || requestSize.height() < s.height())) {
337 qreal hr = qreal(requestSize.height())/s.height();
338 if (ratio == 0.0 || hr < ratio)
342 s.setHeight(qRound(s.height() * ratio));
343 s.setWidth(qRound(s.width() * ratio));
344 imgio.setScaledSize(s);
349 *impsize = imgio.size();
351 if (imgio.read(image)) {
352 if (impsize && impsize->width() < 0)
353 *impsize = image->size();
357 *errorString = QQuickPixmap::tr("Error decoding: %1: %2").arg(url.toString())
358 .arg(imgio.errorString());
363 QQuickPixmapReader::QQuickPixmapReader(QQmlEngine *eng)
364 : QThread(eng), engine(eng), threadObject(0), accessManager(0)
366 eventLoopQuitHack = new QObject;
367 eventLoopQuitHack->moveToThread(this);
368 connect(eventLoopQuitHack, SIGNAL(destroyed(QObject*)), SLOT(quit()), Qt::DirectConnection);
369 start(QThread::LowestPriority);
372 QQuickPixmapReader::~QQuickPixmapReader()
375 readers.remove(engine);
376 readerMutex.unlock();
379 // manually cancel all outstanding jobs.
380 foreach (QQuickPixmapReply *reply, jobs) {
384 QList<QQuickPixmapReply*> activeJobs = replies.values();
385 foreach (QQuickPixmapReply *reply, activeJobs) {
386 if (reply->loading) {
387 cancelled.append(reply);
391 if (threadObject) threadObject->processJobs();
394 eventLoopQuitHack->deleteLater();
398 void QQuickPixmapReader::networkRequestDone(QNetworkReply *reply)
400 QQuickPixmapReply *job = replies.take(reply);
403 job->redirectCount++;
404 if (job->redirectCount < IMAGEREQUEST_MAX_REDIRECT_RECURSION) {
405 QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
406 if (redirect.isValid()) {
407 QUrl url = reply->url().resolved(redirect.toUrl());
408 QNetworkRequest req(url);
409 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
411 reply->deleteLater();
412 reply = networkAccessManager()->get(req);
414 QMetaObject::connect(reply, replyDownloadProgress, job, downloadProgress);
415 QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
417 replies.insert(reply, job);
423 QQuickPixmapReply::ReadError error = QQuickPixmapReply::NoError;
426 if (reply->error()) {
427 error = QQuickPixmapReply::Loading;
428 errorString = reply->errorString();
430 QByteArray all = reply->readAll();
432 buff.open(QIODevice::ReadOnly);
433 if (!readImage(reply->url(), &buff, &image, &errorString, &readSize, job->requestSize))
434 error = QQuickPixmapReply::Decoding;
436 // send completion event to the QQuickPixmapReply
438 if (!cancelled.contains(job))
439 job->postReply(error, errorString, readSize, textureFactoryForImage(image));
442 reply->deleteLater();
444 // kick off event loop again incase we have dropped below max request count
445 threadObject->processJobs();
448 QQuickPixmapReaderThreadObject::QQuickPixmapReaderThreadObject(QQuickPixmapReader *i)
453 void QQuickPixmapReaderThreadObject::processJobs()
455 QCoreApplication::postEvent(this, new QEvent(QEvent::User));
458 bool QQuickPixmapReaderThreadObject::event(QEvent *e)
460 if (e->type() == QEvent::User) {
461 reader->processJobs();
464 return QObject::event(e);
468 void QQuickPixmapReaderThreadObject::networkRequestDone()
470 QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
471 reader->networkRequestDone(reply);
474 void QQuickPixmapReader::processJobs()
476 QMutexLocker locker(&mutex);
479 if (cancelled.isEmpty() && (jobs.isEmpty() || replies.count() >= IMAGEREQUEST_MAX_REQUEST_COUNT))
480 return; // Nothing else to do
482 // Clean cancelled jobs
483 if (cancelled.count()) {
484 for (int i = 0; i < cancelled.count(); ++i) {
485 QQuickPixmapReply *job = cancelled.at(i);
486 QNetworkReply *reply = replies.key(job, 0);
487 if (reply && reply->isRunning()) {
488 // cancel any jobs already started
489 replies.remove(reply);
492 // deleteLater, since not owned by this thread
498 if (!jobs.isEmpty() && replies.count() < IMAGEREQUEST_MAX_REQUEST_COUNT) {
499 QQuickPixmapReply *runningJob = jobs.takeLast();
500 runningJob->loading = true;
502 QUrl url = runningJob->url;
503 QSize requestSize = runningJob->requestSize;
505 processJob(runningJob, url, requestSize);
511 void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &url,
512 const QSize &requestSize)
515 if (url.scheme() == QLatin1String("image")) {
516 // Use QQuickImageProvider
519 QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
520 QQuickImageProvider *provider = static_cast<QQuickImageProvider *>(engine->imageProvider(imageProviderId(url)));
522 imageType = provider->imageType();
524 if (imageType == QQuickImageProvider::Invalid) {
525 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::Loading;
526 QString errorStr = QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString());
529 if (!cancelled.contains(runningJob))
530 runningJob->postReply(errorCode, errorStr, readSize, textureFactoryForImage(image));
532 } else if (imageType == QQuickImageProvider::Image) {
533 QImage image = provider->requestImage(imageId(url), &readSize, requestSize);
534 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
536 if (image.isNull()) {
537 errorCode = QQuickPixmapReply::Loading;
538 errorStr = QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString());
541 if (!cancelled.contains(runningJob))
542 runningJob->postReply(errorCode, errorStr, readSize, textureFactoryForImage(image));
545 QQuickTextureFactory *t = provider->requestTexture(imageId(url), &readSize, requestSize);
546 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
549 errorCode = QQuickPixmapReply::Loading;
550 errorStr = QQuickPixmap::tr("Failed to get texture from provider: %1").arg(url.toString());
553 if (!cancelled.contains(runningJob))
554 runningJob->postReply(errorCode, errorStr, readSize, t);
560 QString lf = QQmlFile::urlToLocalFileOrQrc(url);
562 // Image is local - load/decode immediately
564 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
568 if (f.open(QIODevice::ReadOnly)) {
569 if (!readImage(url, &f, &image, &errorStr, &readSize, requestSize))
570 errorCode = QQuickPixmapReply::Loading;
572 errorStr = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
573 errorCode = QQuickPixmapReply::Loading;
576 if (!cancelled.contains(runningJob))
577 runningJob->postReply(errorCode, errorStr, readSize, textureFactoryForImage(image));
581 QNetworkRequest req(url);
582 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
583 QNetworkReply *reply = networkAccessManager()->get(req);
585 QMetaObject::connect(reply, replyDownloadProgress, runningJob, downloadProgress);
586 QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
588 replies.insert(reply, runningJob);
593 QQuickPixmapReader *QQuickPixmapReader::instance(QQmlEngine *engine)
595 // XXX NOTE: must be called within readerMutex locking.
596 QQuickPixmapReader *reader = readers.value(engine);
598 reader = new QQuickPixmapReader(engine);
599 readers.insert(engine, reader);
605 QQuickPixmapReader *QQuickPixmapReader::existingInstance(QQmlEngine *engine)
607 // XXX NOTE: must be called within readerMutex locking.
608 return readers.value(engine, 0);
611 QQuickPixmapReply *QQuickPixmapReader::getImage(QQuickPixmapData *data)
614 QQuickPixmapReply *reply = new QQuickPixmapReply(data);
615 reply->engineForReader = engine;
618 if (threadObject) threadObject->processJobs();
623 void QQuickPixmapReader::cancel(QQuickPixmapReply *reply)
626 if (reply->loading) {
627 cancelled.append(reply);
630 if (threadObject) threadObject->processJobs();
632 jobs.removeAll(reply);
638 void QQuickPixmapReader::run()
640 if (replyDownloadProgress == -1) {
641 const QMetaObject *nr = &QNetworkReply::staticMetaObject;
642 const QMetaObject *pr = &QQuickPixmapReply::staticMetaObject;
643 const QMetaObject *ir = &QQuickPixmapReaderThreadObject::staticMetaObject;
644 replyDownloadProgress = nr->indexOfSignal("downloadProgress(qint64,qint64)");
645 replyFinished = nr->indexOfSignal("finished()");
646 downloadProgress = pr->indexOfSignal("downloadProgress(qint64,qint64)");
647 threadNetworkRequestDone = ir->indexOfSlot("networkRequestDone()");
651 threadObject = new QQuickPixmapReaderThreadObject(this);
661 class QQuickPixmapKey
668 inline bool operator==(const QQuickPixmapKey &lhs, const QQuickPixmapKey &rhs)
670 return *lhs.size == *rhs.size && *lhs.url == *rhs.url;
673 inline uint qHash(const QQuickPixmapKey &key)
675 return qHash(*key.url) ^ key.size->width() ^ key.size->height();
680 class QQuickPixmapStore : public QObject
685 ~QQuickPixmapStore();
687 void unreferencePixmap(QQuickPixmapData *);
688 void referencePixmap(QQuickPixmapData *);
693 virtual void timerEvent(QTimerEvent *);
696 QHash<QQuickPixmapKey, QQuickPixmapData *> m_cache;
699 void shrinkCache(int remove);
701 QQuickPixmapData *m_unreferencedPixmaps;
702 QQuickPixmapData *m_lastUnreferencedPixmap;
704 int m_unreferencedCost;
708 Q_GLOBAL_STATIC(QQuickPixmapStore, pixmapStore);
711 QQuickPixmapStore::QQuickPixmapStore()
712 : m_unreferencedPixmaps(0), m_lastUnreferencedPixmap(0), m_unreferencedCost(0), m_timerId(-1), m_destroying(false)
716 QQuickPixmapStore::~QQuickPixmapStore()
720 int leakedPixmaps = 0;
721 QList<QQuickPixmapData*> cachedData = m_cache.values();
723 // Prevent unreferencePixmap() from assuming it needs to kick
724 // off the cache expiry timer, as we're shrinking the cache
725 // manually below after releasing all the pixmaps.
728 // unreference all (leaked) pixmaps
729 foreach (QQuickPixmapData* pixmap, cachedData) {
730 int currRefCount = pixmap->refCount;
733 while (currRefCount > 0) {
740 // free all unreferenced pixmaps
741 while (m_lastUnreferencedPixmap) {
746 qDebug("Number of leaked pixmaps: %i", leakedPixmaps);
749 void QQuickPixmapStore::unreferencePixmap(QQuickPixmapData *data)
751 Q_ASSERT(data->prevUnreferenced == 0);
752 Q_ASSERT(data->prevUnreferencedPtr == 0);
753 Q_ASSERT(data->nextUnreferenced == 0);
755 data->nextUnreferenced = m_unreferencedPixmaps;
756 data->prevUnreferencedPtr = &m_unreferencedPixmaps;
757 if (!m_destroying) // the texture factories may have been cleaned up already.
758 m_unreferencedCost += data->cost();
760 m_unreferencedPixmaps = data;
761 if (m_unreferencedPixmaps->nextUnreferenced) {
762 m_unreferencedPixmaps->nextUnreferenced->prevUnreferenced = m_unreferencedPixmaps;
763 m_unreferencedPixmaps->nextUnreferenced->prevUnreferencedPtr = &m_unreferencedPixmaps->nextUnreferenced;
766 if (!m_lastUnreferencedPixmap)
767 m_lastUnreferencedPixmap = data;
769 shrinkCache(-1); // Shrink the cache incase it has become larger than cache_limit
771 if (m_timerId == -1 && m_unreferencedPixmaps && !m_destroying)
772 m_timerId = startTimer(CACHE_EXPIRE_TIME * 1000);
775 void QQuickPixmapStore::referencePixmap(QQuickPixmapData *data)
777 Q_ASSERT(data->prevUnreferencedPtr);
779 *data->prevUnreferencedPtr = data->nextUnreferenced;
780 if (data->nextUnreferenced) {
781 data->nextUnreferenced->prevUnreferencedPtr = data->prevUnreferencedPtr;
782 data->nextUnreferenced->prevUnreferenced = data->prevUnreferenced;
784 if (m_lastUnreferencedPixmap == data)
785 m_lastUnreferencedPixmap = data->prevUnreferenced;
787 data->nextUnreferenced = 0;
788 data->prevUnreferencedPtr = 0;
789 data->prevUnreferenced = 0;
791 m_unreferencedCost -= data->cost();
794 void QQuickPixmapStore::shrinkCache(int remove)
796 while ((remove > 0 || m_unreferencedCost > cache_limit) && m_lastUnreferencedPixmap) {
797 QQuickPixmapData *data = m_lastUnreferencedPixmap;
798 Q_ASSERT(data->nextUnreferenced == 0);
800 *data->prevUnreferencedPtr = 0;
801 m_lastUnreferencedPixmap = data->prevUnreferenced;
802 data->prevUnreferencedPtr = 0;
803 data->prevUnreferenced = 0;
806 remove -= data->cost();
807 m_unreferencedCost -= data->cost();
809 data->removeFromCache();
814 void QQuickPixmapStore::timerEvent(QTimerEvent *)
816 int removalCost = m_unreferencedCost / CACHE_REMOVAL_FRACTION;
818 shrinkCache(removalCost);
820 if (m_unreferencedPixmaps == 0) {
821 killTimer(m_timerId);
826 void QQuickPixmapStore::purgeCache()
828 shrinkCache(m_unreferencedCost);
831 void QQuickPixmap::purgeCache()
833 pixmapStore()->purgeCache();
836 QQuickPixmapReply::QQuickPixmapReply(QQuickPixmapData *d)
837 : data(d), engineForReader(0), requestSize(d->requestSize), url(d->url), loading(false), redirectCount(0)
839 if (finishedIndex == -1) {
840 finishedIndex = QQuickPixmapReply::staticMetaObject.indexOfSignal("finished()");
841 downloadProgressIndex = QQuickPixmapReply::staticMetaObject.indexOfSignal("downloadProgress(qint64,qint64)");
845 QQuickPixmapReply::~QQuickPixmapReply()
849 bool QQuickPixmapReply::event(QEvent *event)
851 if (event->type() == QEvent::User) {
854 Event *de = static_cast<Event *>(event);
855 data->pixmapStatus = (de->error == NoError) ? QQuickPixmap::Ready : QQuickPixmap::Error;
857 if (data->pixmapStatus == QQuickPixmap::Ready) {
858 data->textureFactory = de->textureFactory;
859 data->implicitSize = de->implicitSize;
861 data->errorString = de->errorString;
862 data->removeFromCache(); // We don't continue to cache error'd pixmaps
872 return QObject::event(event);
876 int QQuickPixmapData::cost() const
879 return textureFactory->textureByteCount();
883 void QQuickPixmapData::addref()
886 if (prevUnreferencedPtr)
887 pixmapStore()->referencePixmap(this);
890 void QQuickPixmapData::release()
892 Q_ASSERT(refCount > 0);
896 QQuickPixmapReply *cancelReply = reply;
899 QQuickPixmapReader::readerMutex.lock();
900 QQuickPixmapReader *reader = QQuickPixmapReader::existingInstance(cancelReply->engineForReader);
902 reader->cancel(cancelReply);
903 QQuickPixmapReader::readerMutex.unlock();
906 if (pixmapStatus == QQuickPixmap::Ready) {
907 pixmapStore()->unreferencePixmap(this);
915 void QQuickPixmapData::addToCache()
918 QQuickPixmapKey key = { &url, &requestSize };
919 pixmapStore()->m_cache.insert(key, this);
924 void QQuickPixmapData::removeFromCache()
927 QQuickPixmapKey key = { &url, &requestSize };
928 pixmapStore()->m_cache.remove(key);
933 static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, QQmlEngine *engine, const QUrl &url, const QSize &requestSize, bool *ok)
935 if (url.scheme() == QLatin1String("image")) {
938 QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
939 QQuickImageProvider *provider = static_cast<QQuickImageProvider *>(engine->imageProvider(imageProviderId(url)));
941 imageType = provider->imageType();
944 case QQuickImageProvider::Invalid:
945 return new QQuickPixmapData(declarativePixmap, url, requestSize,
946 QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString()));
947 case QQuickImageProvider::Texture:
949 QQuickTextureFactory *texture = provider->requestTexture(imageId(url), &readSize, requestSize);
952 return new QQuickPixmapData(declarativePixmap, url, texture, readSize, requestSize);
956 case QQuickImageProvider::Image:
958 QImage image = provider->requestImage(imageId(url), &readSize, requestSize);
959 if (!image.isNull()) {
961 return new QQuickPixmapData(declarativePixmap, url, textureFactoryForImage(image), readSize, requestSize);
964 case QQuickImageProvider::Pixmap:
966 QPixmap pixmap = provider->requestPixmap(imageId(url), &readSize, requestSize);
967 if (!pixmap.isNull()) {
969 return new QQuickPixmapData(declarativePixmap, url, textureFactoryForImage(pixmap.toImage()), readSize, requestSize);
974 // provider has bad image type, or provider returned null image
975 return new QQuickPixmapData(declarativePixmap, url, requestSize,
976 QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString()));
979 QString localFile = QQmlFile::urlToLocalFileOrQrc(url);
980 if (localFile.isEmpty())
987 if (f.open(QIODevice::ReadOnly)) {
990 if (readImage(url, &f, &image, &errorString, &readSize, requestSize)) {
992 return new QQuickPixmapData(declarativePixmap, url, textureFactoryForImage(image), readSize, requestSize);
994 errorString = QQuickPixmap::tr("Invalid image data: %1").arg(url.toString());
997 errorString = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
999 return new QQuickPixmapData(declarativePixmap, url, requestSize, errorString);
1003 struct QQuickPixmapNull {
1007 Q_GLOBAL_STATIC(QQuickPixmapNull, nullPixmap);
1009 QQuickPixmap::QQuickPixmap()
1014 QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url)
1020 QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QSize &size)
1023 load(engine, url, size);
1026 QQuickPixmap::~QQuickPixmap()
1029 d->declarativePixmaps.remove(this);
1035 bool QQuickPixmap::isNull() const
1040 bool QQuickPixmap::isReady() const
1042 return status() == Ready;
1045 bool QQuickPixmap::isError() const
1047 return status() == Error;
1050 bool QQuickPixmap::isLoading() const
1052 return status() == Loading;
1055 QString QQuickPixmap::error() const
1058 return d->errorString;
1063 QQuickPixmap::Status QQuickPixmap::status() const
1066 return d->pixmapStatus;
1071 const QUrl &QQuickPixmap::url() const
1076 return nullPixmap()->url;
1079 const QSize &QQuickPixmap::implicitSize() const
1082 return d->implicitSize;
1084 return nullPixmap()->size;
1087 const QSize &QQuickPixmap::requestSize() const
1090 return d->requestSize;
1092 return nullPixmap()->size;
1095 QQuickTextureFactory *QQuickPixmap::textureFactory() const
1098 return d->textureFactory;
1103 QImage QQuickPixmap::image() const
1105 if (d && d->textureFactory)
1106 return d->textureFactory->image();
1110 void QQuickPixmap::setImage(const QImage &p)
1115 d = new QQuickPixmapData(this, textureFactoryForImage(p));
1118 int QQuickPixmap::width() const
1120 if (d && d->textureFactory)
1121 return d->textureFactory->textureSize().width();
1126 int QQuickPixmap::height() const
1128 if (d && d->textureFactory)
1129 return d->textureFactory->textureSize().height();
1134 QRect QQuickPixmap::rect() const
1136 if (d && d->textureFactory)
1137 return QRect(QPoint(), d->textureFactory->textureSize());
1142 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url)
1144 load(engine, url, QSize(), QQuickPixmap::Cache);
1147 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, QQuickPixmap::Options options)
1149 load(engine, url, QSize(), options);
1152 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &size)
1154 load(engine, url, size, QQuickPixmap::Cache);
1157 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &requestSize, QQuickPixmap::Options options)
1160 d->declarativePixmaps.remove(this);
1165 QQuickPixmapKey key = { &url, &requestSize };
1166 QQuickPixmapStore *store = pixmapStore();
1168 QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.find(key);
1170 if (iter == store->m_cache.end()) {
1171 if (options & QQuickPixmap::Asynchronous) {
1172 // pixmaps can only be loaded synchronously
1173 if (url.scheme() == QLatin1String("image")) {
1174 QQuickImageProvider *provider = static_cast<QQuickImageProvider *>(engine->imageProvider(imageProviderId(url)));
1175 if (provider && provider->imageType() == QQuickImageProvider::Pixmap) {
1176 options &= ~QQuickPixmap::Asynchronous;
1181 if (!(options & QQuickPixmap::Asynchronous)) {
1183 d = createPixmapDataSync(this, engine, url, requestSize, &ok);
1185 if (options & QQuickPixmap::Cache)
1189 if (d) // loadable, but encountered error while loading
1196 d = new QQuickPixmapData(this, url, requestSize);
1197 if (options & QQuickPixmap::Cache)
1200 QQuickPixmapReader::readerMutex.lock();
1201 d->reply = QQuickPixmapReader::instance(engine)->getImage(d);
1202 QQuickPixmapReader::readerMutex.unlock();
1206 d->declarativePixmaps.insert(this);
1210 void QQuickPixmap::clear()
1213 d->declarativePixmaps.remove(this);
1219 void QQuickPixmap::clear(QObject *obj)
1223 QObject::disconnect(d->reply, 0, obj, 0);
1224 d->declarativePixmaps.remove(this);
1230 bool QQuickPixmap::connectFinished(QObject *object, const char *method)
1232 if (!d || !d->reply) {
1233 qWarning("QQuickPixmap: connectFinished() called when not loading.");
1237 return QObject::connect(d->reply, SIGNAL(finished()), object, method);
1240 bool QQuickPixmap::connectFinished(QObject *object, int method)
1242 if (!d || !d->reply) {
1243 qWarning("QQuickPixmap: connectFinished() called when not loading.");
1247 return QMetaObject::connect(d->reply, QQuickPixmapReply::finishedIndex, object, method);
1250 bool QQuickPixmap::connectDownloadProgress(QObject *object, const char *method)
1252 if (!d || !d->reply) {
1253 qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
1257 return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), object, method);
1260 bool QQuickPixmap::connectDownloadProgress(QObject *object, int method)
1262 if (!d || !d->reply) {
1263 qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
1267 return QMetaObject::connect(d->reply, QQuickPixmapReply::downloadProgressIndex, object, method);
1272 #include <qquickpixmapcache.moc>