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 | |
85 | QT_BEGIN_NAMESPACE |
86 | |
87 | const QLatin1String QQuickPixmap::itemGrabberScheme = QLatin1String("itemgrabber" ); |
88 | |
89 | Q_LOGGING_CATEGORY(lcImg, "qt.quick.image" ) |
90 | |
91 | #ifndef QT_NO_DEBUG |
92 | static const bool qsg_leak_check = !qEnvironmentVariableIsEmpty(varName: "QML_LEAK_CHECK" ); |
93 | #endif |
94 | |
95 | // The cache limit describes the maximum "junk" in the cache. |
96 | static int cache_limit = 2048 * 1024; // 2048 KB cache limit for embedded in qpixmapcache.cpp |
97 | |
98 | static inline QString imageProviderId(const QUrl &url) |
99 | { |
100 | return url.host(); |
101 | } |
102 | |
103 | static inline QString imageId(const QUrl &url) |
104 | { |
105 | return url.toString(options: QUrl::RemoveScheme | QUrl::RemoveAuthority).mid(position: 1); |
106 | } |
107 | |
108 | QQuickDefaultTextureFactory::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 | |
120 | QSGTexture *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 | |
129 | class QQuickPixmapReader; |
130 | class QQuickPixmapData; |
131 | class QQuickPixmapReply : public QObject |
132 | { |
133 | Q_OBJECT |
134 | public: |
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 | |
163 | Q_SIGNALS: |
164 | void finished(); |
165 | void downloadProgress(qint64, qint64); |
166 | |
167 | protected: |
168 | bool event(QEvent *event) override; |
169 | |
170 | private: |
171 | Q_DISABLE_COPY(QQuickPixmapReply) |
172 | |
173 | public: |
174 | static int finishedIndex; |
175 | static int downloadProgressIndex; |
176 | }; |
177 | |
178 | class QQuickPixmapReaderThreadObject : public QObject { |
179 | Q_OBJECT |
180 | public: |
181 | QQuickPixmapReaderThreadObject(QQuickPixmapReader *); |
182 | void processJobs(); |
183 | bool event(QEvent *e) override; |
184 | public slots: |
185 | void asyncResponseFinished(QQuickImageResponse *response); |
186 | private slots: |
187 | void networkRequestDone(); |
188 | void asyncResponseFinished(); |
189 | private: |
190 | QQuickPixmapReader *reader; |
191 | }; |
192 | |
193 | class QQuickPixmapData; |
194 | class QQuickPixmapReader : public QThread |
195 | { |
196 | Q_OBJECT |
197 | public: |
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 | |
207 | protected: |
208 | void run() override; |
209 | |
210 | private: |
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; |
239 | public: |
240 | static QMutex readerMutex; |
241 | }; |
242 | |
243 | class QQuickPixmapData |
244 | { |
245 | public: |
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 | |
349 | int QQuickPixmapReply::finishedIndex = -1; |
350 | int QQuickPixmapReply::downloadProgressIndex = -1; |
351 | |
352 | // XXX |
353 | QHash<QQmlEngine *,QQuickPixmapReader*> QQuickPixmapReader::readers; |
354 | QMutex QQuickPixmapReader::readerMutex; |
355 | |
356 | int QQuickPixmapReader::replyDownloadProgress = -1; |
357 | int QQuickPixmapReader::replyFinished = -1; |
358 | int QQuickPixmapReader::downloadProgress = -1; |
359 | int QQuickPixmapReader::threadNetworkRequestDone = -1; |
360 | |
361 | |
362 | void 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 | |
369 | QQuickPixmapReply::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 | |
374 | QQuickPixmapReply::Event::~Event() |
375 | { |
376 | delete textureFactory; |
377 | } |
378 | |
379 | #if QT_CONFIG(qml_network) |
380 | QNetworkAccessManager *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 | |
390 | static 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 | |
425 | static 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 | |
472 | static 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 | |
481 | class BackendSupport |
482 | { |
483 | public: |
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 | }; |
497 | Q_GLOBAL_STATIC(BackendSupport, backendSupport); |
498 | |
499 | static 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 | |
516 | QQuickPixmapReader::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 | |
532 | QQuickPixmapReader::~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) |
569 | void 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 | |
625 | void 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 | |
652 | QQuickPixmapReaderThreadObject::QQuickPixmapReaderThreadObject(QQuickPixmapReader *i) |
653 | : reader(i) |
654 | { |
655 | } |
656 | |
657 | void QQuickPixmapReaderThreadObject::processJobs() |
658 | { |
659 | QCoreApplication::postEvent(receiver: this, event: new QEvent(QEvent::User)); |
660 | } |
661 | |
662 | bool 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 | |
672 | void QQuickPixmapReaderThreadObject::networkRequestDone() |
673 | { |
674 | #if QT_CONFIG(qml_network) |
675 | QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); |
676 | reader->networkRequestDone(reply); |
677 | #endif |
678 | } |
679 | |
680 | void QQuickPixmapReaderThreadObject::asyncResponseFinished(QQuickImageResponse *response) |
681 | { |
682 | reader->asyncResponseFinished(response); |
683 | } |
684 | |
685 | void QQuickPixmapReaderThreadObject::asyncResponseFinished() |
686 | { |
687 | QQuickImageResponse *response = static_cast<QQuickImageResponse *>(sender()); |
688 | asyncResponseFinished(response); |
689 | } |
690 | |
691 | void 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 | |
772 | void 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 | |
958 | QQuickPixmapReader *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 | |
970 | QQuickPixmapReader *QQuickPixmapReader::existingInstance(QQmlEngine *engine) |
971 | { |
972 | // XXX NOTE: must be called within readerMutex locking. |
973 | return readers.value(akey: engine, adefaultValue: 0); |
974 | } |
975 | |
976 | QQuickPixmapReply *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 | |
988 | void 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 | |
1007 | void 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 | |
1033 | class QQuickPixmapKey |
1034 | { |
1035 | public: |
1036 | const QUrl *url; |
1037 | const QRect *region; |
1038 | const QSize *size; |
1039 | int frame; |
1040 | QQuickImageProviderOptions options; |
1041 | }; |
1042 | |
1043 | inline 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 | |
1052 | inline 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 | |
1061 | class QQuickPixmapStore : public QObject |
1062 | { |
1063 | Q_OBJECT |
1064 | public: |
1065 | QQuickPixmapStore(); |
1066 | ~QQuickPixmapStore(); |
1067 | |
1068 | void unreferencePixmap(QQuickPixmapData *); |
1069 | void referencePixmap(QQuickPixmapData *); |
1070 | |
1071 | void purgeCache(); |
1072 | |
1073 | protected: |
1074 | void timerEvent(QTimerEvent *) override; |
1075 | |
1076 | public: |
1077 | QHash<QQuickPixmapKey, QQuickPixmapData *> m_cache; |
1078 | |
1079 | private: |
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 | }; |
1089 | Q_GLOBAL_STATIC(QQuickPixmapStore, pixmapStore); |
1090 | |
1091 | |
1092 | QQuickPixmapStore::QQuickPixmapStore() |
1093 | : m_unreferencedPixmaps(nullptr), m_lastUnreferencedPixmap(nullptr), m_unreferencedCost(0), m_timerId(-1), m_destroying(false) |
1094 | { |
1095 | } |
1096 | |
1097 | QQuickPixmapStore::~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 | |
1135 | void 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 | |
1163 | void 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 | |
1182 | void 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 | |
1202 | void 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 | |
1214 | void QQuickPixmapStore::purgeCache() |
1215 | { |
1216 | shrinkCache(remove: m_unreferencedCost); |
1217 | } |
1218 | |
1219 | void QQuickPixmap::purgeCache() |
1220 | { |
1221 | pixmapStore()->purgeCache(); |
1222 | } |
1223 | |
1224 | QQuickPixmapReply::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 | |
1234 | QQuickPixmapReply::~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 | |
1240 | bool 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 | |
1274 | int QQuickPixmapData::cost() const |
1275 | { |
1276 | if (textureFactory) |
1277 | return textureFactory->textureByteCount(); |
1278 | return 0; |
1279 | } |
1280 | |
1281 | void QQuickPixmapData::addref() |
1282 | { |
1283 | ++refCount; |
1284 | PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapReferenceCountChanged>(url, refCount)); |
1285 | if (prevUnreferencedPtr) |
1286 | pixmapStore()->referencePixmap(data: this); |
1287 | } |
1288 | |
1289 | void 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 | |
1322 | void 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 | |
1333 | void 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 | |
1344 | static 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 | |
1451 | struct QQuickPixmapNull { |
1452 | QUrl url; |
1453 | QRect region; |
1454 | QSize size; |
1455 | }; |
1456 | Q_GLOBAL_STATIC(QQuickPixmapNull, nullPixmap); |
1457 | |
1458 | QQuickPixmap::QQuickPixmap() |
1459 | : d(nullptr) |
1460 | { |
1461 | } |
1462 | |
1463 | QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url) |
1464 | : d(nullptr) |
1465 | { |
1466 | load(engine, url); |
1467 | } |
1468 | |
1469 | QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QRect ®ion, const QSize &size) |
1470 | : d(nullptr) |
1471 | { |
1472 | load(engine, url, requestRegion: region, requestSize: size); |
1473 | } |
1474 | |
1475 | QQuickPixmap::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 | |
1482 | QQuickPixmap::~QQuickPixmap() |
1483 | { |
1484 | if (d) { |
1485 | d->declarativePixmaps.remove(n: this); |
1486 | d->release(); |
1487 | d = nullptr; |
1488 | } |
1489 | } |
1490 | |
1491 | bool QQuickPixmap::isNull() const |
1492 | { |
1493 | return d == nullptr; |
1494 | } |
1495 | |
1496 | bool QQuickPixmap::isReady() const |
1497 | { |
1498 | return status() == Ready; |
1499 | } |
1500 | |
1501 | bool QQuickPixmap::isError() const |
1502 | { |
1503 | return status() == Error; |
1504 | } |
1505 | |
1506 | bool QQuickPixmap::isLoading() const |
1507 | { |
1508 | return status() == Loading; |
1509 | } |
1510 | |
1511 | QString QQuickPixmap::error() const |
1512 | { |
1513 | if (d) |
1514 | return d->errorString; |
1515 | else |
1516 | return QString(); |
1517 | } |
1518 | |
1519 | QQuickPixmap::Status QQuickPixmap::status() const |
1520 | { |
1521 | if (d) |
1522 | return d->pixmapStatus; |
1523 | else |
1524 | return Null; |
1525 | } |
1526 | |
1527 | const QUrl &QQuickPixmap::url() const |
1528 | { |
1529 | if (d) |
1530 | return d->url; |
1531 | else |
1532 | return nullPixmap()->url; |
1533 | } |
1534 | |
1535 | const QSize &QQuickPixmap::implicitSize() const |
1536 | { |
1537 | if (d) |
1538 | return d->implicitSize; |
1539 | else |
1540 | return nullPixmap()->size; |
1541 | } |
1542 | |
1543 | const QSize &QQuickPixmap::requestSize() const |
1544 | { |
1545 | if (d) |
1546 | return d->requestSize; |
1547 | else |
1548 | return nullPixmap()->size; |
1549 | } |
1550 | |
1551 | const QRect &QQuickPixmap::requestRegion() const |
1552 | { |
1553 | if (d) |
1554 | return d->requestRegion; |
1555 | else |
1556 | return nullPixmap()->region; |
1557 | } |
1558 | |
1559 | QQuickImageProviderOptions::AutoTransform QQuickPixmap::autoTransform() const |
1560 | { |
1561 | if (d) |
1562 | return d->appliedTransform; |
1563 | else |
1564 | return QQuickImageProviderOptions::UsePluginDefaultTransform; |
1565 | } |
1566 | |
1567 | int QQuickPixmap::frameCount() const |
1568 | { |
1569 | if (d) |
1570 | return d->frameCount; |
1571 | return 0; |
1572 | } |
1573 | |
1574 | QQuickTextureFactory *QQuickPixmap::textureFactory() const |
1575 | { |
1576 | if (d) |
1577 | return d->textureFactory; |
1578 | |
1579 | return nullptr; |
1580 | } |
1581 | |
1582 | QImage QQuickPixmap::image() const |
1583 | { |
1584 | if (d && d->textureFactory) |
1585 | return d->textureFactory->image(); |
1586 | return QImage(); |
1587 | } |
1588 | |
1589 | void QQuickPixmap::setImage(const QImage &p) |
1590 | { |
1591 | clear(); |
1592 | |
1593 | if (!p.isNull()) |
1594 | d = new QQuickPixmapData(this, QQuickTextureFactory::textureFactoryForImage(image: p)); |
1595 | } |
1596 | |
1597 | void 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 | |
1608 | int QQuickPixmap::width() const |
1609 | { |
1610 | if (d && d->textureFactory) |
1611 | return d->textureFactory->textureSize().width(); |
1612 | else |
1613 | return 0; |
1614 | } |
1615 | |
1616 | int QQuickPixmap::height() const |
1617 | { |
1618 | if (d && d->textureFactory) |
1619 | return d->textureFactory->textureSize().height(); |
1620 | else |
1621 | return 0; |
1622 | } |
1623 | |
1624 | QRect QQuickPixmap::rect() const |
1625 | { |
1626 | if (d && d->textureFactory) |
1627 | return QRect(QPoint(), d->textureFactory->textureSize()); |
1628 | else |
1629 | return QRect(); |
1630 | } |
1631 | |
1632 | void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url) |
1633 | { |
1634 | load(engine, url, requestRegion: QRect(), requestSize: QSize(), options: QQuickPixmap::Cache); |
1635 | } |
1636 | |
1637 | void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, QQuickPixmap::Options options) |
1638 | { |
1639 | load(engine, url, requestRegion: QRect(), requestSize: QSize(), options); |
1640 | } |
1641 | |
1642 | void 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 | |
1647 | void 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 | |
1652 | void 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 | |
1741 | void QQuickPixmap::clear() |
1742 | { |
1743 | if (d) { |
1744 | d->declarativePixmaps.remove(n: this); |
1745 | d->release(); |
1746 | d = nullptr; |
1747 | } |
1748 | } |
1749 | |
1750 | void 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 | |
1761 | bool 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 | |
1770 | bool 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 | |
1780 | bool 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 | |
1790 | bool 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 | |
1800 | bool 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 | |
1810 | QColorSpace QQuickPixmap::colorSpace() const |
1811 | { |
1812 | if (!d || !d->textureFactory) |
1813 | return QColorSpace(); |
1814 | return d->textureFactory->image().colorSpace(); |
1815 | } |
1816 | |
1817 | QT_END_NAMESPACE |
1818 | |
1819 | #include <qquickpixmapcache.moc> |
1820 | |
1821 | #include "moc_qquickpixmapcache_p.cpp" |
1822 | |