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