Merge remote-tracking branch 'origin/master' into api_changes
[profile/ivi/qtdeclarative.git] / src / quick / util / qquickpixmapcache.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
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.
16 **
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.
20 **
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.
28 **
29 ** Other Usage
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.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qquickpixmapcache_p.h"
43 #include <qqmlnetworkaccessmanagerfactory.h>
44 #include <qquickimageprovider.h>
45
46 #include <qqmlengine.h>
47 #include <private/qqmlglobal_p.h>
48 #include <private/qqmlengine_p.h>
49
50 #include <QtQuick/private/qsgtexture_p.h>
51 #include <QtQuick/private/qsgcontext_p.h>
52
53 #include <QCoreApplication>
54 #include <QImageReader>
55 #include <QHash>
56 #include <QNetworkReply>
57 #include <QPixmapCache>
58 #include <QFile>
59 #include <QThread>
60 #include <QMutex>
61 #include <QMutexLocker>
62 #include <QWaitCondition>
63 #include <QBuffer>
64 #include <QWaitCondition>
65 #include <QtCore/qdebug.h>
66 #include <private/qobject_p.h>
67 #include <QSslError>
68
69 #define IMAGEREQUEST_MAX_REQUEST_COUNT       8
70 #define IMAGEREQUEST_MAX_REDIRECT_RECURSION 16
71 #define CACHE_EXPIRE_TIME 30
72 #define CACHE_REMOVAL_FRACTION 4
73
74 QT_BEGIN_NAMESPACE
75
76 // The cache limit describes the maximum "junk" in the cache.
77 static int cache_limit = 2048 * 1024; // 2048 KB cache limit for embedded in qpixmapcache.cpp
78
79 static inline QString imageProviderId(const QUrl &url)
80 {
81     return url.host();
82 }
83
84 static inline QString imageId(const QUrl &url)
85 {
86     return url.toString(QUrl::RemoveScheme | QUrl::RemoveAuthority).mid(1);
87 }
88
89 QSGTexture *QQuickDefaultTextureFactory::createTexture(QQuickCanvas *) const
90 {
91     QSGPlainTexture *t = new QSGPlainTexture();
92     t->setImage(im);
93     return t;
94 }
95
96 static QQuickTextureFactory *textureFactoryForImage(const QImage &image)
97 {
98     if (image.isNull())
99         return 0;
100     QQuickTextureFactory *texture = QSGContext::createTextureFactoryFromImage(image);
101     if (texture)
102         return texture;
103     return new QQuickDefaultTextureFactory(image);
104 }
105
106 class QQuickPixmapReader;
107 class QQuickPixmapData;
108 class QQuickPixmapReply : public QObject
109 {
110     Q_OBJECT
111 public:
112     enum ReadError { NoError, Loading, Decoding };
113
114     QQuickPixmapReply(QQuickPixmapData *);
115     ~QQuickPixmapReply();
116
117     QQuickPixmapData *data;
118     QQmlEngine *engineForReader; // always access reader inside readerMutex
119     QSize requestSize;
120     QUrl url;
121
122     bool loading;
123     int redirectCount;
124
125     class Event : public QEvent {
126     public:
127         Event(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory);
128
129         ReadError error;
130         QString errorString;
131         QSize implicitSize;
132         QQuickTextureFactory *textureFactory;
133     };
134     void postReply(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory);
135
136
137 Q_SIGNALS:
138     void finished();
139     void downloadProgress(qint64, qint64);
140
141 protected:
142     bool event(QEvent *event);
143
144 private:
145     Q_DISABLE_COPY(QQuickPixmapReply)
146
147 public:
148     static int finishedIndex;
149     static int downloadProgressIndex;
150 };
151
152 class QQuickPixmapReaderThreadObject : public QObject {
153     Q_OBJECT
154 public:
155     QQuickPixmapReaderThreadObject(QQuickPixmapReader *);
156     void processJobs();
157     virtual bool event(QEvent *e);
158 private slots:
159     void networkRequestDone();
160 private:
161     QQuickPixmapReader *reader;
162 };
163
164 class QQuickPixmapData;
165 class QQuickPixmapReader : public QThread
166 {
167     Q_OBJECT
168 public:
169     QQuickPixmapReader(QQmlEngine *eng);
170     ~QQuickPixmapReader();
171
172     QQuickPixmapReply *getImage(QQuickPixmapData *);
173     void cancel(QQuickPixmapReply *rep);
174
175     static QQuickPixmapReader *instance(QQmlEngine *engine);
176     static QQuickPixmapReader *existingInstance(QQmlEngine *engine);
177
178 protected:
179     void run();
180
181 private:
182     friend class QQuickPixmapReaderThreadObject;
183     void processJobs();
184     void processJob(QQuickPixmapReply *, const QUrl &, const QSize &);
185     void networkRequestDone(QNetworkReply *);
186
187     QList<QQuickPixmapReply*> jobs;
188     QList<QQuickPixmapReply*> cancelled;
189     QQmlEngine *engine;
190     QObject *eventLoopQuitHack;
191
192     QMutex mutex;
193     QQuickPixmapReaderThreadObject *threadObject;
194     QWaitCondition waitCondition;
195
196     QNetworkAccessManager *networkAccessManager();
197     QNetworkAccessManager *accessManager;
198
199     QHash<QNetworkReply*,QQuickPixmapReply*> replies;
200
201     static int replyDownloadProgress;
202     static int replyFinished;
203     static int downloadProgress;
204     static int threadNetworkRequestDone;
205     static QHash<QQmlEngine *,QQuickPixmapReader*> readers;
206 public:
207     static QMutex readerMutex;
208 };
209
210 class QQuickPixmapData
211 {
212 public:
213     QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QSize &s, const QString &e)
214     : refCount(1), inCache(false), pixmapStatus(QQuickPixmap::Error), 
215       url(u), errorString(e), requestSize(s), textureFactory(0), reply(0), prevUnreferenced(0),
216       prevUnreferencedPtr(0), nextUnreferenced(0)
217     {
218         declarativePixmaps.insert(pixmap);
219     }
220
221     QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QSize &r)
222     : refCount(1), inCache(false), pixmapStatus(QQuickPixmap::Loading), 
223       url(u), requestSize(r), textureFactory(0), reply(0), prevUnreferenced(0), prevUnreferencedPtr(0),
224       nextUnreferenced(0)
225     {
226         declarativePixmaps.insert(pixmap);
227     }
228
229     QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, QQuickTextureFactory *texture, const QSize &s, const QSize &r)
230     : refCount(1), inCache(false), privatePixmap(false), pixmapStatus(QQuickPixmap::Ready),
231       url(u), implicitSize(s), requestSize(r), textureFactory(texture), reply(0), prevUnreferenced(0),
232       prevUnreferencedPtr(0), nextUnreferenced(0)
233     {
234         declarativePixmaps.insert(pixmap);
235     }
236
237     QQuickPixmapData(QQuickPixmap *pixmap, QQuickTextureFactory *texture)
238     : refCount(1), inCache(false), privatePixmap(true), pixmapStatus(QQuickPixmap::Ready),
239       textureFactory(texture), reply(0), prevUnreferenced(0),
240       prevUnreferencedPtr(0), nextUnreferenced(0)
241     {
242         if (texture)
243             requestSize = implicitSize = texture->textureSize();
244         declarativePixmaps.insert(pixmap);
245     }
246
247     ~QQuickPixmapData()
248     {
249         while (!declarativePixmaps.isEmpty()) {
250             QQuickPixmap *referencer = declarativePixmaps.first();
251             declarativePixmaps.remove(referencer);
252             referencer->d = 0;
253         }
254         delete textureFactory;
255     }
256
257     int cost() const;
258     void addref();
259     void release();
260     void addToCache();
261     void removeFromCache();
262
263     uint refCount;
264
265     bool inCache:1;
266     bool privatePixmap:1;
267     
268     QQuickPixmap::Status pixmapStatus;
269     QUrl url;
270     QString errorString;
271     QSize implicitSize;
272     QSize requestSize;
273
274     QQuickTextureFactory *textureFactory;
275
276     QIntrusiveList<QQuickPixmap, &QQuickPixmap::dataListNode> declarativePixmaps;
277     QQuickPixmapReply *reply;
278
279     QQuickPixmapData *prevUnreferenced;
280     QQuickPixmapData**prevUnreferencedPtr;
281     QQuickPixmapData *nextUnreferenced;
282 };
283
284 int QQuickPixmapReply::finishedIndex = -1;
285 int QQuickPixmapReply::downloadProgressIndex = -1;
286
287 // XXX
288 QHash<QQmlEngine *,QQuickPixmapReader*> QQuickPixmapReader::readers;
289 QMutex QQuickPixmapReader::readerMutex;
290
291 int QQuickPixmapReader::replyDownloadProgress = -1;
292 int QQuickPixmapReader::replyFinished = -1;
293 int QQuickPixmapReader::downloadProgress = -1;
294 int QQuickPixmapReader::threadNetworkRequestDone = -1;
295
296
297 void QQuickPixmapReply::postReply(ReadError error, const QString &errorString,
298                                         const QSize &implicitSize, QQuickTextureFactory *factory)
299 {
300     loading = false;
301     QCoreApplication::postEvent(this, new Event(error, errorString, implicitSize, factory));
302 }
303
304 QQuickPixmapReply::Event::Event(ReadError e, const QString &s, const QSize &iSize, QQuickTextureFactory *factory)
305     : QEvent(QEvent::User), error(e), errorString(s), implicitSize(iSize), textureFactory(factory)
306 {
307 }
308
309 QNetworkAccessManager *QQuickPixmapReader::networkAccessManager()
310 {
311     if (!accessManager) {
312         Q_ASSERT(threadObject);
313         accessManager = QQmlEnginePrivate::get(engine)->createNetworkAccessManager(threadObject);
314     }
315     return accessManager;
316 }
317
318 static bool readImage(const QUrl& url, QIODevice *dev, QImage *image, QString *errorString, QSize *impsize, 
319                       const QSize &requestSize)
320 {
321     QImageReader imgio(dev);
322
323     bool force_scale = false;
324     if (url.path().endsWith(QLatin1String(".svg"),Qt::CaseInsensitive)) {
325         imgio.setFormat("svg"); // QSvgPlugin::capabilities bug QTBUG-9053
326         force_scale = true;
327     }
328
329     if (requestSize.width() > 0 || requestSize.height() > 0) {
330         QSize s = imgio.size();
331         qreal ratio = 0.0;
332         if (requestSize.width() && (force_scale || requestSize.width() < s.width())) {
333             ratio = qreal(requestSize.width())/s.width();
334         }
335         if (requestSize.height() && (force_scale || requestSize.height() < s.height())) {
336             qreal hr = qreal(requestSize.height())/s.height();
337             if (ratio == 0.0 || hr < ratio)
338                 ratio = hr;
339         }
340         if (ratio > 0.0) {
341             s.setHeight(qRound(s.height() * ratio));
342             s.setWidth(qRound(s.width() * ratio));
343             imgio.setScaledSize(s);
344         }
345     }
346
347     if (impsize)
348         *impsize = imgio.size();
349
350     if (imgio.read(image)) {
351         if (impsize && impsize->width() < 0)
352             *impsize = image->size();
353         return true;
354     } else {
355         if (errorString)
356             *errorString = QQuickPixmap::tr("Error decoding: %1: %2").arg(url.toString())
357                                 .arg(imgio.errorString());
358         return false;
359     }
360 }
361
362 QQuickPixmapReader::QQuickPixmapReader(QQmlEngine *eng)
363 : QThread(eng), engine(eng), threadObject(0), accessManager(0)
364 {
365     eventLoopQuitHack = new QObject;
366     eventLoopQuitHack->moveToThread(this);
367     connect(eventLoopQuitHack, SIGNAL(destroyed(QObject*)), SLOT(quit()), Qt::DirectConnection);
368     start(QThread::LowestPriority);
369 }
370
371 QQuickPixmapReader::~QQuickPixmapReader()
372 {
373     readerMutex.lock();
374     readers.remove(engine);
375     readerMutex.unlock();
376
377     mutex.lock();
378     // manually cancel all outstanding jobs.
379     foreach (QQuickPixmapReply *reply, jobs) {
380         delete reply;
381     }
382     jobs.clear();
383     QList<QQuickPixmapReply*> activeJobs = replies.values();
384     foreach (QQuickPixmapReply *reply, activeJobs) {
385         if (reply->loading) {
386             cancelled.append(reply);
387             reply->data = 0;
388         }
389     }
390     if (threadObject) threadObject->processJobs();
391     mutex.unlock();
392
393     eventLoopQuitHack->deleteLater();
394     wait();
395 }
396
397 void QQuickPixmapReader::networkRequestDone(QNetworkReply *reply)
398 {
399     QQuickPixmapReply *job = replies.take(reply);
400
401     if (job) {
402         job->redirectCount++;
403         if (job->redirectCount < IMAGEREQUEST_MAX_REDIRECT_RECURSION) {
404             QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
405             if (redirect.isValid()) {
406                 QUrl url = reply->url().resolved(redirect.toUrl());
407                 QNetworkRequest req(url);
408                 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
409
410                 reply->deleteLater();
411                 reply = networkAccessManager()->get(req);
412
413                 QMetaObject::connect(reply, replyDownloadProgress, job, downloadProgress);
414                 QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
415
416                 replies.insert(reply, job);
417                 return;
418             }
419         }
420
421         QImage image;
422         QQuickPixmapReply::ReadError error = QQuickPixmapReply::NoError;
423         QString errorString;
424         QSize readSize;
425         if (reply->error()) {
426             error = QQuickPixmapReply::Loading;
427             errorString = reply->errorString();
428         } else {
429             QByteArray all = reply->readAll();
430             QBuffer buff(&all);
431             buff.open(QIODevice::ReadOnly);
432             if (!readImage(reply->url(), &buff, &image, &errorString, &readSize, job->requestSize))
433                 error = QQuickPixmapReply::Decoding;
434        }
435         // send completion event to the QQuickPixmapReply
436         mutex.lock();
437         if (!cancelled.contains(job))
438             job->postReply(error, errorString, readSize, textureFactoryForImage(image));
439         mutex.unlock();
440     }
441     reply->deleteLater();
442
443     // kick off event loop again incase we have dropped below max request count
444     threadObject->processJobs();
445 }
446
447 QQuickPixmapReaderThreadObject::QQuickPixmapReaderThreadObject(QQuickPixmapReader *i)
448 : reader(i)
449 {
450 }
451
452 void QQuickPixmapReaderThreadObject::processJobs() 
453
454     QCoreApplication::postEvent(this, new QEvent(QEvent::User)); 
455 }
456
457 bool QQuickPixmapReaderThreadObject::event(QEvent *e) 
458 {
459     if (e->type() == QEvent::User) { 
460         reader->processJobs(); 
461         return true; 
462     } else { 
463         return QObject::event(e);
464     }
465 }
466
467 void QQuickPixmapReaderThreadObject::networkRequestDone()
468 {
469     QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
470     reader->networkRequestDone(reply);
471 }
472
473 void QQuickPixmapReader::processJobs()
474 {
475     QMutexLocker locker(&mutex);
476
477     while (true) {
478         if (cancelled.isEmpty() && (jobs.isEmpty() || replies.count() >= IMAGEREQUEST_MAX_REQUEST_COUNT)) 
479             return; // Nothing else to do
480
481         // Clean cancelled jobs
482         if (cancelled.count()) {
483             for (int i = 0; i < cancelled.count(); ++i) {
484                 QQuickPixmapReply *job = cancelled.at(i);
485                 QNetworkReply *reply = replies.key(job, 0);
486                 if (reply && reply->isRunning()) {
487                     // cancel any jobs already started
488                     replies.remove(reply);
489                     reply->close();
490                 }
491                 // deleteLater, since not owned by this thread
492                 job->deleteLater();
493             }
494             cancelled.clear();
495         }
496
497         if (!jobs.isEmpty() && replies.count() < IMAGEREQUEST_MAX_REQUEST_COUNT) {
498             QQuickPixmapReply *runningJob = jobs.takeLast();
499             runningJob->loading = true;
500
501             QUrl url = runningJob->url;
502             QSize requestSize = runningJob->requestSize;
503             locker.unlock();
504             processJob(runningJob, url, requestSize);
505             locker.relock();
506         }
507     }
508 }
509
510 void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &url, 
511                                           const QSize &requestSize)
512 {
513     // fetch
514     if (url.scheme() == QLatin1String("image")) {
515         // Use QQuickImageProvider
516         QSize readSize;
517
518         QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
519         QQuickImageProvider *provider = static_cast<QQuickImageProvider *>(engine->imageProvider(imageProviderId(url)));
520         if (provider)
521             imageType = provider->imageType();
522
523         if (imageType == QQuickImageProvider::Invalid) {
524             QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::Loading;
525             QString errorStr = QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString());
526             QImage image;
527             mutex.lock();
528             if (!cancelled.contains(runningJob))
529                 runningJob->postReply(errorCode, errorStr, readSize, textureFactoryForImage(image));
530             mutex.unlock();
531         } else if (imageType == QQuickImageProvider::Image) {
532             QImage image = provider->requestImage(imageId(url), &readSize, requestSize);
533             QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
534             QString errorStr;
535             if (image.isNull()) {
536                 errorCode = QQuickPixmapReply::Loading;
537                 errorStr = QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString());
538             }
539             mutex.lock();
540             if (!cancelled.contains(runningJob))
541                 runningJob->postReply(errorCode, errorStr, readSize, textureFactoryForImage(image));
542             mutex.unlock();
543         } else {
544             QQuickTextureFactory *t = provider->requestTexture(imageId(url), &readSize, requestSize);
545             QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
546             QString errorStr;
547             if (!t) {
548                 errorCode = QQuickPixmapReply::Loading;
549                 errorStr = QQuickPixmap::tr("Failed to get texture from provider: %1").arg(url.toString());
550             }
551             mutex.lock();
552             if (!cancelled.contains(runningJob))
553                 runningJob->postReply(errorCode, errorStr, readSize, t);
554             mutex.unlock();
555
556         }
557
558     } else {
559         QString lf = QQmlEnginePrivate::urlToLocalFileOrQrc(url);
560         if (!lf.isEmpty()) {
561             // Image is local - load/decode immediately
562             QImage image;
563             QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
564             QString errorStr;
565             QFile f(lf);
566             QSize readSize;
567             if (f.open(QIODevice::ReadOnly)) {
568                 if (!readImage(url, &f, &image, &errorStr, &readSize, requestSize))
569                     errorCode = QQuickPixmapReply::Loading;
570             } else {
571                 errorStr = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
572                 errorCode = QQuickPixmapReply::Loading;
573             }
574             mutex.lock();
575             if (!cancelled.contains(runningJob))
576                 runningJob->postReply(errorCode, errorStr, readSize, textureFactoryForImage(image));
577             mutex.unlock();
578         } else {
579             // Network resource
580             QNetworkRequest req(url);
581             req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
582             QNetworkReply *reply = networkAccessManager()->get(req);
583
584             QMetaObject::connect(reply, replyDownloadProgress, runningJob, downloadProgress);
585             QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
586
587             replies.insert(reply, runningJob);
588         }
589     }
590 }
591
592 QQuickPixmapReader *QQuickPixmapReader::instance(QQmlEngine *engine)
593 {
594     // XXX NOTE: must be called within readerMutex locking.
595     QQuickPixmapReader *reader = readers.value(engine);
596     if (!reader) {
597         reader = new QQuickPixmapReader(engine);
598         readers.insert(engine, reader);
599     }
600
601     return reader;
602 }
603
604 QQuickPixmapReader *QQuickPixmapReader::existingInstance(QQmlEngine *engine)
605 {
606     // XXX NOTE: must be called within readerMutex locking.
607     return readers.value(engine, 0);
608 }
609
610 QQuickPixmapReply *QQuickPixmapReader::getImage(QQuickPixmapData *data)
611 {
612     mutex.lock();
613     QQuickPixmapReply *reply = new QQuickPixmapReply(data);
614     reply->engineForReader = engine;
615     jobs.append(reply);
616     // XXX 
617     if (threadObject) threadObject->processJobs();
618     mutex.unlock();
619     return reply;
620 }
621
622 void QQuickPixmapReader::cancel(QQuickPixmapReply *reply)
623 {
624     mutex.lock();
625     if (reply->loading) {
626         cancelled.append(reply);
627         reply->data = 0;
628         // XXX 
629         if (threadObject) threadObject->processJobs();
630     } else {
631         jobs.removeAll(reply);
632         delete reply;
633     }
634     mutex.unlock();
635 }
636
637 void QQuickPixmapReader::run()
638 {
639     if (replyDownloadProgress == -1) {
640         const QMetaObject *nr = &QNetworkReply::staticMetaObject;
641         const QMetaObject *pr = &QQuickPixmapReply::staticMetaObject;
642         const QMetaObject *ir = &QQuickPixmapReaderThreadObject::staticMetaObject;
643         replyDownloadProgress = nr->indexOfSignal("downloadProgress(qint64,qint64)");
644         replyFinished = nr->indexOfSignal("finished()");
645         downloadProgress = pr->indexOfSignal("downloadProgress(qint64,qint64)");
646         threadNetworkRequestDone = ir->indexOfSlot("networkRequestDone()");
647     }
648
649     mutex.lock();
650     threadObject = new QQuickPixmapReaderThreadObject(this);
651     mutex.unlock();
652
653     processJobs();
654     exec();
655
656     delete threadObject;
657     threadObject = 0;
658 }
659
660 class QQuickPixmapKey
661 {
662 public:
663     const QUrl *url;
664     const QSize *size;
665 };
666
667 inline bool operator==(const QQuickPixmapKey &lhs, const QQuickPixmapKey &rhs)
668 {
669     return *lhs.size == *rhs.size && *lhs.url == *rhs.url;
670 }
671
672 inline uint qHash(const QQuickPixmapKey &key)
673 {
674     return qHash(*key.url) ^ key.size->width() ^ key.size->height();
675 }
676
677 class QSGContext;
678
679 class QQuickPixmapStore : public QObject
680 {
681     Q_OBJECT
682 public:
683     QQuickPixmapStore();
684     ~QQuickPixmapStore();
685
686     void unreferencePixmap(QQuickPixmapData *);
687     void referencePixmap(QQuickPixmapData *);
688
689     void purgeCache();
690
691 protected:
692     virtual void timerEvent(QTimerEvent *);
693
694 public:
695     QHash<QQuickPixmapKey, QQuickPixmapData *> m_cache;
696
697 private:
698     void shrinkCache(int remove);
699
700     QQuickPixmapData *m_unreferencedPixmaps;
701     QQuickPixmapData *m_lastUnreferencedPixmap;
702
703     int m_unreferencedCost;
704     int m_timerId;
705     bool m_destroying;
706 };
707 Q_GLOBAL_STATIC(QQuickPixmapStore, pixmapStore);
708
709
710 QQuickPixmapStore::QQuickPixmapStore()
711     : m_unreferencedPixmaps(0), m_lastUnreferencedPixmap(0), m_unreferencedCost(0), m_timerId(-1), m_destroying(false)
712 {
713 }
714
715 QQuickPixmapStore::~QQuickPixmapStore()
716 {
717     m_destroying = true;
718
719     int leakedPixmaps = 0;
720     QList<QQuickPixmapData*> cachedData = m_cache.values();
721
722     // Prevent unreferencePixmap() from assuming it needs to kick
723     // off the cache expiry timer, as we're shrinking the cache
724     // manually below after releasing all the pixmaps.
725     m_timerId = -2;
726
727     // unreference all (leaked) pixmaps
728     foreach (QQuickPixmapData* pixmap, cachedData) {
729         int currRefCount = pixmap->refCount;
730         if (currRefCount) {
731             leakedPixmaps++;
732             while (currRefCount > 0) {
733                 pixmap->release();
734                 currRefCount--;
735             }
736         }
737     }
738
739     // free all unreferenced pixmaps
740     while (m_lastUnreferencedPixmap) {
741         shrinkCache(20);
742     }
743
744     if (leakedPixmaps)
745         qDebug("Number of leaked pixmaps: %i", leakedPixmaps);
746 }
747
748 void QQuickPixmapStore::unreferencePixmap(QQuickPixmapData *data)
749 {
750     Q_ASSERT(data->prevUnreferenced == 0);
751     Q_ASSERT(data->prevUnreferencedPtr == 0);
752     Q_ASSERT(data->nextUnreferenced == 0);
753
754     data->nextUnreferenced = m_unreferencedPixmaps;
755     data->prevUnreferencedPtr = &m_unreferencedPixmaps;
756     if (!m_destroying) // the texture factories may have been cleaned up already.
757         m_unreferencedCost += data->cost();
758
759     m_unreferencedPixmaps = data;
760     if (m_unreferencedPixmaps->nextUnreferenced) {
761         m_unreferencedPixmaps->nextUnreferenced->prevUnreferenced = m_unreferencedPixmaps;
762         m_unreferencedPixmaps->nextUnreferenced->prevUnreferencedPtr = &m_unreferencedPixmaps->nextUnreferenced;
763     }
764
765     if (!m_lastUnreferencedPixmap)
766         m_lastUnreferencedPixmap = data;
767
768     shrinkCache(-1); // Shrink the cache incase it has become larger than cache_limit
769
770     if (m_timerId == -1 && m_unreferencedPixmaps && !m_destroying)
771         m_timerId = startTimer(CACHE_EXPIRE_TIME * 1000);
772 }
773
774 void QQuickPixmapStore::referencePixmap(QQuickPixmapData *data)
775 {
776     Q_ASSERT(data->prevUnreferencedPtr);
777
778     *data->prevUnreferencedPtr = data->nextUnreferenced;
779     if (data->nextUnreferenced) { 
780         data->nextUnreferenced->prevUnreferencedPtr = data->prevUnreferencedPtr;
781         data->nextUnreferenced->prevUnreferenced = data->prevUnreferenced;
782     }
783     if (m_lastUnreferencedPixmap == data)
784         m_lastUnreferencedPixmap = data->prevUnreferenced;
785
786     data->nextUnreferenced = 0;
787     data->prevUnreferencedPtr = 0;
788     data->prevUnreferenced = 0;
789
790     m_unreferencedCost -= data->cost();
791 }
792
793 void QQuickPixmapStore::shrinkCache(int remove)
794 {
795     while ((remove > 0 || m_unreferencedCost > cache_limit) && m_lastUnreferencedPixmap) {
796         QQuickPixmapData *data = m_lastUnreferencedPixmap;
797         Q_ASSERT(data->nextUnreferenced == 0);
798
799         *data->prevUnreferencedPtr = 0;
800         m_lastUnreferencedPixmap = data->prevUnreferenced;
801         data->prevUnreferencedPtr = 0;
802         data->prevUnreferenced = 0;
803
804         if (!m_destroying) {
805             remove -= data->cost();
806             m_unreferencedCost -= data->cost();
807         }
808         data->removeFromCache();
809         delete data;
810     }
811 }
812
813 void QQuickPixmapStore::timerEvent(QTimerEvent *)
814 {
815     int removalCost = m_unreferencedCost / CACHE_REMOVAL_FRACTION;
816
817     shrinkCache(removalCost);
818
819     if (m_unreferencedPixmaps == 0) {
820         killTimer(m_timerId);
821         m_timerId = -1;
822     }
823 }
824
825 void QQuickPixmapStore::purgeCache()
826 {
827     shrinkCache(m_unreferencedCost);
828 }
829
830 void QQuickPixmap::purgeCache()
831 {
832     pixmapStore()->purgeCache();
833 }
834
835 QQuickPixmapReply::QQuickPixmapReply(QQuickPixmapData *d)
836 : data(d), engineForReader(0), requestSize(d->requestSize), url(d->url), loading(false), redirectCount(0)
837 {
838     if (finishedIndex == -1) {
839         finishedIndex = QQuickPixmapReply::staticMetaObject.indexOfSignal("finished()");
840         downloadProgressIndex = QQuickPixmapReply::staticMetaObject.indexOfSignal("downloadProgress(qint64,qint64)");
841     }
842 }
843
844 QQuickPixmapReply::~QQuickPixmapReply()
845 {
846 }
847
848 bool QQuickPixmapReply::event(QEvent *event)
849 {
850     if (event->type() == QEvent::User) {
851
852         if (data) {
853             Event *de = static_cast<Event *>(event);
854             data->pixmapStatus = (de->error == NoError) ? QQuickPixmap::Ready : QQuickPixmap::Error;
855
856             if (data->pixmapStatus == QQuickPixmap::Ready) {
857                 data->textureFactory = de->textureFactory;
858                 data->implicitSize = de->implicitSize;
859             } else {
860                 data->errorString = de->errorString;
861                 data->removeFromCache(); // We don't continue to cache error'd pixmaps
862             }
863
864             data->reply = 0;
865             emit finished();
866         }
867
868         delete this;
869         return true;
870     } else {
871         return QObject::event(event);
872     }
873 }
874
875 int QQuickPixmapData::cost() const
876 {
877     if (textureFactory)
878         return textureFactory->textureByteCount();
879     return 0;
880 }
881
882 void QQuickPixmapData::addref()
883 {
884     ++refCount;
885     if (prevUnreferencedPtr) 
886         pixmapStore()->referencePixmap(this);
887 }
888
889 void QQuickPixmapData::release()
890 {
891     Q_ASSERT(refCount > 0);
892     --refCount;
893     if (refCount == 0) {
894         if (reply) {
895             QQuickPixmapReply *cancelReply = reply;
896             reply->data = 0;
897             reply = 0;
898             QQuickPixmapReader::readerMutex.lock();
899             QQuickPixmapReader *reader = QQuickPixmapReader::existingInstance(cancelReply->engineForReader);
900             if (reader)
901                 reader->cancel(cancelReply);
902             QQuickPixmapReader::readerMutex.unlock();
903         }
904
905         if (pixmapStatus == QQuickPixmap::Ready) {
906             pixmapStore()->unreferencePixmap(this);
907         } else {
908             removeFromCache();
909             delete this;
910         }
911     }
912 }
913
914 void QQuickPixmapData::addToCache()
915 {
916     if (!inCache) {
917         QQuickPixmapKey key = { &url, &requestSize };
918         pixmapStore()->m_cache.insert(key, this);
919         inCache = true;
920     }
921 }
922
923 void QQuickPixmapData::removeFromCache()
924 {
925     if (inCache) {
926         QQuickPixmapKey key = { &url, &requestSize };
927         pixmapStore()->m_cache.remove(key);
928         inCache = false;
929     }
930 }
931
932 static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, QQmlEngine *engine, const QUrl &url, const QSize &requestSize, bool *ok)
933 {
934     if (url.scheme() == QLatin1String("image")) {
935         QSize readSize;
936
937         QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
938         QQuickImageProvider *provider = static_cast<QQuickImageProvider *>(engine->imageProvider(imageProviderId(url)));
939         if (provider)
940             imageType = provider->imageType();
941
942         switch (imageType) {
943             case QQuickImageProvider::Invalid:
944                 return new QQuickPixmapData(declarativePixmap, url, requestSize,
945                     QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString()));
946             case QQuickImageProvider::Texture:
947             {
948                 QQuickTextureFactory *texture = provider->requestTexture(imageId(url), &readSize, requestSize);
949                 if (texture) {
950                     *ok = true;
951                     return new QQuickPixmapData(declarativePixmap, url, texture, readSize, requestSize);
952                 }
953             }
954
955             case QQuickImageProvider::Image:
956             {
957                 QImage image = provider->requestImage(imageId(url), &readSize, requestSize);
958                 if (!image.isNull()) {
959                     *ok = true;
960                     return new QQuickPixmapData(declarativePixmap, url, textureFactoryForImage(image), readSize, requestSize);
961                 }
962             }
963             case QQuickImageProvider::Pixmap:
964             {
965                 QPixmap pixmap = provider->requestPixmap(imageId(url), &readSize, requestSize);
966                 if (!pixmap.isNull()) {
967                     *ok = true;
968                     return new QQuickPixmapData(declarativePixmap, url, textureFactoryForImage(pixmap.toImage()), readSize, requestSize);
969                 }
970             }
971         }
972
973         // provider has bad image type, or provider returned null image
974         return new QQuickPixmapData(declarativePixmap, url, requestSize,
975             QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString()));
976     }
977
978     QString localFile = QQmlEnginePrivate::urlToLocalFileOrQrc(url);
979     if (localFile.isEmpty()) 
980         return 0;
981
982     QFile f(localFile);
983     QSize readSize;
984     QString errorString;
985
986     if (f.open(QIODevice::ReadOnly)) {
987         QImage image;
988
989         if (readImage(url, &f, &image, &errorString, &readSize, requestSize)) {
990             *ok = true;
991             return new QQuickPixmapData(declarativePixmap, url, textureFactoryForImage(image), readSize, requestSize);
992         }
993         errorString = QQuickPixmap::tr("Invalid image data: %1").arg(url.toString());
994
995     } else {
996         errorString = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
997     }
998     return new QQuickPixmapData(declarativePixmap, url, requestSize, errorString);
999 }
1000
1001
1002 struct QQuickPixmapNull {
1003     QUrl url;
1004     QSize size;
1005 };
1006 Q_GLOBAL_STATIC(QQuickPixmapNull, nullPixmap);
1007
1008 QQuickPixmap::QQuickPixmap()
1009 : d(0)
1010 {
1011 }
1012
1013 QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url)
1014 : d(0)
1015 {
1016     load(engine, url);
1017 }
1018
1019 QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QSize &size)
1020 : d(0)
1021 {
1022     load(engine, url, size);
1023 }
1024
1025 QQuickPixmap::~QQuickPixmap()
1026 {
1027     if (d) {
1028         d->declarativePixmaps.remove(this);
1029         d->release();
1030         d = 0;
1031     }
1032 }
1033
1034 bool QQuickPixmap::isNull() const
1035 {
1036     return d == 0;
1037 }
1038
1039 bool QQuickPixmap::isReady() const
1040 {
1041     return status() == Ready;
1042 }
1043
1044 bool QQuickPixmap::isError() const
1045 {
1046     return status() == Error;
1047 }
1048
1049 bool QQuickPixmap::isLoading() const
1050 {
1051     return status() == Loading;
1052 }
1053
1054 QString QQuickPixmap::error() const
1055 {
1056     if (d)
1057         return d->errorString;
1058     else
1059         return QString();
1060 }
1061
1062 QQuickPixmap::Status QQuickPixmap::status() const
1063 {
1064     if (d)
1065         return d->pixmapStatus;
1066     else
1067         return Null;
1068 }
1069
1070 const QUrl &QQuickPixmap::url() const
1071 {
1072     if (d)
1073         return d->url;
1074     else
1075         return nullPixmap()->url;
1076 }
1077
1078 const QSize &QQuickPixmap::implicitSize() const
1079 {
1080     if (d) 
1081         return d->implicitSize;
1082     else
1083         return nullPixmap()->size;
1084 }
1085
1086 const QSize &QQuickPixmap::requestSize() const
1087 {
1088     if (d)
1089         return d->requestSize;
1090     else
1091         return nullPixmap()->size;
1092 }
1093
1094 QQuickTextureFactory *QQuickPixmap::textureFactory() const
1095 {
1096     if (d)
1097         return d->textureFactory;
1098
1099     return 0;
1100 }
1101
1102 QImage QQuickPixmap::image() const
1103 {
1104     if (d && d->textureFactory)
1105         return d->textureFactory->image();
1106     return QImage();
1107 }
1108
1109 void QQuickPixmap::setImage(const QImage &p)
1110 {
1111     clear();
1112
1113     if (!p.isNull())
1114         d = new QQuickPixmapData(this, textureFactoryForImage(p));
1115 }
1116
1117 int QQuickPixmap::width() const
1118 {
1119     if (d && d->textureFactory)
1120         return d->textureFactory->textureSize().width();
1121     else
1122         return 0;
1123 }
1124
1125 int QQuickPixmap::height() const
1126 {
1127     if (d && d->textureFactory)
1128         return d->textureFactory->textureSize().height();
1129     else
1130         return 0;
1131 }
1132
1133 QRect QQuickPixmap::rect() const
1134 {
1135     if (d && d->textureFactory)
1136         return QRect(QPoint(), d->textureFactory->textureSize());
1137     else
1138         return QRect();
1139 }
1140
1141 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url)
1142 {
1143     load(engine, url, QSize(), QQuickPixmap::Cache);
1144 }
1145
1146 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, QQuickPixmap::Options options)
1147 {
1148     load(engine, url, QSize(), options);
1149 }
1150
1151 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &size)
1152 {
1153     load(engine, url, size, QQuickPixmap::Cache);
1154 }
1155
1156 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &requestSize, QQuickPixmap::Options options)
1157 {
1158     if (d) {
1159         d->declarativePixmaps.remove(this);
1160         d->release();
1161         d = 0;
1162     }
1163
1164     QQuickPixmapKey key = { &url, &requestSize };
1165     QQuickPixmapStore *store = pixmapStore();
1166
1167     QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.find(key);
1168
1169     if (iter == store->m_cache.end()) {
1170         if (options & QQuickPixmap::Asynchronous) {
1171             // pixmaps can only be loaded synchronously
1172             if (url.scheme() == QLatin1String("image")) {
1173                 QQuickImageProvider *provider = static_cast<QQuickImageProvider *>(engine->imageProvider(imageProviderId(url)));
1174                 if (provider && provider->imageType() == QQuickImageProvider::Pixmap) {
1175                     options &= ~QQuickPixmap::Asynchronous;
1176                 }
1177             }
1178         }
1179
1180         if (!(options & QQuickPixmap::Asynchronous)) {
1181             bool ok = false;
1182             d = createPixmapDataSync(this, engine, url, requestSize, &ok);
1183             if (ok) {
1184                 if (options & QQuickPixmap::Cache)
1185                     d->addToCache();
1186                 return;
1187             }
1188             if (d)  // loadable, but encountered error while loading
1189                 return;
1190         } 
1191
1192         if (!engine)
1193             return;
1194
1195         d = new QQuickPixmapData(this, url, requestSize);
1196         if (options & QQuickPixmap::Cache)
1197             d->addToCache();
1198
1199         QQuickPixmapReader::readerMutex.lock();
1200         d->reply = QQuickPixmapReader::instance(engine)->getImage(d);
1201         QQuickPixmapReader::readerMutex.unlock();
1202     } else {
1203         d = *iter;
1204         d->addref();
1205         d->declarativePixmaps.insert(this);
1206     }
1207 }
1208
1209 void QQuickPixmap::clear()
1210 {
1211     if (d) {
1212         d->declarativePixmaps.remove(this);
1213         d->release();
1214         d = 0;
1215     }
1216 }
1217
1218 void QQuickPixmap::clear(QObject *obj)
1219 {
1220     if (d) {
1221         if (d->reply) 
1222             QObject::disconnect(d->reply, 0, obj, 0);
1223         d->declarativePixmaps.remove(this);
1224         d->release();
1225         d = 0;
1226     }
1227 }
1228
1229 bool QQuickPixmap::connectFinished(QObject *object, const char *method)
1230 {
1231     if (!d || !d->reply) {
1232         qWarning("QQuickPixmap: connectFinished() called when not loading.");
1233         return false;
1234     }
1235
1236     return QObject::connect(d->reply, SIGNAL(finished()), object, method);
1237 }
1238
1239 bool QQuickPixmap::connectFinished(QObject *object, int method)
1240 {
1241     if (!d || !d->reply) {
1242         qWarning("QQuickPixmap: connectFinished() called when not loading.");
1243         return false;
1244     }
1245
1246     return QMetaObject::connect(d->reply, QQuickPixmapReply::finishedIndex, object, method);
1247 }
1248
1249 bool QQuickPixmap::connectDownloadProgress(QObject *object, const char *method)
1250 {
1251     if (!d || !d->reply) {
1252         qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
1253         return false;
1254     }
1255
1256     return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), object, method);
1257 }
1258
1259 bool QQuickPixmap::connectDownloadProgress(QObject *object, int method)
1260 {
1261     if (!d || !d->reply) {
1262         qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
1263         return false;
1264     }
1265
1266     return QMetaObject::connect(d->reply, QQuickPixmapReply::downloadProgressIndex, object, method);
1267 }
1268
1269 QT_END_NAMESPACE
1270
1271 #include <qquickpixmapcache.moc>