1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qquickpixmapcache_p.h"
43 #include <qqmlnetworkaccessmanagerfactory.h>
44 #include <qquickimageprovider.h>
46 #include <qqmlengine.h>
47 #include <private/qqmlglobal_p.h>
48 #include <private/qqmlengine_p.h>
50 #include <QtQuick/private/qsgtexture_p.h>
51 #include <QtQuick/private/qsgcontext_p.h>
53 #include <QCoreApplication>
54 #include <QImageReader>
56 #include <QNetworkReply>
57 #include <QPixmapCache>
61 #include <QMutexLocker>
62 #include <QWaitCondition>
64 #include <QWaitCondition>
65 #include <QtCore/qdebug.h>
66 #include <private/qobject_p.h>
69 #include <QMetaMethod>
71 #define IMAGEREQUEST_MAX_REQUEST_COUNT 8
72 #define IMAGEREQUEST_MAX_REDIRECT_RECURSION 16
73 #define CACHE_EXPIRE_TIME 30
74 #define CACHE_REMOVAL_FRACTION 4
78 // The cache limit describes the maximum "junk" in the cache.
79 static int cache_limit = 2048 * 1024; // 2048 KB cache limit for embedded in qpixmapcache.cpp
81 static inline QString imageProviderId(const QUrl &url)
86 static inline QString imageId(const QUrl &url)
88 return url.toString(QUrl::RemoveScheme | QUrl::RemoveAuthority).mid(1);
91 QSGTexture *QQuickDefaultTextureFactory::createTexture(QQuickWindow *) const
93 QSGPlainTexture *t = new QSGPlainTexture();
98 static QQuickTextureFactory *textureFactoryForImage(const QImage &image)
102 QQuickTextureFactory *texture = QSGContext::createTextureFactoryFromImage(image);
105 return new QQuickDefaultTextureFactory(image);
108 class QQuickPixmapReader;
109 class QQuickPixmapData;
110 class QQuickPixmapReply : public QObject
114 enum ReadError { NoError, Loading, Decoding };
116 QQuickPixmapReply(QQuickPixmapData *);
117 ~QQuickPixmapReply();
119 QQuickPixmapData *data;
120 QQmlEngine *engineForReader; // always access reader inside readerMutex
127 class Event : public QEvent {
129 Event(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory);
134 QQuickTextureFactory *textureFactory;
136 void postReply(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory);
141 void downloadProgress(qint64, qint64);
144 bool event(QEvent *event);
147 Q_DISABLE_COPY(QQuickPixmapReply)
150 static int finishedIndex;
151 static int downloadProgressIndex;
154 class QQuickPixmapReaderThreadObject : public QObject {
157 QQuickPixmapReaderThreadObject(QQuickPixmapReader *);
159 virtual bool event(QEvent *e);
161 void networkRequestDone();
163 QQuickPixmapReader *reader;
166 class QQuickPixmapData;
167 class QQuickPixmapReader : public QThread
171 QQuickPixmapReader(QQmlEngine *eng);
172 ~QQuickPixmapReader();
174 QQuickPixmapReply *getImage(QQuickPixmapData *);
175 void cancel(QQuickPixmapReply *rep);
177 static QQuickPixmapReader *instance(QQmlEngine *engine);
178 static QQuickPixmapReader *existingInstance(QQmlEngine *engine);
184 friend class QQuickPixmapReaderThreadObject;
186 void processJob(QQuickPixmapReply *, const QUrl &, const QSize &);
187 void networkRequestDone(QNetworkReply *);
189 QList<QQuickPixmapReply*> jobs;
190 QList<QQuickPixmapReply*> cancelled;
192 QObject *eventLoopQuitHack;
195 QQuickPixmapReaderThreadObject *threadObject;
196 QWaitCondition waitCondition;
198 QNetworkAccessManager *networkAccessManager();
199 QNetworkAccessManager *accessManager;
201 QHash<QNetworkReply*,QQuickPixmapReply*> replies;
203 static int replyDownloadProgress;
204 static int replyFinished;
205 static int downloadProgress;
206 static int threadNetworkRequestDone;
207 static QHash<QQmlEngine *,QQuickPixmapReader*> readers;
209 static QMutex readerMutex;
212 class QQuickPixmapData
215 QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QSize &s, const QString &e)
216 : refCount(1), inCache(false), pixmapStatus(QQuickPixmap::Error),
217 url(u), errorString(e), requestSize(s), textureFactory(0), reply(0), prevUnreferenced(0),
218 prevUnreferencedPtr(0), nextUnreferenced(0)
220 declarativePixmaps.insert(pixmap);
223 QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QSize &r)
224 : refCount(1), inCache(false), pixmapStatus(QQuickPixmap::Loading),
225 url(u), requestSize(r), textureFactory(0), reply(0), prevUnreferenced(0), prevUnreferencedPtr(0),
228 declarativePixmaps.insert(pixmap);
231 QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, QQuickTextureFactory *texture, const QSize &s, const QSize &r)
232 : refCount(1), inCache(false), pixmapStatus(QQuickPixmap::Ready),
233 url(u), implicitSize(s), requestSize(r), textureFactory(texture), reply(0), prevUnreferenced(0),
234 prevUnreferencedPtr(0), nextUnreferenced(0)
236 declarativePixmaps.insert(pixmap);
239 QQuickPixmapData(QQuickPixmap *pixmap, QQuickTextureFactory *texture)
240 : refCount(1), inCache(false), pixmapStatus(QQuickPixmap::Ready),
241 textureFactory(texture), reply(0), prevUnreferenced(0),
242 prevUnreferencedPtr(0), nextUnreferenced(0)
245 requestSize = implicitSize = texture->textureSize();
246 declarativePixmaps.insert(pixmap);
251 while (!declarativePixmaps.isEmpty()) {
252 QQuickPixmap *referencer = declarativePixmaps.first();
253 declarativePixmaps.remove(referencer);
256 delete textureFactory;
263 void removeFromCache();
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 const bool force_scale = imgio.format() == "svg" || imgio.format() == "svgz";
326 if (requestSize.width() > 0 || requestSize.height() > 0) {
327 QSize s = imgio.size();
329 if (requestSize.width() && (force_scale || requestSize.width() < s.width())) {
330 ratio = qreal(requestSize.width())/s.width();
332 if (requestSize.height() && (force_scale || requestSize.height() < s.height())) {
333 qreal hr = qreal(requestSize.height())/s.height();
334 if (ratio == 0.0 || hr < ratio)
338 s.setHeight(qRound(s.height() * ratio));
339 s.setWidth(qRound(s.width() * ratio));
340 imgio.setScaledSize(s);
345 *impsize = imgio.size();
347 if (imgio.read(image)) {
348 if (impsize && impsize->width() < 0)
349 *impsize = image->size();
353 *errorString = QQuickPixmap::tr("Error decoding: %1: %2").arg(url.toString())
354 .arg(imgio.errorString());
359 QQuickPixmapReader::QQuickPixmapReader(QQmlEngine *eng)
360 : QThread(eng), engine(eng), threadObject(0), accessManager(0)
362 eventLoopQuitHack = new QObject;
363 eventLoopQuitHack->moveToThread(this);
364 connect(eventLoopQuitHack, SIGNAL(destroyed(QObject*)), SLOT(quit()), Qt::DirectConnection);
365 start(QThread::LowestPriority);
368 QQuickPixmapReader::~QQuickPixmapReader()
371 readers.remove(engine);
372 readerMutex.unlock();
375 // manually cancel all outstanding jobs.
376 foreach (QQuickPixmapReply *reply, jobs) {
377 if (reply->data && reply->data->reply == reply)
378 reply->data->reply = 0;
382 QList<QQuickPixmapReply*> activeJobs = replies.values();
383 foreach (QQuickPixmapReply *reply, activeJobs) {
384 if (reply->loading) {
385 cancelled.append(reply);
389 if (threadObject) threadObject->processJobs();
392 eventLoopQuitHack->deleteLater();
396 void QQuickPixmapReader::networkRequestDone(QNetworkReply *reply)
398 QQuickPixmapReply *job = replies.take(reply);
401 job->redirectCount++;
402 if (job->redirectCount < IMAGEREQUEST_MAX_REDIRECT_RECURSION) {
403 QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
404 if (redirect.isValid()) {
405 QUrl url = reply->url().resolved(redirect.toUrl());
406 QNetworkRequest req(url);
407 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
409 reply->deleteLater();
410 reply = networkAccessManager()->get(req);
412 QMetaObject::connect(reply, replyDownloadProgress, job, downloadProgress);
413 QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
415 replies.insert(reply, job);
421 QQuickPixmapReply::ReadError error = QQuickPixmapReply::NoError;
424 if (reply->error()) {
425 error = QQuickPixmapReply::Loading;
426 errorString = reply->errorString();
428 QByteArray all = reply->readAll();
430 buff.open(QIODevice::ReadOnly);
431 if (!readImage(reply->url(), &buff, &image, &errorString, &readSize, job->requestSize))
432 error = QQuickPixmapReply::Decoding;
434 // send completion event to the QQuickPixmapReply
436 if (!cancelled.contains(job))
437 job->postReply(error, errorString, readSize, textureFactoryForImage(image));
440 reply->deleteLater();
442 // kick off event loop again incase we have dropped below max request count
443 threadObject->processJobs();
446 QQuickPixmapReaderThreadObject::QQuickPixmapReaderThreadObject(QQuickPixmapReader *i)
451 void QQuickPixmapReaderThreadObject::processJobs()
453 QCoreApplication::postEvent(this, new QEvent(QEvent::User));
456 bool QQuickPixmapReaderThreadObject::event(QEvent *e)
458 if (e->type() == QEvent::User) {
459 reader->processJobs();
462 return QObject::event(e);
466 void QQuickPixmapReaderThreadObject::networkRequestDone()
468 QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
469 reader->networkRequestDone(reply);
472 void QQuickPixmapReader::processJobs()
474 QMutexLocker locker(&mutex);
477 if (cancelled.isEmpty() && (jobs.isEmpty() || replies.count() >= IMAGEREQUEST_MAX_REQUEST_COUNT))
478 return; // Nothing else to do
480 // Clean cancelled jobs
481 if (cancelled.count()) {
482 for (int i = 0; i < cancelled.count(); ++i) {
483 QQuickPixmapReply *job = cancelled.at(i);
484 QNetworkReply *reply = replies.key(job, 0);
485 if (reply && reply->isRunning()) {
486 // cancel any jobs already started
487 replies.remove(reply);
490 // deleteLater, since not owned by this thread
496 if (!jobs.isEmpty() && replies.count() < IMAGEREQUEST_MAX_REQUEST_COUNT) {
497 QQuickPixmapReply *runningJob = jobs.takeLast();
498 runningJob->loading = true;
500 QUrl url = runningJob->url;
501 QSize requestSize = runningJob->requestSize;
503 processJob(runningJob, url, requestSize);
509 void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &url,
510 const QSize &requestSize)
513 if (url.scheme() == QLatin1String("image")) {
514 // Use QQuickImageProvider
517 QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
518 QQuickImageProvider *provider = static_cast<QQuickImageProvider *>(engine->imageProvider(imageProviderId(url)));
520 imageType = provider->imageType();
522 if (imageType == QQuickImageProvider::Invalid) {
523 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::Loading;
524 QString errorStr = QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString());
527 if (!cancelled.contains(runningJob))
528 runningJob->postReply(errorCode, errorStr, readSize, textureFactoryForImage(image));
530 } else if (imageType == QQuickImageProvider::Image) {
531 QImage image = provider->requestImage(imageId(url), &readSize, requestSize);
532 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
534 if (image.isNull()) {
535 errorCode = QQuickPixmapReply::Loading;
536 errorStr = QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString());
539 if (!cancelled.contains(runningJob))
540 runningJob->postReply(errorCode, errorStr, readSize, textureFactoryForImage(image));
543 QQuickTextureFactory *t = provider->requestTexture(imageId(url), &readSize, requestSize);
544 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
547 errorCode = QQuickPixmapReply::Loading;
548 errorStr = QQuickPixmap::tr("Failed to get texture from provider: %1").arg(url.toString());
551 if (!cancelled.contains(runningJob))
552 runningJob->postReply(errorCode, errorStr, readSize, t);
558 QString lf = QQmlFile::urlToLocalFileOrQrc(url);
560 // Image is local - load/decode immediately
562 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
566 if (f.open(QIODevice::ReadOnly)) {
567 if (!readImage(url, &f, &image, &errorStr, &readSize, requestSize))
568 errorCode = QQuickPixmapReply::Loading;
570 errorStr = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
571 errorCode = QQuickPixmapReply::Loading;
574 if (!cancelled.contains(runningJob))
575 runningJob->postReply(errorCode, errorStr, readSize, textureFactoryForImage(image));
579 QNetworkRequest req(url);
580 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
581 QNetworkReply *reply = networkAccessManager()->get(req);
583 QMetaObject::connect(reply, replyDownloadProgress, runningJob, downloadProgress);
584 QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
586 replies.insert(reply, runningJob);
591 QQuickPixmapReader *QQuickPixmapReader::instance(QQmlEngine *engine)
593 // XXX NOTE: must be called within readerMutex locking.
594 QQuickPixmapReader *reader = readers.value(engine);
596 reader = new QQuickPixmapReader(engine);
597 readers.insert(engine, reader);
603 QQuickPixmapReader *QQuickPixmapReader::existingInstance(QQmlEngine *engine)
605 // XXX NOTE: must be called within readerMutex locking.
606 return readers.value(engine, 0);
609 QQuickPixmapReply *QQuickPixmapReader::getImage(QQuickPixmapData *data)
612 QQuickPixmapReply *reply = new QQuickPixmapReply(data);
613 reply->engineForReader = engine;
616 if (threadObject) threadObject->processJobs();
621 void QQuickPixmapReader::cancel(QQuickPixmapReply *reply)
624 if (reply->loading) {
625 cancelled.append(reply);
628 if (threadObject) threadObject->processJobs();
630 jobs.removeAll(reply);
636 void QQuickPixmapReader::run()
638 if (replyDownloadProgress == -1) {
639 replyDownloadProgress = QMetaMethod::fromSignal(&QNetworkReply::downloadProgress).methodIndex();
640 replyFinished = QMetaMethod::fromSignal(&QNetworkReply::finished).methodIndex();
641 downloadProgress = QMetaMethod::fromSignal(&QQuickPixmapReply::downloadProgress).methodIndex();
642 const QMetaObject *ir = &QQuickPixmapReaderThreadObject::staticMetaObject;
643 threadNetworkRequestDone = ir->indexOfSlot("networkRequestDone()");
647 threadObject = new QQuickPixmapReaderThreadObject(this);
657 class QQuickPixmapKey
664 inline bool operator==(const QQuickPixmapKey &lhs, const QQuickPixmapKey &rhs)
666 return *lhs.size == *rhs.size && *lhs.url == *rhs.url;
669 inline uint qHash(const QQuickPixmapKey &key)
671 return qHash(*key.url) ^ key.size->width() ^ key.size->height();
676 class QQuickPixmapStore : public QObject
681 ~QQuickPixmapStore();
683 void unreferencePixmap(QQuickPixmapData *);
684 void referencePixmap(QQuickPixmapData *);
689 virtual void timerEvent(QTimerEvent *);
692 QHash<QQuickPixmapKey, QQuickPixmapData *> m_cache;
695 void shrinkCache(int remove);
697 QQuickPixmapData *m_unreferencedPixmaps;
698 QQuickPixmapData *m_lastUnreferencedPixmap;
700 int m_unreferencedCost;
704 Q_GLOBAL_STATIC(QQuickPixmapStore, pixmapStore);
707 QQuickPixmapStore::QQuickPixmapStore()
708 : m_unreferencedPixmaps(0), m_lastUnreferencedPixmap(0), m_unreferencedCost(0), m_timerId(-1), m_destroying(false)
712 QQuickPixmapStore::~QQuickPixmapStore()
716 int leakedPixmaps = 0;
717 QList<QQuickPixmapData*> cachedData = m_cache.values();
719 // Prevent unreferencePixmap() from assuming it needs to kick
720 // off the cache expiry timer, as we're shrinking the cache
721 // manually below after releasing all the pixmaps.
724 // unreference all (leaked) pixmaps
725 foreach (QQuickPixmapData* pixmap, cachedData) {
726 int currRefCount = pixmap->refCount;
729 while (currRefCount > 0) {
736 // free all unreferenced pixmaps
737 while (m_lastUnreferencedPixmap) {
742 qDebug("Number of leaked pixmaps: %i", leakedPixmaps);
745 void QQuickPixmapStore::unreferencePixmap(QQuickPixmapData *data)
747 Q_ASSERT(data->prevUnreferenced == 0);
748 Q_ASSERT(data->prevUnreferencedPtr == 0);
749 Q_ASSERT(data->nextUnreferenced == 0);
751 data->nextUnreferenced = m_unreferencedPixmaps;
752 data->prevUnreferencedPtr = &m_unreferencedPixmaps;
753 if (!m_destroying) // the texture factories may have been cleaned up already.
754 m_unreferencedCost += data->cost();
756 m_unreferencedPixmaps = data;
757 if (m_unreferencedPixmaps->nextUnreferenced) {
758 m_unreferencedPixmaps->nextUnreferenced->prevUnreferenced = m_unreferencedPixmaps;
759 m_unreferencedPixmaps->nextUnreferenced->prevUnreferencedPtr = &m_unreferencedPixmaps->nextUnreferenced;
762 if (!m_lastUnreferencedPixmap)
763 m_lastUnreferencedPixmap = data;
765 shrinkCache(-1); // Shrink the cache incase it has become larger than cache_limit
767 if (m_timerId == -1 && m_unreferencedPixmaps && !m_destroying)
768 m_timerId = startTimer(CACHE_EXPIRE_TIME * 1000);
771 void QQuickPixmapStore::referencePixmap(QQuickPixmapData *data)
773 Q_ASSERT(data->prevUnreferencedPtr);
775 *data->prevUnreferencedPtr = data->nextUnreferenced;
776 if (data->nextUnreferenced) {
777 data->nextUnreferenced->prevUnreferencedPtr = data->prevUnreferencedPtr;
778 data->nextUnreferenced->prevUnreferenced = data->prevUnreferenced;
780 if (m_lastUnreferencedPixmap == data)
781 m_lastUnreferencedPixmap = data->prevUnreferenced;
783 data->nextUnreferenced = 0;
784 data->prevUnreferencedPtr = 0;
785 data->prevUnreferenced = 0;
787 m_unreferencedCost -= data->cost();
790 void QQuickPixmapStore::shrinkCache(int remove)
792 while ((remove > 0 || m_unreferencedCost > cache_limit) && m_lastUnreferencedPixmap) {
793 QQuickPixmapData *data = m_lastUnreferencedPixmap;
794 Q_ASSERT(data->nextUnreferenced == 0);
796 *data->prevUnreferencedPtr = 0;
797 m_lastUnreferencedPixmap = data->prevUnreferenced;
798 data->prevUnreferencedPtr = 0;
799 data->prevUnreferenced = 0;
802 remove -= data->cost();
803 m_unreferencedCost -= data->cost();
805 data->removeFromCache();
810 void QQuickPixmapStore::timerEvent(QTimerEvent *)
812 int removalCost = m_unreferencedCost / CACHE_REMOVAL_FRACTION;
814 shrinkCache(removalCost);
816 if (m_unreferencedPixmaps == 0) {
817 killTimer(m_timerId);
822 void QQuickPixmapStore::purgeCache()
824 shrinkCache(m_unreferencedCost);
827 void QQuickPixmap::purgeCache()
829 pixmapStore()->purgeCache();
832 QQuickPixmapReply::QQuickPixmapReply(QQuickPixmapData *d)
833 : data(d), engineForReader(0), requestSize(d->requestSize), url(d->url), loading(false), redirectCount(0)
835 if (finishedIndex == -1) {
836 finishedIndex = QMetaMethod::fromSignal(&QQuickPixmapReply::finished).methodIndex();
837 downloadProgressIndex = QMetaMethod::fromSignal(&QQuickPixmapReply::downloadProgress).methodIndex();
841 QQuickPixmapReply::~QQuickPixmapReply()
843 // note: this->data->reply must be set to zero if this->data->reply == this
844 // but it must be done within mutex locking, to be guaranteed to be safe.
847 bool QQuickPixmapReply::event(QEvent *event)
849 if (event->type() == QEvent::User) {
852 Event *de = static_cast<Event *>(event);
853 data->pixmapStatus = (de->error == NoError) ? QQuickPixmap::Ready : QQuickPixmap::Error;
855 if (data->pixmapStatus == QQuickPixmap::Ready) {
856 data->textureFactory = de->textureFactory;
857 data->implicitSize = de->implicitSize;
859 data->errorString = de->errorString;
860 data->removeFromCache(); // We don't continue to cache error'd pixmaps
870 return QObject::event(event);
874 int QQuickPixmapData::cost() const
877 return textureFactory->textureByteCount();
881 void QQuickPixmapData::addref()
884 if (prevUnreferencedPtr)
885 pixmapStore()->referencePixmap(this);
888 void QQuickPixmapData::release()
890 Q_ASSERT(refCount > 0);
894 QQuickPixmapReply *cancelReply = reply;
897 QQuickPixmapReader::readerMutex.lock();
898 QQuickPixmapReader *reader = QQuickPixmapReader::existingInstance(cancelReply->engineForReader);
900 reader->cancel(cancelReply);
901 QQuickPixmapReader::readerMutex.unlock();
904 if (pixmapStatus == QQuickPixmap::Ready) {
906 pixmapStore()->unreferencePixmap(this);
916 void QQuickPixmapData::addToCache()
919 QQuickPixmapKey key = { &url, &requestSize };
920 pixmapStore()->m_cache.insert(key, this);
925 void QQuickPixmapData::removeFromCache()
928 QQuickPixmapKey key = { &url, &requestSize };
929 pixmapStore()->m_cache.remove(key);
934 static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, QQmlEngine *engine, const QUrl &url, const QSize &requestSize, bool *ok)
936 if (url.scheme() == QLatin1String("image")) {
939 QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
940 QQuickImageProvider *provider = static_cast<QQuickImageProvider *>(engine->imageProvider(imageProviderId(url)));
942 imageType = provider->imageType();
945 case QQuickImageProvider::Invalid:
946 return new QQuickPixmapData(declarativePixmap, url, requestSize,
947 QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString()));
948 case QQuickImageProvider::Texture:
950 QQuickTextureFactory *texture = provider->requestTexture(imageId(url), &readSize, requestSize);
953 return new QQuickPixmapData(declarativePixmap, url, texture, readSize, requestSize);
957 case QQuickImageProvider::Image:
959 QImage image = provider->requestImage(imageId(url), &readSize, requestSize);
960 if (!image.isNull()) {
962 return new QQuickPixmapData(declarativePixmap, url, textureFactoryForImage(image), readSize, requestSize);
965 case QQuickImageProvider::Pixmap:
967 QPixmap pixmap = provider->requestPixmap(imageId(url), &readSize, requestSize);
968 if (!pixmap.isNull()) {
970 return new QQuickPixmapData(declarativePixmap, url, textureFactoryForImage(pixmap.toImage()), readSize, requestSize);
975 // provider has bad image type, or provider returned null image
976 return new QQuickPixmapData(declarativePixmap, url, requestSize,
977 QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString()));
980 QString localFile = QQmlFile::urlToLocalFileOrQrc(url);
981 if (localFile.isEmpty())
988 if (f.open(QIODevice::ReadOnly)) {
991 if (readImage(url, &f, &image, &errorString, &readSize, requestSize)) {
993 return new QQuickPixmapData(declarativePixmap, url, textureFactoryForImage(image), readSize, requestSize);
995 errorString = QQuickPixmap::tr("Invalid image data: %1").arg(url.toString());
998 errorString = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
1000 return new QQuickPixmapData(declarativePixmap, url, requestSize, errorString);
1004 struct QQuickPixmapNull {
1008 Q_GLOBAL_STATIC(QQuickPixmapNull, nullPixmap);
1010 QQuickPixmap::QQuickPixmap()
1015 QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url)
1021 QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QSize &size)
1024 load(engine, url, size);
1027 QQuickPixmap::~QQuickPixmap()
1030 d->declarativePixmaps.remove(this);
1036 bool QQuickPixmap::isNull() const
1041 bool QQuickPixmap::isReady() const
1043 return status() == Ready;
1046 bool QQuickPixmap::isError() const
1048 return status() == Error;
1051 bool QQuickPixmap::isLoading() const
1053 return status() == Loading;
1056 QString QQuickPixmap::error() const
1059 return d->errorString;
1064 QQuickPixmap::Status QQuickPixmap::status() const
1067 return d->pixmapStatus;
1072 const QUrl &QQuickPixmap::url() const
1077 return nullPixmap()->url;
1080 const QSize &QQuickPixmap::implicitSize() const
1083 return d->implicitSize;
1085 return nullPixmap()->size;
1088 const QSize &QQuickPixmap::requestSize() const
1091 return d->requestSize;
1093 return nullPixmap()->size;
1096 QQuickTextureFactory *QQuickPixmap::textureFactory() const
1099 return d->textureFactory;
1104 QImage QQuickPixmap::image() const
1106 if (d && d->textureFactory)
1107 return d->textureFactory->image();
1111 void QQuickPixmap::setImage(const QImage &p)
1116 d = new QQuickPixmapData(this, textureFactoryForImage(p));
1119 int QQuickPixmap::width() const
1121 if (d && d->textureFactory)
1122 return d->textureFactory->textureSize().width();
1127 int QQuickPixmap::height() const
1129 if (d && d->textureFactory)
1130 return d->textureFactory->textureSize().height();
1135 QRect QQuickPixmap::rect() const
1137 if (d && d->textureFactory)
1138 return QRect(QPoint(), d->textureFactory->textureSize());
1143 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url)
1145 load(engine, url, QSize(), QQuickPixmap::Cache);
1148 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, QQuickPixmap::Options options)
1150 load(engine, url, QSize(), options);
1153 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &size)
1155 load(engine, url, size, QQuickPixmap::Cache);
1158 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &requestSize, QQuickPixmap::Options options)
1161 d->declarativePixmaps.remove(this);
1166 QQuickPixmapKey key = { &url, &requestSize };
1167 QQuickPixmapStore *store = pixmapStore();
1169 QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end();
1171 // If Cache is disabled, the pixmap will always be loaded, even if there is an existing
1173 if (options & QQuickPixmap::Cache)
1174 iter = store->m_cache.find(key);
1176 if (iter == store->m_cache.end()) {
1177 if (url.scheme() == QLatin1String("image")) {
1178 if (QQuickImageProvider *provider = static_cast<QQuickImageProvider *>(engine->imageProvider(imageProviderId(url)))) {
1179 if (provider->imageType() == QQuickImageProvider::Pixmap) {
1180 // pixmaps can only be loaded synchronously
1181 options &= ~QQuickPixmap::Asynchronous;
1182 } else if (provider->flags() & QQuickImageProvider::ForceAsynchronousImageLoading) {
1183 options |= QQuickPixmap::Asynchronous;
1188 if (!(options & QQuickPixmap::Asynchronous)) {
1190 d = createPixmapDataSync(this, engine, url, requestSize, &ok);
1192 if (options & QQuickPixmap::Cache)
1196 if (d) // loadable, but encountered error while loading
1203 d = new QQuickPixmapData(this, url, requestSize);
1204 if (options & QQuickPixmap::Cache)
1207 QQuickPixmapReader::readerMutex.lock();
1208 d->reply = QQuickPixmapReader::instance(engine)->getImage(d);
1209 QQuickPixmapReader::readerMutex.unlock();
1213 d->declarativePixmaps.insert(this);
1217 void QQuickPixmap::clear()
1220 d->declarativePixmaps.remove(this);
1226 void QQuickPixmap::clear(QObject *obj)
1230 QObject::disconnect(d->reply, 0, obj, 0);
1231 d->declarativePixmaps.remove(this);
1237 bool QQuickPixmap::connectFinished(QObject *object, const char *method)
1239 if (!d || !d->reply) {
1240 qWarning("QQuickPixmap: connectFinished() called when not loading.");
1244 return QObject::connect(d->reply, SIGNAL(finished()), object, method);
1247 bool QQuickPixmap::connectFinished(QObject *object, int method)
1249 if (!d || !d->reply) {
1250 qWarning("QQuickPixmap: connectFinished() called when not loading.");
1254 return QMetaObject::connect(d->reply, QQuickPixmapReply::finishedIndex, object, method);
1257 bool QQuickPixmap::connectDownloadProgress(QObject *object, const char *method)
1259 if (!d || !d->reply) {
1260 qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
1264 return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), object, method);
1267 bool QQuickPixmap::connectDownloadProgress(QObject *object, int method)
1269 if (!d || !d->reply) {
1270 qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
1274 return QMetaObject::connect(d->reply, QQuickPixmapReply::downloadProgressIndex, object, method);
1279 #include <qquickpixmapcache.moc>