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 ®ion, 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