1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtQuick module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qquickpixmapcache_p.h"
41 #include <qquickimageprovider.h>
42 #include "qquickimageprovider_p.h"
43 
44 #include <qqmlengine.h>
45 #include <private/qqmlglobal_p.h>
46 #include <private/qqmlengine_p.h>
47 
48 #include <QtGui/private/qguiapplication_p.h>
49 #include <QtGui/private/qimage_p.h>
50 #include <qpa/qplatformintegration.h>
51 
52 #include <QtQuick/private/qsgcontext_p.h>
53 #include <QtQuick/private/qsgtexturereader_p.h>
54 
55 #include <QQuickWindow>
56 #include <QCoreApplication>
57 #include <QImageReader>
58 #include <QHash>
59 #include <QPixmapCache>
60 #include <QFile>
61 #include <QThread>
62 #include <QMutex>
63 #include <QMutexLocker>
64 #include <QBuffer>
65 #include <QtCore/qdebug.h>
66 #include <private/qobject_p.h>
67 #include <QQmlFile>
68 #include <QMetaMethod>
69 
70 #if QT_CONFIG(qml_network)
71 #include <qqmlnetworkaccessmanagerfactory.h>
72 #include <QNetworkReply>
73 #include <QSslError>
74 #endif
75 
76 #include <private/qquickprofiler_p.h>
77 
78 #define IMAGEREQUEST_MAX_NETWORK_REQUEST_COUNT 8
79 #define IMAGEREQUEST_MAX_REDIRECT_RECURSION 16
80 #define CACHE_EXPIRE_TIME 30
81 #define CACHE_REMOVAL_FRACTION 4
82 
83 #define PIXMAP_PROFILE(Code) Q_QUICK_PROFILE(QQuickProfiler::ProfilePixmapCache, Code)
84 
85 QT_BEGIN_NAMESPACE
86 
87 const QLatin1String QQuickPixmap::itemGrabberScheme = QLatin1String("itemgrabber");
88 
89 Q_LOGGING_CATEGORY(lcImg, "qt.quick.image")
90 
91 #ifndef QT_NO_DEBUG
92 static const bool qsg_leak_check = !qEnvironmentVariableIsEmpty("QML_LEAK_CHECK");
93 #endif
94 
95 // The cache limit describes the maximum "junk" in the cache.
96 static int cache_limit = 2048 * 1024; // 2048 KB cache limit for embedded in qpixmapcache.cpp
97 
imageProviderId(const QUrl & url)98 static inline QString imageProviderId(const QUrl &url)
99 {
100     return url.host();
101 }
102 
imageId(const QUrl & url)103 static inline QString imageId(const QUrl &url)
104 {
105     return url.toString(QUrl::RemoveScheme | QUrl::RemoveAuthority).mid(1);
106 }
107 
QQuickDefaultTextureFactory(const QImage & image)108 QQuickDefaultTextureFactory::QQuickDefaultTextureFactory(const QImage &image)
109 {
110     if (image.format() == QImage::Format_ARGB32_Premultiplied
111             || image.format() == QImage::Format_RGB32) {
112         im = image;
113     } else {
114         im = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
115     }
116     size = im.size();
117 }
118 
119 
createTexture(QQuickWindow * window) const120 QSGTexture *QQuickDefaultTextureFactory::createTexture(QQuickWindow *window) const
121 {
122     QSGTexture *t = window->createTextureFromImage(im, QQuickWindow::TextureCanUseAtlas);
123     static bool transient = qEnvironmentVariableIsSet("QSG_TRANSIENT_IMAGES");
124     if (transient)
125         const_cast<QQuickDefaultTextureFactory *>(this)->im = QImage();
126     return t;
127 }
128 
129 class QQuickPixmapReader;
130 class QQuickPixmapData;
131 class QQuickPixmapReply : public QObject
132 {
133     Q_OBJECT
134 public:
135     enum ReadError { NoError, Loading, Decoding };
136 
137     QQuickPixmapReply(QQuickPixmapData *);
138     ~QQuickPixmapReply();
139 
140     QQuickPixmapData *data;
141     QQmlEngine *engineForReader; // always access reader inside readerMutex
142     QRect requestRegion;
143     QSize requestSize;
144     QUrl url;
145 
146     bool loading;
147     QQuickImageProviderOptions providerOptions;
148     int redirectCount;
149 
150     class Event : public QEvent {
151     public:
152         Event(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory);
153         ~Event();
154 
155         ReadError error;
156         QString errorString;
157         QSize implicitSize;
158         QQuickTextureFactory *textureFactory;
159     };
160     void postReply(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory);
161 
162 
163 Q_SIGNALS:
164     void finished();
165     void downloadProgress(qint64, qint64);
166 
167 protected:
168     bool event(QEvent *event) override;
169 
170 private:
171     Q_DISABLE_COPY(QQuickPixmapReply)
172 
173 public:
174     static int finishedIndex;
175     static int downloadProgressIndex;
176 };
177 
178 class QQuickPixmapReaderThreadObject : public QObject {
179     Q_OBJECT
180 public:
181     QQuickPixmapReaderThreadObject(QQuickPixmapReader *);
182     void processJobs();
183     bool event(QEvent *e) override;
184 public slots:
185     void asyncResponseFinished(QQuickImageResponse *response);
186 private slots:
187     void networkRequestDone();
188     void asyncResponseFinished();
189 private:
190     QQuickPixmapReader *reader;
191 };
192 
193 class QQuickPixmapData;
194 class QQuickPixmapReader : public QThread
195 {
196     Q_OBJECT
197 public:
198     QQuickPixmapReader(QQmlEngine *eng);
199     ~QQuickPixmapReader();
200 
201     QQuickPixmapReply *getImage(QQuickPixmapData *);
202     void cancel(QQuickPixmapReply *rep);
203 
204     static QQuickPixmapReader *instance(QQmlEngine *engine);
205     static QQuickPixmapReader *existingInstance(QQmlEngine *engine);
206 
207 protected:
208     void run() override;
209 
210 private:
211     friend class QQuickPixmapReaderThreadObject;
212     void processJobs();
213     void processJob(QQuickPixmapReply *, const QUrl &, const QString &, QQuickImageProvider::ImageType, const QSharedPointer<QQuickImageProvider> &);
214 #if QT_CONFIG(qml_network)
215     void networkRequestDone(QNetworkReply *);
216 #endif
217     void asyncResponseFinished(QQuickImageResponse *);
218 
219     QList<QQuickPixmapReply*> jobs;
220     QList<QQuickPixmapReply*> cancelled;
221     QQmlEngine *engine;
222     QObject *eventLoopQuitHack;
223 
224     QMutex mutex;
225     QQuickPixmapReaderThreadObject *threadObject;
226 
227 #if QT_CONFIG(qml_network)
228     QNetworkAccessManager *networkAccessManager();
229     QNetworkAccessManager *accessManager;
230     QHash<QNetworkReply*,QQuickPixmapReply*> networkJobs;
231 #endif
232     QHash<QQuickImageResponse*,QQuickPixmapReply*> asyncResponses;
233 
234     static int replyDownloadProgress;
235     static int replyFinished;
236     static int downloadProgress;
237     static int threadNetworkRequestDone;
238     static QHash<QQmlEngine *,QQuickPixmapReader*> readers;
239 public:
240     static QMutex readerMutex;
241 };
242 
243 class QQuickPixmapData
244 {
245 public:
QQuickPixmapData(QQuickPixmap * pixmap,const QUrl & u,const QRect & r,const QSize & rs,const QQuickImageProviderOptions & po,const QString & e)246     QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QRect &r, const QSize &rs,
247                      const QQuickImageProviderOptions &po, const QString &e)
248     : refCount(1), frameCount(1), frame(0), inCache(false), pixmapStatus(QQuickPixmap::Error),
249       url(u), errorString(e), requestRegion(r), requestSize(rs),
250       providerOptions(po), appliedTransform(QQuickImageProviderOptions::UsePluginDefaultTransform),
251       textureFactory(nullptr), reply(nullptr), prevUnreferenced(nullptr),
252       prevUnreferencedPtr(nullptr), nextUnreferenced(nullptr)
253 #ifdef Q_OS_WEBOS
254     , storeToCache(true)
255 #endif
256     {
257         declarativePixmaps.insert(pixmap);
258     }
259 
QQuickPixmapData(QQuickPixmap * pixmap,const QUrl & u,const QRect & r,const QSize & s,const QQuickImageProviderOptions & po,QQuickImageProviderOptions::AutoTransform aTransform,int frame=0,int frameCount=1)260     QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QRect &r, const QSize &s, const QQuickImageProviderOptions &po,
261                      QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1)
262     : refCount(1), frameCount(frameCount), frame(frame), inCache(false), pixmapStatus(QQuickPixmap::Loading),
263       url(u), requestRegion(r), requestSize(s),
264       providerOptions(po), appliedTransform(aTransform),
265       textureFactory(nullptr), reply(nullptr), prevUnreferenced(nullptr), prevUnreferencedPtr(nullptr),
266       nextUnreferenced(nullptr)
267 #ifdef Q_OS_WEBOS
268     , storeToCache(true)
269 #endif
270     {
271         declarativePixmaps.insert(pixmap);
272     }
273 
QQuickPixmapData(QQuickPixmap * pixmap,const QUrl & u,QQuickTextureFactory * texture,const QSize & s,const QRect & r,const QSize & rs,const QQuickImageProviderOptions & po,QQuickImageProviderOptions::AutoTransform aTransform,int frame=0,int frameCount=1)274     QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, QQuickTextureFactory *texture,
275                      const QSize &s, const QRect &r, const QSize &rs, const QQuickImageProviderOptions &po,
276                      QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1)
277     : refCount(1), frameCount(frameCount), frame(frame), inCache(false), pixmapStatus(QQuickPixmap::Ready),
278       url(u), implicitSize(s), requestRegion(r), requestSize(rs),
279       providerOptions(po), appliedTransform(aTransform),
280       textureFactory(texture), reply(nullptr), prevUnreferenced(nullptr),
281       prevUnreferencedPtr(nullptr), nextUnreferenced(nullptr)
282 #ifdef Q_OS_WEBOS
283     , storeToCache(true)
284 #endif
285     {
286         declarativePixmaps.insert(pixmap);
287     }
288 
QQuickPixmapData(QQuickPixmap * pixmap,QQuickTextureFactory * texture)289     QQuickPixmapData(QQuickPixmap *pixmap, QQuickTextureFactory *texture)
290     : refCount(1), frameCount(1), frame(0), inCache(false), pixmapStatus(QQuickPixmap::Ready),
291       appliedTransform(QQuickImageProviderOptions::UsePluginDefaultTransform),
292       textureFactory(texture), reply(nullptr), prevUnreferenced(nullptr),
293       prevUnreferencedPtr(nullptr), nextUnreferenced(nullptr)
294 #ifdef Q_OS_WEBOS
295     , storeToCache(true)
296 #endif
297     {
298         if (texture)
299             requestSize = implicitSize = texture->textureSize();
300         declarativePixmaps.insert(pixmap);
301     }
302 
~QQuickPixmapData()303     ~QQuickPixmapData()
304     {
305         while (!declarativePixmaps.isEmpty()) {
306             QQuickPixmap *referencer = declarativePixmaps.first();
307             declarativePixmaps.remove(referencer);
308             referencer->d = nullptr;
309         }
310         delete textureFactory;
311     }
312 
313     int cost() const;
314     void addref();
315     void release();
316     void addToCache();
317     void removeFromCache();
318 
319     uint refCount;
320     int frameCount;
321     int frame;
322 
323     bool inCache:1;
324 
325     QQuickPixmap::Status pixmapStatus;
326     QUrl url;
327     QString errorString;
328     QSize implicitSize;
329     QRect requestRegion;
330     QSize requestSize;
331     QQuickImageProviderOptions providerOptions;
332     QQuickImageProviderOptions::AutoTransform appliedTransform;
333     QColorSpace targetColorSpace;
334 
335     QQuickTextureFactory *textureFactory;
336 
337     QIntrusiveList<QQuickPixmap, &QQuickPixmap::dataListNode> declarativePixmaps;
338     QQuickPixmapReply *reply;
339 
340     QQuickPixmapData *prevUnreferenced;
341     QQuickPixmapData**prevUnreferencedPtr;
342     QQuickPixmapData *nextUnreferenced;
343 
344 #ifdef Q_OS_WEBOS
345     bool storeToCache;
346 #endif
347 };
348 
349 int QQuickPixmapReply::finishedIndex = -1;
350 int QQuickPixmapReply::downloadProgressIndex = -1;
351 
352 // XXX
353 QHash<QQmlEngine *,QQuickPixmapReader*> QQuickPixmapReader::readers;
354 QMutex QQuickPixmapReader::readerMutex;
355 
356 int QQuickPixmapReader::replyDownloadProgress = -1;
357 int QQuickPixmapReader::replyFinished = -1;
358 int QQuickPixmapReader::downloadProgress = -1;
359 int QQuickPixmapReader::threadNetworkRequestDone = -1;
360 
361 
postReply(ReadError error,const QString & errorString,const QSize & implicitSize,QQuickTextureFactory * factory)362 void QQuickPixmapReply::postReply(ReadError error, const QString &errorString,
363                                         const QSize &implicitSize, QQuickTextureFactory *factory)
364 {
365     loading = false;
366     QCoreApplication::postEvent(this, new Event(error, errorString, implicitSize, factory));
367 }
368 
Event(ReadError e,const QString & s,const QSize & iSize,QQuickTextureFactory * factory)369 QQuickPixmapReply::Event::Event(ReadError e, const QString &s, const QSize &iSize, QQuickTextureFactory *factory)
370     : QEvent(QEvent::User), error(e), errorString(s), implicitSize(iSize), textureFactory(factory)
371 {
372 }
373 
~Event()374 QQuickPixmapReply::Event::~Event()
375 {
376     delete textureFactory;
377 }
378 
379 #if QT_CONFIG(qml_network)
networkAccessManager()380 QNetworkAccessManager *QQuickPixmapReader::networkAccessManager()
381 {
382     if (!accessManager) {
383         Q_ASSERT(threadObject);
384         accessManager = QQmlEnginePrivate::get(engine)->createNetworkAccessManager(threadObject);
385     }
386     return accessManager;
387 }
388 #endif
389 
maybeRemoveAlpha(QImage * image)390 static void maybeRemoveAlpha(QImage *image)
391 {
392     // If the image
393     if (image->hasAlphaChannel() && image->data_ptr()
394             && !image->data_ptr()->checkForAlphaPixels()) {
395         switch (image->format()) {
396         case QImage::Format_RGBA8888:
397         case QImage::Format_RGBA8888_Premultiplied:
398             if (image->data_ptr()->convertInPlace(QImage::Format_RGBX8888, Qt::AutoColor))
399                 break;
400 
401             *image = image->convertToFormat(QImage::Format_RGBX8888);
402             break;
403         case QImage::Format_A2BGR30_Premultiplied:
404             if (image->data_ptr()->convertInPlace(QImage::Format_BGR30, Qt::AutoColor))
405                 break;
406 
407             *image = image->convertToFormat(QImage::Format_BGR30);
408             break;
409         case QImage::Format_A2RGB30_Premultiplied:
410             if (image->data_ptr()->convertInPlace(QImage::Format_RGB30, Qt::AutoColor))
411                 break;
412 
413             *image = image->convertToFormat(QImage::Format_RGB30);
414             break;
415         default:
416             if (image->data_ptr()->convertInPlace(QImage::Format_RGB32, Qt::AutoColor))
417                 break;
418 
419             *image = image->convertToFormat(QImage::Format_RGB32);
420             break;
421         }
422     }
423 }
424 
readImage(const QUrl & url,QIODevice * dev,QImage * image,QString * errorString,QSize * impsize,int * frameCount,const QRect & requestRegion,const QSize & requestSize,const QQuickImageProviderOptions & providerOptions,QQuickImageProviderOptions::AutoTransform * appliedTransform=nullptr,int frame=0)425 static bool readImage(const QUrl& url, QIODevice *dev, QImage *image, QString *errorString, QSize *impsize, int *frameCount,
426                       const QRect &requestRegion, const QSize &requestSize, const QQuickImageProviderOptions &providerOptions,
427                       QQuickImageProviderOptions::AutoTransform *appliedTransform = nullptr, int frame = 0)
428 {
429     QImageReader imgio(dev);
430     if (providerOptions.autoTransform() != QQuickImageProviderOptions::UsePluginDefaultTransform)
431         imgio.setAutoTransform(providerOptions.autoTransform() == QQuickImageProviderOptions::ApplyTransform);
432     else if (appliedTransform)
433         *appliedTransform = imgio.autoTransform() ? QQuickImageProviderOptions::ApplyTransform : QQuickImageProviderOptions::DoNotApplyTransform;
434 
435     if (frame < imgio.imageCount())
436         imgio.jumpToImage(frame);
437 
438     if (frameCount)
439         *frameCount = imgio.imageCount();
440 
441     QSize scSize = QQuickImageProviderWithOptions::loadSize(imgio.size(), requestSize, imgio.format(), providerOptions);
442     if (scSize.isValid())
443         imgio.setScaledSize(scSize);
444     if (!requestRegion.isNull())
445         imgio.setScaledClipRect(requestRegion);
446     const QSize originalSize = imgio.size();
447     qCDebug(lcImg) << url << "frame" << frame << "of" << imgio.imageCount()
448                    << "requestRegion" << requestRegion << "QImageReader size" << originalSize << "-> scSize" << scSize;
449 
450     if (impsize)
451         *impsize = originalSize;
452 
453     if (imgio.read(image)) {
454         maybeRemoveAlpha(image);
455         if (impsize && impsize->width() < 0)
456             *impsize = image->size();
457         if (providerOptions.targetColorSpace().isValid()) {
458             if (image->colorSpace().isValid())
459                 image->convertToColorSpace(providerOptions.targetColorSpace());
460             else
461                 image->setColorSpace(providerOptions.targetColorSpace());
462         }
463         return true;
464     } else {
465         if (errorString)
466             *errorString = QQuickPixmap::tr("Error decoding: %1: %2").arg(url.toString())
467                                 .arg(imgio.errorString());
468         return false;
469     }
470 }
471 
fromLatin1List(const QList<QByteArray> & list)472 static QStringList fromLatin1List(const QList<QByteArray> &list)
473 {
474     QStringList res;
475     res.reserve(list.size());
476     for (const QByteArray &item : list)
477         res.append(QString::fromLatin1(item));
478     return res;
479 }
480 
481 class BackendSupport
482 {
483 public:
BackendSupport()484     BackendSupport()
485     {
486         delete QSGContext::createTextureFactoryFromImage(QImage());  // Force init of backend data
487         hasOpenGL = QQuickWindow::sceneGraphBackend().isEmpty();     // i.e. default
488         QList<QByteArray> list;
489         if (hasOpenGL)
490             list.append(QSGTextureReader::supportedFileFormats());
491         list.append(QImageReader::supportedImageFormats());
492         fileSuffixes = fromLatin1List(list);
493     }
494     bool hasOpenGL;
495     QStringList fileSuffixes;
496 };
497 Q_GLOBAL_STATIC(BackendSupport, backendSupport);
498 
existingImageFileForPath(const QString & localFile)499 static QString existingImageFileForPath(const QString &localFile)
500 {
501     // Do nothing if given filepath exists or already has a suffix
502     QFileInfo fi(localFile);
503     if (!fi.suffix().isEmpty() || fi.exists())
504         return localFile;
505 
506     QString tryFile = localFile + QStringLiteral(".xxxx");
507     const int suffixIdx = localFile.length() + 1;
508     for (const QString &suffix : backendSupport()->fileSuffixes) {
509         tryFile.replace(suffixIdx, 10, suffix);
510         if (QFileInfo::exists(tryFile))
511             return tryFile;
512     }
513     return localFile;
514 }
515 
QQuickPixmapReader(QQmlEngine * eng)516 QQuickPixmapReader::QQuickPixmapReader(QQmlEngine *eng)
517 : QThread(eng), engine(eng), threadObject(nullptr)
518 #if QT_CONFIG(qml_network)
519 , accessManager(nullptr)
520 #endif
521 {
522     eventLoopQuitHack = new QObject;
523     eventLoopQuitHack->moveToThread(this);
524     connect(eventLoopQuitHack, SIGNAL(destroyed(QObject*)), SLOT(quit()), Qt::DirectConnection);
525     start(QThread::LowestPriority);
526 #if !QT_CONFIG(thread)
527     // call nonblocking run ourself, as nothread qthread does not
528     run();
529 #endif
530 }
531 
~QQuickPixmapReader()532 QQuickPixmapReader::~QQuickPixmapReader()
533 {
534     readerMutex.lock();
535     readers.remove(engine);
536     readerMutex.unlock();
537 
538     mutex.lock();
539     // manually cancel all outstanding jobs.
540     for (QQuickPixmapReply *reply : qAsConst(jobs)) {
541         if (reply->data && reply->data->reply == reply)
542             reply->data->reply = nullptr;
543         delete reply;
544     }
545     jobs.clear();
546 #if QT_CONFIG(qml_network)
547 
548     const auto cancelJob = [this](QQuickPixmapReply *reply) {
549         if (reply->loading) {
550             cancelled.append(reply);
551             reply->data = nullptr;
552         }
553     };
554 
555     for (auto *reply : qAsConst(networkJobs))
556         cancelJob(reply);
557 
558     for (auto *reply : qAsConst(asyncResponses))
559         cancelJob(reply);
560 #endif
561     if (threadObject) threadObject->processJobs();
562     mutex.unlock();
563 
564     eventLoopQuitHack->deleteLater();
565     wait();
566 }
567 
568 #if QT_CONFIG(qml_network)
networkRequestDone(QNetworkReply * reply)569 void QQuickPixmapReader::networkRequestDone(QNetworkReply *reply)
570 {
571     QQuickPixmapReply *job = networkJobs.take(reply);
572 
573     if (job) {
574         job->redirectCount++;
575         if (job->redirectCount < IMAGEREQUEST_MAX_REDIRECT_RECURSION) {
576             QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
577             if (redirect.isValid()) {
578                 QUrl url = reply->url().resolved(redirect.toUrl());
579                 QNetworkRequest req(url);
580                 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
581 
582                 reply->deleteLater();
583                 reply = networkAccessManager()->get(req);
584 
585                 QMetaObject::connect(reply, replyDownloadProgress, job, downloadProgress);
586                 QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
587 
588                 networkJobs.insert(reply, job);
589                 return;
590             }
591         }
592 
593         QImage image;
594         QQuickPixmapReply::ReadError error = QQuickPixmapReply::NoError;
595         QString errorString;
596         QSize readSize;
597         if (reply->error()) {
598             error = QQuickPixmapReply::Loading;
599             errorString = reply->errorString();
600         } else {
601             QByteArray all = reply->readAll();
602             QBuffer buff(&all);
603             buff.open(QIODevice::ReadOnly);
604             int frameCount;
605             int const frame = job->data ? job->data->frame : 0;
606             if (!readImage(reply->url(), &buff, &image, &errorString, &readSize, &frameCount,
607                            job->requestRegion, job->requestSize, job->providerOptions, nullptr, frame))
608                 error = QQuickPixmapReply::Decoding;
609             else if (job->data)
610                 job->data->frameCount = frameCount;
611         }
612         // send completion event to the QQuickPixmapReply
613         mutex.lock();
614         if (!cancelled.contains(job))
615             job->postReply(error, errorString, readSize, QQuickTextureFactory::textureFactoryForImage(image));
616         mutex.unlock();
617     }
618     reply->deleteLater();
619 
620     // kick off event loop again incase we have dropped below max request count
621     threadObject->processJobs();
622 }
623 #endif // qml_network
624 
asyncResponseFinished(QQuickImageResponse * response)625 void QQuickPixmapReader::asyncResponseFinished(QQuickImageResponse *response)
626 {
627     QQuickPixmapReply *job = asyncResponses.take(response);
628 
629     if (job) {
630         QQuickTextureFactory *t = nullptr;
631         QQuickPixmapReply::ReadError error = QQuickPixmapReply::NoError;
632         QString errorString;
633         if (!response->errorString().isEmpty()) {
634             error = QQuickPixmapReply::Loading;
635             errorString = response->errorString();
636         } else {
637             t = response->textureFactory();
638        }
639         mutex.lock();
640         if (!cancelled.contains(job))
641             job->postReply(error, errorString, t ? t->textureSize() : QSize(), t);
642         else
643             delete t;
644         mutex.unlock();
645     }
646     response->deleteLater();
647 
648     // kick off event loop again incase we have dropped below max request count
649     threadObject->processJobs();
650 }
651 
QQuickPixmapReaderThreadObject(QQuickPixmapReader * i)652 QQuickPixmapReaderThreadObject::QQuickPixmapReaderThreadObject(QQuickPixmapReader *i)
653 : reader(i)
654 {
655 }
656 
processJobs()657 void QQuickPixmapReaderThreadObject::processJobs()
658 {
659     QCoreApplication::postEvent(this, new QEvent(QEvent::User));
660 }
661 
event(QEvent * e)662 bool QQuickPixmapReaderThreadObject::event(QEvent *e)
663 {
664     if (e->type() == QEvent::User) {
665         reader->processJobs();
666         return true;
667     } else {
668         return QObject::event(e);
669     }
670 }
671 
networkRequestDone()672 void QQuickPixmapReaderThreadObject::networkRequestDone()
673 {
674 #if QT_CONFIG(qml_network)
675     QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
676     reader->networkRequestDone(reply);
677 #endif
678 }
679 
asyncResponseFinished(QQuickImageResponse * response)680 void QQuickPixmapReaderThreadObject::asyncResponseFinished(QQuickImageResponse *response)
681 {
682     reader->asyncResponseFinished(response);
683 }
684 
asyncResponseFinished()685 void QQuickPixmapReaderThreadObject::asyncResponseFinished()
686 {
687     QQuickImageResponse *response = static_cast<QQuickImageResponse *>(sender());
688     asyncResponseFinished(response);
689 }
690 
processJobs()691 void QQuickPixmapReader::processJobs()
692 {
693     QMutexLocker locker(&mutex);
694 
695     while (true) {
696         if (cancelled.isEmpty() && jobs.isEmpty())
697             return; // Nothing else to do
698 
699         // Clean cancelled jobs
700         if (!cancelled.isEmpty()) {
701 #if QT_CONFIG(qml_network)
702             for (int i = 0; i < cancelled.count(); ++i) {
703                 QQuickPixmapReply *job = cancelled.at(i);
704                 QNetworkReply *reply = networkJobs.key(job, 0);
705                 if (reply) {
706                     networkJobs.remove(reply);
707                     if (reply->isRunning()) {
708                         // cancel any jobs already started
709                         reply->close();
710                     }
711                 } else {
712                     QQuickImageResponse *asyncResponse = asyncResponses.key(job);
713                     if (asyncResponse) {
714                         asyncResponses.remove(asyncResponse);
715                         asyncResponse->cancel();
716                     }
717                 }
718                 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(job->url));
719                 // deleteLater, since not owned by this thread
720                 job->deleteLater();
721             }
722             cancelled.clear();
723 #endif
724         }
725 
726         if (!jobs.isEmpty()) {
727             // Find a job we can use
728             bool usableJob = false;
729             for (int i = jobs.count() - 1; !usableJob && i >= 0; i--) {
730                 QQuickPixmapReply *job = jobs.at(i);
731                 const QUrl url = job->url;
732                 QString localFile;
733                 QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
734                 QSharedPointer<QQuickImageProvider> provider;
735 
736                 if (url.scheme() == QLatin1String("image")) {
737                     QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(engine);
738                     provider = enginePrivate->imageProvider(imageProviderId(url)).staticCast<QQuickImageProvider>();
739                     if (provider)
740                         imageType = provider->imageType();
741 
742                     usableJob = true;
743                 } else {
744                     localFile = QQmlFile::urlToLocalFileOrQrc(url);
745                     usableJob = !localFile.isEmpty()
746 #if QT_CONFIG(qml_network)
747                             || networkJobs.count() < IMAGEREQUEST_MAX_NETWORK_REQUEST_COUNT
748 #endif
749                             ;
750                 }
751 
752 
753                 if (usableJob) {
754                     jobs.removeAt(i);
755 
756                     job->loading = true;
757 
758                     PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingStarted>(url));
759 
760                     locker.unlock();
761                     processJob(job, url, localFile, imageType, provider);
762                     locker.relock();
763                 }
764             }
765 
766             if (!usableJob)
767                 return;
768         }
769     }
770 }
771 
processJob(QQuickPixmapReply * runningJob,const QUrl & url,const QString & localFile,QQuickImageProvider::ImageType imageType,const QSharedPointer<QQuickImageProvider> & provider)772 void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &url, const QString &localFile,
773                                     QQuickImageProvider::ImageType imageType, const QSharedPointer<QQuickImageProvider> &provider)
774 {
775     // fetch
776     if (url.scheme() == QLatin1String("image")) {
777         // Use QQuickImageProvider
778         QSize readSize;
779 
780         if (imageType == QQuickImageProvider::Invalid) {
781             QString errorStr = QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString());
782             mutex.lock();
783             if (!cancelled.contains(runningJob))
784                 runningJob->postReply(QQuickPixmapReply::Loading, errorStr, readSize, nullptr);
785             mutex.unlock();
786             return;
787         }
788 
789         // This is safe because we ensure that provider does outlive providerV2 and it does not escape the function
790         QQuickImageProviderWithOptions *providerV2 = QQuickImageProviderWithOptions::checkedCast(provider.get());
791 
792         switch (imageType) {
793             case QQuickImageProvider::Invalid:
794             {
795                 // Already handled
796                 break;
797             }
798 
799             case QQuickImageProvider::Image:
800             {
801                 QImage image;
802                 if (providerV2) {
803                     image = providerV2->requestImage(imageId(url), &readSize, runningJob->requestSize, runningJob->providerOptions);
804                 } else {
805                     image = provider->requestImage(imageId(url), &readSize, runningJob->requestSize);
806                 }
807                 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
808                 QString errorStr;
809                 if (image.isNull()) {
810                     errorCode = QQuickPixmapReply::Loading;
811                     errorStr = QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString());
812                 }
813                 mutex.lock();
814                 if (!cancelled.contains(runningJob))
815                     runningJob->postReply(errorCode, errorStr, readSize, QQuickTextureFactory::textureFactoryForImage(image));
816                 mutex.unlock();
817                 break;
818             }
819 
820             case QQuickImageProvider::Pixmap:
821             {
822                 QPixmap pixmap;
823                 if (providerV2) {
824                     pixmap = providerV2->requestPixmap(imageId(url), &readSize, runningJob->requestSize, runningJob->providerOptions);
825                 } else {
826                     pixmap = provider->requestPixmap(imageId(url), &readSize, runningJob->requestSize);
827                 }
828                 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
829                 QString errorStr;
830                 if (pixmap.isNull()) {
831                     errorCode = QQuickPixmapReply::Loading;
832                     errorStr = QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString());
833                 }
834                 mutex.lock();
835                 if (!cancelled.contains(runningJob))
836                     runningJob->postReply(errorCode, errorStr, readSize, QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()));
837                 mutex.unlock();
838                 break;
839             }
840 
841             case QQuickImageProvider::Texture:
842             {
843                 QQuickTextureFactory *t;
844                 if (providerV2) {
845                     t = providerV2->requestTexture(imageId(url), &readSize, runningJob->requestSize, runningJob->providerOptions);
846                 } else {
847                     t = provider->requestTexture(imageId(url), &readSize, runningJob->requestSize);
848                 }
849                 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
850                 QString errorStr;
851                 if (!t) {
852                     errorCode = QQuickPixmapReply::Loading;
853                     errorStr = QQuickPixmap::tr("Failed to get texture from provider: %1").arg(url.toString());
854                 }
855                 mutex.lock();
856                 if (!cancelled.contains(runningJob))
857                     runningJob->postReply(errorCode, errorStr, readSize, t);
858                 else
859                     delete t;
860                 mutex.unlock();
861                 break;
862             }
863 
864             case QQuickImageProvider::ImageResponse:
865             {
866                 QQuickImageResponse *response;
867                 if (providerV2) {
868                     response = providerV2->requestImageResponse(imageId(url), runningJob->requestSize, runningJob->providerOptions);
869                 } else {
870                     QQuickAsyncImageProvider *asyncProvider = static_cast<QQuickAsyncImageProvider*>(provider.get());
871                     response = asyncProvider->requestImageResponse(imageId(url), runningJob->requestSize);
872                 }
873 
874                 {
875                     QObject::connect(response, SIGNAL(finished()), threadObject, SLOT(asyncResponseFinished()));
876                     // as the response object can outlive the provider QSharedPointer, we have to extend the pointee's lifetime by that of the response
877                     // we do this by capturing a copy of the QSharedPointer in a lambda, and dropping it once the lambda has been called
878                     auto provider_copy = provider; // capturing provider would capture it as a const reference, and copy capture with initializer is only available in C++14
879                     QObject::connect(response, &QQuickImageResponse::destroyed, response, [provider_copy]() {
880                         // provider_copy will be deleted when the connection gets deleted
881                     });
882                 }
883                 // Might be that the async provider was so quick it emitted the signal before we
884                 // could connect to it.
885                 if (static_cast<QQuickImageResponsePrivate*>(QObjectPrivate::get(response))->finished.loadAcquire()) {
886                     QMetaObject::invokeMethod(threadObject, "asyncResponseFinished",
887                                               Qt::QueuedConnection, Q_ARG(QQuickImageResponse*, response));
888                 }
889 
890                 asyncResponses.insert(response, runningJob);
891                 break;
892             }
893         }
894 
895     } else {
896         if (!localFile.isEmpty()) {
897             // Image is local - load/decode immediately
898             QImage image;
899             QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
900             QString errorStr;
901             QFile f(existingImageFileForPath(localFile));
902             QSize readSize;
903             if (f.open(QIODevice::ReadOnly)) {
904                 QSGTextureReader texReader(&f, localFile);
905                 if (backendSupport()->hasOpenGL && texReader.isTexture()) {
906                     QQuickTextureFactory *factory = texReader.read();
907                     if (factory) {
908                         readSize = factory->textureSize();
909                     } else {
910                         errorStr = QQuickPixmap::tr("Error decoding: %1").arg(url.toString());
911                         if (f.fileName() != localFile)
912                             errorStr += QString::fromLatin1(" (%1)").arg(f.fileName());
913                         errorCode = QQuickPixmapReply::Decoding;
914                     }
915                     mutex.lock();
916                     if (!cancelled.contains(runningJob))
917                         runningJob->postReply(errorCode, errorStr, readSize, factory);
918                     mutex.unlock();
919                     return;
920                 } else {
921                     int frameCount;
922                     int const frame = runningJob->data ? runningJob->data->frame : 0;
923                     if (!readImage(url, &f, &image, &errorStr, &readSize, &frameCount, runningJob->requestRegion, runningJob->requestSize,
924                                    runningJob->providerOptions, nullptr, frame)) {
925                         errorCode = QQuickPixmapReply::Loading;
926                         if (f.fileName() != localFile)
927                             errorStr += QString::fromLatin1(" (%1)").arg(f.fileName());
928                     } else if (runningJob->data) {
929                         runningJob->data->frameCount = frameCount;
930                     }
931                 }
932             } else {
933                 errorStr = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
934                 errorCode = QQuickPixmapReply::Loading;
935             }
936             mutex.lock();
937             if (!cancelled.contains(runningJob))
938                 runningJob->postReply(errorCode, errorStr, readSize, QQuickTextureFactory::textureFactoryForImage(image));
939             mutex.unlock();
940         } else {
941 #if QT_CONFIG(qml_network)
942             // Network resource
943             QNetworkRequest req(url);
944             req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
945             QNetworkReply *reply = networkAccessManager()->get(req);
946 
947             QMetaObject::connect(reply, replyDownloadProgress, runningJob, downloadProgress);
948             QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
949 
950             networkJobs.insert(reply, runningJob);
951 #else
952 // Silently fail if compiled with no_network
953 #endif
954         }
955     }
956 }
957 
instance(QQmlEngine * engine)958 QQuickPixmapReader *QQuickPixmapReader::instance(QQmlEngine *engine)
959 {
960     // XXX NOTE: must be called within readerMutex locking.
961     QQuickPixmapReader *reader = readers.value(engine);
962     if (!reader) {
963         reader = new QQuickPixmapReader(engine);
964         readers.insert(engine, reader);
965     }
966 
967     return reader;
968 }
969 
existingInstance(QQmlEngine * engine)970 QQuickPixmapReader *QQuickPixmapReader::existingInstance(QQmlEngine *engine)
971 {
972     // XXX NOTE: must be called within readerMutex locking.
973     return readers.value(engine, 0);
974 }
975 
getImage(QQuickPixmapData * data)976 QQuickPixmapReply *QQuickPixmapReader::getImage(QQuickPixmapData *data)
977 {
978     mutex.lock();
979     QQuickPixmapReply *reply = new QQuickPixmapReply(data);
980     reply->engineForReader = engine;
981     jobs.append(reply);
982     // XXX
983     if (threadObject) threadObject->processJobs();
984     mutex.unlock();
985     return reply;
986 }
987 
cancel(QQuickPixmapReply * reply)988 void QQuickPixmapReader::cancel(QQuickPixmapReply *reply)
989 {
990     mutex.lock();
991     if (reply->loading) {
992         cancelled.append(reply);
993         reply->data = nullptr;
994         // XXX
995         if (threadObject) threadObject->processJobs();
996     } else {
997         // If loading was started (reply removed from jobs) but the reply was never processed
998         // (otherwise it would have deleted itself) we need to profile an error.
999         if (jobs.removeAll(reply) == 0) {
1000             PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(reply->url));
1001         }
1002         delete reply;
1003     }
1004     mutex.unlock();
1005 }
1006 
run()1007 void QQuickPixmapReader::run()
1008 {
1009     if (replyDownloadProgress == -1) {
1010 #if QT_CONFIG(qml_network)
1011         replyDownloadProgress = QMetaMethod::fromSignal(&QNetworkReply::downloadProgress).methodIndex();
1012         replyFinished = QMetaMethod::fromSignal(&QNetworkReply::finished).methodIndex();
1013         const QMetaObject *ir = &QQuickPixmapReaderThreadObject::staticMetaObject;
1014         threadNetworkRequestDone = ir->indexOfSlot("networkRequestDone()");
1015 #endif
1016         downloadProgress = QMetaMethod::fromSignal(&QQuickPixmapReply::downloadProgress).methodIndex();
1017     }
1018 
1019     mutex.lock();
1020     threadObject = new QQuickPixmapReaderThreadObject(this);
1021     mutex.unlock();
1022 
1023     processJobs();
1024     exec();
1025 
1026 #if QT_CONFIG(thread)
1027     // nothread exec is empty and returns
1028     delete threadObject;
1029     threadObject = nullptr;
1030 #endif
1031 }
1032 
1033 class QQuickPixmapKey
1034 {
1035 public:
1036     const QUrl *url;
1037     const QRect *region;
1038     const QSize *size;
1039     int frame;
1040     QQuickImageProviderOptions options;
1041 };
1042 
operator ==(const QQuickPixmapKey & lhs,const QQuickPixmapKey & rhs)1043 inline bool operator==(const QQuickPixmapKey &lhs, const QQuickPixmapKey &rhs)
1044 {
1045     return *lhs.url == *rhs.url &&
1046            *lhs.region == *rhs.region &&
1047            *lhs.size == *rhs.size &&
1048             lhs.frame == rhs.frame &&
1049             lhs.options == rhs.options;
1050 }
1051 
qHash(const QQuickPixmapKey & key)1052 inline uint qHash(const QQuickPixmapKey &key)
1053 {
1054     return qHash(*key.url) ^ (key.size->width()*7) ^ (key.size->height()*17) ^ (key.frame*23) ^
1055             (key.region->x()*29) ^ (key.region->y()*31) ^ (key.options.autoTransform() * 0x5c5c5c5c);
1056     // key.region.width() and height() are not included, because the hash function should be simple,
1057     // and they are more likely to be held constant for some batches of images
1058     // (e.g. tiles, or repeatedly cropping to the same viewport at different positions).
1059 }
1060 
1061 class QQuickPixmapStore : public QObject
1062 {
1063     Q_OBJECT
1064 public:
1065     QQuickPixmapStore();
1066     ~QQuickPixmapStore();
1067 
1068     void unreferencePixmap(QQuickPixmapData *);
1069     void referencePixmap(QQuickPixmapData *);
1070 
1071     void purgeCache();
1072 
1073 protected:
1074     void timerEvent(QTimerEvent *) override;
1075 
1076 public:
1077     QHash<QQuickPixmapKey, QQuickPixmapData *> m_cache;
1078 
1079 private:
1080     void shrinkCache(int remove);
1081 
1082     QQuickPixmapData *m_unreferencedPixmaps;
1083     QQuickPixmapData *m_lastUnreferencedPixmap;
1084 
1085     int m_unreferencedCost;
1086     int m_timerId;
1087     bool m_destroying;
1088 };
1089 Q_GLOBAL_STATIC(QQuickPixmapStore, pixmapStore);
1090 
1091 
QQuickPixmapStore()1092 QQuickPixmapStore::QQuickPixmapStore()
1093     : m_unreferencedPixmaps(nullptr), m_lastUnreferencedPixmap(nullptr), m_unreferencedCost(0), m_timerId(-1), m_destroying(false)
1094 {
1095 }
1096 
~QQuickPixmapStore()1097 QQuickPixmapStore::~QQuickPixmapStore()
1098 {
1099     m_destroying = true;
1100 
1101 #ifndef QT_NO_DEBUG
1102     int leakedPixmaps = 0;
1103 #endif
1104     // Prevent unreferencePixmap() from assuming it needs to kick
1105     // off the cache expiry timer, as we're shrinking the cache
1106     // manually below after releasing all the pixmaps.
1107     m_timerId = -2;
1108 
1109     // unreference all (leaked) pixmaps
1110     const auto cache = m_cache; // NOTE: intentional copy (QTBUG-65077); releasing items from the cache modifies m_cache.
1111     for (auto *pixmap : cache) {
1112         int currRefCount = pixmap->refCount;
1113         if (currRefCount) {
1114 #ifndef QT_NO_DEBUG
1115             leakedPixmaps++;
1116 #endif
1117             while (currRefCount > 0) {
1118                 pixmap->release();
1119                 currRefCount--;
1120             }
1121         }
1122     }
1123 
1124     // free all unreferenced pixmaps
1125     while (m_lastUnreferencedPixmap) {
1126         shrinkCache(20);
1127     }
1128 
1129 #ifndef QT_NO_DEBUG
1130     if (leakedPixmaps && qsg_leak_check)
1131         qDebug("Number of leaked pixmaps: %i", leakedPixmaps);
1132 #endif
1133 }
1134 
unreferencePixmap(QQuickPixmapData * data)1135 void QQuickPixmapStore::unreferencePixmap(QQuickPixmapData *data)
1136 {
1137     Q_ASSERT(data->prevUnreferenced == nullptr);
1138     Q_ASSERT(data->prevUnreferencedPtr == nullptr);
1139     Q_ASSERT(data->nextUnreferenced == nullptr);
1140 
1141     data->nextUnreferenced = m_unreferencedPixmaps;
1142     data->prevUnreferencedPtr = &m_unreferencedPixmaps;
1143     if (!m_destroying) // the texture factories may have been cleaned up already.
1144         m_unreferencedCost += data->cost();
1145 
1146     m_unreferencedPixmaps = data;
1147     if (m_unreferencedPixmaps->nextUnreferenced) {
1148         m_unreferencedPixmaps->nextUnreferenced->prevUnreferenced = m_unreferencedPixmaps;
1149         m_unreferencedPixmaps->nextUnreferenced->prevUnreferencedPtr = &m_unreferencedPixmaps->nextUnreferenced;
1150     }
1151 
1152     if (!m_lastUnreferencedPixmap)
1153         m_lastUnreferencedPixmap = data;
1154 
1155     shrinkCache(-1); // Shrink the cache in case it has become larger than cache_limit
1156 
1157     if (m_timerId == -1 && m_unreferencedPixmaps
1158             && !m_destroying && !QCoreApplication::closingDown()) {
1159         m_timerId = startTimer(CACHE_EXPIRE_TIME * 1000);
1160     }
1161 }
1162 
referencePixmap(QQuickPixmapData * data)1163 void QQuickPixmapStore::referencePixmap(QQuickPixmapData *data)
1164 {
1165     Q_ASSERT(data->prevUnreferencedPtr);
1166 
1167     *data->prevUnreferencedPtr = data->nextUnreferenced;
1168     if (data->nextUnreferenced) {
1169         data->nextUnreferenced->prevUnreferencedPtr = data->prevUnreferencedPtr;
1170         data->nextUnreferenced->prevUnreferenced = data->prevUnreferenced;
1171     }
1172     if (m_lastUnreferencedPixmap == data)
1173         m_lastUnreferencedPixmap = data->prevUnreferenced;
1174 
1175     data->nextUnreferenced = nullptr;
1176     data->prevUnreferencedPtr = nullptr;
1177     data->prevUnreferenced = nullptr;
1178 
1179     m_unreferencedCost -= data->cost();
1180 }
1181 
shrinkCache(int remove)1182 void QQuickPixmapStore::shrinkCache(int remove)
1183 {
1184     while ((remove > 0 || m_unreferencedCost > cache_limit) && m_lastUnreferencedPixmap) {
1185         QQuickPixmapData *data = m_lastUnreferencedPixmap;
1186         Q_ASSERT(data->nextUnreferenced == nullptr);
1187 
1188         *data->prevUnreferencedPtr = nullptr;
1189         m_lastUnreferencedPixmap = data->prevUnreferenced;
1190         data->prevUnreferencedPtr = nullptr;
1191         data->prevUnreferenced = nullptr;
1192 
1193         if (!m_destroying) {
1194             remove -= data->cost();
1195             m_unreferencedCost -= data->cost();
1196         }
1197         data->removeFromCache();
1198         delete data;
1199     }
1200 }
1201 
timerEvent(QTimerEvent *)1202 void QQuickPixmapStore::timerEvent(QTimerEvent *)
1203 {
1204     int removalCost = m_unreferencedCost / CACHE_REMOVAL_FRACTION;
1205 
1206     shrinkCache(removalCost);
1207 
1208     if (m_unreferencedPixmaps == nullptr) {
1209         killTimer(m_timerId);
1210         m_timerId = -1;
1211     }
1212 }
1213 
purgeCache()1214 void QQuickPixmapStore::purgeCache()
1215 {
1216     shrinkCache(m_unreferencedCost);
1217 }
1218 
purgeCache()1219 void QQuickPixmap::purgeCache()
1220 {
1221     pixmapStore()->purgeCache();
1222 }
1223 
QQuickPixmapReply(QQuickPixmapData * d)1224 QQuickPixmapReply::QQuickPixmapReply(QQuickPixmapData *d)
1225   : data(d), engineForReader(nullptr), requestRegion(d->requestRegion), requestSize(d->requestSize),
1226     url(d->url), loading(false), providerOptions(d->providerOptions), redirectCount(0)
1227 {
1228     if (finishedIndex == -1) {
1229         finishedIndex = QMetaMethod::fromSignal(&QQuickPixmapReply::finished).methodIndex();
1230         downloadProgressIndex = QMetaMethod::fromSignal(&QQuickPixmapReply::downloadProgress).methodIndex();
1231     }
1232 }
1233 
~QQuickPixmapReply()1234 QQuickPixmapReply::~QQuickPixmapReply()
1235 {
1236     // note: this->data->reply must be set to zero if this->data->reply == this
1237     // but it must be done within mutex locking, to be guaranteed to be safe.
1238 }
1239 
event(QEvent * event)1240 bool QQuickPixmapReply::event(QEvent *event)
1241 {
1242     if (event->type() == QEvent::User) {
1243 
1244         if (data) {
1245             Event *de = static_cast<Event *>(event);
1246             data->pixmapStatus = (de->error == NoError) ? QQuickPixmap::Ready : QQuickPixmap::Error;
1247             if (data->pixmapStatus == QQuickPixmap::Ready) {
1248                 data->textureFactory = de->textureFactory;
1249                 de->textureFactory = nullptr;
1250                 data->implicitSize = de->implicitSize;
1251                 PIXMAP_PROFILE(pixmapLoadingFinished(data->url,
1252                         data->textureFactory != nullptr && data->textureFactory->textureSize().isValid() ?
1253                         data->textureFactory->textureSize() :
1254                         (data->requestSize.isValid() ? data->requestSize : data->implicitSize)));
1255             } else {
1256                 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(data->url));
1257                 data->errorString = de->errorString;
1258                 data->removeFromCache(); // We don't continue to cache error'd pixmaps
1259             }
1260 
1261             data->reply = nullptr;
1262             emit finished();
1263         } else {
1264             PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(url));
1265         }
1266 
1267         delete this;
1268         return true;
1269     } else {
1270         return QObject::event(event);
1271     }
1272 }
1273 
cost() const1274 int QQuickPixmapData::cost() const
1275 {
1276     if (textureFactory)
1277         return textureFactory->textureByteCount();
1278     return 0;
1279 }
1280 
addref()1281 void QQuickPixmapData::addref()
1282 {
1283     ++refCount;
1284     PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapReferenceCountChanged>(url, refCount));
1285     if (prevUnreferencedPtr)
1286         pixmapStore()->referencePixmap(this);
1287 }
1288 
release()1289 void QQuickPixmapData::release()
1290 {
1291     Q_ASSERT(refCount > 0);
1292     --refCount;
1293     PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapReferenceCountChanged>(url, refCount));
1294     if (refCount == 0) {
1295         if (reply) {
1296             QQuickPixmapReply *cancelReply = reply;
1297             reply->data = nullptr;
1298             reply = nullptr;
1299             QQuickPixmapReader::readerMutex.lock();
1300             QQuickPixmapReader *reader = QQuickPixmapReader::existingInstance(cancelReply->engineForReader);
1301             if (reader)
1302                 reader->cancel(cancelReply);
1303             QQuickPixmapReader::readerMutex.unlock();
1304         }
1305 
1306         if (pixmapStatus == QQuickPixmap::Ready
1307 #ifdef Q_OS_WEBOS
1308                 && storeToCache
1309 #endif
1310                 ) {
1311             if (inCache)
1312                 pixmapStore()->unreferencePixmap(this);
1313             else
1314                 delete this;
1315         } else {
1316             removeFromCache();
1317             delete this;
1318         }
1319     }
1320 }
1321 
addToCache()1322 void QQuickPixmapData::addToCache()
1323 {
1324     if (!inCache) {
1325         QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
1326         pixmapStore()->m_cache.insert(key, this);
1327         inCache = true;
1328         PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapCacheCountChanged>(
1329                 url, pixmapStore()->m_cache.count()));
1330     }
1331 }
1332 
removeFromCache()1333 void QQuickPixmapData::removeFromCache()
1334 {
1335     if (inCache) {
1336         QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
1337         pixmapStore()->m_cache.remove(key);
1338         inCache = false;
1339         PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapCacheCountChanged>(
1340                 url, pixmapStore()->m_cache.count()));
1341     }
1342 }
1343 
createPixmapDataSync(QQuickPixmap * declarativePixmap,QQmlEngine * engine,const QUrl & url,const QRect & requestRegion,const QSize & requestSize,const QQuickImageProviderOptions & providerOptions,int frame,bool * ok)1344 static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, QQmlEngine *engine, const QUrl &url,
1345                                               const QRect &requestRegion, const QSize &requestSize,
1346                                               const QQuickImageProviderOptions &providerOptions, int frame, bool *ok)
1347 {
1348     if (url.scheme() == QLatin1String("image")) {
1349         QSize readSize;
1350 
1351         QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
1352         QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(engine);
1353         QSharedPointer<QQuickImageProvider> provider = enginePrivate->imageProvider(imageProviderId(url)).dynamicCast<QQuickImageProvider>();
1354         // it is safe to use get() as providerV2 does not escape and is outlived by provider
1355         QQuickImageProviderWithOptions *providerV2 = QQuickImageProviderWithOptions::checkedCast(provider.get());
1356         if (provider)
1357             imageType = provider->imageType();
1358 
1359         switch (imageType) {
1360             case QQuickImageProvider::Invalid:
1361                 return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions,
1362                     QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString()));
1363             case QQuickImageProvider::Texture:
1364             {
1365                 QQuickTextureFactory *texture = providerV2 ? providerV2->requestTexture(imageId(url), &readSize, requestSize, providerOptions)
1366                                                            : provider->requestTexture(imageId(url), &readSize, requestSize);
1367                 if (texture) {
1368                     *ok = true;
1369                     return new QQuickPixmapData(declarativePixmap, url, texture, readSize, requestRegion, requestSize,
1370                                                 providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
1371                 }
1372                 break;
1373             }
1374 
1375             case QQuickImageProvider::Image:
1376             {
1377                 QImage image = providerV2 ? providerV2->requestImage(imageId(url), &readSize, requestSize, providerOptions)
1378                                           : provider->requestImage(imageId(url), &readSize, requestSize);
1379                 if (!image.isNull()) {
1380                     *ok = true;
1381                     return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image),
1382                                                 readSize, requestRegion, requestSize, providerOptions,
1383                                                 QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
1384                 }
1385                 break;
1386             }
1387             case QQuickImageProvider::Pixmap:
1388             {
1389                 QPixmap pixmap = providerV2 ? providerV2->requestPixmap(imageId(url), &readSize, requestSize, providerOptions)
1390                                             : provider->requestPixmap(imageId(url), &readSize, requestSize);
1391                 if (!pixmap.isNull()) {
1392                     *ok = true;
1393                     return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()),
1394                                                 readSize, requestRegion, requestSize, providerOptions,
1395                                                 QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
1396                 }
1397                 break;
1398             }
1399             case QQuickImageProvider::ImageResponse:
1400             {
1401                 // Fall through, ImageResponse providers never get here
1402                 Q_ASSERT(imageType != QQuickImageProvider::ImageResponse && "Sync call to ImageResponse provider");
1403             }
1404         }
1405 
1406         // provider has bad image type, or provider returned null image
1407         return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions,
1408             QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString()));
1409     }
1410 
1411     QString localFile = QQmlFile::urlToLocalFileOrQrc(url);
1412     if (localFile.isEmpty())
1413         return nullptr;
1414 
1415     QFile f(existingImageFileForPath(localFile));
1416     QSize readSize;
1417     QString errorString;
1418 
1419     if (f.open(QIODevice::ReadOnly)) {
1420         QSGTextureReader texReader(&f, localFile);
1421         if (backendSupport()->hasOpenGL && texReader.isTexture()) {
1422             QQuickTextureFactory *factory = texReader.read();
1423             if (factory) {
1424                 *ok = true;
1425                 return new QQuickPixmapData(declarativePixmap, url, factory, factory->textureSize(), requestRegion, requestSize,
1426                                             providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
1427             } else {
1428                 errorString = QQuickPixmap::tr("Error decoding: %1").arg(url.toString());
1429                 if (f.fileName() != localFile)
1430                     errorString += QString::fromLatin1(" (%1)").arg(f.fileName());
1431             }
1432         } else {
1433             QImage image;
1434             QQuickImageProviderOptions::AutoTransform appliedTransform = providerOptions.autoTransform();
1435             int frameCount;
1436             if (readImage(url, &f, &image, &errorString, &readSize, &frameCount, requestRegion, requestSize, providerOptions, &appliedTransform, frame)) {
1437                 *ok = true;
1438                 return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestRegion, requestSize,
1439                                             providerOptions, appliedTransform, frame, frameCount);
1440             } else if (f.fileName() != localFile) {
1441                 errorString += QString::fromLatin1(" (%1)").arg(f.fileName());
1442             }
1443         }
1444     } else {
1445         errorString = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
1446     }
1447     return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions, errorString);
1448 }
1449 
1450 
1451 struct QQuickPixmapNull {
1452     QUrl url;
1453     QRect region;
1454     QSize size;
1455 };
1456 Q_GLOBAL_STATIC(QQuickPixmapNull, nullPixmap);
1457 
QQuickPixmap()1458 QQuickPixmap::QQuickPixmap()
1459 : d(nullptr)
1460 {
1461 }
1462 
QQuickPixmap(QQmlEngine * engine,const QUrl & url)1463 QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url)
1464 : d(nullptr)
1465 {
1466     load(engine, url);
1467 }
1468 
QQuickPixmap(QQmlEngine * engine,const QUrl & url,const QRect & region,const QSize & size)1469 QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QRect &region, const QSize &size)
1470 : d(nullptr)
1471 {
1472     load(engine, url, region, size);
1473 }
1474 
QQuickPixmap(const QUrl & url,const QImage & image)1475 QQuickPixmap::QQuickPixmap(const QUrl &url, const QImage &image)
1476 {
1477     d = new QQuickPixmapData(this, url, new QQuickDefaultTextureFactory(image), image.size(), QRect(), QSize(),
1478                              QQuickImageProviderOptions(), QQuickImageProviderOptions::UsePluginDefaultTransform);
1479     d->addToCache();
1480 }
1481 
~QQuickPixmap()1482 QQuickPixmap::~QQuickPixmap()
1483 {
1484     if (d) {
1485         d->declarativePixmaps.remove(this);
1486         d->release();
1487         d = nullptr;
1488     }
1489 }
1490 
isNull() const1491 bool QQuickPixmap::isNull() const
1492 {
1493     return d == nullptr;
1494 }
1495 
isReady() const1496 bool QQuickPixmap::isReady() const
1497 {
1498     return status() == Ready;
1499 }
1500 
isError() const1501 bool QQuickPixmap::isError() const
1502 {
1503     return status() == Error;
1504 }
1505 
isLoading() const1506 bool QQuickPixmap::isLoading() const
1507 {
1508     return status() == Loading;
1509 }
1510 
error() const1511 QString QQuickPixmap::error() const
1512 {
1513     if (d)
1514         return d->errorString;
1515     else
1516         return QString();
1517 }
1518 
status() const1519 QQuickPixmap::Status QQuickPixmap::status() const
1520 {
1521     if (d)
1522         return d->pixmapStatus;
1523     else
1524         return Null;
1525 }
1526 
url() const1527 const QUrl &QQuickPixmap::url() const
1528 {
1529     if (d)
1530         return d->url;
1531     else
1532         return nullPixmap()->url;
1533 }
1534 
implicitSize() const1535 const QSize &QQuickPixmap::implicitSize() const
1536 {
1537     if (d)
1538         return d->implicitSize;
1539     else
1540         return nullPixmap()->size;
1541 }
1542 
requestSize() const1543 const QSize &QQuickPixmap::requestSize() const
1544 {
1545     if (d)
1546         return d->requestSize;
1547     else
1548         return nullPixmap()->size;
1549 }
1550 
requestRegion() const1551 const QRect &QQuickPixmap::requestRegion() const
1552 {
1553     if (d)
1554         return d->requestRegion;
1555     else
1556         return nullPixmap()->region;
1557 }
1558 
autoTransform() const1559 QQuickImageProviderOptions::AutoTransform QQuickPixmap::autoTransform() const
1560 {
1561     if (d)
1562         return d->appliedTransform;
1563     else
1564         return QQuickImageProviderOptions::UsePluginDefaultTransform;
1565 }
1566 
frameCount() const1567 int QQuickPixmap::frameCount() const
1568 {
1569     if (d)
1570         return d->frameCount;
1571     return 0;
1572 }
1573 
textureFactory() const1574 QQuickTextureFactory *QQuickPixmap::textureFactory() const
1575 {
1576     if (d)
1577         return d->textureFactory;
1578 
1579     return nullptr;
1580 }
1581 
image() const1582 QImage QQuickPixmap::image() const
1583 {
1584     if (d && d->textureFactory)
1585         return d->textureFactory->image();
1586     return QImage();
1587 }
1588 
setImage(const QImage & p)1589 void QQuickPixmap::setImage(const QImage &p)
1590 {
1591     clear();
1592 
1593     if (!p.isNull())
1594         d = new QQuickPixmapData(this, QQuickTextureFactory::textureFactoryForImage(p));
1595 }
1596 
setPixmap(const QQuickPixmap & other)1597 void QQuickPixmap::setPixmap(const QQuickPixmap &other)
1598 {
1599     clear();
1600 
1601     if (other.d) {
1602         d = other.d;
1603         d->addref();
1604         d->declarativePixmaps.insert(this);
1605     }
1606 }
1607 
width() const1608 int QQuickPixmap::width() const
1609 {
1610     if (d && d->textureFactory)
1611         return d->textureFactory->textureSize().width();
1612     else
1613         return 0;
1614 }
1615 
height() const1616 int QQuickPixmap::height() const
1617 {
1618     if (d && d->textureFactory)
1619         return d->textureFactory->textureSize().height();
1620     else
1621         return 0;
1622 }
1623 
rect() const1624 QRect QQuickPixmap::rect() const
1625 {
1626     if (d && d->textureFactory)
1627         return QRect(QPoint(), d->textureFactory->textureSize());
1628     else
1629         return QRect();
1630 }
1631 
load(QQmlEngine * engine,const QUrl & url)1632 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url)
1633 {
1634     load(engine, url, QRect(), QSize(), QQuickPixmap::Cache);
1635 }
1636 
load(QQmlEngine * engine,const QUrl & url,QQuickPixmap::Options options)1637 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, QQuickPixmap::Options options)
1638 {
1639     load(engine, url, QRect(), QSize(), options);
1640 }
1641 
load(QQmlEngine * engine,const QUrl & url,const QRect & requestRegion,const QSize & requestSize)1642 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize)
1643 {
1644     load(engine, url, requestRegion, requestSize, QQuickPixmap::Cache);
1645 }
1646 
load(QQmlEngine * engine,const QUrl & url,const QRect & requestRegion,const QSize & requestSize,QQuickPixmap::Options options)1647 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize, QQuickPixmap::Options options)
1648 {
1649     load(engine, url, requestRegion, requestSize, options, QQuickImageProviderOptions());
1650 }
1651 
load(QQmlEngine * engine,const QUrl & url,const QRect & requestRegion,const QSize & requestSize,QQuickPixmap::Options options,const QQuickImageProviderOptions & providerOptions,int frame,int frameCount)1652 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize,
1653                         QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions, int frame, int frameCount)
1654 {
1655     if (d) {
1656         d->declarativePixmaps.remove(this);
1657         d->release();
1658         d = nullptr;
1659     }
1660 
1661     QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
1662     QQuickPixmapStore *store = pixmapStore();
1663 
1664     QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end();
1665 
1666 #ifdef Q_OS_WEBOS
1667     QQuickPixmap::Options orgOptions = options;
1668     // In webOS, we suppose that cache is always enabled to share image instances along its source.
1669     // So, original option(orgOptions) for cache only decides whether to store the instances when it's unreferenced.
1670     options |= QQuickPixmap::Cache;
1671 #endif
1672 
1673     // If Cache is disabled, the pixmap will always be loaded, even if there is an existing
1674     // cached version. Unless it's an itemgrabber url, since the cache is used to pass
1675     // the result between QQuickItemGrabResult and QQuickImage.
1676     if (url.scheme() == itemGrabberScheme) {
1677         QRect dummyRegion;
1678         QSize dummySize;
1679         if (requestSize != dummySize)
1680             qWarning() << "Ignoring sourceSize request for image url that came from grabToImage. Use the targetSize parameter of the grabToImage() function instead.";
1681         const QQuickPixmapKey grabberKey = { &url, &dummyRegion, &dummySize, 0, QQuickImageProviderOptions() };
1682         iter = store->m_cache.find(grabberKey);
1683     } else if (options & QQuickPixmap::Cache)
1684         iter = store->m_cache.find(key);
1685 
1686     if (iter == store->m_cache.end()) {
1687         if (url.scheme() == QLatin1String("image")) {
1688             QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(engine);
1689             if (auto provider = enginePrivate->imageProvider(imageProviderId(url)).staticCast<QQuickImageProvider>()) {
1690                 const bool threadedPixmaps = QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedPixmaps);
1691                 if (!threadedPixmaps && provider->imageType() == QQuickImageProvider::Pixmap) {
1692                     // pixmaps can only be loaded synchronously
1693                     options &= ~QQuickPixmap::Asynchronous;
1694                 } else if (provider->flags() & QQuickImageProvider::ForceAsynchronousImageLoading) {
1695                     options |= QQuickPixmap::Asynchronous;
1696                 }
1697             }
1698         }
1699 
1700         if (!(options & QQuickPixmap::Asynchronous)) {
1701             bool ok = false;
1702             PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingStarted>(url));
1703             d = createPixmapDataSync(this, engine, url, requestRegion, requestSize, providerOptions, frame, &ok);
1704             if (ok) {
1705                 PIXMAP_PROFILE(pixmapLoadingFinished(url, QSize(width(), height())));
1706                 if (options & QQuickPixmap::Cache)
1707                     d->addToCache();
1708 #ifdef Q_OS_WEBOS
1709                 d->storeToCache = orgOptions & QQuickPixmap::Cache;
1710 #endif
1711                 return;
1712             }
1713             if (d) { // loadable, but encountered error while loading
1714                 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(url));
1715                 return;
1716             }
1717         }
1718 
1719         if (!engine)
1720             return;
1721 
1722 
1723         d = new QQuickPixmapData(this, url, requestRegion, requestSize, providerOptions,
1724                                  QQuickImageProviderOptions::UsePluginDefaultTransform, frame, frameCount);
1725         if (options & QQuickPixmap::Cache)
1726             d->addToCache();
1727 #ifdef Q_OS_WEBOS
1728         d->storeToCache = orgOptions & QQuickPixmap::Cache;
1729 #endif
1730 
1731         QQuickPixmapReader::readerMutex.lock();
1732         d->reply = QQuickPixmapReader::instance(engine)->getImage(d);
1733         QQuickPixmapReader::readerMutex.unlock();
1734     } else {
1735         d = *iter;
1736         d->addref();
1737         d->declarativePixmaps.insert(this);
1738     }
1739 }
1740 
clear()1741 void QQuickPixmap::clear()
1742 {
1743     if (d) {
1744         d->declarativePixmaps.remove(this);
1745         d->release();
1746         d = nullptr;
1747     }
1748 }
1749 
clear(QObject * obj)1750 void QQuickPixmap::clear(QObject *obj)
1751 {
1752     if (d) {
1753         if (d->reply)
1754             QObject::disconnect(d->reply, nullptr, obj, nullptr);
1755         d->declarativePixmaps.remove(this);
1756         d->release();
1757         d = nullptr;
1758     }
1759 }
1760 
isCached(const QUrl & url,const QRect & requestRegion,const QSize & requestSize,const int frame,const QQuickImageProviderOptions & options)1761 bool QQuickPixmap::isCached(const QUrl &url, const QRect &requestRegion, const QSize &requestSize,
1762                             const int frame, const QQuickImageProviderOptions &options)
1763 {
1764     QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, options };
1765     QQuickPixmapStore *store = pixmapStore();
1766 
1767     return store->m_cache.contains(key);
1768 }
1769 
connectFinished(QObject * object,const char * method)1770 bool QQuickPixmap::connectFinished(QObject *object, const char *method)
1771 {
1772     if (!d || !d->reply) {
1773         qWarning("QQuickPixmap: connectFinished() called when not loading.");
1774         return false;
1775     }
1776 
1777     return QObject::connect(d->reply, SIGNAL(finished()), object, method);
1778 }
1779 
connectFinished(QObject * object,int method)1780 bool QQuickPixmap::connectFinished(QObject *object, int method)
1781 {
1782     if (!d || !d->reply) {
1783         qWarning("QQuickPixmap: connectFinished() called when not loading.");
1784         return false;
1785     }
1786 
1787     return QMetaObject::connect(d->reply, QQuickPixmapReply::finishedIndex, object, method);
1788 }
1789 
connectDownloadProgress(QObject * object,const char * method)1790 bool QQuickPixmap::connectDownloadProgress(QObject *object, const char *method)
1791 {
1792     if (!d || !d->reply) {
1793         qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
1794         return false;
1795     }
1796 
1797     return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), object, method);
1798 }
1799 
connectDownloadProgress(QObject * object,int method)1800 bool QQuickPixmap::connectDownloadProgress(QObject *object, int method)
1801 {
1802     if (!d || !d->reply) {
1803         qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
1804         return false;
1805     }
1806 
1807     return QMetaObject::connect(d->reply, QQuickPixmapReply::downloadProgressIndex, object, method);
1808 }
1809 
colorSpace() const1810 QColorSpace QQuickPixmap::colorSpace() const
1811 {
1812     if (!d || !d->textureFactory)
1813         return QColorSpace();
1814     return d->textureFactory->image().colorSpace();
1815 }
1816 
1817 QT_END_NAMESPACE
1818 
1819 #include <qquickpixmapcache.moc>
1820 
1821 #include "moc_qquickpixmapcache_p.cpp"
1822