1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquickpixmapcache_p.h"
41#include <qquickimageprovider.h>
42#include "qquickimageprovider_p.h"
43
44#include <qqmlengine.h>
45#include <private/qqmlglobal_p.h>
46#include <private/qqmlengine_p.h>
47
48#include <QtGui/private/qguiapplication_p.h>
49#include <QtGui/private/qimage_p.h>
50#include <qpa/qplatformintegration.h>
51
52#include <QtQuick/private/qsgcontext_p.h>
53#include <QtQuick/private/qsgtexturereader_p.h>
54
55#include <QQuickWindow>
56#include <QCoreApplication>
57#include <QImageReader>
58#include <QHash>
59#include <QPixmapCache>
60#include <QFile>
61#include <QThread>
62#include <QMutex>
63#include <QMutexLocker>
64#include <QBuffer>
65#include <QtCore/qdebug.h>
66#include <private/qobject_p.h>
67#include <QQmlFile>
68#include <QMetaMethod>
69
70#if QT_CONFIG(qml_network)
71#include <qqmlnetworkaccessmanagerfactory.h>
72#include <QNetworkReply>
73#include <QSslError>
74#endif
75
76#include <private/qquickprofiler_p.h>
77
78#define IMAGEREQUEST_MAX_NETWORK_REQUEST_COUNT 8
79#define IMAGEREQUEST_MAX_REDIRECT_RECURSION 16
80#define CACHE_EXPIRE_TIME 30
81#define CACHE_REMOVAL_FRACTION 4
82
83#define PIXMAP_PROFILE(Code) Q_QUICK_PROFILE(QQuickProfiler::ProfilePixmapCache, Code)
84
85QT_BEGIN_NAMESPACE
86
87const QLatin1String QQuickPixmap::itemGrabberScheme = QLatin1String("itemgrabber");
88
89Q_LOGGING_CATEGORY(lcImg, "qt.quick.image")
90
91#ifndef QT_NO_DEBUG
92static const bool qsg_leak_check = !qEnvironmentVariableIsEmpty(varName: "QML_LEAK_CHECK");
93#endif
94
95// The cache limit describes the maximum "junk" in the cache.
96static int cache_limit = 2048 * 1024; // 2048 KB cache limit for embedded in qpixmapcache.cpp
97
98static inline QString imageProviderId(const QUrl &url)
99{
100 return url.host();
101}
102
103static inline QString imageId(const QUrl &url)
104{
105 return url.toString(options: QUrl::RemoveScheme | QUrl::RemoveAuthority).mid(position: 1);
106}
107
108QQuickDefaultTextureFactory::QQuickDefaultTextureFactory(const QImage &image)
109{
110 if (image.format() == QImage::Format_ARGB32_Premultiplied
111 || image.format() == QImage::Format_RGB32) {
112 im = image;
113 } else {
114 im = image.convertToFormat(f: QImage::Format_ARGB32_Premultiplied);
115 }
116 size = im.size();
117}
118
119
120QSGTexture *QQuickDefaultTextureFactory::createTexture(QQuickWindow *window) const
121{
122 QSGTexture *t = window->createTextureFromImage(image: im, options: QQuickWindow::TextureCanUseAtlas);
123 static bool transient = qEnvironmentVariableIsSet(varName: "QSG_TRANSIENT_IMAGES");
124 if (transient)
125 const_cast<QQuickDefaultTextureFactory *>(this)->im = QImage();
126 return t;
127}
128
129class QQuickPixmapReader;
130class QQuickPixmapData;
131class QQuickPixmapReply : public QObject
132{
133 Q_OBJECT
134public:
135 enum ReadError { NoError, Loading, Decoding };
136
137 QQuickPixmapReply(QQuickPixmapData *);
138 ~QQuickPixmapReply();
139
140 QQuickPixmapData *data;
141 QQmlEngine *engineForReader; // always access reader inside readerMutex
142 QRect requestRegion;
143 QSize requestSize;
144 QUrl url;
145
146 bool loading;
147 QQuickImageProviderOptions providerOptions;
148 int redirectCount;
149
150 class Event : public QEvent {
151 public:
152 Event(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory);
153 ~Event();
154
155 ReadError error;
156 QString errorString;
157 QSize implicitSize;
158 QQuickTextureFactory *textureFactory;
159 };
160 void postReply(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory);
161
162
163Q_SIGNALS:
164 void finished();
165 void downloadProgress(qint64, qint64);
166
167protected:
168 bool event(QEvent *event) override;
169
170private:
171 Q_DISABLE_COPY(QQuickPixmapReply)
172
173public:
174 static int finishedIndex;
175 static int downloadProgressIndex;
176};
177
178class QQuickPixmapReaderThreadObject : public QObject {
179 Q_OBJECT
180public:
181 QQuickPixmapReaderThreadObject(QQuickPixmapReader *);
182 void processJobs();
183 bool event(QEvent *e) override;
184public slots:
185 void asyncResponseFinished(QQuickImageResponse *response);
186private slots:
187 void networkRequestDone();
188 void asyncResponseFinished();
189private:
190 QQuickPixmapReader *reader;
191};
192
193class QQuickPixmapData;
194class QQuickPixmapReader : public QThread
195{
196 Q_OBJECT
197public:
198 QQuickPixmapReader(QQmlEngine *eng);
199 ~QQuickPixmapReader();
200
201 QQuickPixmapReply *getImage(QQuickPixmapData *);
202 void cancel(QQuickPixmapReply *rep);
203
204 static QQuickPixmapReader *instance(QQmlEngine *engine);
205 static QQuickPixmapReader *existingInstance(QQmlEngine *engine);
206
207protected:
208 void run() override;
209
210private:
211 friend class QQuickPixmapReaderThreadObject;
212 void processJobs();
213 void processJob(QQuickPixmapReply *, const QUrl &, const QString &, QQuickImageProvider::ImageType, const QSharedPointer<QQuickImageProvider> &);
214#if QT_CONFIG(qml_network)
215 void networkRequestDone(QNetworkReply *);
216#endif
217 void asyncResponseFinished(QQuickImageResponse *);
218
219 QList<QQuickPixmapReply*> jobs;
220 QList<QQuickPixmapReply*> cancelled;
221 QQmlEngine *engine;
222 QObject *eventLoopQuitHack;
223
224 QMutex mutex;
225 QQuickPixmapReaderThreadObject *threadObject;
226
227#if QT_CONFIG(qml_network)
228 QNetworkAccessManager *networkAccessManager();
229 QNetworkAccessManager *accessManager;
230 QHash<QNetworkReply*,QQuickPixmapReply*> networkJobs;
231#endif
232 QHash<QQuickImageResponse*,QQuickPixmapReply*> asyncResponses;
233
234 static int replyDownloadProgress;
235 static int replyFinished;
236 static int downloadProgress;
237 static int threadNetworkRequestDone;
238 static QHash<QQmlEngine *,QQuickPixmapReader*> readers;
239public:
240 static QMutex readerMutex;
241};
242
243class QQuickPixmapData
244{
245public:
246 QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QRect &r, const QSize &rs,
247 const QQuickImageProviderOptions &po, const QString &e)
248 : refCount(1), frameCount(1), frame(0), inCache(false), pixmapStatus(QQuickPixmap::Error),
249 url(u), errorString(e), requestRegion(r), requestSize(rs),
250 providerOptions(po), appliedTransform(QQuickImageProviderOptions::UsePluginDefaultTransform),
251 textureFactory(nullptr), reply(nullptr), prevUnreferenced(nullptr),
252 prevUnreferencedPtr(nullptr), nextUnreferenced(nullptr)
253#ifdef Q_OS_WEBOS
254 , storeToCache(true)
255#endif
256 {
257 declarativePixmaps.insert(n: pixmap);
258 }
259
260 QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QRect &r, const QSize &s, const QQuickImageProviderOptions &po,
261 QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1)
262 : refCount(1), frameCount(frameCount), frame(frame), inCache(false), pixmapStatus(QQuickPixmap::Loading),
263 url(u), requestRegion(r), requestSize(s),
264 providerOptions(po), appliedTransform(aTransform),
265 textureFactory(nullptr), reply(nullptr), prevUnreferenced(nullptr), prevUnreferencedPtr(nullptr),
266 nextUnreferenced(nullptr)
267#ifdef Q_OS_WEBOS
268 , storeToCache(true)
269#endif
270 {
271 declarativePixmaps.insert(n: pixmap);
272 }
273
274 QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, QQuickTextureFactory *texture,
275 const QSize &s, const QRect &r, const QSize &rs, const QQuickImageProviderOptions &po,
276 QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1)
277 : refCount(1), frameCount(frameCount), frame(frame), inCache(false), pixmapStatus(QQuickPixmap::Ready),
278 url(u), implicitSize(s), requestRegion(r), requestSize(rs),
279 providerOptions(po), appliedTransform(aTransform),
280 textureFactory(texture), reply(nullptr), prevUnreferenced(nullptr),
281 prevUnreferencedPtr(nullptr), nextUnreferenced(nullptr)
282#ifdef Q_OS_WEBOS
283 , storeToCache(true)
284#endif
285 {
286 declarativePixmaps.insert(n: pixmap);
287 }
288
289 QQuickPixmapData(QQuickPixmap *pixmap, QQuickTextureFactory *texture)
290 : refCount(1), frameCount(1), frame(0), inCache(false), pixmapStatus(QQuickPixmap::Ready),
291 appliedTransform(QQuickImageProviderOptions::UsePluginDefaultTransform),
292 textureFactory(texture), reply(nullptr), prevUnreferenced(nullptr),
293 prevUnreferencedPtr(nullptr), nextUnreferenced(nullptr)
294#ifdef Q_OS_WEBOS
295 , storeToCache(true)
296#endif
297 {
298 if (texture)
299 requestSize = implicitSize = texture->textureSize();
300 declarativePixmaps.insert(n: pixmap);
301 }
302
303 ~QQuickPixmapData()
304 {
305 while (!declarativePixmaps.isEmpty()) {
306 QQuickPixmap *referencer = declarativePixmaps.first();
307 declarativePixmaps.remove(n: referencer);
308 referencer->d = nullptr;
309 }
310 delete textureFactory;
311 }
312
313 int cost() const;
314 void addref();
315 void release();
316 void addToCache();
317 void removeFromCache();
318
319 uint refCount;
320 int frameCount;
321 int frame;
322
323 bool inCache:1;
324
325 QQuickPixmap::Status pixmapStatus;
326 QUrl url;
327 QString errorString;
328 QSize implicitSize;
329 QRect requestRegion;
330 QSize requestSize;
331 QQuickImageProviderOptions providerOptions;
332 QQuickImageProviderOptions::AutoTransform appliedTransform;
333 QColorSpace targetColorSpace;
334
335 QQuickTextureFactory *textureFactory;
336
337 QIntrusiveList<QQuickPixmap, &QQuickPixmap::dataListNode> declarativePixmaps;
338 QQuickPixmapReply *reply;
339
340 QQuickPixmapData *prevUnreferenced;
341 QQuickPixmapData**prevUnreferencedPtr;
342 QQuickPixmapData *nextUnreferenced;
343
344#ifdef Q_OS_WEBOS
345 bool storeToCache;
346#endif
347};
348
349int QQuickPixmapReply::finishedIndex = -1;
350int QQuickPixmapReply::downloadProgressIndex = -1;
351
352// XXX
353QHash<QQmlEngine *,QQuickPixmapReader*> QQuickPixmapReader::readers;
354QMutex QQuickPixmapReader::readerMutex;
355
356int QQuickPixmapReader::replyDownloadProgress = -1;
357int QQuickPixmapReader::replyFinished = -1;
358int QQuickPixmapReader::downloadProgress = -1;
359int QQuickPixmapReader::threadNetworkRequestDone = -1;
360
361
362void QQuickPixmapReply::postReply(ReadError error, const QString &errorString,
363 const QSize &implicitSize, QQuickTextureFactory *factory)
364{
365 loading = false;
366 QCoreApplication::postEvent(receiver: this, event: new Event(error, errorString, implicitSize, factory));
367}
368
369QQuickPixmapReply::Event::Event(ReadError e, const QString &s, const QSize &iSize, QQuickTextureFactory *factory)
370 : QEvent(QEvent::User), error(e), errorString(s), implicitSize(iSize), textureFactory(factory)
371{
372}
373
374QQuickPixmapReply::Event::~Event()
375{
376 delete textureFactory;
377}
378
379#if QT_CONFIG(qml_network)
380QNetworkAccessManager *QQuickPixmapReader::networkAccessManager()
381{
382 if (!accessManager) {
383 Q_ASSERT(threadObject);
384 accessManager = QQmlEnginePrivate::get(e: engine)->createNetworkAccessManager(parent: threadObject);
385 }
386 return accessManager;
387}
388#endif
389
390static void maybeRemoveAlpha(QImage *image)
391{
392 // If the image
393 if (image->hasAlphaChannel() && image->data_ptr()
394 && !image->data_ptr()->checkForAlphaPixels()) {
395 switch (image->format()) {
396 case QImage::Format_RGBA8888:
397 case QImage::Format_RGBA8888_Premultiplied:
398 if (image->data_ptr()->convertInPlace(newFormat: QImage::Format_RGBX8888, Qt::AutoColor))
399 break;
400
401 *image = image->convertToFormat(f: QImage::Format_RGBX8888);
402 break;
403 case QImage::Format_A2BGR30_Premultiplied:
404 if (image->data_ptr()->convertInPlace(newFormat: QImage::Format_BGR30, Qt::AutoColor))
405 break;
406
407 *image = image->convertToFormat(f: QImage::Format_BGR30);
408 break;
409 case QImage::Format_A2RGB30_Premultiplied:
410 if (image->data_ptr()->convertInPlace(newFormat: QImage::Format_RGB30, Qt::AutoColor))
411 break;
412
413 *image = image->convertToFormat(f: QImage::Format_RGB30);
414 break;
415 default:
416 if (image->data_ptr()->convertInPlace(newFormat: QImage::Format_RGB32, Qt::AutoColor))
417 break;
418
419 *image = image->convertToFormat(f: QImage::Format_RGB32);
420 break;
421 }
422 }
423}
424
425static bool readImage(const QUrl& url, QIODevice *dev, QImage *image, QString *errorString, QSize *impsize, int *frameCount,
426 const QRect &requestRegion, const QSize &requestSize, const QQuickImageProviderOptions &providerOptions,
427 QQuickImageProviderOptions::AutoTransform *appliedTransform = nullptr, int frame = 0)
428{
429 QImageReader imgio(dev);
430 if (providerOptions.autoTransform() != QQuickImageProviderOptions::UsePluginDefaultTransform)
431 imgio.setAutoTransform(providerOptions.autoTransform() == QQuickImageProviderOptions::ApplyTransform);
432 else if (appliedTransform)
433 *appliedTransform = imgio.autoTransform() ? QQuickImageProviderOptions::ApplyTransform : QQuickImageProviderOptions::DoNotApplyTransform;
434
435 if (frame < imgio.imageCount())
436 imgio.jumpToImage(imageNumber: frame);
437
438 if (frameCount)
439 *frameCount = imgio.imageCount();
440
441 QSize scSize = QQuickImageProviderWithOptions::loadSize(originalSize: imgio.size(), requestedSize: requestSize, format: imgio.format(), options: providerOptions);
442 if (scSize.isValid())
443 imgio.setScaledSize(scSize);
444 if (!requestRegion.isNull())
445 imgio.setScaledClipRect(requestRegion);
446 const QSize originalSize = imgio.size();
447 qCDebug(lcImg) << url << "frame" << frame << "of" << imgio.imageCount()
448 << "requestRegion" << requestRegion << "QImageReader size" << originalSize << "-> scSize" << scSize;
449
450 if (impsize)
451 *impsize = originalSize;
452
453 if (imgio.read(image)) {
454 maybeRemoveAlpha(image);
455 if (impsize && impsize->width() < 0)
456 *impsize = image->size();
457 if (providerOptions.targetColorSpace().isValid()) {
458 if (image->colorSpace().isValid())
459 image->convertToColorSpace(providerOptions.targetColorSpace());
460 else
461 image->setColorSpace(providerOptions.targetColorSpace());
462 }
463 return true;
464 } else {
465 if (errorString)
466 *errorString = QQuickPixmap::tr(sourceText: "Error decoding: %1: %2").arg(a: url.toString())
467 .arg(a: imgio.errorString());
468 return false;
469 }
470}
471
472static QStringList fromLatin1List(const QList<QByteArray> &list)
473{
474 QStringList res;
475 res.reserve(alloc: list.size());
476 for (const QByteArray &item : list)
477 res.append(t: QString::fromLatin1(str: item));
478 return res;
479}
480
481class BackendSupport
482{
483public:
484 BackendSupport()
485 {
486 delete QSGContext::createTextureFactoryFromImage(image: QImage()); // Force init of backend data
487 hasOpenGL = QQuickWindow::sceneGraphBackend().isEmpty(); // i.e. default
488 QList<QByteArray> list;
489 if (hasOpenGL)
490 list.append(t: QSGTextureReader::supportedFileFormats());
491 list.append(t: QImageReader::supportedImageFormats());
492 fileSuffixes = fromLatin1List(list);
493 }
494 bool hasOpenGL;
495 QStringList fileSuffixes;
496};
497Q_GLOBAL_STATIC(BackendSupport, backendSupport);
498
499static QString existingImageFileForPath(const QString &localFile)
500{
501 // Do nothing if given filepath exists or already has a suffix
502 QFileInfo fi(localFile);
503 if (!fi.suffix().isEmpty() || fi.exists())
504 return localFile;
505
506 QString tryFile = localFile + QStringLiteral(".xxxx");
507 const int suffixIdx = localFile.length() + 1;
508 for (const QString &suffix : backendSupport()->fileSuffixes) {
509 tryFile.replace(i: suffixIdx, len: 10, after: suffix);
510 if (QFileInfo::exists(file: tryFile))
511 return tryFile;
512 }
513 return localFile;
514}
515
516QQuickPixmapReader::QQuickPixmapReader(QQmlEngine *eng)
517: QThread(eng), engine(eng), threadObject(nullptr)
518#if QT_CONFIG(qml_network)
519, accessManager(nullptr)
520#endif
521{
522 eventLoopQuitHack = new QObject;
523 eventLoopQuitHack->moveToThread(thread: this);
524 connect(asender: eventLoopQuitHack, SIGNAL(destroyed(QObject*)), SLOT(quit()), atype: Qt::DirectConnection);
525 start(QThread::LowestPriority);
526#if !QT_CONFIG(thread)
527 // call nonblocking run ourself, as nothread qthread does not
528 run();
529#endif
530}
531
532QQuickPixmapReader::~QQuickPixmapReader()
533{
534 readerMutex.lock();
535 readers.remove(akey: engine);
536 readerMutex.unlock();
537
538 mutex.lock();
539 // manually cancel all outstanding jobs.
540 for (QQuickPixmapReply *reply : qAsConst(t&: jobs)) {
541 if (reply->data && reply->data->reply == reply)
542 reply->data->reply = nullptr;
543 delete reply;
544 }
545 jobs.clear();
546#if QT_CONFIG(qml_network)
547
548 const auto cancelJob = [this](QQuickPixmapReply *reply) {
549 if (reply->loading) {
550 cancelled.append(t: reply);
551 reply->data = nullptr;
552 }
553 };
554
555 for (auto *reply : qAsConst(t&: networkJobs))
556 cancelJob(reply);
557
558 for (auto *reply : qAsConst(t&: asyncResponses))
559 cancelJob(reply);
560#endif
561 if (threadObject) threadObject->processJobs();
562 mutex.unlock();
563
564 eventLoopQuitHack->deleteLater();
565 wait();
566}
567
568#if QT_CONFIG(qml_network)
569void QQuickPixmapReader::networkRequestDone(QNetworkReply *reply)
570{
571 QQuickPixmapReply *job = networkJobs.take(akey: reply);
572
573 if (job) {
574 job->redirectCount++;
575 if (job->redirectCount < IMAGEREQUEST_MAX_REDIRECT_RECURSION) {
576 QVariant redirect = reply->attribute(code: QNetworkRequest::RedirectionTargetAttribute);
577 if (redirect.isValid()) {
578 QUrl url = reply->url().resolved(relative: redirect.toUrl());
579 QNetworkRequest req(url);
580 req.setAttribute(code: QNetworkRequest::HttpPipeliningAllowedAttribute, value: true);
581
582 reply->deleteLater();
583 reply = networkAccessManager()->get(request: req);
584
585 QMetaObject::connect(sender: reply, signal_index: replyDownloadProgress, receiver: job, method_index: downloadProgress);
586 QMetaObject::connect(sender: reply, signal_index: replyFinished, receiver: threadObject, method_index: threadNetworkRequestDone);
587
588 networkJobs.insert(akey: reply, avalue: job);
589 return;
590 }
591 }
592
593 QImage image;
594 QQuickPixmapReply::ReadError error = QQuickPixmapReply::NoError;
595 QString errorString;
596 QSize readSize;
597 if (reply->error()) {
598 error = QQuickPixmapReply::Loading;
599 errorString = reply->errorString();
600 } else {
601 QByteArray all = reply->readAll();
602 QBuffer buff(&all);
603 buff.open(openMode: QIODevice::ReadOnly);
604 int frameCount;
605 int const frame = job->data ? job->data->frame : 0;
606 if (!readImage(url: reply->url(), dev: &buff, image: &image, errorString: &errorString, impsize: &readSize, frameCount: &frameCount,
607 requestRegion: job->requestRegion, requestSize: job->requestSize, providerOptions: job->providerOptions, appliedTransform: nullptr, frame))
608 error = QQuickPixmapReply::Decoding;
609 else if (job->data)
610 job->data->frameCount = frameCount;
611 }
612 // send completion event to the QQuickPixmapReply
613 mutex.lock();
614 if (!cancelled.contains(t: job))
615 job->postReply(error, errorString, implicitSize: readSize, factory: QQuickTextureFactory::textureFactoryForImage(image));
616 mutex.unlock();
617 }
618 reply->deleteLater();
619
620 // kick off event loop again incase we have dropped below max request count
621 threadObject->processJobs();
622}
623#endif // qml_network
624
625void QQuickPixmapReader::asyncResponseFinished(QQuickImageResponse *response)
626{
627 QQuickPixmapReply *job = asyncResponses.take(akey: response);
628
629 if (job) {
630 QQuickTextureFactory *t = nullptr;
631 QQuickPixmapReply::ReadError error = QQuickPixmapReply::NoError;
632 QString errorString;
633 if (!response->errorString().isEmpty()) {
634 error = QQuickPixmapReply::Loading;
635 errorString = response->errorString();
636 } else {
637 t = response->textureFactory();
638 }
639 mutex.lock();
640 if (!cancelled.contains(t: job))
641 job->postReply(error, errorString, implicitSize: t ? t->textureSize() : QSize(), factory: t);
642 else
643 delete t;
644 mutex.unlock();
645 }
646 response->deleteLater();
647
648 // kick off event loop again incase we have dropped below max request count
649 threadObject->processJobs();
650}
651
652QQuickPixmapReaderThreadObject::QQuickPixmapReaderThreadObject(QQuickPixmapReader *i)
653: reader(i)
654{
655}
656
657void QQuickPixmapReaderThreadObject::processJobs()
658{
659 QCoreApplication::postEvent(receiver: this, event: new QEvent(QEvent::User));
660}
661
662bool QQuickPixmapReaderThreadObject::event(QEvent *e)
663{
664 if (e->type() == QEvent::User) {
665 reader->processJobs();
666 return true;
667 } else {
668 return QObject::event(event: e);
669 }
670}
671
672void QQuickPixmapReaderThreadObject::networkRequestDone()
673{
674#if QT_CONFIG(qml_network)
675 QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
676 reader->networkRequestDone(reply);
677#endif
678}
679
680void QQuickPixmapReaderThreadObject::asyncResponseFinished(QQuickImageResponse *response)
681{
682 reader->asyncResponseFinished(response);
683}
684
685void QQuickPixmapReaderThreadObject::asyncResponseFinished()
686{
687 QQuickImageResponse *response = static_cast<QQuickImageResponse *>(sender());
688 asyncResponseFinished(response);
689}
690
691void QQuickPixmapReader::processJobs()
692{
693 QMutexLocker locker(&mutex);
694
695 while (true) {
696 if (cancelled.isEmpty() && jobs.isEmpty())
697 return; // Nothing else to do
698
699 // Clean cancelled jobs
700 if (!cancelled.isEmpty()) {
701#if QT_CONFIG(qml_network)
702 for (int i = 0; i < cancelled.count(); ++i) {
703 QQuickPixmapReply *job = cancelled.at(i);
704 QNetworkReply *reply = networkJobs.key(avalue: job, defaultValue: 0);
705 if (reply) {
706 networkJobs.remove(akey: reply);
707 if (reply->isRunning()) {
708 // cancel any jobs already started
709 reply->close();
710 }
711 } else {
712 QQuickImageResponse *asyncResponse = asyncResponses.key(avalue: job);
713 if (asyncResponse) {
714 asyncResponses.remove(akey: asyncResponse);
715 asyncResponse->cancel();
716 }
717 }
718 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(job->url));
719 // deleteLater, since not owned by this thread
720 job->deleteLater();
721 }
722 cancelled.clear();
723#endif
724 }
725
726 if (!jobs.isEmpty()) {
727 // Find a job we can use
728 bool usableJob = false;
729 for (int i = jobs.count() - 1; !usableJob && i >= 0; i--) {
730 QQuickPixmapReply *job = jobs.at(i);
731 const QUrl url = job->url;
732 QString localFile;
733 QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
734 QSharedPointer<QQuickImageProvider> provider;
735
736 if (url.scheme() == QLatin1String("image")) {
737 QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(e: engine);
738 provider = enginePrivate->imageProvider(providerId: imageProviderId(url)).staticCast<QQuickImageProvider>();
739 if (provider)
740 imageType = provider->imageType();
741
742 usableJob = true;
743 } else {
744 localFile = QQmlFile::urlToLocalFileOrQrc(url);
745 usableJob = !localFile.isEmpty()
746#if QT_CONFIG(qml_network)
747 || networkJobs.count() < IMAGEREQUEST_MAX_NETWORK_REQUEST_COUNT
748#endif
749 ;
750 }
751
752
753 if (usableJob) {
754 jobs.removeAt(i);
755
756 job->loading = true;
757
758 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingStarted>(url));
759
760 locker.unlock();
761 processJob(job, url, localFile, imageType, provider);
762 locker.relock();
763 }
764 }
765
766 if (!usableJob)
767 return;
768 }
769 }
770}
771
772void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &url, const QString &localFile,
773 QQuickImageProvider::ImageType imageType, const QSharedPointer<QQuickImageProvider> &provider)
774{
775 // fetch
776 if (url.scheme() == QLatin1String("image")) {
777 // Use QQuickImageProvider
778 QSize readSize;
779
780 if (imageType == QQuickImageProvider::Invalid) {
781 QString errorStr = QQuickPixmap::tr(sourceText: "Invalid image provider: %1").arg(a: url.toString());
782 mutex.lock();
783 if (!cancelled.contains(t: runningJob))
784 runningJob->postReply(error: QQuickPixmapReply::Loading, errorString: errorStr, implicitSize: readSize, factory: nullptr);
785 mutex.unlock();
786 return;
787 }
788
789 // This is safe because we ensure that provider does outlive providerV2 and it does not escape the function
790 QQuickImageProviderWithOptions *providerV2 = QQuickImageProviderWithOptions::checkedCast(provider: provider.get());
791
792 switch (imageType) {
793 case QQuickImageProvider::Invalid:
794 {
795 // Already handled
796 break;
797 }
798
799 case QQuickImageProvider::Image:
800 {
801 QImage image;
802 if (providerV2) {
803 image = providerV2->requestImage(id: imageId(url), size: &readSize, requestedSize: runningJob->requestSize, options: runningJob->providerOptions);
804 } else {
805 image = provider->requestImage(id: imageId(url), size: &readSize, requestedSize: runningJob->requestSize);
806 }
807 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
808 QString errorStr;
809 if (image.isNull()) {
810 errorCode = QQuickPixmapReply::Loading;
811 errorStr = QQuickPixmap::tr(sourceText: "Failed to get image from provider: %1").arg(a: url.toString());
812 }
813 mutex.lock();
814 if (!cancelled.contains(t: runningJob))
815 runningJob->postReply(error: errorCode, errorString: errorStr, implicitSize: readSize, factory: QQuickTextureFactory::textureFactoryForImage(image));
816 mutex.unlock();
817 break;
818 }
819
820 case QQuickImageProvider::Pixmap:
821 {
822 QPixmap pixmap;
823 if (providerV2) {
824 pixmap = providerV2->requestPixmap(id: imageId(url), size: &readSize, requestedSize: runningJob->requestSize, options: runningJob->providerOptions);
825 } else {
826 pixmap = provider->requestPixmap(id: imageId(url), size: &readSize, requestedSize: runningJob->requestSize);
827 }
828 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
829 QString errorStr;
830 if (pixmap.isNull()) {
831 errorCode = QQuickPixmapReply::Loading;
832 errorStr = QQuickPixmap::tr(sourceText: "Failed to get image from provider: %1").arg(a: url.toString());
833 }
834 mutex.lock();
835 if (!cancelled.contains(t: runningJob))
836 runningJob->postReply(error: errorCode, errorString: errorStr, implicitSize: readSize, factory: QQuickTextureFactory::textureFactoryForImage(image: pixmap.toImage()));
837 mutex.unlock();
838 break;
839 }
840
841 case QQuickImageProvider::Texture:
842 {
843 QQuickTextureFactory *t;
844 if (providerV2) {
845 t = providerV2->requestTexture(id: imageId(url), size: &readSize, requestedSize: runningJob->requestSize, options: runningJob->providerOptions);
846 } else {
847 t = provider->requestTexture(id: imageId(url), size: &readSize, requestedSize: runningJob->requestSize);
848 }
849 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
850 QString errorStr;
851 if (!t) {
852 errorCode = QQuickPixmapReply::Loading;
853 errorStr = QQuickPixmap::tr(sourceText: "Failed to get texture from provider: %1").arg(a: url.toString());
854 }
855 mutex.lock();
856 if (!cancelled.contains(t: runningJob))
857 runningJob->postReply(error: errorCode, errorString: errorStr, implicitSize: readSize, factory: t);
858 else
859 delete t;
860 mutex.unlock();
861 break;
862 }
863
864 case QQuickImageProvider::ImageResponse:
865 {
866 QQuickImageResponse *response;
867 if (providerV2) {
868 response = providerV2->requestImageResponse(id: imageId(url), requestedSize: runningJob->requestSize, options: runningJob->providerOptions);
869 } else {
870 QQuickAsyncImageProvider *asyncProvider = static_cast<QQuickAsyncImageProvider*>(provider.get());
871 response = asyncProvider->requestImageResponse(id: imageId(url), requestedSize: runningJob->requestSize);
872 }
873
874 {
875 QObject::connect(sender: response, SIGNAL(finished()), receiver: threadObject, SLOT(asyncResponseFinished()));
876 // as the response object can outlive the provider QSharedPointer, we have to extend the pointee's lifetime by that of the response
877 // we do this by capturing a copy of the QSharedPointer in a lambda, and dropping it once the lambda has been called
878 auto provider_copy = provider; // capturing provider would capture it as a const reference, and copy capture with initializer is only available in C++14
879 QObject::connect(sender: response, signal: &QQuickImageResponse::destroyed, context: response, slot: [provider_copy]() {
880 // provider_copy will be deleted when the connection gets deleted
881 });
882 }
883 // Might be that the async provider was so quick it emitted the signal before we
884 // could connect to it.
885 if (static_cast<QQuickImageResponsePrivate*>(QObjectPrivate::get(o: response))->finished.loadAcquire()) {
886 QMetaObject::invokeMethod(obj: threadObject, member: "asyncResponseFinished",
887 type: Qt::QueuedConnection, Q_ARG(QQuickImageResponse*, response));
888 }
889
890 asyncResponses.insert(akey: response, avalue: runningJob);
891 break;
892 }
893 }
894
895 } else {
896 if (!localFile.isEmpty()) {
897 // Image is local - load/decode immediately
898 QImage image;
899 QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
900 QString errorStr;
901 QFile f(existingImageFileForPath(localFile));
902 QSize readSize;
903 if (f.open(flags: QIODevice::ReadOnly)) {
904 QSGTextureReader texReader(&f, localFile);
905 if (backendSupport()->hasOpenGL && texReader.isTexture()) {
906 QQuickTextureFactory *factory = texReader.read();
907 if (factory) {
908 readSize = factory->textureSize();
909 } else {
910 errorStr = QQuickPixmap::tr(sourceText: "Error decoding: %1").arg(a: url.toString());
911 if (f.fileName() != localFile)
912 errorStr += QString::fromLatin1(str: " (%1)").arg(a: f.fileName());
913 errorCode = QQuickPixmapReply::Decoding;
914 }
915 mutex.lock();
916 if (!cancelled.contains(t: runningJob))
917 runningJob->postReply(error: errorCode, errorString: errorStr, implicitSize: readSize, factory);
918 mutex.unlock();
919 return;
920 } else {
921 int frameCount;
922 int const frame = runningJob->data ? runningJob->data->frame : 0;
923 if (!readImage(url, dev: &f, image: &image, errorString: &errorStr, impsize: &readSize, frameCount: &frameCount, requestRegion: runningJob->requestRegion, requestSize: runningJob->requestSize,
924 providerOptions: runningJob->providerOptions, appliedTransform: nullptr, frame)) {
925 errorCode = QQuickPixmapReply::Loading;
926 if (f.fileName() != localFile)
927 errorStr += QString::fromLatin1(str: " (%1)").arg(a: f.fileName());
928 } else if (runningJob->data) {
929 runningJob->data->frameCount = frameCount;
930 }
931 }
932 } else {
933 errorStr = QQuickPixmap::tr(sourceText: "Cannot open: %1").arg(a: url.toString());
934 errorCode = QQuickPixmapReply::Loading;
935 }
936 mutex.lock();
937 if (!cancelled.contains(t: runningJob))
938 runningJob->postReply(error: errorCode, errorString: errorStr, implicitSize: readSize, factory: QQuickTextureFactory::textureFactoryForImage(image));
939 mutex.unlock();
940 } else {
941#if QT_CONFIG(qml_network)
942 // Network resource
943 QNetworkRequest req(url);
944 req.setAttribute(code: QNetworkRequest::HttpPipeliningAllowedAttribute, value: true);
945 QNetworkReply *reply = networkAccessManager()->get(request: req);
946
947 QMetaObject::connect(sender: reply, signal_index: replyDownloadProgress, receiver: runningJob, method_index: downloadProgress);
948 QMetaObject::connect(sender: reply, signal_index: replyFinished, receiver: threadObject, method_index: threadNetworkRequestDone);
949
950 networkJobs.insert(akey: reply, avalue: runningJob);
951#else
952// Silently fail if compiled with no_network
953#endif
954 }
955 }
956}
957
958QQuickPixmapReader *QQuickPixmapReader::instance(QQmlEngine *engine)
959{
960 // XXX NOTE: must be called within readerMutex locking.
961 QQuickPixmapReader *reader = readers.value(akey: engine);
962 if (!reader) {
963 reader = new QQuickPixmapReader(engine);
964 readers.insert(akey: engine, avalue: reader);
965 }
966
967 return reader;
968}
969
970QQuickPixmapReader *QQuickPixmapReader::existingInstance(QQmlEngine *engine)
971{
972 // XXX NOTE: must be called within readerMutex locking.
973 return readers.value(akey: engine, adefaultValue: 0);
974}
975
976QQuickPixmapReply *QQuickPixmapReader::getImage(QQuickPixmapData *data)
977{
978 mutex.lock();
979 QQuickPixmapReply *reply = new QQuickPixmapReply(data);
980 reply->engineForReader = engine;
981 jobs.append(t: reply);
982 // XXX
983 if (threadObject) threadObject->processJobs();
984 mutex.unlock();
985 return reply;
986}
987
988void QQuickPixmapReader::cancel(QQuickPixmapReply *reply)
989{
990 mutex.lock();
991 if (reply->loading) {
992 cancelled.append(t: reply);
993 reply->data = nullptr;
994 // XXX
995 if (threadObject) threadObject->processJobs();
996 } else {
997 // If loading was started (reply removed from jobs) but the reply was never processed
998 // (otherwise it would have deleted itself) we need to profile an error.
999 if (jobs.removeAll(t: reply) == 0) {
1000 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(reply->url));
1001 }
1002 delete reply;
1003 }
1004 mutex.unlock();
1005}
1006
1007void QQuickPixmapReader::run()
1008{
1009 if (replyDownloadProgress == -1) {
1010#if QT_CONFIG(qml_network)
1011 replyDownloadProgress = QMetaMethod::fromSignal(signal: &QNetworkReply::downloadProgress).methodIndex();
1012 replyFinished = QMetaMethod::fromSignal(signal: &QNetworkReply::finished).methodIndex();
1013 const QMetaObject *ir = &QQuickPixmapReaderThreadObject::staticMetaObject;
1014 threadNetworkRequestDone = ir->indexOfSlot(slot: "networkRequestDone()");
1015#endif
1016 downloadProgress = QMetaMethod::fromSignal(signal: &QQuickPixmapReply::downloadProgress).methodIndex();
1017 }
1018
1019 mutex.lock();
1020 threadObject = new QQuickPixmapReaderThreadObject(this);
1021 mutex.unlock();
1022
1023 processJobs();
1024 exec();
1025
1026#if QT_CONFIG(thread)
1027 // nothread exec is empty and returns
1028 delete threadObject;
1029 threadObject = nullptr;
1030#endif
1031}
1032
1033class QQuickPixmapKey
1034{
1035public:
1036 const QUrl *url;
1037 const QRect *region;
1038 const QSize *size;
1039 int frame;
1040 QQuickImageProviderOptions options;
1041};
1042
1043inline bool operator==(const QQuickPixmapKey &lhs, const QQuickPixmapKey &rhs)
1044{
1045 return *lhs.url == *rhs.url &&
1046 *lhs.region == *rhs.region &&
1047 *lhs.size == *rhs.size &&
1048 lhs.frame == rhs.frame &&
1049 lhs.options == rhs.options;
1050}
1051
1052inline uint qHash(const QQuickPixmapKey &key)
1053{
1054 return qHash(url: *key.url) ^ (key.size->width()*7) ^ (key.size->height()*17) ^ (key.frame*23) ^
1055 (key.region->x()*29) ^ (key.region->y()*31) ^ (key.options.autoTransform() * 0x5c5c5c5c);
1056 // key.region.width() and height() are not included, because the hash function should be simple,
1057 // and they are more likely to be held constant for some batches of images
1058 // (e.g. tiles, or repeatedly cropping to the same viewport at different positions).
1059}
1060
1061class QQuickPixmapStore : public QObject
1062{
1063 Q_OBJECT
1064public:
1065 QQuickPixmapStore();
1066 ~QQuickPixmapStore();
1067
1068 void unreferencePixmap(QQuickPixmapData *);
1069 void referencePixmap(QQuickPixmapData *);
1070
1071 void purgeCache();
1072
1073protected:
1074 void timerEvent(QTimerEvent *) override;
1075
1076public:
1077 QHash<QQuickPixmapKey, QQuickPixmapData *> m_cache;
1078
1079private:
1080 void shrinkCache(int remove);
1081
1082 QQuickPixmapData *m_unreferencedPixmaps;
1083 QQuickPixmapData *m_lastUnreferencedPixmap;
1084
1085 int m_unreferencedCost;
1086 int m_timerId;
1087 bool m_destroying;
1088};
1089Q_GLOBAL_STATIC(QQuickPixmapStore, pixmapStore);
1090
1091
1092QQuickPixmapStore::QQuickPixmapStore()
1093 : m_unreferencedPixmaps(nullptr), m_lastUnreferencedPixmap(nullptr), m_unreferencedCost(0), m_timerId(-1), m_destroying(false)
1094{
1095}
1096
1097QQuickPixmapStore::~QQuickPixmapStore()
1098{
1099 m_destroying = true;
1100
1101#ifndef QT_NO_DEBUG
1102 int leakedPixmaps = 0;
1103#endif
1104 // Prevent unreferencePixmap() from assuming it needs to kick
1105 // off the cache expiry timer, as we're shrinking the cache
1106 // manually below after releasing all the pixmaps.
1107 m_timerId = -2;
1108
1109 // unreference all (leaked) pixmaps
1110 const auto cache = m_cache; // NOTE: intentional copy (QTBUG-65077); releasing items from the cache modifies m_cache.
1111 for (auto *pixmap : cache) {
1112 int currRefCount = pixmap->refCount;
1113 if (currRefCount) {
1114#ifndef QT_NO_DEBUG
1115 leakedPixmaps++;
1116#endif
1117 while (currRefCount > 0) {
1118 pixmap->release();
1119 currRefCount--;
1120 }
1121 }
1122 }
1123
1124 // free all unreferenced pixmaps
1125 while (m_lastUnreferencedPixmap) {
1126 shrinkCache(remove: 20);
1127 }
1128
1129#ifndef QT_NO_DEBUG
1130 if (leakedPixmaps && qsg_leak_check)
1131 qDebug(msg: "Number of leaked pixmaps: %i", leakedPixmaps);
1132#endif
1133}
1134
1135void QQuickPixmapStore::unreferencePixmap(QQuickPixmapData *data)
1136{
1137 Q_ASSERT(data->prevUnreferenced == nullptr);
1138 Q_ASSERT(data->prevUnreferencedPtr == nullptr);
1139 Q_ASSERT(data->nextUnreferenced == nullptr);
1140
1141 data->nextUnreferenced = m_unreferencedPixmaps;
1142 data->prevUnreferencedPtr = &m_unreferencedPixmaps;
1143 if (!m_destroying) // the texture factories may have been cleaned up already.
1144 m_unreferencedCost += data->cost();
1145
1146 m_unreferencedPixmaps = data;
1147 if (m_unreferencedPixmaps->nextUnreferenced) {
1148 m_unreferencedPixmaps->nextUnreferenced->prevUnreferenced = m_unreferencedPixmaps;
1149 m_unreferencedPixmaps->nextUnreferenced->prevUnreferencedPtr = &m_unreferencedPixmaps->nextUnreferenced;
1150 }
1151
1152 if (!m_lastUnreferencedPixmap)
1153 m_lastUnreferencedPixmap = data;
1154
1155 shrinkCache(remove: -1); // Shrink the cache in case it has become larger than cache_limit
1156
1157 if (m_timerId == -1 && m_unreferencedPixmaps
1158 && !m_destroying && !QCoreApplication::closingDown()) {
1159 m_timerId = startTimer(CACHE_EXPIRE_TIME * 1000);
1160 }
1161}
1162
1163void QQuickPixmapStore::referencePixmap(QQuickPixmapData *data)
1164{
1165 Q_ASSERT(data->prevUnreferencedPtr);
1166
1167 *data->prevUnreferencedPtr = data->nextUnreferenced;
1168 if (data->nextUnreferenced) {
1169 data->nextUnreferenced->prevUnreferencedPtr = data->prevUnreferencedPtr;
1170 data->nextUnreferenced->prevUnreferenced = data->prevUnreferenced;
1171 }
1172 if (m_lastUnreferencedPixmap == data)
1173 m_lastUnreferencedPixmap = data->prevUnreferenced;
1174
1175 data->nextUnreferenced = nullptr;
1176 data->prevUnreferencedPtr = nullptr;
1177 data->prevUnreferenced = nullptr;
1178
1179 m_unreferencedCost -= data->cost();
1180}
1181
1182void QQuickPixmapStore::shrinkCache(int remove)
1183{
1184 while ((remove > 0 || m_unreferencedCost > cache_limit) && m_lastUnreferencedPixmap) {
1185 QQuickPixmapData *data = m_lastUnreferencedPixmap;
1186 Q_ASSERT(data->nextUnreferenced == nullptr);
1187
1188 *data->prevUnreferencedPtr = nullptr;
1189 m_lastUnreferencedPixmap = data->prevUnreferenced;
1190 data->prevUnreferencedPtr = nullptr;
1191 data->prevUnreferenced = nullptr;
1192
1193 if (!m_destroying) {
1194 remove -= data->cost();
1195 m_unreferencedCost -= data->cost();
1196 }
1197 data->removeFromCache();
1198 delete data;
1199 }
1200}
1201
1202void QQuickPixmapStore::timerEvent(QTimerEvent *)
1203{
1204 int removalCost = m_unreferencedCost / CACHE_REMOVAL_FRACTION;
1205
1206 shrinkCache(remove: removalCost);
1207
1208 if (m_unreferencedPixmaps == nullptr) {
1209 killTimer(id: m_timerId);
1210 m_timerId = -1;
1211 }
1212}
1213
1214void QQuickPixmapStore::purgeCache()
1215{
1216 shrinkCache(remove: m_unreferencedCost);
1217}
1218
1219void QQuickPixmap::purgeCache()
1220{
1221 pixmapStore()->purgeCache();
1222}
1223
1224QQuickPixmapReply::QQuickPixmapReply(QQuickPixmapData *d)
1225 : data(d), engineForReader(nullptr), requestRegion(d->requestRegion), requestSize(d->requestSize),
1226 url(d->url), loading(false), providerOptions(d->providerOptions), redirectCount(0)
1227{
1228 if (finishedIndex == -1) {
1229 finishedIndex = QMetaMethod::fromSignal(signal: &QQuickPixmapReply::finished).methodIndex();
1230 downloadProgressIndex = QMetaMethod::fromSignal(signal: &QQuickPixmapReply::downloadProgress).methodIndex();
1231 }
1232}
1233
1234QQuickPixmapReply::~QQuickPixmapReply()
1235{
1236 // note: this->data->reply must be set to zero if this->data->reply == this
1237 // but it must be done within mutex locking, to be guaranteed to be safe.
1238}
1239
1240bool QQuickPixmapReply::event(QEvent *event)
1241{
1242 if (event->type() == QEvent::User) {
1243
1244 if (data) {
1245 Event *de = static_cast<Event *>(event);
1246 data->pixmapStatus = (de->error == NoError) ? QQuickPixmap::Ready : QQuickPixmap::Error;
1247 if (data->pixmapStatus == QQuickPixmap::Ready) {
1248 data->textureFactory = de->textureFactory;
1249 de->textureFactory = nullptr;
1250 data->implicitSize = de->implicitSize;
1251 PIXMAP_PROFILE(pixmapLoadingFinished(data->url,
1252 data->textureFactory != nullptr && data->textureFactory->textureSize().isValid() ?
1253 data->textureFactory->textureSize() :
1254 (data->requestSize.isValid() ? data->requestSize : data->implicitSize)));
1255 } else {
1256 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(data->url));
1257 data->errorString = de->errorString;
1258 data->removeFromCache(); // We don't continue to cache error'd pixmaps
1259 }
1260
1261 data->reply = nullptr;
1262 emit finished();
1263 } else {
1264 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(url));
1265 }
1266
1267 delete this;
1268 return true;
1269 } else {
1270 return QObject::event(event);
1271 }
1272}
1273
1274int QQuickPixmapData::cost() const
1275{
1276 if (textureFactory)
1277 return textureFactory->textureByteCount();
1278 return 0;
1279}
1280
1281void QQuickPixmapData::addref()
1282{
1283 ++refCount;
1284 PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapReferenceCountChanged>(url, refCount));
1285 if (prevUnreferencedPtr)
1286 pixmapStore()->referencePixmap(data: this);
1287}
1288
1289void QQuickPixmapData::release()
1290{
1291 Q_ASSERT(refCount > 0);
1292 --refCount;
1293 PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapReferenceCountChanged>(url, refCount));
1294 if (refCount == 0) {
1295 if (reply) {
1296 QQuickPixmapReply *cancelReply = reply;
1297 reply->data = nullptr;
1298 reply = nullptr;
1299 QQuickPixmapReader::readerMutex.lock();
1300 QQuickPixmapReader *reader = QQuickPixmapReader::existingInstance(engine: cancelReply->engineForReader);
1301 if (reader)
1302 reader->cancel(reply: cancelReply);
1303 QQuickPixmapReader::readerMutex.unlock();
1304 }
1305
1306 if (pixmapStatus == QQuickPixmap::Ready
1307#ifdef Q_OS_WEBOS
1308 && storeToCache
1309#endif
1310 ) {
1311 if (inCache)
1312 pixmapStore()->unreferencePixmap(data: this);
1313 else
1314 delete this;
1315 } else {
1316 removeFromCache();
1317 delete this;
1318 }
1319 }
1320}
1321
1322void QQuickPixmapData::addToCache()
1323{
1324 if (!inCache) {
1325 QQuickPixmapKey key = { .url: &url, .region: &requestRegion, .size: &requestSize, .frame: frame, .options: providerOptions };
1326 pixmapStore()->m_cache.insert(akey: key, avalue: this);
1327 inCache = true;
1328 PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapCacheCountChanged>(
1329 url, pixmapStore()->m_cache.count()));
1330 }
1331}
1332
1333void QQuickPixmapData::removeFromCache()
1334{
1335 if (inCache) {
1336 QQuickPixmapKey key = { .url: &url, .region: &requestRegion, .size: &requestSize, .frame: frame, .options: providerOptions };
1337 pixmapStore()->m_cache.remove(akey: key);
1338 inCache = false;
1339 PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapCacheCountChanged>(
1340 url, pixmapStore()->m_cache.count()));
1341 }
1342}
1343
1344static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, QQmlEngine *engine, const QUrl &url,
1345 const QRect &requestRegion, const QSize &requestSize,
1346 const QQuickImageProviderOptions &providerOptions, int frame, bool *ok)
1347{
1348 if (url.scheme() == QLatin1String("image")) {
1349 QSize readSize;
1350
1351 QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
1352 QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(e: engine);
1353 QSharedPointer<QQuickImageProvider> provider = enginePrivate->imageProvider(providerId: imageProviderId(url)).dynamicCast<QQuickImageProvider>();
1354 // it is safe to use get() as providerV2 does not escape and is outlived by provider
1355 QQuickImageProviderWithOptions *providerV2 = QQuickImageProviderWithOptions::checkedCast(provider: provider.get());
1356 if (provider)
1357 imageType = provider->imageType();
1358
1359 switch (imageType) {
1360 case QQuickImageProvider::Invalid:
1361 return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions,
1362 QQuickPixmap::tr(sourceText: "Invalid image provider: %1").arg(a: url.toString()));
1363 case QQuickImageProvider::Texture:
1364 {
1365 QQuickTextureFactory *texture = providerV2 ? providerV2->requestTexture(id: imageId(url), size: &readSize, requestedSize: requestSize, options: providerOptions)
1366 : provider->requestTexture(id: imageId(url), size: &readSize, requestedSize: requestSize);
1367 if (texture) {
1368 *ok = true;
1369 return new QQuickPixmapData(declarativePixmap, url, texture, readSize, requestRegion, requestSize,
1370 providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
1371 }
1372 break;
1373 }
1374
1375 case QQuickImageProvider::Image:
1376 {
1377 QImage image = providerV2 ? providerV2->requestImage(id: imageId(url), size: &readSize, requestedSize: requestSize, options: providerOptions)
1378 : provider->requestImage(id: imageId(url), size: &readSize, requestedSize: requestSize);
1379 if (!image.isNull()) {
1380 *ok = true;
1381 return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image),
1382 readSize, requestRegion, requestSize, providerOptions,
1383 QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
1384 }
1385 break;
1386 }
1387 case QQuickImageProvider::Pixmap:
1388 {
1389 QPixmap pixmap = providerV2 ? providerV2->requestPixmap(id: imageId(url), size: &readSize, requestedSize: requestSize, options: providerOptions)
1390 : provider->requestPixmap(id: imageId(url), size: &readSize, requestedSize: requestSize);
1391 if (!pixmap.isNull()) {
1392 *ok = true;
1393 return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image: pixmap.toImage()),
1394 readSize, requestRegion, requestSize, providerOptions,
1395 QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
1396 }
1397 break;
1398 }
1399 case QQuickImageProvider::ImageResponse:
1400 {
1401 // Fall through, ImageResponse providers never get here
1402 Q_ASSERT(imageType != QQuickImageProvider::ImageResponse && "Sync call to ImageResponse provider");
1403 }
1404 }
1405
1406 // provider has bad image type, or provider returned null image
1407 return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions,
1408 QQuickPixmap::tr(sourceText: "Failed to get image from provider: %1").arg(a: url.toString()));
1409 }
1410
1411 QString localFile = QQmlFile::urlToLocalFileOrQrc(url);
1412 if (localFile.isEmpty())
1413 return nullptr;
1414
1415 QFile f(existingImageFileForPath(localFile));
1416 QSize readSize;
1417 QString errorString;
1418
1419 if (f.open(flags: QIODevice::ReadOnly)) {
1420 QSGTextureReader texReader(&f, localFile);
1421 if (backendSupport()->hasOpenGL && texReader.isTexture()) {
1422 QQuickTextureFactory *factory = texReader.read();
1423 if (factory) {
1424 *ok = true;
1425 return new QQuickPixmapData(declarativePixmap, url, factory, factory->textureSize(), requestRegion, requestSize,
1426 providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
1427 } else {
1428 errorString = QQuickPixmap::tr(sourceText: "Error decoding: %1").arg(a: url.toString());
1429 if (f.fileName() != localFile)
1430 errorString += QString::fromLatin1(str: " (%1)").arg(a: f.fileName());
1431 }
1432 } else {
1433 QImage image;
1434 QQuickImageProviderOptions::AutoTransform appliedTransform = providerOptions.autoTransform();
1435 int frameCount;
1436 if (readImage(url, dev: &f, image: &image, errorString: &errorString, impsize: &readSize, frameCount: &frameCount, requestRegion, requestSize, providerOptions, appliedTransform: &appliedTransform, frame)) {
1437 *ok = true;
1438 return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestRegion, requestSize,
1439 providerOptions, appliedTransform, frame, frameCount);
1440 } else if (f.fileName() != localFile) {
1441 errorString += QString::fromLatin1(str: " (%1)").arg(a: f.fileName());
1442 }
1443 }
1444 } else {
1445 errorString = QQuickPixmap::tr(sourceText: "Cannot open: %1").arg(a: url.toString());
1446 }
1447 return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions, errorString);
1448}
1449
1450
1451struct QQuickPixmapNull {
1452 QUrl url;
1453 QRect region;
1454 QSize size;
1455};
1456Q_GLOBAL_STATIC(QQuickPixmapNull, nullPixmap);
1457
1458QQuickPixmap::QQuickPixmap()
1459: d(nullptr)
1460{
1461}
1462
1463QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url)
1464: d(nullptr)
1465{
1466 load(engine, url);
1467}
1468
1469QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QRect &region, const QSize &size)
1470: d(nullptr)
1471{
1472 load(engine, url, requestRegion: region, requestSize: size);
1473}
1474
1475QQuickPixmap::QQuickPixmap(const QUrl &url, const QImage &image)
1476{
1477 d = new QQuickPixmapData(this, url, new QQuickDefaultTextureFactory(image), image.size(), QRect(), QSize(),
1478 QQuickImageProviderOptions(), QQuickImageProviderOptions::UsePluginDefaultTransform);
1479 d->addToCache();
1480}
1481
1482QQuickPixmap::~QQuickPixmap()
1483{
1484 if (d) {
1485 d->declarativePixmaps.remove(n: this);
1486 d->release();
1487 d = nullptr;
1488 }
1489}
1490
1491bool QQuickPixmap::isNull() const
1492{
1493 return d == nullptr;
1494}
1495
1496bool QQuickPixmap::isReady() const
1497{
1498 return status() == Ready;
1499}
1500
1501bool QQuickPixmap::isError() const
1502{
1503 return status() == Error;
1504}
1505
1506bool QQuickPixmap::isLoading() const
1507{
1508 return status() == Loading;
1509}
1510
1511QString QQuickPixmap::error() const
1512{
1513 if (d)
1514 return d->errorString;
1515 else
1516 return QString();
1517}
1518
1519QQuickPixmap::Status QQuickPixmap::status() const
1520{
1521 if (d)
1522 return d->pixmapStatus;
1523 else
1524 return Null;
1525}
1526
1527const QUrl &QQuickPixmap::url() const
1528{
1529 if (d)
1530 return d->url;
1531 else
1532 return nullPixmap()->url;
1533}
1534
1535const QSize &QQuickPixmap::implicitSize() const
1536{
1537 if (d)
1538 return d->implicitSize;
1539 else
1540 return nullPixmap()->size;
1541}
1542
1543const QSize &QQuickPixmap::requestSize() const
1544{
1545 if (d)
1546 return d->requestSize;
1547 else
1548 return nullPixmap()->size;
1549}
1550
1551const QRect &QQuickPixmap::requestRegion() const
1552{
1553 if (d)
1554 return d->requestRegion;
1555 else
1556 return nullPixmap()->region;
1557}
1558
1559QQuickImageProviderOptions::AutoTransform QQuickPixmap::autoTransform() const
1560{
1561 if (d)
1562 return d->appliedTransform;
1563 else
1564 return QQuickImageProviderOptions::UsePluginDefaultTransform;
1565}
1566
1567int QQuickPixmap::frameCount() const
1568{
1569 if (d)
1570 return d->frameCount;
1571 return 0;
1572}
1573
1574QQuickTextureFactory *QQuickPixmap::textureFactory() const
1575{
1576 if (d)
1577 return d->textureFactory;
1578
1579 return nullptr;
1580}
1581
1582QImage QQuickPixmap::image() const
1583{
1584 if (d && d->textureFactory)
1585 return d->textureFactory->image();
1586 return QImage();
1587}
1588
1589void QQuickPixmap::setImage(const QImage &p)
1590{
1591 clear();
1592
1593 if (!p.isNull())
1594 d = new QQuickPixmapData(this, QQuickTextureFactory::textureFactoryForImage(image: p));
1595}
1596
1597void QQuickPixmap::setPixmap(const QQuickPixmap &other)
1598{
1599 clear();
1600
1601 if (other.d) {
1602 d = other.d;
1603 d->addref();
1604 d->declarativePixmaps.insert(n: this);
1605 }
1606}
1607
1608int QQuickPixmap::width() const
1609{
1610 if (d && d->textureFactory)
1611 return d->textureFactory->textureSize().width();
1612 else
1613 return 0;
1614}
1615
1616int QQuickPixmap::height() const
1617{
1618 if (d && d->textureFactory)
1619 return d->textureFactory->textureSize().height();
1620 else
1621 return 0;
1622}
1623
1624QRect QQuickPixmap::rect() const
1625{
1626 if (d && d->textureFactory)
1627 return QRect(QPoint(), d->textureFactory->textureSize());
1628 else
1629 return QRect();
1630}
1631
1632void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url)
1633{
1634 load(engine, url, requestRegion: QRect(), requestSize: QSize(), options: QQuickPixmap::Cache);
1635}
1636
1637void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, QQuickPixmap::Options options)
1638{
1639 load(engine, url, requestRegion: QRect(), requestSize: QSize(), options);
1640}
1641
1642void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize)
1643{
1644 load(engine, url, requestRegion, requestSize, options: QQuickPixmap::Cache);
1645}
1646
1647void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize, QQuickPixmap::Options options)
1648{
1649 load(engine, url, requestRegion, requestSize, options, providerOptions: QQuickImageProviderOptions());
1650}
1651
1652void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize,
1653 QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions, int frame, int frameCount)
1654{
1655 if (d) {
1656 d->declarativePixmaps.remove(n: this);
1657 d->release();
1658 d = nullptr;
1659 }
1660
1661 QQuickPixmapKey key = { .url: &url, .region: &requestRegion, .size: &requestSize, .frame: frame, .options: providerOptions };
1662 QQuickPixmapStore *store = pixmapStore();
1663
1664 QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end();
1665
1666#ifdef Q_OS_WEBOS
1667 QQuickPixmap::Options orgOptions = options;
1668 // In webOS, we suppose that cache is always enabled to share image instances along its source.
1669 // So, original option(orgOptions) for cache only decides whether to store the instances when it's unreferenced.
1670 options |= QQuickPixmap::Cache;
1671#endif
1672
1673 // If Cache is disabled, the pixmap will always be loaded, even if there is an existing
1674 // cached version. Unless it's an itemgrabber url, since the cache is used to pass
1675 // the result between QQuickItemGrabResult and QQuickImage.
1676 if (url.scheme() == itemGrabberScheme) {
1677 QRect dummyRegion;
1678 QSize dummySize;
1679 if (requestSize != dummySize)
1680 qWarning() << "Ignoring sourceSize request for image url that came from grabToImage. Use the targetSize parameter of the grabToImage() function instead.";
1681 const QQuickPixmapKey grabberKey = { .url: &url, .region: &dummyRegion, .size: &dummySize, .frame: 0, .options: QQuickImageProviderOptions() };
1682 iter = store->m_cache.find(akey: grabberKey);
1683 } else if (options & QQuickPixmap::Cache)
1684 iter = store->m_cache.find(akey: key);
1685
1686 if (iter == store->m_cache.end()) {
1687 if (url.scheme() == QLatin1String("image")) {
1688 QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(e: engine);
1689 if (auto provider = enginePrivate->imageProvider(providerId: imageProviderId(url)).staticCast<QQuickImageProvider>()) {
1690 const bool threadedPixmaps = QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::ThreadedPixmaps);
1691 if (!threadedPixmaps && provider->imageType() == QQuickImageProvider::Pixmap) {
1692 // pixmaps can only be loaded synchronously
1693 options &= ~QQuickPixmap::Asynchronous;
1694 } else if (provider->flags() & QQuickImageProvider::ForceAsynchronousImageLoading) {
1695 options |= QQuickPixmap::Asynchronous;
1696 }
1697 }
1698 }
1699
1700 if (!(options & QQuickPixmap::Asynchronous)) {
1701 bool ok = false;
1702 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingStarted>(url));
1703 d = createPixmapDataSync(declarativePixmap: this, engine, url, requestRegion, requestSize, providerOptions, frame, ok: &ok);
1704 if (ok) {
1705 PIXMAP_PROFILE(pixmapLoadingFinished(url, QSize(width(), height())));
1706 if (options & QQuickPixmap::Cache)
1707 d->addToCache();
1708#ifdef Q_OS_WEBOS
1709 d->storeToCache = orgOptions & QQuickPixmap::Cache;
1710#endif
1711 return;
1712 }
1713 if (d) { // loadable, but encountered error while loading
1714 PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingError>(url));
1715 return;
1716 }
1717 }
1718
1719 if (!engine)
1720 return;
1721
1722
1723 d = new QQuickPixmapData(this, url, requestRegion, requestSize, providerOptions,
1724 QQuickImageProviderOptions::UsePluginDefaultTransform, frame, frameCount);
1725 if (options & QQuickPixmap::Cache)
1726 d->addToCache();
1727#ifdef Q_OS_WEBOS
1728 d->storeToCache = orgOptions & QQuickPixmap::Cache;
1729#endif
1730
1731 QQuickPixmapReader::readerMutex.lock();
1732 d->reply = QQuickPixmapReader::instance(engine)->getImage(data: d);
1733 QQuickPixmapReader::readerMutex.unlock();
1734 } else {
1735 d = *iter;
1736 d->addref();
1737 d->declarativePixmaps.insert(n: this);
1738 }
1739}
1740
1741void QQuickPixmap::clear()
1742{
1743 if (d) {
1744 d->declarativePixmaps.remove(n: this);
1745 d->release();
1746 d = nullptr;
1747 }
1748}
1749
1750void QQuickPixmap::clear(QObject *obj)
1751{
1752 if (d) {
1753 if (d->reply)
1754 QObject::disconnect(sender: d->reply, signal: nullptr, receiver: obj, member: nullptr);
1755 d->declarativePixmaps.remove(n: this);
1756 d->release();
1757 d = nullptr;
1758 }
1759}
1760
1761bool QQuickPixmap::isCached(const QUrl &url, const QRect &requestRegion, const QSize &requestSize,
1762 const int frame, const QQuickImageProviderOptions &options)
1763{
1764 QQuickPixmapKey key = { .url: &url, .region: &requestRegion, .size: &requestSize, .frame: frame, .options: options };
1765 QQuickPixmapStore *store = pixmapStore();
1766
1767 return store->m_cache.contains(akey: key);
1768}
1769
1770bool QQuickPixmap::connectFinished(QObject *object, const char *method)
1771{
1772 if (!d || !d->reply) {
1773 qWarning(msg: "QQuickPixmap: connectFinished() called when not loading.");
1774 return false;
1775 }
1776
1777 return QObject::connect(sender: d->reply, SIGNAL(finished()), receiver: object, member: method);
1778}
1779
1780bool QQuickPixmap::connectFinished(QObject *object, int method)
1781{
1782 if (!d || !d->reply) {
1783 qWarning(msg: "QQuickPixmap: connectFinished() called when not loading.");
1784 return false;
1785 }
1786
1787 return QMetaObject::connect(sender: d->reply, signal_index: QQuickPixmapReply::finishedIndex, receiver: object, method_index: method);
1788}
1789
1790bool QQuickPixmap::connectDownloadProgress(QObject *object, const char *method)
1791{
1792 if (!d || !d->reply) {
1793 qWarning(msg: "QQuickPixmap: connectDownloadProgress() called when not loading.");
1794 return false;
1795 }
1796
1797 return QObject::connect(sender: d->reply, SIGNAL(downloadProgress(qint64,qint64)), receiver: object, member: method);
1798}
1799
1800bool QQuickPixmap::connectDownloadProgress(QObject *object, int method)
1801{
1802 if (!d || !d->reply) {
1803 qWarning(msg: "QQuickPixmap: connectDownloadProgress() called when not loading.");
1804 return false;
1805 }
1806
1807 return QMetaObject::connect(sender: d->reply, signal_index: QQuickPixmapReply::downloadProgressIndex, receiver: object, method_index: method);
1808}
1809
1810QColorSpace QQuickPixmap::colorSpace() const
1811{
1812 if (!d || !d->textureFactory)
1813 return QColorSpace();
1814 return d->textureFactory->image().colorSpace();
1815}
1816
1817QT_END_NAMESPACE
1818
1819#include <qquickpixmapcache.moc>
1820
1821#include "moc_qquickpixmapcache_p.cpp"
1822

source code of qtdeclarative/src/quick/util/qquickpixmapcache.cpp