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