1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <QtQuick/private/qquickpixmapcache_p.h>
5#include <QtQuick/private/qquickimageprovider_p.h>
6#include <QtQuick/private/qquickprofiler_p.h>
7#include <QtQuick/private/qsgcontext_p.h>
8#include <QtQuick/private/qsgrenderer_p.h>
9#include <QtQuick/private/qsgtexturereader_p.h>
10#include <QtQuick/qquickwindow.h>
11
12#include <QtGui/private/qguiapplication_p.h>
13#include <QtGui/private/qimage_p.h>
14#include <QtGui/qpa/qplatformintegration.h>
15#include <QtGui/qimagereader.h>
16#include <QtGui/qpixmapcache.h>
17
18#include <QtQml/private/qqmlglobal_p.h>
19#include <QtQml/private/qqmlengine_p.h>
20#include <QtQml/qqmlfile.h>
21
22#include <QtCore/private/qobject_p.h>
23#include <QtCore/qcoreapplication.h>
24#include <QtCore/qhash.h>
25#include <QtCore/qfile.h>
26#include <QtCore/qthread.h>
27#include <QtCore/qmutex.h>
28#include <QtCore/qbuffer.h>
29#include <QtCore/qdebug.h>
30#include <QtCore/qmetaobject.h>
31#include <QtCore/qscopeguard.h>
32
33#if QT_CONFIG(qml_network)
34#include <QtQml/qqmlnetworkaccessmanagerfactory.h>
35#include <QtNetwork/qnetworkreply.h>
36#include <QtNetwork/qsslerror.h>
37#endif
38
39#include <private/qdebug_p.h>
40
41#define IMAGEREQUEST_MAX_NETWORK_REQUEST_COUNT 8
42#define IMAGEREQUEST_MAX_REDIRECT_RECURSION 16
43
44// After QQuickPixmapStore::unreferencePixmap() it may get deleted via a timer in 30 seconds
45#define CACHE_EXPIRE_TIME 30
46
47// How many (1/4) of the unreferenced pixmaps to delete in QQuickPixmapStore::timerEvent()
48#define CACHE_REMOVAL_FRACTION 4
49
50#define PIXMAP_PROFILE(Code) Q_QUICK_PROFILE(QQuickProfiler::ProfilePixmapCache, Code)
51
52#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
53# define USE_THREADED_DOWNLOAD 1
54#else
55# define USE_THREADED_DOWNLOAD 0
56#endif
57
58QT_BEGIN_NAMESPACE
59
60#if defined(QT_DEBUG) && QT_CONFIG(thread)
61class ThreadAffinityMarker
62{
63public:
64 ThreadAffinityMarker() { attachToCurrentThread(); }
65
66 void assertOnAssignedThread()
67 {
68 QMutexLocker locker(&m_mutex);
69 if (!m_assignedThread)
70 attachToCurrentThread();
71 Q_ASSERT_X(m_assignedThread == QThread::currentThreadId(), Q_FUNC_INFO,
72 "Running on a wrong thread!");
73 }
74
75 void detachFromCurrentThread()
76 {
77 QMutexLocker locker(&m_mutex);
78 m_assignedThread = nullptr;
79 }
80
81 void attachToCurrentThread() { m_assignedThread = QThread::currentThreadId(); }
82
83private:
84 Qt::HANDLE m_assignedThread;
85 QMutex m_mutex;
86};
87# define Q_THREAD_AFFINITY_MARKER(x) ThreadAffinityMarker x
88# define Q_ASSERT_CALLED_ON_VALID_THREAD(x) x.assertOnAssignedThread()
89# define Q_DETACH_THREAD_AFFINITY_MARKER(x) x.detachFromCurrentThread()
90#else
91# define Q_THREAD_AFFINITY_MARKER(x)
92# define Q_ASSERT_CALLED_ON_VALID_THREAD(x)
93# define Q_DETACH_THREAD_AFFINITY_MARKER(x)
94#endif
95
96const QLatin1String QQuickPixmap::itemGrabberScheme = QLatin1String("itemgrabber");
97
98Q_LOGGING_CATEGORY(lcImg, "qt.quick.image")
99
100/*! \internal
101 The maximum currently-unused image data that can be stored for potential
102 later reuse, in bytes. See QQuickPixmapStore::shrinkCache()
103*/
104static int cache_limit = 2048 * 1024;
105
106static inline QString imageProviderId(const QUrl &url)
107{
108 return url.host();
109}
110
111static inline QString imageId(const QUrl &url)
112{
113 return url.toString(options: QUrl::RemoveScheme | QUrl::RemoveAuthority).mid(position: 1);
114}
115
116QQuickDefaultTextureFactory::QQuickDefaultTextureFactory(const QImage &image)
117{
118 if (image.format() == QImage::Format_ARGB32_Premultiplied
119 || image.format() == QImage::Format_RGB32) {
120 im = image;
121 } else {
122 im = image.convertToFormat(f: QImage::Format_ARGB32_Premultiplied);
123 }
124 size = im.size();
125}
126
127
128QSGTexture *QQuickDefaultTextureFactory::createTexture(QQuickWindow *window) const
129{
130 QSGTexture *t = window->createTextureFromImage(image: im, options: QQuickWindow::TextureCanUseAtlas);
131 static bool transient = qEnvironmentVariableIsSet(varName: "QSG_TRANSIENT_IMAGES");
132 if (transient)
133 const_cast<QQuickDefaultTextureFactory *>(this)->im = QImage();
134 return t;
135}
136
137class QQuickPixmapReader;
138class QQuickPixmapData;
139class QQuickPixmapReply : public QObject
140{
141 Q_OBJECT
142public:
143 enum ReadError { NoError, Loading, Decoding };
144
145 QQuickPixmapReply(QQuickPixmapData *);
146 ~QQuickPixmapReply();
147
148 QQuickPixmapData *data;
149 QQmlEngine *engineForReader; // always access reader inside readerMutex
150 QRect requestRegion;
151 QSize requestSize;
152 QUrl url;
153
154 bool loading;
155 QQuickImageProviderOptions providerOptions;
156 int redirectCount;
157
158 class Event : public QEvent {
159 public:
160 Event(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory);
161 ~Event();
162
163 ReadError error;
164 QString errorString;
165 QSize implicitSize;
166 QQuickTextureFactory *textureFactory;
167 };
168 void postReply(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory);
169
170
171Q_SIGNALS:
172 void finished();
173 void downloadProgress(qint64, qint64);
174
175protected:
176 bool event(QEvent *event) override;
177
178private:
179 Q_DISABLE_COPY(QQuickPixmapReply)
180
181public:
182 static int finishedMethodIndex;
183 static int downloadProgressMethodIndex;
184};
185
186/*! \internal
187 Serves as an endpoint for notifications on the connected reader's thread, thus enforcing
188 execution of their continuation on the thread. */
189class ReaderThreadExecutionEnforcer : public QObject
190{
191 Q_OBJECT
192public:
193 enum Event {
194 ProcessJobs = QEvent::User,
195 };
196
197 ReaderThreadExecutionEnforcer(QQuickPixmapReader *reader);
198
199 /*! \internal
200 Forces the execution of processJobs() on the original reader on the thread it's running on.
201 */
202 void processJobsOnReaderThreadLater();
203
204public slots:
205 void asyncResponseFinished(QQuickImageResponse *response);
206 void asyncResponseFinished();
207private slots:
208 void networkRequestDone();
209private:
210 bool event(QEvent *e) override;
211
212 QQuickPixmapReader *reader;
213};
214
215class QQuickPixmapData;
216class QQuickPixmapReader : public QThread
217{
218 Q_OBJECT
219public:
220 QQuickPixmapReader(QQmlEngine *eng);
221 ~QQuickPixmapReader();
222
223 QQuickPixmapReply *getImage(QQuickPixmapData *);
224 void cancel(QQuickPixmapReply *rep);
225
226 static QQuickPixmapReader *instance(QQmlEngine *engine);
227 static QQuickPixmapReader *existingInstance(QQmlEngine *engine);
228 void startJob(QQuickPixmapReply *job);
229
230protected:
231 void run() override;
232
233private:
234 friend class ReaderThreadExecutionEnforcer;
235 void processJobs();
236 void processJob(QQuickPixmapReply *, const QUrl &, const QString &, QQuickImageProvider::ImageType, const QSharedPointer<QQuickImageProvider> &);
237#if QT_CONFIG(qml_network)
238 void networkRequestDone(QNetworkReply *);
239#endif
240 void asyncResponseFinished(QQuickImageResponse *);
241
242 QList<QQuickPixmapReply*> jobs;
243 QList<QQuickPixmapReply *> cancelledJobs;
244 QQmlEngine *engine;
245
246#if QT_CONFIG(quick_pixmap_cache_threaded_download)
247 /*! \internal
248 Returns a pointer to the thread object owned by the run loop in QQuickPixmapReader::run.
249 */
250 ReaderThreadExecutionEnforcer *readerThreadExecutionEnforcer()
251 {
252 return runLoopReaderThreadExecutionEnforcer;
253 }
254 QObject *eventLoopQuitHack;
255 QMutex mutex;
256 ReaderThreadExecutionEnforcer *runLoopReaderThreadExecutionEnforcer = nullptr;
257#else
258 /*! \internal
259 Returns a pointer to the thread object owned by this instance.
260 */
261 ReaderThreadExecutionEnforcer *readerThreadExecutionEnforcer()
262 {
263 return ownedReaderThreadExecutionEnforcer.get();
264 }
265 std::unique_ptr<ReaderThreadExecutionEnforcer> ownedReaderThreadExecutionEnforcer;
266#endif
267
268#if QT_CONFIG(qml_network)
269 QNetworkAccessManager *networkAccessManager();
270 QNetworkAccessManager *accessManager;
271 QHash<QNetworkReply*,QQuickPixmapReply*> networkJobs;
272#endif
273 QHash<QQuickImageResponse*,QQuickPixmapReply*> asyncResponses;
274
275 Q_THREAD_AFFINITY_MARKER(m_creatorThreadAffinityMarker);
276 Q_THREAD_AFFINITY_MARKER(m_readerThreadAffinityMarker);
277
278 static int replyDownloadProgressMethodIndex;
279 static int replyFinishedMethodIndex;
280 static int downloadProgressMethodIndex;
281 static int threadNetworkRequestDoneMethodIndex;
282 static QHash<QQmlEngine *,QQuickPixmapReader*> readers;
283public:
284 static QMutex readerMutex;
285};
286
287#if QT_CONFIG(quick_pixmap_cache_threaded_download)
288# define PIXMAP_READER_LOCK() QMutexLocker locker(&mutex)
289#else
290# define PIXMAP_READER_LOCK()
291#endif
292
293class QQuickPixmapStore;
294
295/*! \internal
296 The private storage for QQuickPixmap.
297*/
298class QQuickPixmapData
299{
300public:
301 QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QRect &r, const QSize &rs,
302 const QQuickImageProviderOptions &po, const QString &e)
303 : refCount(1), frameCount(1), frame(0), inCache(false), pixmapStatus(QQuickPixmap::Error),
304 url(u), errorString(e), requestRegion(r), requestSize(rs),
305 providerOptions(po), appliedTransform(QQuickImageProviderOptions::UsePluginDefaultTransform),
306 textureFactory(nullptr), reply(nullptr), prevUnreferenced(nullptr),
307 prevUnreferencedPtr(nullptr), nextUnreferenced(nullptr)
308#ifdef Q_OS_WEBOS
309 , storeToCache(true)
310#endif
311 {
312 declarativePixmaps.insert(n: pixmap);
313 }
314
315 QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QRect &r, const QSize &s, const QQuickImageProviderOptions &po,
316 QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1)
317 : refCount(1), frameCount(frameCount), frame(frame), inCache(false), pixmapStatus(QQuickPixmap::Loading),
318 url(u), requestRegion(r), requestSize(s),
319 providerOptions(po), appliedTransform(aTransform),
320 textureFactory(nullptr), reply(nullptr), prevUnreferenced(nullptr), prevUnreferencedPtr(nullptr),
321 nextUnreferenced(nullptr)
322#ifdef Q_OS_WEBOS
323 , storeToCache(true)
324#endif
325 {
326 declarativePixmaps.insert(n: pixmap);
327 }
328
329 QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, QQuickTextureFactory *texture,
330 const QSize &s, const QRect &r, const QSize &rs, const QQuickImageProviderOptions &po,
331 QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1)
332 : refCount(1), frameCount(frameCount), frame(frame), inCache(false), pixmapStatus(QQuickPixmap::Ready),
333 url(u), implicitSize(s), requestRegion(r), requestSize(rs),
334 providerOptions(po), appliedTransform(aTransform),
335 textureFactory(texture), reply(nullptr), prevUnreferenced(nullptr),
336 prevUnreferencedPtr(nullptr), nextUnreferenced(nullptr)
337#ifdef Q_OS_WEBOS
338 , storeToCache(true)
339#endif
340 {
341 declarativePixmaps.insert(n: pixmap);
342 }
343
344 QQuickPixmapData(QQuickPixmap *pixmap, QQuickTextureFactory *texture)
345 : refCount(1), frameCount(1), frame(0), inCache(false), pixmapStatus(QQuickPixmap::Ready),
346 appliedTransform(QQuickImageProviderOptions::UsePluginDefaultTransform),
347 textureFactory(texture), reply(nullptr), prevUnreferenced(nullptr),
348 prevUnreferencedPtr(nullptr), nextUnreferenced(nullptr)
349#ifdef Q_OS_WEBOS
350 , storeToCache(true)
351#endif
352 {
353 if (texture)
354 requestSize = implicitSize = texture->textureSize();
355 declarativePixmaps.insert(n: pixmap);
356 }
357
358 ~QQuickPixmapData()
359 {
360 while (!declarativePixmaps.isEmpty()) {
361 QQuickPixmap *referencer = declarativePixmaps.first();
362 declarativePixmaps.remove(n: referencer);
363 referencer->d = nullptr;
364 }
365 delete textureFactory;
366 }
367
368 int cost() const;
369 void addref();
370 void release(QQuickPixmapStore *store = nullptr);
371 void addToCache();
372 void removeFromCache(QQuickPixmapStore *store = nullptr);
373
374 uint refCount;
375 int frameCount;
376 int frame;
377
378 bool inCache:1;
379
380 QQuickPixmap::Status pixmapStatus;
381 QUrl url;
382 QString errorString;
383 QSize implicitSize;
384 QRect requestRegion;
385 QSize requestSize;
386 QQuickImageProviderOptions providerOptions;
387 QQuickImageProviderOptions::AutoTransform appliedTransform;
388 QColorSpace targetColorSpace;
389
390 QIODevice *specialDevice = nullptr;
391
392 // actual image data, after loading
393 QQuickTextureFactory *textureFactory;
394
395 // linked list of referencers, just to prevent memory leaks: see QQuickPixmapData dtor
396 QIntrusiveList<QQuickPixmap, &QQuickPixmap::dataListNode> declarativePixmaps;
397
398 QQuickPixmapReply *reply;
399
400 // prev/next pointers to form a linked list for dereferencing pixmaps that are currently unused
401 // (those get lazily deleted in QQuickPixmapStore::shrinkCache())
402 QQuickPixmapData *prevUnreferenced;
403 QQuickPixmapData**prevUnreferencedPtr;
404 QQuickPixmapData *nextUnreferenced;
405
406#ifdef Q_OS_WEBOS
407 bool storeToCache;
408#endif
409};
410
411int QQuickPixmapReply::finishedMethodIndex = -1;
412int QQuickPixmapReply::downloadProgressMethodIndex = -1;
413
414// XXX
415QHash<QQmlEngine *,QQuickPixmapReader*> QQuickPixmapReader::readers;
416QMutex QQuickPixmapReader::readerMutex;
417
418int QQuickPixmapReader::replyDownloadProgressMethodIndex = -1;
419int QQuickPixmapReader::replyFinishedMethodIndex = -1;
420int QQuickPixmapReader::downloadProgressMethodIndex = -1;
421int QQuickPixmapReader::threadNetworkRequestDoneMethodIndex = -1;
422
423void QQuickPixmapReply::postReply(ReadError error, const QString &errorString,
424 const QSize &implicitSize, QQuickTextureFactory *factory)
425{
426 loading = false;
427 QCoreApplication::postEvent(receiver: this, event: new Event(error, errorString, implicitSize, factory));
428}
429
430QQuickPixmapReply::Event::Event(ReadError e, const QString &s, const QSize &iSize, QQuickTextureFactory *factory)
431 : QEvent(QEvent::User), error(e), errorString(s), implicitSize(iSize), textureFactory(factory)
432{
433}
434
435QQuickPixmapReply::Event::~Event()
436{
437 delete textureFactory;
438}
439
440#if QT_CONFIG(qml_network)
441QNetworkAccessManager *QQuickPixmapReader::networkAccessManager()
442{
443 if (!accessManager) {
444 Q_ASSERT(readerThreadExecutionEnforcer());
445 accessManager = QQmlEnginePrivate::get(e: engine)->createNetworkAccessManager(
446 parent: readerThreadExecutionEnforcer());
447 }
448 return accessManager;
449}
450#endif
451
452static void maybeRemoveAlpha(QImage *image)
453{
454 // If the image
455 if (image->hasAlphaChannel() && image->data_ptr()
456 && !image->data_ptr()->checkForAlphaPixels()) {
457 switch (image->format()) {
458 case QImage::Format_RGBA8888:
459 case QImage::Format_RGBA8888_Premultiplied:
460 if (image->data_ptr()->convertInPlace(newFormat: QImage::Format_RGBX8888, Qt::AutoColor))
461 break;
462
463 *image = image->convertToFormat(f: QImage::Format_RGBX8888);
464 break;
465 case QImage::Format_A2BGR30_Premultiplied:
466 if (image->data_ptr()->convertInPlace(newFormat: QImage::Format_BGR30, Qt::AutoColor))
467 break;
468
469 *image = image->convertToFormat(f: QImage::Format_BGR30);
470 break;
471 case QImage::Format_A2RGB30_Premultiplied:
472 if (image->data_ptr()->convertInPlace(newFormat: QImage::Format_RGB30, Qt::AutoColor))
473 break;
474
475 *image = image->convertToFormat(f: QImage::Format_RGB30);
476 break;
477 default:
478 if (image->data_ptr()->convertInPlace(newFormat: QImage::Format_RGB32, Qt::AutoColor))
479 break;
480
481 *image = image->convertToFormat(f: QImage::Format_RGB32);
482 break;
483 }
484 }
485}
486
487static bool readImage(const QUrl& url, QIODevice *dev, QImage *image, QString *errorString, QSize *impsize, int *frameCount,
488 const QRect &requestRegion, const QSize &requestSize, const QQuickImageProviderOptions &providerOptions,
489 QQuickImageProviderOptions::AutoTransform *appliedTransform = nullptr, int frame = 0,
490 qreal devicePixelRatio = 1.0)
491{
492 QImageReader imgio(dev);
493 if (providerOptions.autoTransform() != QQuickImageProviderOptions::UsePluginDefaultTransform)
494 imgio.setAutoTransform(providerOptions.autoTransform() == QQuickImageProviderOptions::ApplyTransform);
495 else if (appliedTransform)
496 *appliedTransform = imgio.autoTransform() ? QQuickImageProviderOptions::ApplyTransform : QQuickImageProviderOptions::DoNotApplyTransform;
497
498 if (frame < imgio.imageCount())
499 imgio.jumpToImage(imageNumber: frame);
500
501 if (frameCount)
502 *frameCount = imgio.imageCount();
503
504 QSize scSize = QQuickImageProviderWithOptions::loadSize(originalSize: imgio.size(), requestedSize: requestSize, format: imgio.format(), options: providerOptions, devicePixelRatio);
505 if (scSize.isValid())
506 imgio.setScaledSize(scSize);
507 if (!requestRegion.isNull())
508 imgio.setScaledClipRect(requestRegion);
509 const QSize originalSize = imgio.size();
510 qCDebug(lcImg) << url << "frame" << frame << "of" << imgio.imageCount()
511 << "requestRegion" << requestRegion << "QImageReader size" << originalSize << "-> scSize" << scSize;
512
513 if (impsize)
514 *impsize = originalSize;
515
516 if (imgio.read(image)) {
517 maybeRemoveAlpha(image);
518 if (impsize && impsize->width() < 0)
519 *impsize = image->size();
520 if (providerOptions.targetColorSpace().isValid()) {
521 if (image->colorSpace().isValid())
522 image->convertToColorSpace(providerOptions.targetColorSpace());
523 else
524 image->setColorSpace(providerOptions.targetColorSpace());
525 }
526 return true;
527 } else {
528 if (errorString)
529 *errorString = QQuickPixmap::tr(sourceText: "Error decoding: %1: %2").arg(a: url.toString())
530 .arg(a: imgio.errorString());
531 return false;
532 }
533}
534
535static QStringList fromLatin1List(const QList<QByteArray> &list)
536{
537 QStringList res;
538 res.reserve(asize: list.size());
539 for (const QByteArray &item : list)
540 res.append(t: QString::fromLatin1(ba: item));
541 return res;
542}
543
544class BackendSupport
545{
546public:
547 BackendSupport()
548 {
549 delete QSGContext::createTextureFactoryFromImage(image: QImage()); // Force init of backend data
550 hasOpenGL = QQuickWindow::sceneGraphBackend().isEmpty(); // i.e. default
551 QList<QByteArray> list;
552 if (hasOpenGL)
553 list.append(other: QSGTextureReader::supportedFileFormats());
554 list.append(other: QImageReader::supportedImageFormats());
555 fileSuffixes = fromLatin1List(list);
556 }
557 bool hasOpenGL;
558 QStringList fileSuffixes;
559};
560Q_GLOBAL_STATIC(BackendSupport, backendSupport);
561
562static QString existingImageFileForPath(const QString &localFile)
563{
564 // Do nothing if given filepath exists or already has a suffix
565 QFileInfo fi(localFile);
566 if (!fi.suffix().isEmpty() || fi.exists())
567 return localFile;
568
569 QString tryFile = localFile + QStringLiteral(".xxxx");
570 const int suffixIdx = localFile.size() + 1;
571 for (const QString &suffix : backendSupport()->fileSuffixes) {
572 tryFile.replace(i: suffixIdx, len: 10, after: suffix);
573 if (QFileInfo::exists(file: tryFile))
574 return tryFile;
575 }
576 return localFile;
577}
578
579QQuickPixmapReader::QQuickPixmapReader(QQmlEngine *eng)
580: QThread(eng), engine(eng)
581#if QT_CONFIG(qml_network)
582, accessManager(nullptr)
583#endif
584{
585 Q_DETACH_THREAD_AFFINITY_MARKER(m_readerThreadAffinityMarker);
586#if QT_CONFIG(quick_pixmap_cache_threaded_download)
587 eventLoopQuitHack = new QObject;
588 eventLoopQuitHack->moveToThread(thread: this);
589 QObject::connect(sender: eventLoopQuitHack, signal: &QObject::destroyed, context: this, slot: &QThread::quit, type: Qt::DirectConnection);
590 start(QThread::LowestPriority);
591#else
592 run(); // Call nonblocking run for ourselves.
593#endif
594}
595
596QQuickPixmapReader::~QQuickPixmapReader()
597{
598 Q_ASSERT_CALLED_ON_VALID_THREAD(m_creatorThreadAffinityMarker);
599
600 readerMutex.lock();
601 readers.remove(key: engine);
602 readerMutex.unlock();
603
604 {
605 PIXMAP_READER_LOCK();
606 // manually cancel all outstanding jobs.
607 for (QQuickPixmapReply *reply : std::as_const(t&: jobs)) {
608 if (reply->data && reply->data->reply == reply)
609 reply->data->reply = nullptr;
610 delete reply;
611 }
612 jobs.clear();
613#if QT_CONFIG(qml_network)
614 const auto cancelJob = [this](QQuickPixmapReply *reply) {
615 if (reply->loading) {
616 cancelledJobs.append(t: reply);
617 reply->data = nullptr;
618 }
619 };
620
621 for (auto *reply : std::as_const(t&: networkJobs))
622 cancelJob(reply);
623
624 for (auto *reply : std::as_const(t&: asyncResponses))
625 cancelJob(reply);
626#endif
627#if !QT_CONFIG(quick_pixmap_cache_threaded_download)
628 // In this case we won't be waiting, but we are on the correct thread already, so we can
629 // perform housekeeping synchronously now.
630 processJobs();
631#else // QT_CONFIG(quick_pixmap_cache_threaded_download) is true
632 // Perform housekeeping on all the jobs cancelled above soon...
633 if (readerThreadExecutionEnforcer())
634 readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
635#endif
636 }
637
638#if QT_CONFIG(quick_pixmap_cache_threaded_download)
639 // ... schedule stopping of this thread via the eventLoopQuitHack (processJobs scheduled above
640 // will run first) ...
641 eventLoopQuitHack->deleteLater();
642 // ... and wait() for it all to finish, as the thread will only quit after eventLoopQuitHack
643 // has been deleted.
644 wait();
645#endif
646
647#if QT_CONFIG(qml_network)
648 // While we've been waiting, the other thread may have added
649 // more replies. No one will care about them anymore.
650
651 auto deleteReply = [](QQuickPixmapReply *reply) {
652 if (reply->data && reply->data->reply == reply)
653 reply->data->reply = nullptr;
654 delete reply;
655 };
656
657 for (QQuickPixmapReply *reply : std::as_const(t&: networkJobs))
658 deleteReply(reply);
659
660 for (QQuickPixmapReply *reply : std::as_const(t&: asyncResponses))
661 deleteReply(reply);
662
663 networkJobs.clear();
664 asyncResponses.clear();
665#endif
666}
667
668#if QT_CONFIG(qml_network)
669void QQuickPixmapReader::networkRequestDone(QNetworkReply *reply)
670{
671 Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
672
673 QQuickPixmapReply *job = networkJobs.take(key: reply);
674
675 if (job) {
676 job->redirectCount++;
677 if (job->redirectCount < IMAGEREQUEST_MAX_REDIRECT_RECURSION) {
678 QVariant redirect = reply->attribute(code: QNetworkRequest::RedirectionTargetAttribute);
679 if (redirect.isValid()) {
680 QUrl url = reply->url().resolved(relative: redirect.toUrl());
681 QNetworkRequest req(url);
682 req.setAttribute(code: QNetworkRequest::HttpPipeliningAllowedAttribute, value: true);
683
684 reply->deleteLater();
685 reply = networkAccessManager()->get(request: req);
686
687 QMetaObject::connect(sender: reply, signal_index: replyDownloadProgressMethodIndex, receiver: job,
688 method_index: downloadProgressMethodIndex);
689 QMetaObject::connect(sender: reply, signal_index: replyFinishedMethodIndex,
690 receiver: readerThreadExecutionEnforcer(),
691 method_index: threadNetworkRequestDoneMethodIndex);
692
693 networkJobs.insert(key: reply, value: job);
694 return;
695 }
696 }
697
698 QImage image;
699 QQuickPixmapReply::ReadError error = QQuickPixmapReply::NoError;
700 QString errorString;
701 QSize readSize;
702 QQuickTextureFactory *factory = nullptr;
703 if (reply->error()) {
704 error = QQuickPixmapReply::Loading;
705 errorString = reply->errorString();
706 } else {
707 QByteArray all = reply->readAll();
708 QBuffer buff(&all);
709 buff.open(openMode: QIODevice::ReadOnly);
710 QSGTextureReader texReader(&buff, reply->url().fileName());
711 if (backendSupport()->hasOpenGL && texReader.isTexture()) {
712 factory = texReader.read();
713 if (factory) {
714 readSize = factory->textureSize();
715 } else {
716 error = QQuickPixmapReply::Decoding;
717 errorString = QQuickPixmap::tr(sourceText: "Error decoding: %1").arg(a: reply->url().toString());
718 }
719 } else {
720 int frameCount;
721 int const frame = job->data ? job->data->frame : 0;
722 if (!readImage(url: reply->url(), dev: &buff, image: &image, errorString: &errorString, impsize: &readSize, frameCount: &frameCount,
723 requestRegion: job->requestRegion, requestSize: job->requestSize, providerOptions: job->providerOptions, appliedTransform: nullptr, frame))
724 error = QQuickPixmapReply::Decoding;
725 else if (job->data)
726 job->data->frameCount = frameCount;
727 }
728 }
729 // send completion event to the QQuickPixmapReply
730 if (!factory)
731 factory = QQuickTextureFactory::textureFactoryForImage(image);
732
733 PIXMAP_READER_LOCK();
734 if (!cancelledJobs.contains(t: job))
735 job->postReply(error, errorString, implicitSize: readSize, factory);
736 }
737 reply->deleteLater();
738
739 // kick off event loop again in case we have dropped below max request count
740 readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
741}
742#endif // qml_network
743
744void QQuickPixmapReader::asyncResponseFinished(QQuickImageResponse *response)
745{
746 Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
747
748 QQuickPixmapReply *job = asyncResponses.take(key: response);
749
750 if (job) {
751 QQuickTextureFactory *t = nullptr;
752 QQuickPixmapReply::ReadError error = QQuickPixmapReply::NoError;
753 QString errorString;
754 if (!response->errorString().isEmpty()) {
755 error = QQuickPixmapReply::Loading;
756 errorString = response->errorString();
757 } else {
758 t = response->textureFactory();
759 }
760
761 PIXMAP_READER_LOCK();
762 if (!cancelledJobs.contains(t: job))
763 job->postReply(error, errorString, implicitSize: t ? t->textureSize() : QSize(), factory: t);
764 else
765 delete t;
766 }
767 response->deleteLater();
768
769 // kick off event loop again in case we have dropped below max request count
770 readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
771}
772
773ReaderThreadExecutionEnforcer::ReaderThreadExecutionEnforcer(QQuickPixmapReader *i) : reader(i) { }
774
775void ReaderThreadExecutionEnforcer::processJobsOnReaderThreadLater()
776{
777 QCoreApplication::postEvent(
778 receiver: this, event: new QEvent(QEvent::Type(ReaderThreadExecutionEnforcer::ProcessJobs)));
779}
780
781bool ReaderThreadExecutionEnforcer::event(QEvent *e)
782{
783 switch (e->type()) {
784 case QEvent::Type(ReaderThreadExecutionEnforcer::ProcessJobs):
785 reader->processJobs();
786 return true;
787 default:
788 return QObject::event(event: e);
789 }
790}
791
792void ReaderThreadExecutionEnforcer::networkRequestDone()
793{
794#if QT_CONFIG(qml_network)
795 QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
796 reader->networkRequestDone(reply);
797#endif
798}
799
800void ReaderThreadExecutionEnforcer::asyncResponseFinished(QQuickImageResponse *response)
801{
802 reader->asyncResponseFinished(response);
803}
804
805void ReaderThreadExecutionEnforcer::asyncResponseFinished()
806{
807 QQuickImageResponse *response = static_cast<QQuickImageResponse *>(sender());
808 asyncResponseFinished(response);
809}
810
811void QQuickPixmapReader::processJobs()
812{
813 Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
814
815 PIXMAP_READER_LOCK();
816 while (true) {
817 if (cancelledJobs.isEmpty() && jobs.isEmpty())
818 return; // Nothing else to do
819
820 // Clean cancelled jobs
821 if (!cancelledJobs.isEmpty()) {
822 for (int i = 0; i < cancelledJobs.size(); ++i) {
823 QQuickPixmapReply *job = cancelledJobs.at(i);
824#if QT_CONFIG(qml_network)
825 QNetworkReply *reply = networkJobs.key(value: job, defaultKey: 0);
826 if (reply) {
827 networkJobs.remove(key: reply);
828 if (reply->isRunning()) {
829 // cancel any jobs already started
830 reply->close();
831 }
832 } else {
833 QQuickImageResponse *asyncResponse = asyncResponses.key(value: job);
834 if (asyncResponse) {
835 asyncResponses.remove(key: asyncResponse);
836 asyncResponse->cancel();
837 }
838 }
839 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(job->url));
840#endif
841 // deleteLater, since not owned by this thread
842 job->deleteLater();
843 }
844 cancelledJobs.clear();
845 }
846
847 if (!jobs.isEmpty()) {
848 // Find a job we can use
849 bool usableJob = false;
850 for (int i = jobs.size() - 1; !usableJob && i >= 0; i--) {
851 QQuickPixmapReply *job = jobs.at(i);
852 const QUrl url = job->url;
853 QString localFile;
854 QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
855 QSharedPointer<QQuickImageProvider> provider;
856
857 if (url.scheme() == QLatin1String("image")) {
858 QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(e: engine);
859 provider = enginePrivate->imageProvider(providerId: imageProviderId(url)).staticCast<QQuickImageProvider>();
860 if (provider)
861 imageType = provider->imageType();
862
863 usableJob = true;
864 } else {
865 localFile = QQmlFile::urlToLocalFileOrQrc(url);
866 usableJob = !localFile.isEmpty()
867#if QT_CONFIG(qml_network)
868 || networkJobs.size() < IMAGEREQUEST_MAX_NETWORK_REQUEST_COUNT
869#endif
870 ;
871 }
872
873 if (usableJob) {
874 jobs.removeAt(i);
875
876 job->loading = true;
877
878 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingStarted>(url));
879
880#if QT_CONFIG(quick_pixmap_cache_threaded_download)
881 locker.unlock();
882 auto relockMutexGuard = qScopeGuard(f: ([&locker]() {
883 locker.relock();
884 }));
885#endif
886 processJob(job, url, localFile, imageType, provider);
887 }
888 }
889
890 if (!usableJob)
891 return;
892 }
893 }
894}
895
896void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &url, const QString &localFile,
897 QQuickImageProvider::ImageType imageType, const QSharedPointer<QQuickImageProvider> &provider)
898{
899 Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
900
901 // fetch
902 if (url.scheme() == QLatin1String("image")) {
903 // Use QQuickImageProvider
904 QSize readSize;
905
906 if (imageType == QQuickImageProvider::Invalid) {
907 QString errorStr = QQuickPixmap::tr(sourceText: "Invalid image provider: %1").arg(a: url.toString());
908 PIXMAP_READER_LOCK();
909 if (!cancelledJobs.contains(t: runningJob))
910 runningJob->postReply(error: QQuickPixmapReply::Loading, errorString: errorStr, implicitSize: readSize, factory: nullptr);
911 return;
912 }
913
914 // This is safe because we ensure that provider does outlive providerV2 and it does not escape the function
915 QQuickImageProviderWithOptions *providerV2 = QQuickImageProviderWithOptions::checkedCast(provider: provider.get());
916
917 switch (imageType) {
918 case QQuickImageProvider::Invalid:
919 {
920 // Already handled
921 break;
922 }
923
924 case QQuickImageProvider::Image:
925 {
926 QImage image;
927 if (providerV2) {
928 image = providerV2->requestImage(id: imageId(url), size: &readSize, requestedSize: runningJob->requestSize, options: runningJob->providerOptions);
929 } else {
930 image = provider->requestImage(id: imageId(url), size: &readSize, requestedSize: runningJob->requestSize);
931 }
932 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
933 QString errorStr;
934 if (image.isNull()) {
935 errorCode = QQuickPixmapReply::Loading;
936 errorStr = QQuickPixmap::tr(sourceText: "Failed to get image from provider: %1").arg(a: url.toString());
937 }
938 PIXMAP_READER_LOCK();
939 if (!cancelledJobs.contains(t: runningJob)) {
940 runningJob->postReply(error: errorCode, errorString: errorStr, implicitSize: readSize,
941 factory: QQuickTextureFactory::textureFactoryForImage(image));
942 }
943 break;
944 }
945
946 case QQuickImageProvider::Pixmap:
947 {
948 QPixmap pixmap;
949 if (providerV2) {
950 pixmap = providerV2->requestPixmap(id: imageId(url), size: &readSize, requestedSize: runningJob->requestSize, options: runningJob->providerOptions);
951 } else {
952 pixmap = provider->requestPixmap(id: imageId(url), size: &readSize, requestedSize: runningJob->requestSize);
953 }
954 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
955 QString errorStr;
956 if (pixmap.isNull()) {
957 errorCode = QQuickPixmapReply::Loading;
958 errorStr = QQuickPixmap::tr(sourceText: "Failed to get image from provider: %1").arg(a: url.toString());
959 }
960
961 PIXMAP_READER_LOCK();
962 if (!cancelledJobs.contains(t: runningJob)) {
963 runningJob->postReply(
964 error: errorCode, errorString: errorStr, implicitSize: readSize,
965 factory: QQuickTextureFactory::textureFactoryForImage(image: pixmap.toImage()));
966 }
967 break;
968 }
969
970 case QQuickImageProvider::Texture:
971 {
972 QQuickTextureFactory *t;
973 if (providerV2) {
974 t = providerV2->requestTexture(id: imageId(url), size: &readSize, requestedSize: runningJob->requestSize, options: runningJob->providerOptions);
975 } else {
976 t = provider->requestTexture(id: imageId(url), size: &readSize, requestedSize: runningJob->requestSize);
977 }
978 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
979 QString errorStr;
980 if (!t) {
981 errorCode = QQuickPixmapReply::Loading;
982 errorStr = QQuickPixmap::tr(sourceText: "Failed to get texture from provider: %1").arg(a: url.toString());
983 }
984 PIXMAP_READER_LOCK();
985 if (!cancelledJobs.contains(t: runningJob))
986 runningJob->postReply(error: errorCode, errorString: errorStr, implicitSize: readSize, factory: t);
987 else
988 delete t;
989 break;
990 }
991
992 case QQuickImageProvider::ImageResponse:
993 {
994 QQuickImageResponse *response;
995 if (providerV2) {
996 response = providerV2->requestImageResponse(id: imageId(url), requestedSize: runningJob->requestSize, options: runningJob->providerOptions);
997 } else {
998 QQuickAsyncImageProvider *asyncProvider = static_cast<QQuickAsyncImageProvider*>(provider.get());
999 response = asyncProvider->requestImageResponse(id: imageId(url), requestedSize: runningJob->requestSize);
1000 }
1001
1002 {
1003 QObject::connect(sender: response, signal: &QQuickImageResponse::finished, context: readerThreadExecutionEnforcer(),
1004 slot: qOverload<>(&ReaderThreadExecutionEnforcer::asyncResponseFinished));
1005 // as the response object can outlive the provider QSharedPointer, we have to extend the pointee's lifetime by that of the response
1006 // we do this by capturing a copy of the QSharedPointer in a lambda, and dropping it once the lambda has been called
1007 auto provider_copy = provider; // capturing provider would capture it as a const reference, and copy capture with initializer is only available in C++14
1008 QObject::connect(sender: response, signal: &QQuickImageResponse::destroyed, context: response, slot: [provider_copy]() {
1009 // provider_copy will be deleted when the connection gets deleted
1010 });
1011 }
1012 // Might be that the async provider was so quick it emitted the signal before we
1013 // could connect to it.
1014 //
1015 // loadAcquire() synchronizes-with storeRelease() in QQuickImageResponsePrivate::_q_finished():
1016 if (static_cast<QQuickImageResponsePrivate*>(QObjectPrivate::get(o: response))->finished.loadAcquire()) {
1017 QMetaObject::invokeMethod(obj: readerThreadExecutionEnforcer(), member: "asyncResponseFinished",
1018 c: Qt::QueuedConnection,
1019 Q_ARG(QQuickImageResponse *, response));
1020 }
1021
1022 asyncResponses.insert(key: response, value: runningJob);
1023 break;
1024 }
1025 }
1026
1027 } else {
1028 if (!localFile.isEmpty()) {
1029 // Image is local - load/decode immediately
1030 QImage image;
1031 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
1032 QString errorStr;
1033 QSize readSize;
1034
1035 if (runningJob->data && runningJob->data->specialDevice) {
1036 int frameCount;
1037 if (!readImage(url, dev: runningJob->data->specialDevice, image: &image, errorString: &errorStr, impsize: &readSize, frameCount: &frameCount,
1038 requestRegion: runningJob->requestRegion, requestSize: runningJob->requestSize,
1039 providerOptions: runningJob->providerOptions, appliedTransform: nullptr, frame: runningJob->data->frame)) {
1040 errorCode = QQuickPixmapReply::Loading;
1041 } else if (runningJob->data) {
1042 runningJob->data->frameCount = frameCount;
1043 }
1044 } else {
1045 QFile f(existingImageFileForPath(localFile));
1046 if (f.open(flags: QIODevice::ReadOnly)) {
1047 QSGTextureReader texReader(&f, localFile);
1048 if (backendSupport()->hasOpenGL && texReader.isTexture()) {
1049 QQuickTextureFactory *factory = texReader.read();
1050 if (factory) {
1051 readSize = factory->textureSize();
1052 } else {
1053 errorStr = QQuickPixmap::tr(sourceText: "Error decoding: %1").arg(a: url.toString());
1054 if (f.fileName() != localFile)
1055 errorStr += QString::fromLatin1(ba: " (%1)").arg(a: f.fileName());
1056 errorCode = QQuickPixmapReply::Decoding;
1057 }
1058 PIXMAP_READER_LOCK();
1059 if (!cancelledJobs.contains(t: runningJob))
1060 runningJob->postReply(error: errorCode, errorString: errorStr, implicitSize: readSize, factory);
1061 return;
1062 } else {
1063 int frameCount;
1064 int const frame = runningJob->data ? runningJob->data->frame : 0;
1065 if (!readImage(url, dev: &f, image: &image, errorString: &errorStr, impsize: &readSize, frameCount: &frameCount,
1066 requestRegion: runningJob->requestRegion, requestSize: runningJob->requestSize,
1067 providerOptions: runningJob->providerOptions, appliedTransform: nullptr, frame)) {
1068 errorCode = QQuickPixmapReply::Loading;
1069 if (f.fileName() != localFile)
1070 errorStr += QString::fromLatin1(ba: " (%1)").arg(a: f.fileName());
1071 } else if (runningJob->data) {
1072 runningJob->data->frameCount = frameCount;
1073 }
1074 }
1075 } else {
1076 errorStr = QQuickPixmap::tr(sourceText: "Cannot open: %1").arg(a: url.toString());
1077 errorCode = QQuickPixmapReply::Loading;
1078 }
1079 }
1080 PIXMAP_READER_LOCK();
1081 if (!cancelledJobs.contains(t: runningJob)) {
1082 runningJob->postReply(error: errorCode, errorString: errorStr, implicitSize: readSize,
1083 factory: QQuickTextureFactory::textureFactoryForImage(image));
1084 }
1085 } else {
1086#if QT_CONFIG(qml_network)
1087 // Network resource
1088 QNetworkRequest req(url);
1089 req.setAttribute(code: QNetworkRequest::HttpPipeliningAllowedAttribute, value: true);
1090 QNetworkReply *reply = networkAccessManager()->get(request: req);
1091
1092 QMetaObject::connect(sender: reply, signal_index: replyDownloadProgressMethodIndex, receiver: runningJob,
1093 method_index: downloadProgressMethodIndex);
1094 QMetaObject::connect(sender: reply, signal_index: replyFinishedMethodIndex, receiver: readerThreadExecutionEnforcer(),
1095 method_index: threadNetworkRequestDoneMethodIndex);
1096
1097 networkJobs.insert(key: reply, value: runningJob);
1098#else
1099// Silently fail if compiled with no_network
1100#endif
1101 }
1102 }
1103}
1104
1105QQuickPixmapReader *QQuickPixmapReader::instance(QQmlEngine *engine)
1106{
1107 // XXX NOTE: must be called within readerMutex locking.
1108 QQuickPixmapReader *reader = readers.value(key: engine);
1109 if (!reader) {
1110 reader = new QQuickPixmapReader(engine);
1111 readers.insert(key: engine, value: reader);
1112 }
1113
1114 return reader;
1115}
1116
1117QQuickPixmapReader *QQuickPixmapReader::existingInstance(QQmlEngine *engine)
1118{
1119 // XXX NOTE: must be called within readerMutex locking.
1120 return readers.value(key: engine, defaultValue: 0);
1121}
1122
1123QQuickPixmapReply *QQuickPixmapReader::getImage(QQuickPixmapData *data)
1124{
1125 QQuickPixmapReply *reply = new QQuickPixmapReply(data);
1126 reply->engineForReader = engine;
1127 return reply;
1128}
1129
1130void QQuickPixmapReader::startJob(QQuickPixmapReply *job)
1131{
1132 PIXMAP_READER_LOCK();
1133 jobs.append(t: job);
1134 if (readerThreadExecutionEnforcer())
1135 readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
1136}
1137
1138void QQuickPixmapReader::cancel(QQuickPixmapReply *reply)
1139{
1140 PIXMAP_READER_LOCK();
1141 if (reply->loading) {
1142 cancelledJobs.append(t: reply);
1143 reply->data = nullptr;
1144 // XXX
1145 if (readerThreadExecutionEnforcer())
1146 readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
1147 } else {
1148 // If loading was started (reply removed from jobs) but the reply was never processed
1149 // (otherwise it would have deleted itself) we need to profile an error.
1150 if (jobs.removeAll(t: reply) == 0) {
1151 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(reply->url));
1152 }
1153 delete reply;
1154 }
1155}
1156
1157void QQuickPixmapReader::run()
1158{
1159 Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
1160
1161 if (replyDownloadProgressMethodIndex == -1) {
1162#if QT_CONFIG(qml_network)
1163 replyDownloadProgressMethodIndex =
1164 QMetaMethod::fromSignal(signal: &QNetworkReply::downloadProgress).methodIndex();
1165 replyFinishedMethodIndex = QMetaMethod::fromSignal(signal: &QNetworkReply::finished).methodIndex();
1166 const QMetaObject *ir = &ReaderThreadExecutionEnforcer::staticMetaObject;
1167 threadNetworkRequestDoneMethodIndex = ir->indexOfSlot(slot: "networkRequestDone()");
1168#endif
1169 downloadProgressMethodIndex =
1170 QMetaMethod::fromSignal(signal: &QQuickPixmapReply::downloadProgress).methodIndex();
1171 }
1172
1173#if QT_CONFIG(quick_pixmap_cache_threaded_download)
1174 const auto guard = qScopeGuard(f: [this]() {
1175 // We need to delete the runLoopReaderThreadExecutionEnforcer from the same thread.
1176 PIXMAP_READER_LOCK();
1177 delete runLoopReaderThreadExecutionEnforcer;
1178 runLoopReaderThreadExecutionEnforcer = nullptr;
1179 });
1180
1181 {
1182 PIXMAP_READER_LOCK();
1183 Q_ASSERT(!runLoopReaderThreadExecutionEnforcer);
1184 runLoopReaderThreadExecutionEnforcer = new ReaderThreadExecutionEnforcer(this);
1185 }
1186
1187 processJobs();
1188 exec();
1189#else
1190 ownedReaderThreadExecutionEnforcer = std::make_unique<ReaderThreadExecutionEnforcer>(this);
1191 processJobs();
1192#endif
1193}
1194
1195class QQuickPixmapKey
1196{
1197public:
1198 const QUrl *url;
1199 const QRect *region;
1200 const QSize *size;
1201 int frame;
1202 QQuickImageProviderOptions options;
1203};
1204
1205inline bool operator==(const QQuickPixmapKey &lhs, const QQuickPixmapKey &rhs)
1206{
1207 return *lhs.url == *rhs.url &&
1208 *lhs.region == *rhs.region &&
1209 *lhs.size == *rhs.size &&
1210 lhs.frame == rhs.frame &&
1211 lhs.options == rhs.options;
1212}
1213
1214inline size_t qHash(const QQuickPixmapKey &key, size_t seed) noexcept
1215{
1216 return qHashMulti(seed, args: *key.url, args: *key.region, args: *key.size, args: key.frame, args: key.options.autoTransform());
1217}
1218
1219#ifndef QT_NO_DEBUG_STREAM
1220inline QDebug operator<<(QDebug debug, const QQuickPixmapKey &key)
1221{
1222 QDebugStateSaver saver(debug);
1223 debug.nospace();
1224 if (!key.url) {
1225 debug << "QQuickPixmapKey(0)";
1226 return debug;
1227 }
1228
1229 debug << "QQuickPixmapKey(" << key.url->toString() << " frame=" << key.frame;
1230 if (!key.region->isEmpty()) {
1231 debug << " region=";
1232 QtDebugUtils::formatQRect(debug, rect: *key.region);
1233 }
1234 if (!key.size->isEmpty()) {
1235 debug << " size=";
1236 QtDebugUtils::formatQSize(debug, size: *key.size);
1237 }
1238 debug << ')';
1239 return debug;
1240}
1241#endif
1242
1243class QQuickPixmapStore : public QObject
1244{
1245 Q_OBJECT
1246public:
1247 QQuickPixmapStore();
1248 ~QQuickPixmapStore();
1249
1250 void unreferencePixmap(QQuickPixmapData *);
1251 void referencePixmap(QQuickPixmapData *);
1252
1253 void purgeCache();
1254
1255protected:
1256 void timerEvent(QTimerEvent *) override;
1257
1258public:
1259 QHash<QQuickPixmapKey, QQuickPixmapData *> m_cache;
1260 QMutex m_cacheMutex; // avoid simultaneous iteration and modification
1261
1262private:
1263 void shrinkCache(int remove);
1264
1265 QQuickPixmapData *m_unreferencedPixmaps;
1266 QQuickPixmapData *m_lastUnreferencedPixmap;
1267
1268 int m_unreferencedCost;
1269 int m_timerId;
1270 bool m_destroying;
1271};
1272Q_GLOBAL_STATIC(QQuickPixmapStore, pixmapStore);
1273
1274
1275QQuickPixmapStore::QQuickPixmapStore()
1276 : m_unreferencedPixmaps(nullptr), m_lastUnreferencedPixmap(nullptr), m_unreferencedCost(0), m_timerId(-1), m_destroying(false)
1277{
1278}
1279
1280QQuickPixmapStore::~QQuickPixmapStore()
1281{
1282 m_destroying = true;
1283
1284#ifndef QT_NO_DEBUG
1285 int leakedPixmaps = 0;
1286#endif
1287 // Prevent unreferencePixmap() from assuming it needs to kick
1288 // off the cache expiry timer, as we're shrinking the cache
1289 // manually below after releasing all the pixmaps.
1290 m_timerId = -2;
1291
1292 // unreference all (leaked) pixmaps
1293 const auto cache = m_cache; // NOTE: intentional copy (QTBUG-65077); releasing items from the cache modifies m_cache.
1294 for (auto *pixmap : cache) {
1295 auto currRefCount = pixmap->refCount;
1296 if (currRefCount) {
1297#ifndef QT_NO_DEBUG
1298 leakedPixmaps++;
1299#endif
1300 while (currRefCount > 0) {
1301 pixmap->release(store: this);
1302 currRefCount--;
1303 }
1304 }
1305 }
1306
1307 // free all unreferenced pixmaps
1308 while (m_lastUnreferencedPixmap) {
1309 shrinkCache(remove: 20);
1310 }
1311
1312#ifndef QT_NO_DEBUG
1313 if (leakedPixmaps && _q_sg_leak_check)
1314 qDebug(msg: "Number of leaked pixmaps: %i", leakedPixmaps);
1315#endif
1316}
1317
1318/*! \internal
1319 Declare that \a data is currently unused so that shrinkCache() can lazily
1320 delete it later.
1321*/
1322void QQuickPixmapStore::unreferencePixmap(QQuickPixmapData *data)
1323{
1324 Q_ASSERT(data->prevUnreferenced == nullptr);
1325 Q_ASSERT(data->prevUnreferencedPtr == nullptr);
1326 Q_ASSERT(data->nextUnreferenced == nullptr);
1327
1328 data->nextUnreferenced = m_unreferencedPixmaps;
1329 data->prevUnreferencedPtr = &m_unreferencedPixmaps;
1330 if (!m_destroying) { // the texture factories may have been cleaned up already.
1331 m_unreferencedCost += data->cost();
1332 qCDebug(lcImg) << data->url << "had cost" << data->cost() << "of total unreferenced" << m_unreferencedCost;
1333 }
1334
1335 m_unreferencedPixmaps = data;
1336 if (m_unreferencedPixmaps->nextUnreferenced) {
1337 m_unreferencedPixmaps->nextUnreferenced->prevUnreferenced = m_unreferencedPixmaps;
1338 m_unreferencedPixmaps->nextUnreferenced->prevUnreferencedPtr = &m_unreferencedPixmaps->nextUnreferenced;
1339 }
1340
1341 if (!m_lastUnreferencedPixmap)
1342 m_lastUnreferencedPixmap = data;
1343
1344 shrinkCache(remove: -1); // Shrink the cache in case it has become larger than cache_limit
1345
1346 if (m_timerId == -1 && m_unreferencedPixmaps
1347 && !m_destroying && !QCoreApplication::closingDown()) {
1348 m_timerId = startTimer(CACHE_EXPIRE_TIME * 1000);
1349 }
1350}
1351
1352/*! \internal
1353 Declare that \a data is being used (by a QQuickPixmap) so that
1354 shrinkCache() won't delete it. (This is not reference counting though.)
1355*/
1356void QQuickPixmapStore::referencePixmap(QQuickPixmapData *data)
1357{
1358 Q_ASSERT(data->prevUnreferencedPtr);
1359
1360 *data->prevUnreferencedPtr = data->nextUnreferenced;
1361 if (data->nextUnreferenced) {
1362 data->nextUnreferenced->prevUnreferencedPtr = data->prevUnreferencedPtr;
1363 data->nextUnreferenced->prevUnreferenced = data->prevUnreferenced;
1364 }
1365 if (m_lastUnreferencedPixmap == data)
1366 m_lastUnreferencedPixmap = data->prevUnreferenced;
1367
1368 data->nextUnreferenced = nullptr;
1369 data->prevUnreferencedPtr = nullptr;
1370 data->prevUnreferenced = nullptr;
1371
1372 m_unreferencedCost -= data->cost();
1373 qCDebug(lcImg) << data->url << "subtracts cost" << data->cost() << "of total" << m_unreferencedCost;
1374}
1375
1376/*! \internal
1377 Delete the least-recently-released QQuickPixmapData instances
1378 until the remaining bytes are less than cache_limit.
1379*/
1380void QQuickPixmapStore::shrinkCache(int remove)
1381{
1382 qCDebug(lcImg) << "reduce unreferenced cost" << m_unreferencedCost << "to less than limit" << cache_limit;
1383 while ((remove > 0 || m_unreferencedCost > cache_limit) && m_lastUnreferencedPixmap) {
1384 QQuickPixmapData *data = m_lastUnreferencedPixmap;
1385 Q_ASSERT(data->nextUnreferenced == nullptr);
1386
1387 *data->prevUnreferencedPtr = nullptr;
1388 m_lastUnreferencedPixmap = data->prevUnreferenced;
1389 data->prevUnreferencedPtr = nullptr;
1390 data->prevUnreferenced = nullptr;
1391
1392 if (!m_destroying) {
1393 remove -= data->cost();
1394 m_unreferencedCost -= data->cost();
1395 }
1396 data->removeFromCache(store: this);
1397 delete data;
1398 }
1399}
1400
1401void QQuickPixmapStore::timerEvent(QTimerEvent *)
1402{
1403 int removalCost = m_unreferencedCost / CACHE_REMOVAL_FRACTION;
1404
1405 shrinkCache(remove: removalCost);
1406
1407 if (m_unreferencedPixmaps == nullptr) {
1408 killTimer(id: m_timerId);
1409 m_timerId = -1;
1410 }
1411}
1412
1413void QQuickPixmapStore::purgeCache()
1414{
1415 shrinkCache(remove: m_unreferencedCost);
1416}
1417
1418void QQuickPixmap::purgeCache()
1419{
1420 pixmapStore()->purgeCache();
1421}
1422
1423QQuickPixmapReply::QQuickPixmapReply(QQuickPixmapData *d)
1424 : data(d), engineForReader(nullptr), requestRegion(d->requestRegion), requestSize(d->requestSize),
1425 url(d->url), loading(false), providerOptions(d->providerOptions), redirectCount(0)
1426{
1427 if (finishedMethodIndex == -1) {
1428 finishedMethodIndex = QMetaMethod::fromSignal(signal: &QQuickPixmapReply::finished).methodIndex();
1429 downloadProgressMethodIndex =
1430 QMetaMethod::fromSignal(signal: &QQuickPixmapReply::downloadProgress).methodIndex();
1431 }
1432}
1433
1434QQuickPixmapReply::~QQuickPixmapReply()
1435{
1436 // note: this->data->reply must be set to zero if this->data->reply == this
1437 // but it must be done within mutex locking, to be guaranteed to be safe.
1438}
1439
1440bool QQuickPixmapReply::event(QEvent *event)
1441{
1442 if (event->type() == QEvent::User) {
1443
1444 if (data) {
1445 Event *de = static_cast<Event *>(event);
1446 data->pixmapStatus = (de->error == NoError) ? QQuickPixmap::Ready : QQuickPixmap::Error;
1447 if (data->pixmapStatus == QQuickPixmap::Ready) {
1448 data->textureFactory = de->textureFactory;
1449 de->textureFactory = nullptr;
1450 data->implicitSize = de->implicitSize;
1451 PIXMAP_PROFILE(pixmapLoadingFinished(data->url,
1452 data->textureFactory != nullptr && data->textureFactory->textureSize().isValid() ?
1453 data->textureFactory->textureSize() :
1454 (data->requestSize.isValid() ? data->requestSize : data->implicitSize)));
1455 } else {
1456 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(data->url));
1457 data->errorString = de->errorString;
1458 data->removeFromCache(); // We don't continue to cache error'd pixmaps
1459 }
1460
1461 data->reply = nullptr;
1462 emit finished();
1463 } else {
1464 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(url));
1465 }
1466
1467 delete this;
1468 return true;
1469 } else {
1470 return QObject::event(event);
1471 }
1472}
1473
1474int QQuickPixmapData::cost() const
1475{
1476 if (textureFactory)
1477 return textureFactory->textureByteCount();
1478 return 0;
1479}
1480
1481void QQuickPixmapData::addref()
1482{
1483 ++refCount;
1484 PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapReferenceCountChanged>(url, refCount));
1485 if (prevUnreferencedPtr)
1486 pixmapStore()->referencePixmap(data: this);
1487}
1488
1489void QQuickPixmapData::release(QQuickPixmapStore *store)
1490{
1491 Q_ASSERT(refCount > 0);
1492 --refCount;
1493 PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapReferenceCountChanged>(url, refCount));
1494 if (refCount == 0) {
1495 if (reply) {
1496 QQuickPixmapReply *cancelReply = reply;
1497 reply->data = nullptr;
1498 reply = nullptr;
1499 QQuickPixmapReader::readerMutex.lock();
1500 QQuickPixmapReader *reader = QQuickPixmapReader::existingInstance(engine: cancelReply->engineForReader);
1501 if (reader)
1502 reader->cancel(reply: cancelReply);
1503 QQuickPixmapReader::readerMutex.unlock();
1504 }
1505
1506 store = store ? store : pixmapStore();
1507 if (pixmapStatus == QQuickPixmap::Ready
1508#ifdef Q_OS_WEBOS
1509 && storeToCache
1510#endif
1511 ) {
1512 if (inCache)
1513 store->unreferencePixmap(data: this);
1514 else
1515 delete this;
1516 } else {
1517 removeFromCache(store);
1518 delete this;
1519 }
1520 }
1521}
1522
1523/*! \internal
1524 Add this to the global static QQuickPixmapStore.
1525
1526 \note The actual image will end up in QQuickPixmapData::textureFactory.
1527 At the time addToCache() is called, it's generally not yet loaded; so the
1528 qCDebug() below cannot say how much data we're committing to storing.
1529 (On the other hand, removeFromCache() can tell.) QQuickTextureFactory is an
1530 abstraction for image data. See QQuickDefaultTextureFactory for example:
1531 it stores a QImage directly. Other QQuickTextureFactory subclasses store data
1532 in other ways.
1533*/
1534void QQuickPixmapData::addToCache()
1535{
1536 if (!inCache) {
1537 QQuickPixmapKey key = { .url: &url, .region: &requestRegion, .size: &requestSize, .frame: frame, .options: providerOptions };
1538 QMutexLocker locker(&pixmapStore()->m_cacheMutex);
1539 if (lcImg().isDebugEnabled()) {
1540 qCDebug(lcImg) << "adding" << key << "to total" << pixmapStore()->m_cache.size();
1541 for (auto it = pixmapStore()->m_cache.keyBegin(); it != pixmapStore()->m_cache.keyEnd(); ++it) {
1542 if (*(it->url) == url && it->frame == frame)
1543 qDebug(catFunc: lcImg) << " similar pre-existing:" << *it;
1544 }
1545 }
1546 pixmapStore()->m_cache.insert(key, value: this);
1547 inCache = true;
1548 PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapCacheCountChanged>(
1549 url, pixmapStore()->m_cache.size()));
1550 }
1551}
1552
1553void QQuickPixmapData::removeFromCache(QQuickPixmapStore *store)
1554{
1555 if (inCache) {
1556 if (!store)
1557 store = pixmapStore();
1558 QQuickPixmapKey key = { .url: &url, .region: &requestRegion, .size: &requestSize, .frame: frame, .options: providerOptions };
1559 QMutexLocker locker(&pixmapStore()->m_cacheMutex);
1560 store->m_cache.remove(key);
1561 qCDebug(lcImg) << "removed" << key << implicitSize << "; total remaining" << pixmapStore()->m_cache.size();
1562 inCache = false;
1563 PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapCacheCountChanged>(
1564 url, store->m_cache.size()));
1565 }
1566}
1567
1568static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, QQmlEngine *engine, const QUrl &url,
1569 const QRect &requestRegion, const QSize &requestSize,
1570 const QQuickImageProviderOptions &providerOptions, int frame, bool *ok,
1571 qreal devicePixelRatio)
1572{
1573 if (url.scheme() == QLatin1String("image")) {
1574 QSize readSize;
1575
1576 QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
1577 QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(e: engine);
1578 QSharedPointer<QQuickImageProvider> provider = enginePrivate->imageProvider(providerId: imageProviderId(url)).objectCast<QQuickImageProvider>();
1579 // it is safe to use get() as providerV2 does not escape and is outlived by provider
1580 QQuickImageProviderWithOptions *providerV2 = QQuickImageProviderWithOptions::checkedCast(provider: provider.get());
1581 if (provider)
1582 imageType = provider->imageType();
1583
1584 switch (imageType) {
1585 case QQuickImageProvider::Invalid:
1586 return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions,
1587 QQuickPixmap::tr(sourceText: "Invalid image provider: %1").arg(a: url.toString()));
1588 case QQuickImageProvider::Texture:
1589 {
1590 QQuickTextureFactory *texture = providerV2 ? providerV2->requestTexture(id: imageId(url), size: &readSize, requestedSize: requestSize, options: providerOptions)
1591 : provider->requestTexture(id: imageId(url), size: &readSize, requestedSize: requestSize);
1592 if (texture) {
1593 *ok = true;
1594 return new QQuickPixmapData(declarativePixmap, url, texture, readSize, requestRegion, requestSize,
1595 providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
1596 }
1597 break;
1598 }
1599
1600 case QQuickImageProvider::Image:
1601 {
1602 QImage image = providerV2 ? providerV2->requestImage(id: imageId(url), size: &readSize, requestedSize: requestSize, options: providerOptions)
1603 : provider->requestImage(id: imageId(url), size: &readSize, requestedSize: requestSize);
1604 if (!image.isNull()) {
1605 *ok = true;
1606 return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image),
1607 readSize, requestRegion, requestSize, providerOptions,
1608 QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
1609 }
1610 break;
1611 }
1612 case QQuickImageProvider::Pixmap:
1613 {
1614 QPixmap pixmap = providerV2 ? providerV2->requestPixmap(id: imageId(url), size: &readSize, requestedSize: requestSize, options: providerOptions)
1615 : provider->requestPixmap(id: imageId(url), size: &readSize, requestedSize: requestSize);
1616 if (!pixmap.isNull()) {
1617 *ok = true;
1618 return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image: pixmap.toImage()),
1619 readSize, requestRegion, requestSize, providerOptions,
1620 QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
1621 }
1622 break;
1623 }
1624 case QQuickImageProvider::ImageResponse:
1625 {
1626 // Fall through, ImageResponse providers never get here
1627 Q_ASSERT(imageType != QQuickImageProvider::ImageResponse && "Sync call to ImageResponse provider");
1628 }
1629 }
1630
1631 // provider has bad image type, or provider returned null image
1632 return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions,
1633 QQuickPixmap::tr(sourceText: "Failed to get image from provider: %1").arg(a: url.toString()));
1634 }
1635
1636 QString localFile = QQmlFile::urlToLocalFileOrQrc(url);
1637 if (localFile.isEmpty())
1638 return nullptr;
1639
1640 QFile f(existingImageFileForPath(localFile));
1641 QSize readSize;
1642 QString errorString;
1643
1644 if (f.open(flags: QIODevice::ReadOnly)) {
1645 QSGTextureReader texReader(&f, localFile);
1646 if (backendSupport()->hasOpenGL && texReader.isTexture()) {
1647 QQuickTextureFactory *factory = texReader.read();
1648 if (factory) {
1649 *ok = true;
1650 return new QQuickPixmapData(declarativePixmap, url, factory, factory->textureSize(), requestRegion, requestSize,
1651 providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
1652 } else {
1653 errorString = QQuickPixmap::tr(sourceText: "Error decoding: %1").arg(a: url.toString());
1654 if (f.fileName() != localFile)
1655 errorString += QString::fromLatin1(ba: " (%1)").arg(a: f.fileName());
1656 }
1657 } else {
1658 QImage image;
1659 QQuickImageProviderOptions::AutoTransform appliedTransform = providerOptions.autoTransform();
1660 int frameCount;
1661 if (readImage(url, dev: &f, image: &image, errorString: &errorString, impsize: &readSize, frameCount: &frameCount, requestRegion, requestSize,
1662 providerOptions, appliedTransform: &appliedTransform, frame, devicePixelRatio)) {
1663 *ok = true;
1664 return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestRegion, requestSize,
1665 providerOptions, appliedTransform, frame, frameCount);
1666 } else if (f.fileName() != localFile) {
1667 errorString += QString::fromLatin1(ba: " (%1)").arg(a: f.fileName());
1668 }
1669 }
1670 } else {
1671 errorString = QQuickPixmap::tr(sourceText: "Cannot open: %1").arg(a: url.toString());
1672 }
1673 return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions, errorString);
1674}
1675
1676
1677struct QQuickPixmapNull {
1678 QUrl url;
1679 QRect region;
1680 QSize size;
1681};
1682Q_GLOBAL_STATIC(QQuickPixmapNull, nullPixmap);
1683
1684QQuickPixmap::QQuickPixmap()
1685: d(nullptr)
1686{
1687}
1688
1689QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url)
1690: d(nullptr)
1691{
1692 load(engine, url);
1693}
1694
1695QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QRect &region, const QSize &size)
1696: d(nullptr)
1697{
1698 load(engine, url, requestRegion: region, requestSize: size);
1699}
1700
1701QQuickPixmap::QQuickPixmap(const QUrl &url, const QImage &image)
1702{
1703 d = new QQuickPixmapData(this, url, new QQuickDefaultTextureFactory(image), image.size(), QRect(), QSize(),
1704 QQuickImageProviderOptions(), QQuickImageProviderOptions::UsePluginDefaultTransform);
1705 d->addToCache();
1706}
1707
1708QQuickPixmap::~QQuickPixmap()
1709{
1710 if (d) {
1711 d->declarativePixmaps.remove(n: this);
1712 d->release();
1713 d = nullptr;
1714 }
1715}
1716
1717bool QQuickPixmap::isNull() const
1718{
1719 return d == nullptr;
1720}
1721
1722bool QQuickPixmap::isReady() const
1723{
1724 return status() == Ready;
1725}
1726
1727bool QQuickPixmap::isError() const
1728{
1729 return status() == Error;
1730}
1731
1732bool QQuickPixmap::isLoading() const
1733{
1734 return status() == Loading;
1735}
1736
1737QString QQuickPixmap::error() const
1738{
1739 if (d)
1740 return d->errorString;
1741 else
1742 return QString();
1743}
1744
1745QQuickPixmap::Status QQuickPixmap::status() const
1746{
1747 if (d)
1748 return d->pixmapStatus;
1749 else
1750 return Null;
1751}
1752
1753const QUrl &QQuickPixmap::url() const
1754{
1755 if (d)
1756 return d->url;
1757 else
1758 return nullPixmap()->url;
1759}
1760
1761const QSize &QQuickPixmap::implicitSize() const
1762{
1763 if (d)
1764 return d->implicitSize;
1765 else
1766 return nullPixmap()->size;
1767}
1768
1769const QSize &QQuickPixmap::requestSize() const
1770{
1771 if (d)
1772 return d->requestSize;
1773 else
1774 return nullPixmap()->size;
1775}
1776
1777const QRect &QQuickPixmap::requestRegion() const
1778{
1779 if (d)
1780 return d->requestRegion;
1781 else
1782 return nullPixmap()->region;
1783}
1784
1785QQuickImageProviderOptions::AutoTransform QQuickPixmap::autoTransform() const
1786{
1787 if (d)
1788 return d->appliedTransform;
1789 else
1790 return QQuickImageProviderOptions::UsePluginDefaultTransform;
1791}
1792
1793int QQuickPixmap::frameCount() const
1794{
1795 if (d)
1796 return d->frameCount;
1797 return 0;
1798}
1799
1800QQuickTextureFactory *QQuickPixmap::textureFactory() const
1801{
1802 if (d)
1803 return d->textureFactory;
1804
1805 return nullptr;
1806}
1807
1808QImage QQuickPixmap::image() const
1809{
1810 if (d && d->textureFactory)
1811 return d->textureFactory->image();
1812 return QImage();
1813}
1814
1815void QQuickPixmap::setImage(const QImage &p)
1816{
1817 clear();
1818
1819 if (!p.isNull()) {
1820 if (d)
1821 d->release();
1822 d = new QQuickPixmapData(this, QQuickTextureFactory::textureFactoryForImage(image: p));
1823 }
1824}
1825
1826void QQuickPixmap::setPixmap(const QQuickPixmap &other)
1827{
1828 if (d == other.d)
1829 return;
1830 clear();
1831
1832 if (other.d) {
1833 if (d)
1834 d->release();
1835 d = other.d;
1836 d->addref();
1837 d->declarativePixmaps.insert(n: this);
1838 }
1839}
1840
1841int QQuickPixmap::width() const
1842{
1843 if (d && d->textureFactory)
1844 return d->textureFactory->textureSize().width();
1845 else
1846 return 0;
1847}
1848
1849int QQuickPixmap::height() const
1850{
1851 if (d && d->textureFactory)
1852 return d->textureFactory->textureSize().height();
1853 else
1854 return 0;
1855}
1856
1857QRect QQuickPixmap::rect() const
1858{
1859 if (d && d->textureFactory)
1860 return QRect(QPoint(), d->textureFactory->textureSize());
1861 else
1862 return QRect();
1863}
1864
1865void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url)
1866{
1867 load(engine, url, requestRegion: QRect(), requestSize: QSize(), options: QQuickPixmap::Cache);
1868}
1869
1870void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, QQuickPixmap::Options options)
1871{
1872 load(engine, url, requestRegion: QRect(), requestSize: QSize(), options);
1873}
1874
1875void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize)
1876{
1877 load(engine, url, requestRegion, requestSize, options: QQuickPixmap::Cache);
1878}
1879
1880void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize, QQuickPixmap::Options options)
1881{
1882 load(engine, url, requestRegion, requestSize, options, providerOptions: QQuickImageProviderOptions());
1883}
1884
1885void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize,
1886 QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions, int frame, int frameCount,
1887 qreal devicePixelRatio)
1888{
1889 if (d) {
1890 d->declarativePixmaps.remove(n: this);
1891 d->release();
1892 d = nullptr;
1893 }
1894
1895 QQuickPixmapKey key = { .url: &url, .region: &requestRegion, .size: &requestSize, .frame: frame, .options: providerOptions };
1896 QQuickPixmapStore *store = pixmapStore();
1897
1898 QMutexLocker locker(&pixmapStore()->m_cacheMutex);
1899 QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end();
1900
1901#ifdef Q_OS_WEBOS
1902 QQuickPixmap::Options orgOptions = options;
1903 // In webOS, we suppose that cache is always enabled to share image instances along its source.
1904 // So, original option(orgOptions) for cache only decides whether to store the instances when it's unreferenced.
1905 options |= QQuickPixmap::Cache;
1906#endif
1907
1908 // If Cache is disabled, the pixmap will always be loaded, even if there is an existing
1909 // cached version. Unless it's an itemgrabber url, since the cache is used to pass
1910 // the result between QQuickItemGrabResult and QQuickImage.
1911 if (url.scheme() == itemGrabberScheme) {
1912 QRect dummyRegion;
1913 QSize dummySize;
1914 if (requestSize != dummySize)
1915 qWarning() << "Ignoring sourceSize request for image url that came from grabToImage. Use the targetSize parameter of the grabToImage() function instead.";
1916 const QQuickPixmapKey grabberKey = { .url: &url, .region: &dummyRegion, .size: &dummySize, .frame: 0, .options: QQuickImageProviderOptions() };
1917 iter = store->m_cache.find(key: grabberKey);
1918 } else if (options & QQuickPixmap::Cache)
1919 iter = store->m_cache.find(key);
1920
1921 if (iter == store->m_cache.end()) {
1922 locker.unlock();
1923 if (url.scheme() == QLatin1String("image")) {
1924 QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(e: engine);
1925 if (auto provider = enginePrivate->imageProvider(providerId: imageProviderId(url)).staticCast<QQuickImageProvider>()) {
1926 const bool threadedPixmaps = QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::ThreadedPixmaps);
1927 if (!threadedPixmaps && provider->imageType() == QQuickImageProvider::Pixmap) {
1928 // pixmaps can only be loaded synchronously
1929 options &= ~QQuickPixmap::Asynchronous;
1930 } else if (provider->flags() & QQuickImageProvider::ForceAsynchronousImageLoading) {
1931 options |= QQuickPixmap::Asynchronous;
1932 }
1933 }
1934 }
1935
1936 if (!(options & QQuickPixmap::Asynchronous)) {
1937 bool ok = false;
1938 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingStarted>(url));
1939 d = createPixmapDataSync(declarativePixmap: this, engine, url, requestRegion, requestSize, providerOptions, frame, ok: &ok, devicePixelRatio);
1940 if (ok) {
1941 PIXMAP_PROFILE(pixmapLoadingFinished(url, QSize(width(), height())));
1942 if (options & QQuickPixmap::Cache)
1943 d->addToCache();
1944#ifdef Q_OS_WEBOS
1945 d->storeToCache = orgOptions & QQuickPixmap::Cache;
1946#endif
1947 return;
1948 }
1949 if (d) { // loadable, but encountered error while loading
1950 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(url));
1951 return;
1952 }
1953 }
1954
1955 if (!engine)
1956 return;
1957
1958
1959 d = new QQuickPixmapData(this, url, requestRegion, requestSize, providerOptions,
1960 QQuickImageProviderOptions::UsePluginDefaultTransform, frame, frameCount);
1961 if (options & QQuickPixmap::Cache)
1962 d->addToCache();
1963#ifdef Q_OS_WEBOS
1964 d->storeToCache = orgOptions & QQuickPixmap::Cache;
1965#endif
1966
1967 QQuickPixmapReader::readerMutex.lock();
1968 QQuickPixmapReader *reader = QQuickPixmapReader::instance(engine);
1969 d->reply = reader->getImage(data: d);
1970 reader->startJob(job: d->reply);
1971 QQuickPixmapReader::readerMutex.unlock();
1972 } else {
1973 d = *iter;
1974 d->addref();
1975 d->declarativePixmaps.insert(n: this);
1976 qCDebug(lcImg) << "loaded from cache" << url << "frame" << frame;
1977 }
1978}
1979
1980/*! \internal
1981 Attempts to load an image from the given \a url via the given \a device.
1982 This is for special cases when the QImageIOHandler can benefit from reusing
1983 the I/O device, or from something extra that a subclass of QIODevice
1984 carries with it. So far, this code doesn't support loading anything other
1985 than a QImage, for example compressed textures. It can be added if needed.
1986*/
1987void QQuickPixmap::loadImageFromDevice(QQmlEngine *engine, QIODevice *device, const QUrl &url,
1988 const QRect &requestRegion, const QSize &requestSize,
1989 const QQuickImageProviderOptions &providerOptions, int frame, int frameCount)
1990{
1991 auto oldD = d;
1992 QQuickPixmapKey key = { .url: &url, .region: &requestRegion, .size: &requestSize, .frame: frame, .options: providerOptions };
1993 QQuickPixmapStore *store = pixmapStore();
1994 QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end();
1995 QMutexLocker locker(&store->m_cacheMutex);
1996 iter = store->m_cache.find(key);
1997 if (iter == store->m_cache.end()) {
1998 if (!engine)
1999 return;
2000
2001 locker.unlock();
2002 d = new QQuickPixmapData(this, url, requestRegion, requestSize, providerOptions,
2003 QQuickImageProviderOptions::UsePluginDefaultTransform, frame, frameCount);
2004 d->specialDevice = device;
2005 d->addToCache();
2006
2007 QQuickPixmapReader::readerMutex.lock();
2008 QQuickPixmapReader *reader = QQuickPixmapReader::instance(engine);
2009 d->reply = reader->getImage(data: d);
2010 if (oldD) {
2011 QObject::connect(sender: d->reply, signal: &QQuickPixmapReply::destroyed, context: store, slot: [oldD]() {
2012 oldD->release();
2013 }, type: Qt::QueuedConnection);
2014 }
2015 reader->startJob(job: d->reply);
2016 QQuickPixmapReader::readerMutex.unlock();
2017 } else {
2018 d = *iter;
2019 d->addref();
2020 d->declarativePixmaps.insert(n: this);
2021 qCDebug(lcImg) << "loaded from cache" << url << "frame" << frame << "refCount" << d->refCount;
2022 locker.unlock();
2023 if (oldD)
2024 oldD->release();
2025 }
2026}
2027
2028void QQuickPixmap::clear()
2029{
2030 if (d) {
2031 d->declarativePixmaps.remove(n: this);
2032 d->release();
2033 d = nullptr;
2034 }
2035}
2036
2037void QQuickPixmap::clear(QObject *obj)
2038{
2039 if (d) {
2040 if (d->reply)
2041 QObject::disconnect(sender: d->reply, signal: nullptr, receiver: obj, member: nullptr);
2042 d->declarativePixmaps.remove(n: this);
2043 d->release();
2044 d = nullptr;
2045 }
2046}
2047
2048bool QQuickPixmap::isCached(const QUrl &url, const QRect &requestRegion, const QSize &requestSize,
2049 const int frame, const QQuickImageProviderOptions &options)
2050{
2051 QQuickPixmapKey key = { .url: &url, .region: &requestRegion, .size: &requestSize, .frame: frame, .options: options };
2052 QQuickPixmapStore *store = pixmapStore();
2053
2054 return store->m_cache.contains(key);
2055}
2056
2057bool QQuickPixmap::connectFinished(QObject *object, const char *method)
2058{
2059 if (!d || !d->reply) {
2060 qWarning(msg: "QQuickPixmap: connectFinished() called when not loading.");
2061 return false;
2062 }
2063
2064 return QObject::connect(sender: d->reply, SIGNAL(finished()), receiver: object, member: method);
2065}
2066
2067bool QQuickPixmap::connectFinished(QObject *object, int method)
2068{
2069 if (!d || !d->reply) {
2070 qWarning(msg: "QQuickPixmap: connectFinished() called when not loading.");
2071 return false;
2072 }
2073
2074 return QMetaObject::connect(sender: d->reply, signal_index: QQuickPixmapReply::finishedMethodIndex, receiver: object, method_index: method);
2075}
2076
2077bool QQuickPixmap::connectDownloadProgress(QObject *object, const char *method)
2078{
2079 if (!d || !d->reply) {
2080 qWarning(msg: "QQuickPixmap: connectDownloadProgress() called when not loading.");
2081 return false;
2082 }
2083
2084 return QObject::connect(sender: d->reply, SIGNAL(downloadProgress(qint64,qint64)), receiver: object,
2085 member: method);
2086}
2087
2088bool QQuickPixmap::connectDownloadProgress(QObject *object, int method)
2089{
2090 if (!d || !d->reply) {
2091 qWarning(msg: "QQuickPixmap: connectDownloadProgress() called when not loading.");
2092 return false;
2093 }
2094
2095 return QMetaObject::connect(sender: d->reply, signal_index: QQuickPixmapReply::downloadProgressMethodIndex, receiver: object,
2096 method_index: method);
2097}
2098
2099QColorSpace QQuickPixmap::colorSpace() const
2100{
2101 if (!d || !d->textureFactory)
2102 return QColorSpace();
2103 return d->textureFactory->image().colorSpace();
2104}
2105
2106QT_END_NAMESPACE
2107
2108#include <qquickpixmapcache.moc>
2109
2110#include "moc_qquickpixmapcache_p.cpp"
2111

source code of qtdeclarative/src/quick/util/qquickpixmapcache.cpp