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