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 usableJob = !localFile.isEmpty()
867#if QT_CONFIG(qml_network)
868 || networkJobs.size() < IMAGEREQUEST_MAX_NETWORK_REQUEST_COUNT
869#endif
870 ;
871 }
872
873 if (usableJob) {
874 jobs.removeAt(i);
875
876 job->loading = true;
877
878 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingStarted>(url));
879
880#if QT_CONFIG(quick_pixmap_cache_threaded_download)
881 locker.unlock();
882 auto relockMutexGuard = qScopeGuard(([&locker]() {
883 locker.relock();
884 }));
885#endif
886 processJob(job, url, localFile, imageType, provider);
887 }
888 }
889
890 if (!usableJob)
891 return;
892 }
893 }
894}
895
896void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &url, const QString &localFile,
897 QQuickImageProvider::ImageType imageType, const QSharedPointer<QQuickImageProvider> &provider)
898{
899 Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
900
901 // fetch
902 if (url.scheme() == QLatin1String("image")) {
903 // Use QQuickImageProvider
904 QSize readSize;
905
906 if (imageType == QQuickImageProvider::Invalid) {
907 QString errorStr = QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString());
909 if (!cancelledJobs.contains(runningJob))
910 runningJob->postReply(QQuickPixmapReply::Loading, errorStr, readSize, nullptr);
911 return;
912 }
913
914 // This is safe because we ensure that provider does outlive providerV2 and it does not escape the function
915 QQuickImageProviderWithOptions *providerV2 = QQuickImageProviderWithOptions::checkedCast(provider.get());
916
917 switch (imageType) {
918 case QQuickImageProvider::Invalid:
919 {
920 // Already handled
921 break;
922 }
923
924 case QQuickImageProvider::Image:
925 {
926 QImage image;
927 if (providerV2) {
928 image = providerV2->requestImage(imageId(url), &readSize, runningJob->requestSize, runningJob->providerOptions);
929 } else {
930 image = provider->requestImage(imageId(url), &readSize, runningJob->requestSize);
931 }
932 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
933 QString errorStr;
934 if (image.isNull()) {
935 errorCode = QQuickPixmapReply::Loading;
936 errorStr = QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString());
937 }
939 if (!cancelledJobs.contains(runningJob)) {
940 runningJob->postReply(errorCode, errorStr, readSize,
941 QQuickTextureFactory::textureFactoryForImage(image));
942 }
943 break;
944 }
945
946 case QQuickImageProvider::Pixmap:
947 {
948 QPixmap pixmap;
949 if (providerV2) {
950 pixmap = providerV2->requestPixmap(imageId(url), &readSize, runningJob->requestSize, runningJob->providerOptions);
951 } else {
952 pixmap = provider->requestPixmap(imageId(url), &readSize, runningJob->requestSize);
953 }
954 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
955 QString errorStr;
956 if (pixmap.isNull()) {
957 errorCode = QQuickPixmapReply::Loading;
958 errorStr = QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString());
959 }
960
962 if (!cancelledJobs.contains(runningJob)) {
963 runningJob->postReply(
964 errorCode, errorStr, readSize,
965 QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()));
966 }
967 break;
968 }
969
970 case QQuickImageProvider::Texture:
971 {
972 QQuickTextureFactory *t;
973 if (providerV2) {
974 t = providerV2->requestTexture(imageId(url), &readSize, runningJob->requestSize, runningJob->providerOptions);
975 } else {
976 t = provider->requestTexture(imageId(url), &readSize, runningJob->requestSize);
977 }
978 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
979 QString errorStr;
980 if (!t) {
981 errorCode = QQuickPixmapReply::Loading;
982 errorStr = QQuickPixmap::tr("Failed to get texture from provider: %1").arg(url.toString());
983 }
985 if (!cancelledJobs.contains(runningJob))
986 runningJob->postReply(errorCode, errorStr, readSize, t);
987 else
988 delete t;
989 break;
990 }
991
992 case QQuickImageProvider::ImageResponse:
993 {
994 QQuickImageResponse *response;
995 if (providerV2) {
996 response = providerV2->requestImageResponse(imageId(url), runningJob->requestSize, runningJob->providerOptions);
997 } else {
998 QQuickAsyncImageProvider *asyncProvider = static_cast<QQuickAsyncImageProvider*>(provider.get());
999 response = asyncProvider->requestImageResponse(imageId(url), runningJob->requestSize);
1000 }
1001
1002 {
1003 QObject::connect(response, &QQuickImageResponse::finished, readerThreadExecutionEnforcer(),
1004 qOverload<>(&ReaderThreadExecutionEnforcer::asyncResponseFinished));
1005 // as the response object can outlive the provider QSharedPointer, we have to extend the pointee's lifetime by that of the response
1006 // we do this by capturing a copy of the QSharedPointer in a lambda, and dropping it once the lambda has been called
1007 auto provider_copy = provider; // capturing provider would capture it as a const reference, and copy capture with initializer is only available in C++14
1008 QObject::connect(response, &QQuickImageResponse::destroyed, response, [provider_copy]() {
1009 // provider_copy will be deleted when the connection gets deleted
1010 });
1011 }
1012 // Might be that the async provider was so quick it emitted the signal before we
1013 // could connect to it.
1014 //
1015 // loadAcquire() synchronizes-with storeRelease() in QQuickImageResponsePrivate::_q_finished():
1016 if (static_cast<QQuickImageResponsePrivate*>(QObjectPrivate::get(response))->finished.loadAcquire()) {
1017 QMetaObject::invokeMethod(readerThreadExecutionEnforcer(), "asyncResponseFinished",
1018 Qt::QueuedConnection,
1019 Q_ARG(QQuickImageResponse *, response));
1020 }
1021
1022 asyncResponses.insert(response, runningJob);
1023 break;
1024 }
1025 }
1026
1027 } else {
1028 if (!localFile.isEmpty()) {
1029 // Image is local - load/decode immediately
1030 QImage image;
1031 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
1032 QString errorStr;
1033 QSize readSize;
1034
1035 if (runningJob->data && runningJob->data->fromSpecialDevice) {
1036 auto specialDevice = runningJob->data->specialDevice;
1037 if (specialDevice.isNull() || QObjectPrivate::get(specialDevice.data())->deleteLaterCalled) {
1038 qCDebug(lcImg) << "readImage job aborted" << url;
1039 return;
1040 }
1041 int frameCount;
1042 // Ensure that specialDevice's thread affinity is _this_ thread, to avoid deleteLater()
1043 // deleting prematurely, before readImage() is done. But this is only possible if it has already
1044 // relinquished its initial thread affinity.
1045 if (!specialDevice->thread()) {
1046 qCDebug(lcQsgLeak) << specialDevice.data() << ": changing thread affinity so that"
1047 << QThread::currentThread() << "will handle any deleteLater() calls";
1048 specialDevice->moveToThread(QThread::currentThread());
1049 }
1050 if (!readImage(url, specialDevice.data(), &image, &errorStr, &readSize, &frameCount,
1051 runningJob->requestRegion, runningJob->requestSize,
1052 runningJob->providerOptions, nullptr, runningJob->data->frame,
1053 runningJob->data->devicePixelRatio)) {
1054 errorCode = QQuickPixmapReply::Loading;
1055 } else if (runningJob->data) {
1056 runningJob->data->frameCount = frameCount;
1057 }
1058 } else {
1059 QFile f(existingImageFileForPath(localFile));
1060 if (f.open(QIODevice::ReadOnly)) {
1061 QSGTextureReader texReader(&f, localFile);
1062 if (backendSupport()->hasOpenGL && texReader.isTexture()) {
1063 QQuickTextureFactory *factory = texReader.read();
1064 if (factory) {
1065 readSize = factory->textureSize();
1066 } else {
1067 errorStr = QQuickPixmap::tr("Error decoding: %1").arg(url.toString());
1068 if (f.fileName() != localFile)
1069 errorStr += QString::fromLatin1(" (%1)").arg(f.fileName());
1070 errorCode = QQuickPixmapReply::Decoding;
1071 }
1073 if (!cancelledJobs.contains(runningJob))
1074 runningJob->postReply(errorCode, errorStr, readSize, factory);
1075 return;
1076 } else {
1077 int frameCount;
1078 int const frame = runningJob->data ? runningJob->data->frame : 0;
1079 const qreal dpr = runningJob->data ? runningJob->data->devicePixelRatio : 1;
1080 if (!readImage(url, &f, &image, &errorStr, &readSize, &frameCount,
1081 runningJob->requestRegion, runningJob->requestSize,
1082 runningJob->providerOptions, nullptr, frame, dpr)) {
1083 errorCode = QQuickPixmapReply::Loading;
1084 if (f.fileName() != localFile)
1085 errorStr += QString::fromLatin1(" (%1)").arg(f.fileName());
1086 } else if (runningJob->data) {
1087 runningJob->data->frameCount = frameCount;
1088 }
1089 }
1090 } else {
1091 errorStr = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
1092 errorCode = QQuickPixmapReply::Loading;
1093 }
1094 }
1096 if (!cancelledJobs.contains(runningJob)) {
1097 runningJob->postReply(errorCode, errorStr, readSize,
1098 QQuickTextureFactory::textureFactoryForImage(image));
1099 }
1100 } else {
1101#if QT_CONFIG(qml_network)
1102 // Network resource
1103 QNetworkRequest req(url);
1104 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
1105 QNetworkReply *reply = networkAccessManager()->get(req);
1106
1107 QMetaObject::connect(reply, replyDownloadProgressMethodIndex, runningJob,
1108 downloadProgressMethodIndex);
1109 QMetaObject::connect(reply, replyFinishedMethodIndex, readerThreadExecutionEnforcer(),
1110 threadNetworkRequestDoneMethodIndex);
1111
1112 networkJobs.insert(reply, runningJob);
1113#else
1114// Silently fail if compiled with no_network
1115#endif
1116 }
1117 }
1118}
1119
1120QQuickPixmapReader *QQuickPixmapReader::instance(QQmlEngine *engine)
1121{
1122 // XXX NOTE: must be called within readerMutex locking.
1123 QQuickPixmapReader *reader = readers.value(engine);
1124 if (!reader) {
1125 reader = new QQuickPixmapReader(engine);
1126 readers.insert(engine, reader);
1127 }
1128
1129 return reader;
1130}
1131
1132QQuickPixmapReader *QQuickPixmapReader::existingInstance(QQmlEngine *engine)
1133{
1134 // XXX NOTE: must be called within readerMutex locking.
1135 return readers.value(engine, 0);
1136}
1137
1139{
1140 QQuickPixmapReply *reply = new QQuickPixmapReply(data);
1141 reply->engineForReader = engine;
1142 return reply;
1143}
1144
1146{
1148 jobs.append(job);
1149 if (readerThreadExecutionEnforcer())
1150 readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
1151}
1152
1154{
1156 if (reply->loading) {
1157 cancelledJobs.append(reply);
1158 reply->data = nullptr;
1159 // XXX
1160 if (readerThreadExecutionEnforcer())
1161 readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
1162 } else {
1163 // If loading was started (reply removed from jobs) but the reply was never processed
1164 // (otherwise it would have deleted itself) we need to profile an error.
1165 if (jobs.removeAll(reply) == 0) {
1166 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(reply->url));
1167 }
1168 delete reply;
1169 }
1170}
1171
1173{
1174 Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
1175
1176 if (replyDownloadProgressMethodIndex == -1) {
1177#if QT_CONFIG(qml_network)
1178 replyDownloadProgressMethodIndex =
1179 QMetaMethod::fromSignal(&QNetworkReply::downloadProgress).methodIndex();
1180 replyFinishedMethodIndex = QMetaMethod::fromSignal(&QNetworkReply::finished).methodIndex();
1181 const QMetaObject *ir = &ReaderThreadExecutionEnforcer::staticMetaObject;
1182 threadNetworkRequestDoneMethodIndex = ir->indexOfSlot("networkRequestDone()");
1183#endif
1184 downloadProgressMethodIndex =
1185 QMetaMethod::fromSignal(&QQuickPixmapReply::downloadProgress).methodIndex();
1186 }
1187
1188#if QT_CONFIG(quick_pixmap_cache_threaded_download)
1189 const auto guard = qScopeGuard([this]() {
1190 // We need to delete the runLoopReaderThreadExecutionEnforcer from the same thread.
1191 PIXMAP_READER_LOCK();
1192 delete runLoopReaderThreadExecutionEnforcer;
1193 runLoopReaderThreadExecutionEnforcer = nullptr;
1194 });
1195
1196 {
1197 PIXMAP_READER_LOCK();
1198 Q_ASSERT(!runLoopReaderThreadExecutionEnforcer);
1199 runLoopReaderThreadExecutionEnforcer = new ReaderThreadExecutionEnforcer(this);
1200 }
1201
1202 processJobs();
1203 exec();
1204#else
1205 ownedReaderThreadExecutionEnforcer = std::make_unique<ReaderThreadExecutionEnforcer>(this);
1206 processJobs();
1207#endif
1208}
1209
1210inline bool operator==(const QQuickPixmapKey &lhs, const QQuickPixmapKey &rhs)
1211{
1212 return *lhs.url == *rhs.url &&
1213 *lhs.region == *rhs.region &&
1214 *lhs.size == *rhs.size &&
1215 lhs.frame == rhs.frame &&
1216 lhs.options == rhs.options;
1217}
1218
1219inline size_t qHash(const QQuickPixmapKey &key, size_t seed) noexcept
1220{
1221 return qHashMulti(seed, *key.url, *key.region, *key.size, key.frame, key.options.autoTransform());
1222}
1223
1224#ifndef QT_NO_DEBUG_STREAM
1225inline QDebug operator<<(QDebug debug, const QQuickPixmapKey &key)
1226{
1227 QDebugStateSaver saver(debug);
1228 debug.nospace();
1229 if (!key.url) {
1230 debug << "QQuickPixmapKey(0)";
1231 return debug;
1232 }
1233
1234 debug << "QQuickPixmapKey(" << key.url->toString() << " frame=" << key.frame;
1235 if (!key.region->isEmpty()) {
1236 debug << " region=";
1237 QtDebugUtils::formatQRect(debug, *key.region);
1238 }
1239 if (!key.size->isEmpty()) {
1240 debug << " size=";
1241 QtDebugUtils::formatQSize(debug, *key.size);
1242 }
1243 debug << ')';
1244 return debug;
1245}
1246#endif
1247
1248QQuickPixmapCache *QQuickPixmapCache::instance()
1249{
1250 static QQuickPixmapCache self;
1251 return &self;
1252}
1253
1254QQuickPixmapCache::~QQuickPixmapCache()
1255{
1256 destroyCache();
1257}
1258
1259/*! \internal
1260 Empty the cache completely, to prevent leaks. Returns the number of
1261 leaked pixmaps (should always be \c 0).
1262
1263 This is work the destructor needs to do, but we put it into a function
1264 only to make it testable in autotests, because the static instance()
1265 cannot be destroyed before shutdown.
1266*/
1267int QQuickPixmapCache::destroyCache()
1268{
1269 if (m_destroying)
1270 return -1;
1271
1272 m_destroying = true;
1273
1274 // Prevent unreferencePixmap() from assuming it needs to kick
1275 // off the cache expiry timer, as we're shrinking the cache
1276 // manually below after releasing all the pixmaps.
1277 m_timerId = -2;
1278
1279 // unreference all (leaked) pixmaps
1280 int leakedPixmaps = 0;
1281 const auto cache = m_cache; // NOTE: intentional copy (QTBUG-65077); releasing items from the cache modifies m_cache.
1282 for (auto *pixmap : cache) {
1283 auto currRefCount = pixmap->refCount;
1284 if (currRefCount) {
1285 leakedPixmaps++;
1286 qCDebug(lcQsgLeak) << "leaked pixmap: refCount" << pixmap->refCount << pixmap->url << "frame" << pixmap->frame
1287 << "size" << pixmap->requestSize << "region" << pixmap->requestRegion;
1288 while (currRefCount > 0) {
1289 pixmap->release(this);
1290 currRefCount--;
1291 }
1292 }
1293 }
1294
1295 // free all unreferenced pixmaps
1296 while (m_lastUnreferencedPixmap)
1297 shrinkCache(20);
1298
1299 qCDebug(lcQsgLeak, "Number of leaked pixmaps: %i", leakedPixmaps);
1300 return leakedPixmaps;
1301}
1302
1303qsizetype QQuickPixmapCache::referencedCost() const
1304{
1305 qsizetype ret = 0;
1306 QMutexLocker locker(&m_cacheMutex);
1307 for (const auto *pixmap : std::as_const(m_cache)) {
1308 if (pixmap->refCount)
1309 ret += pixmap->cost();
1310 }
1311 return ret;
1312}
1313
1314/*! \internal
1315 Declare that \a data is currently unused so that shrinkCache() can lazily
1316 delete it later.
1317*/
1318void QQuickPixmapCache::unreferencePixmap(QQuickPixmapData *data)
1319{
1320 Q_ASSERT(data->prevUnreferenced == nullptr);
1321 Q_ASSERT(data->prevUnreferencedPtr == nullptr);
1322 Q_ASSERT(data->nextUnreferenced == nullptr);
1323
1324 data->nextUnreferenced = m_unreferencedPixmaps;
1325 data->prevUnreferencedPtr = &m_unreferencedPixmaps;
1326 if (!m_destroying) { // the texture factories may have been cleaned up already.
1327 m_unreferencedCost += data->cost();
1328 qCDebug(lcImg) << data->url << "had cost" << data->cost() << "of total unreferenced" << m_unreferencedCost;
1329 }
1330
1331 m_unreferencedPixmaps = data;
1332 if (m_unreferencedPixmaps->nextUnreferenced) {
1333 m_unreferencedPixmaps->nextUnreferenced->prevUnreferenced = m_unreferencedPixmaps;
1334 m_unreferencedPixmaps->nextUnreferenced->prevUnreferencedPtr = &m_unreferencedPixmaps->nextUnreferenced;
1335 }
1336
1337 if (!m_lastUnreferencedPixmap)
1338 m_lastUnreferencedPixmap = data;
1339
1340 shrinkCache(-1); // Shrink the cache in case it has become larger than cache_limit
1341
1342 if (m_timerId == -1 && m_unreferencedPixmaps
1343 && !m_destroying && !QCoreApplication::closingDown()) {
1344 m_timerId = startTimer(CACHE_EXPIRE_TIME * 1000);
1345 }
1346}
1347
1348/*! \internal
1349 Declare that \a data is being used (by a QQuickPixmap) so that
1350 shrinkCache() won't delete it. (This is not reference counting though.)
1351*/
1352void QQuickPixmapCache::referencePixmap(QQuickPixmapData *data)
1353{
1354 Q_ASSERT(data->prevUnreferencedPtr);
1355
1356 *data->prevUnreferencedPtr = data->nextUnreferenced;
1357 if (data->nextUnreferenced) {
1358 data->nextUnreferenced->prevUnreferencedPtr = data->prevUnreferencedPtr;
1359 data->nextUnreferenced->prevUnreferenced = data->prevUnreferenced;
1360 }
1361 if (m_lastUnreferencedPixmap == data)
1362 m_lastUnreferencedPixmap = data->prevUnreferenced;
1363
1364 data->nextUnreferenced = nullptr;
1365 data->prevUnreferencedPtr = nullptr;
1366 data->prevUnreferenced = nullptr;
1367
1368 m_unreferencedCost -= data->cost();
1369 qCDebug(lcImg) << data->url << "subtracts cost" << data->cost() << "of total" << m_unreferencedCost;
1370}
1371
1372/*! \internal
1373 Delete the least-recently-released QQuickPixmapData instances
1374 until the remaining bytes are less than cache_limit.
1375*/
1376void QQuickPixmapCache::shrinkCache(int remove)
1377{
1378 qCDebug(lcImg) << "reduce unreferenced cost" << m_unreferencedCost << "to less than limit" << cache_limit;
1379 while ((remove > 0 || m_unreferencedCost > cache_limit) && m_lastUnreferencedPixmap) {
1380 QQuickPixmapData *data = m_lastUnreferencedPixmap;
1381 Q_ASSERT(data->nextUnreferenced == nullptr);
1382
1383 *data->prevUnreferencedPtr = nullptr;
1384 m_lastUnreferencedPixmap = data->prevUnreferenced;
1385 data->prevUnreferencedPtr = nullptr;
1386 data->prevUnreferenced = nullptr;
1387
1388 if (!m_destroying) {
1389 remove -= data->cost();
1390 m_unreferencedCost -= data->cost();
1391 }
1392 data->removeFromCache(this);
1393 delete data;
1394 }
1395}
1396
1397void QQuickPixmapCache::timerEvent(QTimerEvent *)
1398{
1399 int removalCost = m_unreferencedCost / CACHE_REMOVAL_FRACTION;
1400
1401 shrinkCache(removalCost);
1402
1403 if (m_unreferencedPixmaps == nullptr) {
1404 killTimer(m_timerId);
1405 m_timerId = -1;
1406 }
1407}
1408
1409void QQuickPixmapCache::purgeCache()
1410{
1411 shrinkCache(m_unreferencedCost);
1412}
1413
1414void QQuickPixmap::purgeCache()
1415{
1416 QQuickPixmapCache::instance()->purgeCache();
1417}
1418
1422{
1423 if (finishedMethodIndex == -1) {
1424 finishedMethodIndex = QMetaMethod::fromSignal(&QQuickPixmapReply::finished).methodIndex();
1425 downloadProgressMethodIndex =
1426 QMetaMethod::fromSignal(&QQuickPixmapReply::downloadProgress).methodIndex();
1427 }
1428}
1429
1431{
1432 // note: this->data->reply must be set to zero if this->data->reply == this
1433 // but it must be done within mutex locking, to be guaranteed to be safe.
1434}
1435
1436bool QQuickPixmapReply::event(QEvent *event)
1437{
1438 if (event->type() == QEvent::User) {
1439
1440 if (data) {
1441 Event *de = static_cast<Event *>(event);
1442 data->pixmapStatus = (de->error == NoError) ? QQuickPixmap::Ready : QQuickPixmap::Error;
1443 if (data->pixmapStatus == QQuickPixmap::Ready) {
1444 data->textureFactory = de->textureFactory;
1445 de->textureFactory = nullptr;
1446 data->implicitSize = de->implicitSize;
1447 PIXMAP_PROFILE(pixmapLoadingFinished(data->url,
1448 data->textureFactory != nullptr && data->textureFactory->textureSize().isValid() ?
1449 data->textureFactory->textureSize() :
1450 (data->requestSize.isValid() ? data->requestSize : data->implicitSize)));
1451 } else {
1452 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(data->url));
1453 data->errorString = de->errorString;
1454 data->removeFromCache(); // We don't continue to cache error'd pixmaps
1455 }
1456
1457 data->reply = nullptr;
1458 emit finished();
1459 } else {
1460 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(url));
1461 }
1462
1463 delete this;
1464 return true;
1465 } else {
1466 return QObject::event(event);
1467 }
1468}
1469
1471{
1472 if (textureFactory)
1473 return textureFactory->textureByteCount();
1474 return 0;
1475}
1476
1478{
1479 ++refCount;
1480 PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapReferenceCountChanged>(url, refCount));
1481 if (prevUnreferencedPtr)
1482 QQuickPixmapCache::instance()->referencePixmap(this);
1483}
1484
1485void QQuickPixmapData::release(QQuickPixmapCache *store)
1486{
1487 Q_ASSERT(refCount > 0);
1488 --refCount;
1489 PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapReferenceCountChanged>(url, refCount));
1490 if (refCount == 0) {
1491 if (reply) {
1492 QQuickPixmapReply *cancelReply = reply;
1493 reply->data = nullptr;
1494 reply = nullptr;
1495 QQuickPixmapReader::readerMutex.lock();
1496 QQuickPixmapReader *reader = QQuickPixmapReader::existingInstance(cancelReply->engineForReader);
1497 if (reader)
1498 reader->cancel(cancelReply);
1499 QQuickPixmapReader::readerMutex.unlock();
1500 }
1501
1502 store = store ? store : QQuickPixmapCache::instance();
1503 if (pixmapStatus == QQuickPixmap::Ready
1504#ifdef Q_OS_WEBOS
1505 && storeToCache
1506#endif
1507 ) {
1508 if (inCache)
1509 store->unreferencePixmap(this);
1510 else
1511 delete this;
1512 } else {
1513 removeFromCache(store);
1514 delete this;
1515 }
1516 }
1517}
1518
1519/*! \internal
1520 Add this to the QQuickPixmapCache singleton.
1521
1522 \note The actual image will end up in QQuickPixmapData::textureFactory.
1523 At the time addToCache() is called, it's generally not yet loaded; so the
1524 qCDebug() below cannot say how much data we're committing to storing.
1525 (On the other hand, removeFromCache() can tell.) QQuickTextureFactory is an
1526 abstraction for image data. See QQuickDefaultTextureFactory for example:
1527 it stores a QImage directly. Other QQuickTextureFactory subclasses store data
1528 in other ways.
1529*/
1531{
1532 if (!inCache) {
1533 QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
1534 QMutexLocker locker(&QQuickPixmapCache::instance()->m_cacheMutex);
1535 if (lcImg().isDebugEnabled()) {
1536 qCDebug(lcImg) << "adding" << key << "to total" << QQuickPixmapCache::instance()->m_cache.size();
1537 for (auto it = QQuickPixmapCache::instance()->m_cache.keyBegin(); it != QQuickPixmapCache::instance()->m_cache.keyEnd(); ++it) {
1538 if (*(it->url) == url && it->frame == frame)
1539 qCDebug(lcImg) << " similar pre-existing:" << *it;
1540 }
1541 }
1542 QQuickPixmapCache::instance()->m_cache.insert(key, this);
1543 inCache = true;
1544 PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapCacheCountChanged>(
1545 url, QQuickPixmapCache::instance()->m_cache.size()));
1546 }
1547}
1548
1549void QQuickPixmapData::removeFromCache(QQuickPixmapCache *store)
1550{
1551 if (inCache) {
1552 if (!store)
1553 store = QQuickPixmapCache::instance();
1554 QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
1555 QMutexLocker locker(&QQuickPixmapCache::instance()->m_cacheMutex);
1556 store->m_cache.remove(key);
1557 qCDebug(lcImg) << "removed" << key << implicitSize << "; total remaining" << QQuickPixmapCache::instance()->m_cache.size();
1558 inCache = false;
1559 PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapCacheCountChanged>(
1560 url, store->m_cache.size()));
1561 }
1562}
1563
1564static QQuickPixmapData* createPixmapDataSync(QQmlEngine *engine, const QUrl &url,
1565 const QRect &requestRegion, const QSize &requestSize,
1566 const QQuickImageProviderOptions &providerOptions, int frame, bool *ok,
1567 qreal devicePixelRatio)
1568{
1569 if (url.scheme() == QLatin1String("image")) {
1570 QSize readSize;
1571
1572 QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
1573 QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(engine);
1574 QSharedPointer<QQuickImageProvider> provider = enginePrivate->imageProvider(imageProviderId(url)).objectCast<QQuickImageProvider>();
1575 // it is safe to use get() as providerV2 does not escape and is outlived by provider
1576 QQuickImageProviderWithOptions *providerV2 = QQuickImageProviderWithOptions::checkedCast(provider.get());
1577 if (provider)
1578 imageType = provider->imageType();
1579
1580 switch (imageType) {
1581 case QQuickImageProvider::Invalid:
1582 return new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
1583 QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString()));
1584 case QQuickImageProvider::Texture:
1585 {
1586 QQuickTextureFactory *texture = providerV2 ? providerV2->requestTexture(imageId(url), &readSize, requestSize, providerOptions)
1587 : provider->requestTexture(imageId(url), &readSize, requestSize);
1588 if (texture) {
1589 *ok = true;
1590 return new QQuickPixmapData(url, texture, readSize, requestRegion, requestSize,
1591 providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
1592 }
1593 break;
1594 }
1595
1596 case QQuickImageProvider::Image:
1597 {
1598 QImage image = providerV2 ? providerV2->requestImage(imageId(url), &readSize, requestSize, providerOptions)
1599 : provider->requestImage(imageId(url), &readSize, requestSize);
1600 if (!image.isNull()) {
1601 *ok = true;
1602 return new QQuickPixmapData(url, QQuickTextureFactory::textureFactoryForImage(image),
1603 readSize, requestRegion, requestSize, providerOptions,
1604 QQuickImageProviderOptions::UsePluginDefaultTransform,
1605 frame, 1, devicePixelRatio);
1606 }
1607 break;
1608 }
1609 case QQuickImageProvider::Pixmap:
1610 {
1611 QPixmap pixmap = providerV2 ? providerV2->requestPixmap(imageId(url), &readSize, requestSize, providerOptions)
1612 : provider->requestPixmap(imageId(url), &readSize, requestSize);
1613 if (!pixmap.isNull()) {
1614 *ok = true;
1615 return new QQuickPixmapData(url, QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()),
1616 readSize, requestRegion, requestSize, providerOptions,
1617 QQuickImageProviderOptions::UsePluginDefaultTransform,
1618 frame, 1, devicePixelRatio);
1619 }
1620 break;
1621 }
1622 case QQuickImageProvider::ImageResponse:
1623 {
1624 // Fall through, ImageResponse providers never get here
1625 Q_ASSERT(imageType != QQuickImageProvider::ImageResponse && "Sync call to ImageResponse provider");
1626 }
1627 }
1628
1629 // provider has bad image type, or provider returned null image
1630 return new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
1631 QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString()));
1632 }
1633
1634 QString localFile = QQmlFile::urlToLocalFileOrQrc(url);
1635 if (localFile.isEmpty())
1636 return nullptr;
1637
1638 QFile f(existingImageFileForPath(localFile));
1639 QSize readSize;
1640 QString errorString;
1641
1642 if (f.open(QIODevice::ReadOnly)) {
1643 QSGTextureReader texReader(&f, localFile);
1644 if (backendSupport()->hasOpenGL && texReader.isTexture()) {
1645 QQuickTextureFactory *factory = texReader.read();
1646 if (factory) {
1647 *ok = true;
1648 return new QQuickPixmapData(url, factory, factory->textureSize(), requestRegion, requestSize,
1649 providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
1650 } else {
1651 errorString = QQuickPixmap::tr("Error decoding: %1").arg(url.toString());
1652 if (f.fileName() != localFile)
1653 errorString += QString::fromLatin1(" (%1)").arg(f.fileName());
1654 }
1655 } else {
1656 QImage image;
1657 QQuickImageProviderOptions::AutoTransform appliedTransform = providerOptions.autoTransform();
1658 int frameCount;
1659 if (readImage(url, &f, &image, &errorString, &readSize, &frameCount, requestRegion, requestSize,
1660 providerOptions, &appliedTransform, frame, devicePixelRatio)) {
1661 *ok = true;
1662 return new QQuickPixmapData(url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestRegion, requestSize,
1663 providerOptions, appliedTransform, frame, frameCount);
1664 } else if (f.fileName() != localFile) {
1665 errorString += QString::fromLatin1(" (%1)").arg(f.fileName());
1666 }
1667 }
1668 } else {
1669 errorString = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
1670 }
1671 return new QQuickPixmapData(url, requestRegion, requestSize, providerOptions, errorString);
1672}
1673
1674
1681
1682QQuickPixmap::QQuickPixmap()
1683: d(nullptr)
1684{
1685}
1686
1687QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url)
1688: d(nullptr)
1689{
1690 load(engine, url);
1691}
1692
1693QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, Options options)
1694: d(nullptr)
1695{
1696 load(engine, url, options);
1697}
1698
1699QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QRect &region, const QSize &size)
1700: d(nullptr)
1701{
1702 load(engine, url, region, size);
1703}
1704
1705QQuickPixmap::QQuickPixmap(const QUrl &url, const QImage &image)
1706{
1707 d = new QQuickPixmapData(url, new QQuickDefaultTextureFactory(image), image.size(), QRect(), QSize(),
1708 QQuickImageProviderOptions(), QQuickImageProviderOptions::UsePluginDefaultTransform);
1709 d->addToCache();
1710}
1711
1712QQuickPixmap::~QQuickPixmap()
1713{
1714 if (d) {
1715 d->release();
1716 d = nullptr;
1717 }
1718}
1719
1720bool QQuickPixmap::isNull() const
1721{
1722 return d == nullptr;
1723}
1724
1725bool QQuickPixmap::isReady() const
1726{
1727 return status() == Ready;
1728}
1729
1730bool QQuickPixmap::isError() const
1731{
1732 return status() == Error;
1733}
1734
1735bool QQuickPixmap::isLoading() const
1736{
1737 return status() == Loading;
1738}
1739
1740QString QQuickPixmap::error() const
1741{
1742 if (d)
1743 return d->errorString;
1744 else
1745 return QString();
1746}
1747
1748QQuickPixmap::Status QQuickPixmap::status() const
1749{
1750 if (d)
1751 return d->pixmapStatus;
1752 else
1753 return Null;
1754}
1755
1756const QUrl &QQuickPixmap::url() const
1757{
1758 if (d)
1759 return d->url;
1760 else
1761 return nullPixmap()->url;
1762}
1763
1764const QSize &QQuickPixmap::implicitSize() const
1765{
1766 if (d)
1767 return d->implicitSize;
1768 else
1769 return nullPixmap()->size;
1770}
1771
1772const QSize &QQuickPixmap::requestSize() const
1773{
1774 if (d)
1775 return d->requestSize;
1776 else
1777 return nullPixmap()->size;
1778}
1779
1780const QRect &QQuickPixmap::requestRegion() const
1781{
1782 if (d)
1783 return d->requestRegion;
1784 else
1785 return nullPixmap()->region;
1786}
1787
1788QQuickImageProviderOptions::AutoTransform QQuickPixmap::autoTransform() const
1789{
1790 if (d)
1791 return d->appliedTransform;
1792 else
1793 return QQuickImageProviderOptions::UsePluginDefaultTransform;
1794}
1795
1796int QQuickPixmap::frameCount() const
1797{
1798 if (d)
1799 return d->frameCount;
1800 return 0;
1801}
1802
1803QQuickTextureFactory *QQuickPixmap::textureFactory() const
1804{
1805 if (d)
1806 return d->textureFactory;
1807
1808 return nullptr;
1809}
1810
1811QImage QQuickPixmap::image() const
1812{
1813 if (d && d->textureFactory)
1814 return d->textureFactory->image();
1815 return QImage();
1816}
1817
1818void QQuickPixmap::setImage(const QImage &p)
1819{
1820 clear();
1821
1822 if (!p.isNull()) {
1823 if (d)
1824 d->release();
1825 d = new QQuickPixmapData(QQuickTextureFactory::textureFactoryForImage(p));
1826 }
1827}
1828
1829void QQuickPixmap::setPixmap(const QQuickPixmap &other)
1830{
1831 if (d == other.d)
1832 return;
1833 clear();
1834
1835 if (other.d) {
1836 if (d)
1837 d->release();
1838 d = other.d;
1839 d->addref();
1840 }
1841}
1842
1843int QQuickPixmap::width() const
1844{
1845 if (d && d->textureFactory)
1846 return d->textureFactory->textureSize().width();
1847 else
1848 return 0;
1849}
1850
1851int QQuickPixmap::height() const
1852{
1853 if (d && d->textureFactory)
1854 return d->textureFactory->textureSize().height();
1855 else
1856 return 0;
1857}
1858
1859QRect QQuickPixmap::rect() const
1860{
1861 if (d && d->textureFactory)
1862 return QRect(QPoint(), d->textureFactory->textureSize());
1863 else
1864 return QRect();
1865}
1866
1867void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url)
1868{
1869 load(engine, url, QRect(), QSize(), QQuickPixmap::Cache);
1870}
1871
1872void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, QQuickPixmap::Options options)
1873{
1874 load(engine, url, QRect(), QSize(), options);
1875}
1876
1877void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize)
1878{
1879 load(engine, url, requestRegion, requestSize, QQuickPixmap::Cache);
1880}
1881
1882void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize, QQuickPixmap::Options options)
1883{
1884 load(engine, url, requestRegion, requestSize, options, QQuickImageProviderOptions());
1885}
1886
1887void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize,
1888 QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions, int frame, int frameCount,
1889 qreal devicePixelRatio)
1890{
1891 if (d) {
1892 d->release();
1893 d = nullptr;
1894 }
1895
1896 QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
1897 QQuickPixmapCache *store = QQuickPixmapCache::instance();
1898
1899 QMutexLocker locker(&QQuickPixmapCache::instance()->m_cacheMutex);
1900 QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end();
1901
1902#ifdef Q_OS_WEBOS
1903 QQuickPixmap::Options orgOptions = options;
1904 // In webOS, we suppose that cache is always enabled to share image instances along its source.
1905 // So, original option(orgOptions) for cache only decides whether to store the instances when it's unreferenced.
1906 options |= QQuickPixmap::Cache;
1907#endif
1908
1909 // If Cache is disabled, the pixmap will always be loaded, even if there is an existing
1910 // cached version. Unless it's an itemgrabber url, since the cache is used to pass
1911 // the result between QQuickItemGrabResult and QQuickImage.
1912 if (url.scheme() == itemGrabberScheme) {
1913 QRect dummyRegion;
1914 QSize dummySize;
1915 if (requestSize != dummySize)
1916 qWarning() << "Ignoring sourceSize request for image url that came from grabToImage. Use the targetSize parameter of the grabToImage() function instead.";
1917 const QQuickPixmapKey grabberKey = { &url, &dummyRegion, &dummySize, 0, QQuickImageProviderOptions() };
1918 iter = store->m_cache.find(grabberKey);
1919 } else if (options & QQuickPixmap::Cache)
1920 iter = store->m_cache.find(key);
1921
1922 if (iter == store->m_cache.end()) {
1923 if (!engine)
1924 return;
1925
1926 locker.unlock();
1927
1928 if (url.scheme() == QLatin1String("image")) {
1929 QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(engine);
1930 if (auto provider = enginePrivate->imageProvider(imageProviderId(url)).staticCast<QQuickImageProvider>()) {
1931 const bool threadedPixmaps = QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedPixmaps);
1932 if (!threadedPixmaps && provider->imageType() == QQuickImageProvider::Pixmap) {
1933 // pixmaps can only be loaded synchronously
1934 options &= ~QQuickPixmap::Asynchronous;
1935 } else if (provider->flags() & QQuickImageProvider::ForceAsynchronousImageLoading) {
1936 options |= QQuickPixmap::Asynchronous;
1937 }
1938 }
1939 }
1940
1941 if (!(options & QQuickPixmap::Asynchronous)) {
1942 bool ok = false;
1943 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingStarted>(url));
1944 d = createPixmapDataSync(engine, url, requestRegion, requestSize, providerOptions, frame, &ok, devicePixelRatio);
1945 if (ok) {
1946 PIXMAP_PROFILE(pixmapLoadingFinished(url, QSize(width(), height())));
1947 if (options & QQuickPixmap::Cache)
1948 d->addToCache();
1949#ifdef Q_OS_WEBOS
1950 d->storeToCache = orgOptions & QQuickPixmap::Cache;
1951#endif
1952 return;
1953 }
1954 if (d) { // loadable, but encountered error while loading
1955 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(url));
1956 return;
1957 }
1958 }
1959
1960 d = new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
1961 QQuickImageProviderOptions::UsePluginDefaultTransform, frame,
1962 frameCount, devicePixelRatio);
1963 if (options & QQuickPixmap::Cache)
1964 d->addToCache();
1965#ifdef Q_OS_WEBOS
1966 d->storeToCache = orgOptions & QQuickPixmap::Cache;
1967#endif
1968
1969 QQuickPixmapReader::readerMutex.lock();
1970 QQuickPixmapReader *reader = QQuickPixmapReader::instance(engine);
1971 d->reply = reader->getImage(d);
1972 reader->startJob(d->reply);
1973 QQuickPixmapReader::readerMutex.unlock();
1974 } else {
1975 d = *iter;
1976 d->addref();
1977 qCDebug(lcImg) << "loaded from cache" << url << "frame" << frame;
1978 }
1979}
1980
1981/*! \internal
1982 Attempts to load an image from the given \a url via the given \a device.
1983 This is for special cases when the QImageIOHandler can benefit from reusing
1984 the I/O device, or from something extra that a subclass of QIODevice
1985 carries with it. So far, this code doesn't support loading anything other
1986 than a QImage, for example compressed textures. It can be added if needed.
1987*/
1988void QQuickPixmap::loadImageFromDevice(QQmlEngine *engine, QIODevice *device, const QUrl &url,
1989 const QRect &requestRegion, const QSize &requestSize,
1990 const QQuickImageProviderOptions &providerOptions, int frame, int frameCount)
1991{
1992 auto oldD = d;
1993 QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
1994 QQuickPixmapCache *store = QQuickPixmapCache::instance();
1995 QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end();
1996 QMutexLocker locker(&store->m_cacheMutex);
1997 iter = store->m_cache.find(key);
1998 if (iter == store->m_cache.end()) {
1999 if (!engine)
2000 return;
2001
2002 locker.unlock();
2003 d = new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
2004 QQuickImageProviderOptions::UsePluginDefaultTransform, frame, frameCount);
2005 d->specialDevice = device;
2006 d->fromSpecialDevice = true;
2007 d->addToCache();
2008
2009 QQuickPixmapReader::readerMutex.lock();
2010 QQuickPixmapReader *reader = QQuickPixmapReader::instance(engine);
2011 d->reply = reader->getImage(d);
2012 if (oldD) {
2013 QObject::connect(d->reply, &QQuickPixmapReply::destroyed, store, [oldD]() {
2014 oldD->release();
2015 }, Qt::QueuedConnection);
2016 }
2017 reader->startJob(d->reply);
2018 QQuickPixmapReader::readerMutex.unlock();
2019 } else {
2020 d = *iter;
2021 d->addref();
2022 qCDebug(lcImg) << "loaded from cache" << url << "frame" << frame << "refCount" << d->refCount;
2023 locker.unlock();
2024 if (oldD)
2025 oldD->release();
2026 }
2027}
2028
2029void QQuickPixmap::clear()
2030{
2031 if (d) {
2032 d->release();
2033 d = nullptr;
2034 }
2035}
2036
2037void QQuickPixmap::clear(QObject *obj)
2038{
2039 if (d) {
2040 if (d->reply)
2041 QObject::disconnect(d->reply, nullptr, obj, nullptr);
2042 d->release();
2043 d = nullptr;
2044 }
2045}
2046
2047bool QQuickPixmap::isCached(const QUrl &url, const QRect &requestRegion, const QSize &requestSize,
2048 const int frame, const QQuickImageProviderOptions &options)
2049{
2050 QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, options };
2051 QQuickPixmapCache *store = QQuickPixmapCache::instance();
2052
2053 return store->m_cache.contains(key);
2054}
2055
2056bool QQuickPixmap::isScalableImageFormat(const QUrl &url)
2057{
2058 if (url.scheme() == "image"_L1)
2059 return true;
2060
2061 const QString stringUrl = url.path(QUrl::PrettyDecoded);
2062 return stringUrl.endsWith("svg"_L1)
2063 || stringUrl.endsWith("svgz"_L1)
2064 || stringUrl.endsWith("pdf"_L1);
2065}
2066
2067bool QQuickPixmap::connectFinished(QObject *object, const char *method)
2068{
2069 if (!d || !d->reply) {
2070 qWarning("QQuickPixmap: connectFinished() called when not loading.");
2071 return false;
2072 }
2073
2074 return QObject::connect(d->reply, SIGNAL(finished()), object, method);
2075}
2076
2077bool QQuickPixmap::connectFinished(QObject *object, int method)
2078{
2079 if (!d || !d->reply) {
2080 qWarning("QQuickPixmap: connectFinished() called when not loading.");
2081 return false;
2082 }
2083
2084 return QMetaObject::connect(d->reply, QQuickPixmapReply::finishedMethodIndex, object, method);
2085}
2086
2087bool QQuickPixmap::connectDownloadProgress(QObject *object, const char *method)
2088{
2089 if (!d || !d->reply) {
2090 qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
2091 return false;
2092 }
2093
2094 return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), object,
2095 method);
2096}
2097
2098bool QQuickPixmap::connectDownloadProgress(QObject *object, int method)
2099{
2100 if (!d || !d->reply) {
2101 qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
2102 return false;
2103 }
2104
2105 return QMetaObject::connect(d->reply, QQuickPixmapReply::downloadProgressMethodIndex, object,
2106 method);
2107}
2108
2109QColorSpace QQuickPixmap::colorSpace() const
2110{
2111 if (!d || !d->textureFactory)
2112 return QColorSpace();
2113 return d->textureFactory->image().colorSpace();
2114}
2115
2116QT_END_NAMESPACE
2117
2118#include <qquickpixmapcache.moc>
2119
2120#include "moc_qquickpixmap_p.cpp"
2121#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:803
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