| 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 |  |