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 }
266#else
267 /*! \internal
268 Returns a pointer to the thread object owned by this instance.
269 */
270 ReaderThreadExecutionEnforcer *readerThreadExecutionEnforcer()
271 {
272 return ownedReaderThreadExecutionEnforcer.get();
273 }
274 std::unique_ptr<ReaderThreadExecutionEnforcer> ownedReaderThreadExecutionEnforcer;
275#endif
276
277#if QT_CONFIG(qml_network)
281#endif
282 QHash<QQuickImageResponse*,QQuickPixmapReply*> asyncResponses;
283
284 Q_THREAD_AFFINITY_MARKER(m_creatorThreadAffinityMarker);
285 Q_THREAD_AFFINITY_MARKER(m_readerThreadAffinityMarker);
286
287 static int replyDownloadProgressMethodIndex;
288 static int replyFinishedMethodIndex;
289 static int downloadProgressMethodIndex;
290 static int threadNetworkRequestDoneMethodIndex;
291 static QHash<QQmlEngine *,QQuickPixmapReader*> readers;
292public:
294};
295
296#if QT_CONFIG(quick_pixmap_cache_threaded_download)
297# define PIXMAP_READER_LOCK() QMutexLocker locker(&mutex)
298#else
299# define PIXMAP_READER_LOCK()
300#endif
301
302class QQuickPixmapCache;
303
304/*! \internal
305 The private storage for QQuickPixmap.
306*/
308{
309public:
310 QQuickPixmapData(const QUrl &u, const QRect &r, const QSize &rs,
311 const QQuickImageProviderOptions &po, const QString &e)
315 textureFactory(nullptr), reply(nullptr), prevUnreferenced(nullptr),
316 prevUnreferencedPtr(nullptr), nextUnreferenced(nullptr)
317#ifdef Q_OS_WEBOS
318 , storeToCache(true)
319#endif
320 {
321 }
322
323 QQuickPixmapData(const QUrl &u, const QRect &r, const QSize &s, const QQuickImageProviderOptions &po,
324 QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1, qreal dpr = 1)
325 : refCount(1), frameCount(frameCount), frame(frame), inCache(false), fromSpecialDevice(false), pixmapStatus(QQuickPixmap::Loading),
328 textureFactory(nullptr), reply(nullptr), prevUnreferenced(nullptr), prevUnreferencedPtr(nullptr),
329 nextUnreferenced(nullptr)
330#ifdef Q_OS_WEBOS
331 , storeToCache(true)
332#endif
333 {
334 }
335
336 QQuickPixmapData(const QUrl &u, QQuickTextureFactory *texture,
337 const QSize &s, const QRect &r, const QSize &rs, const QQuickImageProviderOptions &po,
338 QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1, qreal dpr = 1)
339 : refCount(1), frameCount(frameCount), frame(frame), inCache(false), fromSpecialDevice(false), pixmapStatus(QQuickPixmap::Ready),
342 textureFactory(texture), reply(nullptr), prevUnreferenced(nullptr),
343 prevUnreferencedPtr(nullptr), nextUnreferenced(nullptr)
344#ifdef Q_OS_WEBOS
345 , storeToCache(true)
346#endif
347 {
348 }
349
350 QQuickPixmapData(QQuickTextureFactory *texture)
353 textureFactory(texture), reply(nullptr), prevUnreferenced(nullptr),
354 prevUnreferencedPtr(nullptr), nextUnreferenced(nullptr)
355#ifdef Q_OS_WEBOS
356 , storeToCache(true)
357#endif
358 {
359 if (texture)
360 requestSize = implicitSize = texture->textureSize();
361 }
362
364 {
365 delete textureFactory;
366 }
367
368 int cost() const;
369 void addref();
370 void release(QQuickPixmapCache *store = nullptr);
372 void removeFromCache(QQuickPixmapCache *store = nullptr);
373
376 int frame;
377
378 bool inCache:1;
380
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 start(QThread::LowestPriority);
609#else
610 run(); // Call nonblocking run for ourselves.
611#endif
612}
613
615{
616 Q_ASSERT_CALLED_ON_VALID_THREAD(m_creatorThreadAffinityMarker);
617
618 readerMutex.lock();
619 readers.remove(engine);
620 readerMutex.unlock();
621
622 {
624 // manually cancel all outstanding jobs.
625 for (QQuickPixmapReply *reply : std::as_const(jobs)) {
626 if (reply->data && reply->data->reply == reply)
627 reply->data->reply = nullptr;
628 delete reply;
629 }
630 jobs.clear();
631 const auto cancelJob = [this](QQuickPixmapReply *reply) {
632 if (reply->loading) {
633 cancelledJobs.append(reply);
634 reply->data = nullptr;
635 }
636 };
637#if QT_CONFIG(qml_network)
638 for (auto *reply : std::as_const(networkJobs))
639 cancelJob(reply);
640#endif
641 for (auto *reply : std::as_const(asyncResponses))
642 cancelJob(reply);
643#if !QT_CONFIG(quick_pixmap_cache_threaded_download)
644 // In this case we won't be waiting, but we are on the correct thread already, so we can
645 // perform housekeeping synchronously now.
646 processJobs();
647#else // QT_CONFIG(quick_pixmap_cache_threaded_download) is true
648 // Perform housekeeping on all the jobs cancelled above soon...
649 if (readerThreadExecutionEnforcer())
650 readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
651#endif
652 }
653
654#if QT_CONFIG(quick_pixmap_cache_threaded_download)
655 quit();
656 wait();
657#endif
658
659 // While we've been waiting, the other thread may have added
660 // more replies. No one will care about them anymore.
661
662 auto deleteReply = [](QQuickPixmapReply *reply) {
663 if (reply->data && reply->data->reply == reply)
664 reply->data->reply = nullptr;
665 delete reply;
666 };
667#if QT_CONFIG(qml_network)
668 for (QQuickPixmapReply *reply : std::as_const(networkJobs))
669 deleteReply(reply);
670#endif
671 for (QQuickPixmapReply *reply : std::as_const(asyncResponses))
672 deleteReply(reply);
673
674#if QT_CONFIG(qml_network)
675 networkJobs.clear();
676#endif
677 asyncResponses.clear();
678}
679
680#if QT_CONFIG(qml_network)
681void QQuickPixmapReader::networkRequestDone(QNetworkReply *reply)
682{
683 Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
684
685 QQuickPixmapReply *job = networkJobs.take(reply);
686
687 if (job) {
688 QImage image;
689 QQuickPixmapReply::ReadError error = QQuickPixmapReply::NoError;
690 QString errorString;
691 QSize readSize;
692 QQuickTextureFactory *factory = nullptr;
693 if (reply->error()) {
694 error = QQuickPixmapReply::Loading;
695 errorString = reply->errorString();
696 } else {
697 QByteArray all = reply->readAll();
698 QBuffer buff(&all);
699 buff.open(QIODevice::ReadOnly);
700 QSGTextureReader texReader(&buff, reply->url().fileName());
701 if (backendSupport()->hasOpenGL && texReader.isTexture()) {
702 factory = texReader.read();
703 if (factory) {
704 readSize = factory->textureSize();
705 } else {
706 error = QQuickPixmapReply::Decoding;
707 errorString = QQuickPixmap::tr("Error decoding: %1").arg(reply->url().toString());
708 }
709 } else {
710 int frameCount;
711 int const frame = job->data ? job->data->frame : 0;
712 const qreal dpr = job->data ? job->data->devicePixelRatio : 1;
713 if (!readImage(reply->url(), &buff, &image, &errorString, &readSize, &frameCount,
714 job->requestRegion, job->requestSize, job->providerOptions, nullptr, frame, dpr))
715 error = QQuickPixmapReply::Decoding;
716 else if (job->data)
717 job->data->frameCount = frameCount;
718 }
719 }
720 // send completion event to the QQuickPixmapReply
721 if (!factory)
722 factory = QQuickTextureFactory::textureFactoryForImage(image);
723
724 PIXMAP_READER_LOCK();
725 if (!cancelledJobs.contains(job))
726 job->postReply(error, errorString, readSize, factory);
727 }
728 reply->deleteLater();
729
730 // kick off event loop again in case we have dropped below max request count
731 readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
732}
733#endif // qml_network
734
735void QQuickPixmapReader::asyncResponseFinished(QQuickImageResponse *response)
736{
737 Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
738
739 QQuickPixmapReply *job = asyncResponses.take(response);
740
741 if (job) {
742 QQuickTextureFactory *t = nullptr;
743 QQuickPixmapReply::ReadError error = QQuickPixmapReply::NoError;
744 QString errorString;
745 if (!response->errorString().isEmpty()) {
746 error = QQuickPixmapReply::Loading;
747 errorString = response->errorString();
748 } else {
749 t = response->textureFactory();
750 }
751
753 if (!cancelledJobs.contains(job))
754 job->postReply(error, errorString, t ? t->textureSize() : QSize(), t);
755 else
756 delete t;
757 }
758 response->deleteLater();
759
760 // kick off event loop again in case we have dropped below max request count
761 readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
762}
763
765
767{
768 QCoreApplication::postEvent(
769 this, new QEvent(QEvent::Type(ReaderThreadExecutionEnforcer::ProcessJobs)));
770}
771
772bool ReaderThreadExecutionEnforcer::event(QEvent *e)
773{
774 switch (e->type()) {
775 case QEvent::Type(ReaderThreadExecutionEnforcer::ProcessJobs):
776 reader->processJobs();
777 return true;
778 default:
779 return QObject::event(e);
780 }
781}
782
783void ReaderThreadExecutionEnforcer::networkRequestDone()
784{
785#if QT_CONFIG(qml_network)
786 QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
787 reader->networkRequestDone(reply);
788#endif
789}
790
791void ReaderThreadExecutionEnforcer::asyncResponseFinished(QQuickImageResponse *response)
792{
793 reader->asyncResponseFinished(response);
794}
795
797{
798 QQuickImageResponse *response = static_cast<QQuickImageResponse *>(sender());
800}
801
802void QQuickPixmapReader::processJobs()
803{
804 Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
805
807 while (true) {
808 if (cancelledJobs.isEmpty() && jobs.isEmpty())
809 return; // Nothing else to do
810
811 // Clean cancelled jobs
812 if (!cancelledJobs.isEmpty()) {
813 for (int i = 0; i < cancelledJobs.size(); ++i) {
814 QQuickPixmapReply *job = cancelledJobs.at(i);
815#if QT_CONFIG(qml_network)
816 QNetworkReply *reply = networkJobs.key(job, 0);
817 if (reply) {
818 networkJobs.remove(reply);
819 if (reply->isRunning()) {
820 // cancel any jobs already started
821 reply->close();
822 }
823 } else
824#endif
825 {
826 QQuickImageResponse *asyncResponse = asyncResponses.key(job);
827 if (asyncResponse) {
828 asyncResponses.remove(asyncResponse);
829 asyncResponse->cancel();
830 }
831 }
832 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(job->url));
833 // deleteLater, since not owned by this thread
834 job->deleteLater();
835 }
836 cancelledJobs.clear();
837 }
838
839 if (!jobs.isEmpty()) {
840 // Find a job we can use
841 bool usableJob = false;
842 for (int i = jobs.size() - 1; !usableJob && i >= 0; i--) {
843 QQuickPixmapReply *job = jobs.at(i);
844 const QUrl url = job->url;
845 QString localFile;
846 QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
847 QSharedPointer<QQuickImageProvider> provider;
848
849 if (url.scheme() == QLatin1String("image")) {
850 QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(engine);
851 provider = enginePrivate->imageProvider(imageProviderId(url)).staticCast<QQuickImageProvider>();
852 if (provider)
853 imageType = provider->imageType();
854
855 usableJob = true;
856 } else {
857 localFile = QQmlFile::urlToLocalFileOrQrc(url);
858 // A content URI can be local or remote, there is no reliable way to tell.
859 // Assume local here on the reader thread where it will not block GUI-thread.
860 if (localFile.isEmpty() && url.scheme() == QLatin1String("content"))
861 localFile = url.toString();
862 usableJob = !localFile.isEmpty()
863#if QT_CONFIG(qml_network)
864 || networkJobs.size() < IMAGEREQUEST_MAX_NETWORK_REQUEST_COUNT
865#endif
866 ;
867 }
868
869 if (usableJob) {
870 jobs.removeAt(i);
871
872 job->loading = true;
873
874 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingStarted>(url));
875
876#if QT_CONFIG(quick_pixmap_cache_threaded_download)
877 locker.unlock();
878 auto relockMutexGuard = qScopeGuard(([&locker]() {
879 locker.relock();
880 }));
881#endif
882 processJob(job, url, localFile, imageType, provider);
883 }
884 }
885
886 if (!usableJob)
887 return;
888 }
889 }
890}
891
892void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &url, const QString &localFile,
893 QQuickImageProvider::ImageType imageType, const QSharedPointer<QQuickImageProvider> &provider)
894{
895 Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
896
897 // fetch
898 if (url.scheme() == QLatin1String("image")) {
899 // Use QQuickImageProvider
900 QSize readSize;
901
902 if (imageType == QQuickImageProvider::Invalid) {
903 QString errorStr = QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString());
905 if (!cancelledJobs.contains(runningJob))
906 runningJob->postReply(QQuickPixmapReply::Loading, errorStr, readSize, nullptr);
907 return;
908 }
909
910 // This is safe because we ensure that provider does outlive providerV2 and it does not escape the function
911 QQuickImageProviderWithOptions *providerV2 = QQuickImageProviderWithOptions::checkedCast(provider.get());
912
913 switch (imageType) {
914 case QQuickImageProvider::Invalid:
915 {
916 // Already handled
917 break;
918 }
919
920 case QQuickImageProvider::Image:
921 {
922 QImage image;
923 if (providerV2) {
924 image = providerV2->requestImage(imageId(url), &readSize, runningJob->requestSize, runningJob->providerOptions);
925 } else {
926 image = provider->requestImage(imageId(url), &readSize, runningJob->requestSize);
927 }
928 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
929 QString errorStr;
930 if (image.isNull()) {
931 errorCode = QQuickPixmapReply::Loading;
932 errorStr = QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString());
933 }
935 if (!cancelledJobs.contains(runningJob)) {
936 runningJob->postReply(errorCode, errorStr, readSize,
937 QQuickTextureFactory::textureFactoryForImage(image));
938 }
939 break;
940 }
941
942 case QQuickImageProvider::Pixmap:
943 {
944 QPixmap pixmap;
945 if (providerV2) {
946 pixmap = providerV2->requestPixmap(imageId(url), &readSize, runningJob->requestSize, runningJob->providerOptions);
947 } else {
948 pixmap = provider->requestPixmap(imageId(url), &readSize, runningJob->requestSize);
949 }
950 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
951 QString errorStr;
952 if (pixmap.isNull()) {
953 errorCode = QQuickPixmapReply::Loading;
954 errorStr = QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString());
955 }
956
958 if (!cancelledJobs.contains(runningJob)) {
959 runningJob->postReply(
960 errorCode, errorStr, readSize,
961 QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()));
962 }
963 break;
964 }
965
966 case QQuickImageProvider::Texture:
967 {
968 QQuickTextureFactory *t;
969 if (providerV2) {
970 t = providerV2->requestTexture(imageId(url), &readSize, runningJob->requestSize, runningJob->providerOptions);
971 } else {
972 t = provider->requestTexture(imageId(url), &readSize, runningJob->requestSize);
973 }
974 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
975 QString errorStr;
976 if (!t) {
977 errorCode = QQuickPixmapReply::Loading;
978 errorStr = QQuickPixmap::tr("Failed to get texture from provider: %1").arg(url.toString());
979 }
981 if (!cancelledJobs.contains(runningJob))
982 runningJob->postReply(errorCode, errorStr, readSize, t);
983 else
984 delete t;
985 break;
986 }
987
988 case QQuickImageProvider::ImageResponse:
989 {
990 QQuickImageResponse *response;
991 if (providerV2) {
992 response = providerV2->requestImageResponse(imageId(url), runningJob->requestSize, runningJob->providerOptions);
993 } else {
994 QQuickAsyncImageProvider *asyncProvider = static_cast<QQuickAsyncImageProvider*>(provider.get());
995 response = asyncProvider->requestImageResponse(imageId(url), runningJob->requestSize);
996 }
997
998 {
999 QObject::connect(response, &QQuickImageResponse::finished, readerThreadExecutionEnforcer(),
1000 qOverload<>(&ReaderThreadExecutionEnforcer::asyncResponseFinished));
1001 // as the response object can outlive the provider QSharedPointer, we have to extend the pointee's lifetime by that of the response
1002 // we do this by capturing a copy of the QSharedPointer in a lambda, and dropping it once the lambda has been called
1003 auto provider_copy = provider; // capturing provider would capture it as a const reference, and copy capture with initializer is only available in C++14
1004 QObject::connect(response, &QQuickImageResponse::destroyed, response, [provider_copy]() {
1005 // provider_copy will be deleted when the connection gets deleted
1006 });
1007 }
1008 // Might be that the async provider was so quick it emitted the signal before we
1009 // could connect to it.
1010 //
1011 // loadAcquire() synchronizes-with storeRelease() in QQuickImageResponsePrivate::_q_finished():
1012 if (static_cast<QQuickImageResponsePrivate*>(QObjectPrivate::get(response))->finished.loadAcquire()) {
1013 QMetaObject::invokeMethod(readerThreadExecutionEnforcer(), "asyncResponseFinished",
1014 Qt::QueuedConnection,
1015 Q_ARG(QQuickImageResponse *, response));
1016 }
1017
1018 asyncResponses.insert(response, runningJob);
1019 break;
1020 }
1021 }
1022
1023 } else {
1024 if (!localFile.isEmpty()) {
1025 // Image is local - load/decode immediately
1026 QImage image;
1027 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
1028 QString errorStr;
1029 QSize readSize;
1030
1031 if (runningJob->data && runningJob->data->fromSpecialDevice) {
1032 auto specialDevice = runningJob->data->specialDevice;
1033 if (specialDevice.isNull() || QObjectPrivate::get(specialDevice.data())->deleteLaterCalled) {
1034 qCDebug(lcImg) << "readImage job aborted" << url;
1035 return;
1036 }
1037 int frameCount;
1038 // Ensure that specialDevice's thread affinity is _this_ thread, to avoid deleteLater()
1039 // deleting prematurely, before readImage() is done. But this is only possible if it has already
1040 // relinquished its initial thread affinity.
1041 if (!specialDevice->thread()) {
1042 qCDebug(lcQsgLeak) << specialDevice.data() << ": changing thread affinity so that"
1043 << QThread::currentThread() << "will handle any deleteLater() calls";
1044 specialDevice->moveToThread(QThread::currentThread());
1045 }
1046 if (!readImage(url, specialDevice.data(), &image, &errorStr, &readSize, &frameCount,
1047 runningJob->requestRegion, runningJob->requestSize,
1048 runningJob->providerOptions, nullptr, runningJob->data->frame,
1049 runningJob->data->devicePixelRatio)) {
1050 errorCode = QQuickPixmapReply::Loading;
1051 } else if (runningJob->data) {
1052 runningJob->data->frameCount = frameCount;
1053 }
1054 } else {
1055 QFile f(existingImageFileForPath(localFile));
1056 if (f.open(QIODevice::ReadOnly)) {
1057 QSGTextureReader texReader(&f, localFile);
1058 if (backendSupport()->hasOpenGL && texReader.isTexture()) {
1059 QQuickTextureFactory *factory = texReader.read();
1060 if (factory) {
1061 readSize = factory->textureSize();
1062 } else {
1063 errorStr = QQuickPixmap::tr("Error decoding: %1").arg(url.toString());
1064 if (f.fileName() != localFile)
1065 errorStr += QString::fromLatin1(" (%1)").arg(f.fileName());
1066 errorCode = QQuickPixmapReply::Decoding;
1067 }
1069 if (!cancelledJobs.contains(runningJob))
1070 runningJob->postReply(errorCode, errorStr, readSize, factory);
1071 return;
1072 } else {
1073 int frameCount;
1074 int const frame = runningJob->data ? runningJob->data->frame : 0;
1075 const qreal dpr = runningJob->data ? runningJob->data->devicePixelRatio : 1;
1076 if (!readImage(url, &f, &image, &errorStr, &readSize, &frameCount,
1077 runningJob->requestRegion, runningJob->requestSize,
1078 runningJob->providerOptions, nullptr, frame, dpr)) {
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,
1601 frame, 1, devicePixelRatio);
1602 }
1603 break;
1604 }
1605 case QQuickImageProvider::Pixmap:
1606 {
1607 QPixmap pixmap = providerV2 ? providerV2->requestPixmap(imageId(url), &readSize, requestSize, providerOptions)
1608 : provider->requestPixmap(imageId(url), &readSize, requestSize);
1609 if (!pixmap.isNull()) {
1610 *ok = true;
1611 return new QQuickPixmapData(url, QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()),
1612 readSize, requestRegion, requestSize, providerOptions,
1613 QQuickImageProviderOptions::UsePluginDefaultTransform,
1614 frame, 1, devicePixelRatio);
1615 }
1616 break;
1617 }
1618 case QQuickImageProvider::ImageResponse:
1619 {
1620 // Fall through, ImageResponse providers never get here
1621 Q_ASSERT(imageType != QQuickImageProvider::ImageResponse && "Sync call to ImageResponse provider");
1622 }
1623 }
1624
1625 // provider has bad image type, or provider returned null image
1626 return new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
1627 QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString()));
1628 }
1629
1630 QString localFile = QQmlFile::urlToLocalFileOrQrc(url);
1631 if (localFile.isEmpty())
1632 return nullptr;
1633
1634 QFile f(existingImageFileForPath(localFile));
1635 QSize readSize;
1636 QString errorString;
1637
1638 if (f.open(QIODevice::ReadOnly)) {
1639 QSGTextureReader texReader(&f, localFile);
1640 if (backendSupport()->hasOpenGL && texReader.isTexture()) {
1641 QQuickTextureFactory *factory = texReader.read();
1642 if (factory) {
1643 *ok = true;
1644 return new QQuickPixmapData(url, factory, factory->textureSize(), requestRegion, requestSize,
1645 providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
1646 } else {
1647 errorString = QQuickPixmap::tr("Error decoding: %1").arg(url.toString());
1648 if (f.fileName() != localFile)
1649 errorString += QString::fromLatin1(" (%1)").arg(f.fileName());
1650 }
1651 } else {
1652 QImage image;
1653 QQuickImageProviderOptions::AutoTransform appliedTransform = providerOptions.autoTransform();
1654 int frameCount;
1655 if (readImage(url, &f, &image, &errorString, &readSize, &frameCount, requestRegion, requestSize,
1656 providerOptions, &appliedTransform, frame, devicePixelRatio)) {
1657 *ok = true;
1658 return new QQuickPixmapData(url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestRegion, requestSize,
1659 providerOptions, appliedTransform, frame, frameCount);
1660 } else if (f.fileName() != localFile) {
1661 errorString += QString::fromLatin1(" (%1)").arg(f.fileName());
1662 }
1663 }
1664 } else {
1665 errorString = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
1666 }
1667 return new QQuickPixmapData(url, requestRegion, requestSize, providerOptions, errorString);
1668}
1669
1670
1677
1678QQuickPixmap::QQuickPixmap()
1679: d(nullptr)
1680{
1681}
1682
1683QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url)
1684: d(nullptr)
1685{
1686 load(engine, url);
1687}
1688
1689QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, Options options)
1690: d(nullptr)
1691{
1692 load(engine, url, options);
1693}
1694
1695QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QRect &region, const QSize &size)
1696: d(nullptr)
1697{
1698 load(engine, url, region, size);
1699}
1700
1701QQuickPixmap::QQuickPixmap(const QUrl &url, const QImage &image)
1702{
1703 d = new QQuickPixmapData(url, new QQuickDefaultTextureFactory(image), image.size(), QRect(), QSize(),
1704 QQuickImageProviderOptions(), QQuickImageProviderOptions::UsePluginDefaultTransform,
1705 0, 1, image.devicePixelRatio());
1706 d->addToCache();
1707}
1708
1709QQuickPixmap::~QQuickPixmap()
1710{
1711 if (d) {
1712 d->release();
1713 d = nullptr;
1714 }
1715}
1716
1717bool QQuickPixmap::isNull() const
1718{
1719 return d == nullptr;
1720}
1721
1722bool QQuickPixmap::isReady() const
1723{
1724 return status() == Ready;
1725}
1726
1727bool QQuickPixmap::isError() const
1728{
1729 return status() == Error;
1730}
1731
1732bool QQuickPixmap::isLoading() const
1733{
1734 return status() == Loading;
1735}
1736
1737QString QQuickPixmap::error() const
1738{
1739 if (d)
1740 return d->errorString;
1741 else
1742 return QString();
1743}
1744
1745QQuickPixmap::Status QQuickPixmap::status() const
1746{
1747 if (d)
1748 return d->pixmapStatus;
1749 else
1750 return Null;
1751}
1752
1753const QUrl &QQuickPixmap::url() const
1754{
1755 if (d)
1756 return d->url;
1757 else
1758 return nullPixmap()->url;
1759}
1760
1761const QSize &QQuickPixmap::implicitSize() const
1762{
1763 if (d)
1764 return d->implicitSize;
1765 else
1766 return nullPixmap()->size;
1767}
1768
1769const QSize &QQuickPixmap::requestSize() const
1770{
1771 if (d)
1772 return d->requestSize;
1773 else
1774 return nullPixmap()->size;
1775}
1776
1777const QRect &QQuickPixmap::requestRegion() const
1778{
1779 if (d)
1780 return d->requestRegion;
1781 else
1782 return nullPixmap()->region;
1783}
1784
1785QQuickImageProviderOptions::AutoTransform QQuickPixmap::autoTransform() const
1786{
1787 if (d)
1788 return d->appliedTransform;
1789 else
1790 return QQuickImageProviderOptions::UsePluginDefaultTransform;
1791}
1792
1793int QQuickPixmap::frameCount() const
1794{
1795 if (d)
1796 return d->frameCount;
1797 return 0;
1798}
1799
1800QQuickTextureFactory *QQuickPixmap::textureFactory() const
1801{
1802 if (d)
1803 return d->textureFactory;
1804
1805 return nullptr;
1806}
1807
1808QImage QQuickPixmap::image() const
1809{
1810 if (d && d->textureFactory)
1811 return d->textureFactory->image();
1812 return QImage();
1813}
1814
1815void QQuickPixmap::setImage(const QImage &p)
1816{
1817 clear();
1818
1819 if (!p.isNull()) {
1820 if (d)
1821 d->release();
1822 d = new QQuickPixmapData(QQuickTextureFactory::textureFactoryForImage(p));
1823 }
1824}
1825
1826void QQuickPixmap::setPixmap(const QQuickPixmap &other)
1827{
1828 if (d == other.d)
1829 return;
1830 clear();
1831
1832 if (other.d) {
1833 if (d)
1834 d->release();
1835 d = other.d;
1836 d->addref();
1837 }
1838}
1839
1840int QQuickPixmap::width() const
1841{
1842 if (d && d->textureFactory)
1843 return d->textureFactory->textureSize().width();
1844 else
1845 return 0;
1846}
1847
1848int QQuickPixmap::height() const
1849{
1850 if (d && d->textureFactory)
1851 return d->textureFactory->textureSize().height();
1852 else
1853 return 0;
1854}
1855
1856qreal QQuickPixmap::devicePixelRatio() const
1857{
1858 return d ? d->devicePixelRatio : 1.0;
1859}
1860
1861QRect QQuickPixmap::rect() const
1862{
1863 if (d && d->textureFactory)
1864 return QRect(QPoint(), d->textureFactory->textureSize());
1865 else
1866 return QRect();
1867}
1868
1869void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url)
1870{
1871 load(engine, url, QRect(), QSize(), QQuickPixmap::Cache);
1872}
1873
1874void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, QQuickPixmap::Options options)
1875{
1876 load(engine, url, QRect(), QSize(), options);
1877}
1878
1879void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize)
1880{
1881 load(engine, url, requestRegion, requestSize, QQuickPixmap::Cache);
1882}
1883
1884void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize, QQuickPixmap::Options options)
1885{
1886 load(engine, url, requestRegion, requestSize, options, QQuickImageProviderOptions());
1887}
1888
1889void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize,
1890 QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions, int frame, int frameCount,
1891 qreal devicePixelRatio)
1892{
1893 if (d) {
1894 d->release();
1895 d = nullptr;
1896 }
1897
1898 QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
1899 QQuickPixmapCache *store = QQuickPixmapCache::instance();
1900
1901 QMutexLocker locker(&QQuickPixmapCache::instance()->m_cacheMutex);
1902 QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end();
1903
1904#ifdef Q_OS_WEBOS
1905 QQuickPixmap::Options orgOptions = options;
1906 // In webOS, we suppose that cache is always enabled to share image instances along its source.
1907 // So, original option(orgOptions) for cache only decides whether to store the instances when it's unreferenced.
1908 options |= QQuickPixmap::Cache;
1909#endif
1910
1911 // If Cache is disabled, the pixmap will always be loaded, even if there is an existing
1912 // cached version. Unless it's an itemgrabber url, since the cache is used to pass
1913 // the result between QQuickItemGrabResult and QQuickImage.
1914 if (url.scheme() == itemGrabberScheme) {
1915 QRect dummyRegion;
1916 QSize dummySize;
1917 if (requestSize != dummySize)
1918 qWarning() << "Ignoring sourceSize request for image url that came from grabToImage. Use the targetSize parameter of the grabToImage() function instead.";
1919 const QQuickPixmapKey grabberKey = { &url, &dummyRegion, &dummySize, 0, QQuickImageProviderOptions() };
1920 iter = store->m_cache.find(grabberKey);
1921 } else if (options & QQuickPixmap::Cache)
1922 iter = store->m_cache.find(key);
1923
1924 if (iter == store->m_cache.end()) {
1925 if (!engine)
1926 return;
1927
1928 locker.unlock();
1929
1930 if (url.scheme() == QLatin1String("image")) {
1931 QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(engine);
1932 if (auto provider = enginePrivate->imageProvider(imageProviderId(url)).staticCast<QQuickImageProvider>()) {
1933 const bool threadedPixmaps = QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedPixmaps);
1934 if (!threadedPixmaps && provider->imageType() == QQuickImageProvider::Pixmap) {
1935 // pixmaps can only be loaded synchronously
1936 options &= ~QQuickPixmap::Asynchronous;
1937 } else if (provider->flags() & QQuickImageProvider::ForceAsynchronousImageLoading) {
1938 options |= QQuickPixmap::Asynchronous;
1939 }
1940 }
1941 }
1942
1943 if (!(options & QQuickPixmap::Asynchronous)) {
1944 bool ok = false;
1945 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingStarted>(url));
1946 d = createPixmapDataSync(engine, url, requestRegion, requestSize, providerOptions, frame, &ok, devicePixelRatio);
1947 if (ok) {
1948 PIXMAP_PROFILE(pixmapLoadingFinished(url, QSize(width(), height())));
1949 if (options & QQuickPixmap::Cache)
1950 d->addToCache();
1951#ifdef Q_OS_WEBOS
1952 d->storeToCache = orgOptions & QQuickPixmap::Cache;
1953#endif
1954 return;
1955 }
1956 if (d) { // loadable, but encountered error while loading
1957 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(url));
1958 return;
1959 }
1960 }
1961
1962 d = new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
1963 QQuickImageProviderOptions::UsePluginDefaultTransform, frame,
1964 frameCount, devicePixelRatio);
1965 if (options & QQuickPixmap::Cache)
1966 d->addToCache();
1967#ifdef Q_OS_WEBOS
1968 d->storeToCache = orgOptions & QQuickPixmap::Cache;
1969#endif
1970
1971 QQuickPixmapReader::readerMutex.lock();
1972 QQuickPixmapReader *reader = QQuickPixmapReader::instance(engine);
1973 d->reply = reader->getImage(d);
1974 reader->startJob(d->reply);
1975 QQuickPixmapReader::readerMutex.unlock();
1976 } else {
1977 d = *iter;
1978 d->addref();
1979 qCDebug(lcImg) << "loaded from cache" << url << "frame" << frame;
1980 }
1981}
1982
1983/*! \internal
1984 Attempts to load an image from the given \a url via the given \a device.
1985 This is for special cases when the QImageIOHandler can benefit from reusing
1986 the I/O device, or from something extra that a subclass of QIODevice
1987 carries with it. So far, this code doesn't support loading anything other
1988 than a QImage, for example compressed textures. It can be added if needed.
1989*/
1990void QQuickPixmap::loadImageFromDevice(QQmlEngine *engine, QIODevice *device, const QUrl &url,
1991 const QRect &requestRegion, const QSize &requestSize,
1992 const QQuickImageProviderOptions &providerOptions, int frame, int frameCount)
1993{
1994 auto oldD = d;
1995 QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
1996 QQuickPixmapCache *store = QQuickPixmapCache::instance();
1997 QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end();
1998 QMutexLocker locker(&store->m_cacheMutex);
1999 iter = store->m_cache.find(key);
2000 if (iter == store->m_cache.end()) {
2001 if (!engine)
2002 return;
2003
2004 locker.unlock();
2005 d = new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
2006 QQuickImageProviderOptions::UsePluginDefaultTransform, frame, frameCount);
2007 d->specialDevice = device;
2008 d->fromSpecialDevice = true;
2009 d->addToCache();
2010
2011 QQuickPixmapReader::readerMutex.lock();
2012 QQuickPixmapReader *reader = QQuickPixmapReader::instance(engine);
2013 d->reply = reader->getImage(d);
2014 if (oldD) {
2015 QObject::connect(d->reply, &QQuickPixmapReply::destroyed, store, [oldD]() {
2016 oldD->release();
2017 }, Qt::QueuedConnection);
2018 }
2019 reader->startJob(d->reply);
2020 QQuickPixmapReader::readerMutex.unlock();
2021 } else {
2022 d = *iter;
2023 d->addref();
2024 qCDebug(lcImg) << "loaded from cache" << url << "frame" << frame << "refCount" << d->refCount;
2025 locker.unlock();
2026 if (oldD)
2027 oldD->release();
2028 }
2029}
2030
2031void QQuickPixmap::clear()
2032{
2033 if (d) {
2034 d->release();
2035 d = nullptr;
2036 }
2037}
2038
2039void QQuickPixmap::clear(QObject *obj)
2040{
2041 if (d) {
2042 if (d->reply)
2043 QObject::disconnect(d->reply, nullptr, obj, nullptr);
2044 d->release();
2045 d = nullptr;
2046 }
2047}
2048
2049bool QQuickPixmap::isCached(const QUrl &url, const QRect &requestRegion, const QSize &requestSize,
2050 const int frame, const QQuickImageProviderOptions &options)
2051{
2052 QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, options };
2053 QQuickPixmapCache *store = QQuickPixmapCache::instance();
2054
2055 return store->m_cache.contains(key);
2056}
2057
2058bool QQuickPixmap::isScalableImageFormat(const QUrl &url)
2059{
2060 if (url.scheme() == "image"_L1)
2061 return true;
2062
2063 const QString stringUrl = url.path(QUrl::PrettyDecoded);
2064 return stringUrl.endsWith("svg"_L1)
2065 || stringUrl.endsWith("svgz"_L1)
2066 || stringUrl.endsWith("pdf"_L1);
2067}
2068
2069bool QQuickPixmap::connectFinished(QObject *object, const char *method)
2070{
2071 if (!d || !d->reply) {
2072 qWarning("QQuickPixmap: connectFinished() called when not loading.");
2073 return false;
2074 }
2075
2076 return QObject::connect(d->reply, SIGNAL(finished()), object, method);
2077}
2078
2079bool QQuickPixmap::connectFinished(QObject *object, int method)
2080{
2081 if (!d || !d->reply) {
2082 qWarning("QQuickPixmap: connectFinished() called when not loading.");
2083 return false;
2084 }
2085
2086 return QMetaObject::connect(d->reply, QQuickPixmapReply::finishedMethodIndex, object, method);
2087}
2088
2089bool QQuickPixmap::connectDownloadProgress(QObject *object, const char *method)
2090{
2091 if (!d || !d->reply) {
2092 qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
2093 return false;
2094 }
2095
2096 return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), object,
2097 method);
2098}
2099
2100bool QQuickPixmap::connectDownloadProgress(QObject *object, int method)
2101{
2102 if (!d || !d->reply) {
2103 qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
2104 return false;
2105 }
2106
2107 return QMetaObject::connect(d->reply, QQuickPixmapReply::downloadProgressMethodIndex, object,
2108 method);
2109}
2110
2111QColorSpace QQuickPixmap::colorSpace() const
2112{
2113 if (!d || !d->textureFactory)
2114 return QColorSpace();
2115 return d->textureFactory->image().colorSpace();
2116}
2117
2118QT_END_NAMESPACE
2119
2120#include <qquickpixmapcache.moc>
2121
2122#include "moc_qquickpixmap_p.cpp"
2123#include "moc_qquickpixmapcache_p.cpp"
friend bool operator==(const QByteArray::FromBase64Result &lhs, const QByteArray::FromBase64Result &rhs) noexcept
Returns true if lhs and rhs are equal, otherwise returns false.
Definition qbytearray.h:842
QQuickPixmapData(const QUrl &u, const QRect &r, const QSize &s, const QQuickImageProviderOptions &po, QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1, qreal dpr=1)
QQuickPixmapData(const QUrl &u, const QRect &r, const QSize &rs, const QQuickImageProviderOptions &po, const QString &e)
QQuickImageProviderOptions::AutoTransform appliedTransform
QQuickPixmapData(const QUrl &u, QQuickTextureFactory *texture, const QSize &s, const QRect &r, const QSize &rs, const QQuickImageProviderOptions &po, QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1, qreal dpr=1)
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