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