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

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