diff options
author | Shawn Rutledge <[email protected]> | 2015-12-03 17:48:38 +0100 |
---|---|---|
committer | Shawn Rutledge <[email protected]> | 2019-08-24 12:26:20 +0200 |
commit | 5d995ae122aa07486ead849560b74d2b62b883bb (patch) | |
tree | fba57d7eac473f9a41923bfc7719a5a87a281f79 | |
parent | 506b4093b47f8ecf29c2e1e83e73e47e9ac767ad (diff) |
Move currentFrame and frameCount properties up to QQuickImageBase
AnimatedImage already had these properties, but some typically non-animated
image formats such as PDF, TIFF and ICO can also support multiple pages.
In either case, the currentFrame property can be used to select a specific
frame or page. However an AnimatedImage uses a QMovie to do that, whereas
a plain Image uses QQuickPixmap. So the accessors need to be virtual in
order to have these different implementations.
[ChangeLog][QtQuick][Image] Image and BorderImage now have currentFrame
and frameCount properties which can be used to step through the frames of
multi-page image formats such as TIFF, WEBP and ICO.
Task-number: QTBUG-77506
Change-Id: Id4d95a99a26a862957e44b1bd8ffe06d7eababef
Reviewed-by: Eirik Aavitsland <[email protected]>
23 files changed, 447 insertions, 39 deletions
diff --git a/examples/quick/imageelements/content/multi.ico b/examples/quick/imageelements/content/multi.ico Binary files differnew file mode 100644 index 0000000000..b748ceaa29 --- /dev/null +++ b/examples/quick/imageelements/content/multi.ico diff --git a/examples/quick/imageelements/framestepping.qml b/examples/quick/imageelements/framestepping.qml new file mode 100644 index 0000000000..f5bad46e7b --- /dev/null +++ b/examples/quick/imageelements/framestepping.qml @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.14 + +Rectangle { + width: 480 + height: 320 + Image { + id: img + anchors.centerIn: parent + cache: true + source: "content/multi.ico" + + Shortcut { + sequence: StandardKey.MoveToNextPage + enabled: img.currentFrame < img.frameCount - 1 + onActivated: img.currentFrame++ + } + Shortcut { + sequence: StandardKey.MoveToPreviousPage + enabled: img.currentFrame > 0 + onActivated: img.currentFrame-- + } + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.margins: 6 + horizontalAlignment: Text.AlignHCenter + text: "frame " + (img.currentFrame + 1) + " of " + img.frameCount + + "\nPress PgUp/PgDn to switch frames" + } +} diff --git a/examples/quick/imageelements/imageelements.qml b/examples/quick/imageelements/imageelements.qml index dfb4d24ea6..91f2e034f7 100644 --- a/examples/quick/imageelements/imageelements.qml +++ b/examples/quick/imageelements/imageelements.qml @@ -64,6 +64,8 @@ Item { addExample("AnimatedImage", "An image which plays animated formats", Qt.resolvedUrl("animatedimage.qml")); addExample("AnimatedSprite", "A simple sprite-based animation", Qt.resolvedUrl("animatedsprite.qml")); addExample("SpriteSequence", "A sprite-based animation with complex transitions", Qt.resolvedUrl("spritesequence.qml")); + addExample("FrameStepping", "A multi-frame non-animated image", Qt.resolvedUrl("framestepping.qml")); + addExample("MultiBorderImage", "A multi-frame image with scaled borders", Qt.resolvedUrl("multiframeborderimage.qml")); } } } diff --git a/examples/quick/imageelements/imageelements.qrc b/examples/quick/imageelements/imageelements.qrc index 2488fb083b..cedef2204c 100644 --- a/examples/quick/imageelements/imageelements.qrc +++ b/examples/quick/imageelements/imageelements.qrc @@ -8,6 +8,7 @@ <file>content/colors-stretch.sci</file> <file>content/colors.png</file> <file>content/ImageCell.qml</file> + <file>content/multi.ico</file> <file>content/MyBorderImage.qml</file> <file>content/qt-logo.png</file> <file>content/shadow.png</file> @@ -18,6 +19,8 @@ <file>animatedimage.qml</file> <file>animatedsprite.qml</file> <file>borderimage.qml</file> + <file>framestepping.qml</file> + <file>multiframeborderimage.qml</file> <file>image.qml</file> <file>shadows.qml</file> <file>spritesequence.qml</file> diff --git a/examples/quick/imageelements/multiframeborderimage.qml b/examples/quick/imageelements/multiframeborderimage.qml new file mode 100644 index 0000000000..0805ea4243 --- /dev/null +++ b/examples/quick/imageelements/multiframeborderimage.qml @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.14 + +Rectangle { + width: 480 + height: 320 + BorderImage { + id: img + anchors.fill: parent + anchors.margins: 6 + cache: true + source: "content/multi.ico" + border { left: 19; top: 19; right: 19; bottom: 19 } + horizontalTileMode: BorderImage.Stretch + + Shortcut { + sequence: StandardKey.MoveToNextPage + enabled: img.currentFrame < img.frameCount - 1 + onActivated: img.currentFrame++ + } + Shortcut { + sequence: StandardKey.MoveToPreviousPage + enabled: img.currentFrame > 0 + onActivated: img.currentFrame-- + } + } + + Text { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: "frame " + (img.currentFrame + 1) + " of " + img.frameCount + + "\nPress PgUp/PgDn to switch frames" + } +} diff --git a/src/quick/items/qquickanimatedimage.cpp b/src/quick/items/qquickanimatedimage.cpp index fe445425e7..bebefc1b22 100644 --- a/src/quick/items/qquickanimatedimage.cpp +++ b/src/quick/items/qquickanimatedimage.cpp @@ -67,7 +67,7 @@ QQuickPixmap* QQuickAnimatedImagePrivate::infoForCurrentFrame(QQmlEngine *engine .arg(current)); } if (!requestedUrl.isEmpty()) { - if (QQuickPixmap::isCached(requestedUrl, QSize(), QQuickImageProviderOptions())) + if (QQuickPixmap::isCached(requestedUrl, QSize(), 0, QQuickImageProviderOptions())) pixmap = new QQuickPixmap(engine, requestedUrl); else pixmap = new QQuickPixmap(requestedUrl, movie->currentImage()); @@ -139,6 +139,8 @@ QQuickAnimatedImage::QQuickAnimatedImage(QQuickItem *parent) : QQuickImage(*(new QQuickAnimatedImagePrivate), parent) { connect(this, &QQuickImageBase::cacheChanged, this, &QQuickAnimatedImage::onCacheChanged); + connect(this, &QQuickImageBase::currentFrameChanged, this, &QQuickAnimatedImage::frameChanged); + connect(this, &QQuickImageBase::frameCountChanged, this, &QQuickAnimatedImage::frameCountChanged); } QQuickAnimatedImage::~QQuickAnimatedImage() @@ -464,7 +466,7 @@ void QQuickAnimatedImage::movieUpdate() if (d->movie) { d->setPixmap(*d->infoForCurrentFrame(qmlEngine(this))); - emit frameChanged(); + emit currentFrameChanged(); } } diff --git a/src/quick/items/qquickanimatedimage_p.h b/src/quick/items/qquickanimatedimage_p.h index 6b5db215bd..ef5af6b387 100644 --- a/src/quick/items/qquickanimatedimage_p.h +++ b/src/quick/items/qquickanimatedimage_p.h @@ -85,10 +85,10 @@ public: bool isPaused() const; void setPaused(bool pause); - int currentFrame() const; - void setCurrentFrame(int frame); + int currentFrame() const override; + void setCurrentFrame(int frame) override; - int frameCount() const; + int frameCount() const override; qreal speed() const; void setSpeed(qreal speed); diff --git a/src/quick/items/qquickborderimage.cpp b/src/quick/items/qquickborderimage.cpp index c53a39ca09..430fa1b094 100644 --- a/src/quick/items/qquickborderimage.cpp +++ b/src/quick/items/qquickborderimage.cpp @@ -343,7 +343,7 @@ void QQuickBorderImage::load() QUrl loadUrl = d->url; resolve2xLocalFile(d->url, targetDevicePixelRatio, &loadUrl, &d->devicePixelRatio); - d->pix.load(qmlEngine(this), loadUrl, d->sourcesize * d->devicePixelRatio, options); + d->pix.load(qmlEngine(this), loadUrl, d->sourcesize * d->devicePixelRatio, options, QQuickImageProviderOptions(), d->currentFrame, d->frameCount); if (d->pix.isLoading()) { if (d->progress != 0.0) { @@ -534,6 +534,10 @@ void QQuickBorderImage::requestFinished() d->oldSourceSize = sourceSize(); emit sourceSizeChanged(); } + if (d->frameCount != d->pix.frameCount()) { + d->frameCount = d->pix.frameCount(); + emit frameCountChanged(); + } pixmapChange(); } @@ -693,6 +697,18 @@ void QQuickBorderImage::pixmapChange() update(); } +/*! + \qmlproperty int QtQuick::BorderImage::currentFrame + \qmlproperty int QtQuick::BorderImage::frameCount + \since 5.14 + + currentFrame is the frame that is currently visible. The default is \c 0. + You can set it to a number between \c 0 and \c {frameCount - 1} to display a + different frame, if the image contains multiple frames. + + frameCount is the number of frames in the image. Most images have only one frame. +*/ + QT_END_NAMESPACE #include "moc_qquickborderimage_p.cpp" diff --git a/src/quick/items/qquickimage.cpp b/src/quick/items/qquickimage.cpp index dfbe271606..87b848b21b 100644 --- a/src/quick/items/qquickimage.cpp +++ b/src/quick/items/qquickimage.cpp @@ -889,4 +889,16 @@ void QQuickImage::setMipmap(bool use) By default, this property is set to false. */ +/*! + \qmlproperty int QtQuick::Image::currentFrame + \qmlproperty int QtQuick::Image::frameCount + \since 5.14 + + currentFrame is the frame that is currently visible. The default is \c 0. + You can set it to a number between \c 0 and \c {frameCount - 1} to display a + different frame, if the image contains multiple frames. + + frameCount is the number of frames in the image. Most images have only one frame. +*/ + QT_END_NAMESPACE diff --git a/src/quick/items/qquickimagebase.cpp b/src/quick/items/qquickimagebase.cpp index 40857964a9..8bab14bfd1 100644 --- a/src/quick/items/qquickimagebase.cpp +++ b/src/quick/items/qquickimagebase.cpp @@ -211,6 +211,36 @@ bool QQuickImageBase::mirror() const return d->mirror; } +void QQuickImageBase::setCurrentFrame(int frame) +{ + Q_D(QQuickImageBase); + if (frame == d->currentFrame || frame < 0 || (isComponentComplete() && frame >= d->pix.frameCount())) + return; + + d->currentFrame = frame; + + if (isComponentComplete()) { + if (frame > 0) + d->cache = false; + load(); + update(); + } + + emit currentFrameChanged(); +} + +int QQuickImageBase::currentFrame() const +{ + Q_D(const QQuickImageBase); + return d->currentFrame; +} + +int QQuickImageBase::frameCount() const +{ + Q_D(const QQuickImageBase); + return d->frameCount; +} + void QQuickImageBase::load() { Q_D(QQuickImageBase); @@ -261,7 +291,7 @@ void QQuickImageBase::load() resolve2xLocalFile(d->url, targetDevicePixelRatio, &loadUrl, &d->devicePixelRatio); } - d->pix.load(qmlEngine(this), loadUrl, d->sourcesize * d->devicePixelRatio, options, d->providerOptions); + d->pix.load(qmlEngine(this), loadUrl, d->sourcesize * d->devicePixelRatio, options, d->providerOptions, d->currentFrame, d->frameCount); if (d->pix.isLoading()) { if (d->progress != 0.0) { @@ -320,6 +350,11 @@ void QQuickImageBase::requestFinished() d->oldAutoTransform = autoTransform(); emitAutoTransformBaseChanged(); } + if (d->frameCount != d->pix.frameCount()) { + d->frameCount = d->pix.frameCount(); + emit frameCountChanged(); + } + update(); } diff --git a/src/quick/items/qquickimagebase_p.h b/src/quick/items/qquickimagebase_p.h index d8d0be9b8c..8cd59c8cea 100644 --- a/src/quick/items/qquickimagebase_p.h +++ b/src/quick/items/qquickimagebase_p.h @@ -68,6 +68,8 @@ class Q_QUICK_PRIVATE_EXPORT QQuickImageBase : public QQuickImplicitSizeItem Q_PROPERTY(bool cache READ cache WRITE setCache NOTIFY cacheChanged) Q_PROPERTY(QSize sourceSize READ sourceSize WRITE setSourceSize RESET resetSourceSize NOTIFY sourceSizeChanged) Q_PROPERTY(bool mirror READ mirror WRITE setMirror NOTIFY mirrorChanged) + Q_PROPERTY(int currentFrame READ currentFrame WRITE setCurrentFrame NOTIFY currentFrameChanged REVISION 14) + Q_PROPERTY(int frameCount READ frameCount NOTIFY frameCountChanged REVISION 14) public: QQuickImageBase(QQuickItem *parent=nullptr); @@ -95,6 +97,11 @@ public: virtual void setMirror(bool mirror); bool mirror() const; + virtual void setCurrentFrame(int frame); + virtual int currentFrame() const; + + virtual int frameCount() const; + virtual void setAutoTransform(bool transform); bool autoTransform() const; @@ -112,6 +119,8 @@ Q_SIGNALS: void asynchronousChanged(); void cacheChanged(); void mirrorChanged(); + Q_REVISION(14) void currentFrameChanged(); + Q_REVISION(14) void frameCountChanged(); protected: virtual void load(); diff --git a/src/quick/items/qquickimagebase_p_p.h b/src/quick/items/qquickimagebase_p_p.h index 1b771166a2..88e18ba32e 100644 --- a/src/quick/items/qquickimagebase_p_p.h +++ b/src/quick/items/qquickimagebase_p_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. @@ -68,6 +68,8 @@ public: : status(QQuickImageBase::Null), progress(0.0), devicePixelRatio(1.0), + currentFrame(0), + frameCount(0), async(false), cache(true), mirror(false), @@ -85,6 +87,8 @@ public: QSize oldSourceSize; qreal devicePixelRatio; QQuickImageProviderOptions providerOptions; + int currentFrame; + int frameCount; bool async : 1; bool cache : 1; bool mirror: 1; diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp index d8ad104a6d..6ce079a0dc 100644 --- a/src/quick/items/qquickitemsmodule.cpp +++ b/src/quick/items/qquickitemsmodule.cpp @@ -491,6 +491,9 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor) #if QT_CONFIG(wheelevent) qmlRegisterType<QQuickWheelHandler>(uri, 2, 14, "WheelHandler"); #endif + qmlRegisterUncreatableType<QQuickImageBase, 14>(uri, 2, 14, "ImageBase", + QQuickPointerHandler::tr("ImageBase is an abstract base class")); + qmlRegisterType<QQuickImage, 14>(uri, 2, 14, "Image"); } static void initResources() diff --git a/src/quick/util/qquickpixmapcache.cpp b/src/quick/util/qquickpixmapcache.cpp index 78a4e5b998..56ad8ebf0b 100644 --- a/src/quick/util/qquickpixmapcache.cpp +++ b/src/quick/util/qquickpixmapcache.cpp @@ -241,7 +241,7 @@ class QQuickPixmapData { public: QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QSize &s, const QQuickImageProviderOptions &po, const QString &e) - : refCount(1), inCache(false), pixmapStatus(QQuickPixmap::Error), + : refCount(1), frameCount(1), frame(0), inCache(false), pixmapStatus(QQuickPixmap::Error), url(u), errorString(e), requestSize(s), providerOptions(po), appliedTransform(QQuickImageProviderOptions::UsePluginDefaultTransform), textureFactory(nullptr), reply(nullptr), prevUnreferenced(nullptr), @@ -250,8 +250,8 @@ public: declarativePixmaps.insert(pixmap); } - QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QSize &r, const QQuickImageProviderOptions &po, QQuickImageProviderOptions::AutoTransform aTransform) - : refCount(1), inCache(false), pixmapStatus(QQuickPixmap::Loading), + QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QSize &r, const QQuickImageProviderOptions &po, QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1) + : refCount(1), frameCount(frameCount), frame(frame), inCache(false), pixmapStatus(QQuickPixmap::Loading), url(u), requestSize(r), providerOptions(po), appliedTransform(aTransform), textureFactory(nullptr), reply(nullptr), prevUnreferenced(nullptr), prevUnreferencedPtr(nullptr), @@ -261,8 +261,8 @@ public: } QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, QQuickTextureFactory *texture, - const QSize &s, const QSize &r, const QQuickImageProviderOptions &po, QQuickImageProviderOptions::AutoTransform aTransform) - : refCount(1), inCache(false), pixmapStatus(QQuickPixmap::Ready), + const QSize &s, const QSize &r, const QQuickImageProviderOptions &po, QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1) + : refCount(1), frameCount(frameCount), frame(frame), inCache(false), pixmapStatus(QQuickPixmap::Ready), url(u), implicitSize(s), requestSize(r), providerOptions(po), appliedTransform(aTransform), textureFactory(texture), reply(nullptr), prevUnreferenced(nullptr), @@ -272,7 +272,7 @@ public: } QQuickPixmapData(QQuickPixmap *pixmap, QQuickTextureFactory *texture) - : refCount(1), inCache(false), pixmapStatus(QQuickPixmap::Ready), + : refCount(1), frameCount(1), frame(0), inCache(false), pixmapStatus(QQuickPixmap::Ready), appliedTransform(QQuickImageProviderOptions::UsePluginDefaultTransform), textureFactory(texture), reply(nullptr), prevUnreferenced(nullptr), prevUnreferencedPtr(nullptr), nextUnreferenced(nullptr) @@ -299,6 +299,8 @@ public: void removeFromCache(); uint refCount; + int frameCount; + int frame; bool inCache:1; @@ -396,9 +398,9 @@ static void maybeRemoveAlpha(QImage *image) } } -static bool readImage(const QUrl& url, QIODevice *dev, QImage *image, QString *errorString, QSize *impsize, +static bool readImage(const QUrl& url, QIODevice *dev, QImage *image, QString *errorString, QSize *impsize, int *frameCount, const QSize &requestSize, const QQuickImageProviderOptions &providerOptions, - QQuickImageProviderOptions::AutoTransform *appliedTransform = nullptr) + QQuickImageProviderOptions::AutoTransform *appliedTransform = nullptr, int frame = 0) { QImageReader imgio(dev); if (providerOptions.autoTransform() != QQuickImageProviderOptions::UsePluginDefaultTransform) @@ -406,6 +408,12 @@ static bool readImage(const QUrl& url, QIODevice *dev, QImage *image, QString *e else if (appliedTransform) *appliedTransform = imgio.autoTransform() ? QQuickImageProviderOptions::ApplyTransform : QQuickImageProviderOptions::DoNotApplyTransform; + if (frame < imgio.imageCount()) + imgio.jumpToImage(frame); + + if (frameCount) + *frameCount = imgio.imageCount(); + QSize scSize = QQuickImageProviderWithOptions::loadSize(imgio.size(), requestSize, imgio.format(), providerOptions); if (scSize.isValid()) imgio.setScaledSize(scSize); @@ -558,9 +566,12 @@ void QQuickPixmapReader::networkRequestDone(QNetworkReply *reply) QByteArray all = reply->readAll(); QBuffer buff(&all); buff.open(QIODevice::ReadOnly); - if (!readImage(reply->url(), &buff, &image, &errorString, &readSize, job->requestSize, job->providerOptions)) + int frameCount; + if (!readImage(reply->url(), &buff, &image, &errorString, &readSize, &frameCount, job->requestSize, job->providerOptions, nullptr, job->data->frame)) error = QQuickPixmapReply::Decoding; - } + else + job->data->frameCount = frameCount; + } // send completion event to the QQuickPixmapReply mutex.lock(); if (!cancelled.contains(job)) @@ -870,10 +881,13 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u mutex.unlock(); return; } else { - if (!readImage(url, &f, &image, &errorStr, &readSize, runningJob->requestSize, runningJob->providerOptions)) { + int frameCount; + if (!readImage(url, &f, &image, &errorStr, &readSize, &frameCount, runningJob->requestSize, runningJob->providerOptions, nullptr, runningJob->data->frame)) { errorCode = QQuickPixmapReply::Loading; if (f.fileName() != localFile) errorStr += QString::fromLatin1(" (%1)").arg(f.fileName()); + } else if (runningJob->data) { + runningJob->data->frameCount = frameCount; } } } else { @@ -982,17 +996,18 @@ class QQuickPixmapKey public: const QUrl *url; const QSize *size; + int frame; QQuickImageProviderOptions options; }; inline bool operator==(const QQuickPixmapKey &lhs, const QQuickPixmapKey &rhs) { - return *lhs.size == *rhs.size && *lhs.url == *rhs.url && lhs.options == rhs.options; + return *lhs.size == *rhs.size && *lhs.url == *rhs.url && lhs.options == rhs.options && lhs.frame == rhs.frame; } inline uint qHash(const QQuickPixmapKey &key) { - return qHash(*key.url) ^ (key.size->width()*7) ^ (key.size->height()*17) ^ (key.options.autoTransform() * 0x5c5c5c5c); + return qHash(*key.url) ^ (key.size->width()*7) ^ (key.size->height()*17) ^ (key.frame*23) ^ (key.options.autoTransform() * 0x5c5c5c5c); } class QQuickPixmapStore : public QObject @@ -1254,7 +1269,7 @@ void QQuickPixmapData::release() void QQuickPixmapData::addToCache() { if (!inCache) { - QQuickPixmapKey key = { &url, &requestSize, providerOptions }; + QQuickPixmapKey key = { &url, &requestSize, frame, providerOptions }; pixmapStore()->m_cache.insert(key, this); inCache = true; PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapCacheCountChanged>( @@ -1265,7 +1280,7 @@ void QQuickPixmapData::addToCache() void QQuickPixmapData::removeFromCache() { if (inCache) { - QQuickPixmapKey key = { &url, &requestSize, providerOptions }; + QQuickPixmapKey key = { &url, &requestSize, frame, providerOptions }; pixmapStore()->m_cache.remove(key); inCache = false; PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapCacheCountChanged>( @@ -1273,7 +1288,7 @@ void QQuickPixmapData::removeFromCache() } } -static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, QQmlEngine *engine, const QUrl &url, const QSize &requestSize, const QQuickImageProviderOptions &providerOptions, bool *ok) +static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, QQmlEngine *engine, const QUrl &url, const QSize &requestSize, const QQuickImageProviderOptions &providerOptions, int frame, bool *ok) { if (url.scheme() == QLatin1String("image")) { QSize readSize; @@ -1296,7 +1311,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q : provider->requestTexture(imageId(url), &readSize, requestSize); if (texture) { *ok = true; - return new QQuickPixmapData(declarativePixmap, url, texture, readSize, requestSize, providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform); + return new QQuickPixmapData(declarativePixmap, url, texture, readSize, requestSize, providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame); } break; } @@ -1307,7 +1322,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q : provider->requestImage(imageId(url), &readSize, requestSize); if (!image.isNull()) { *ok = true; - return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestSize, providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform); + return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestSize, providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame); } break; } @@ -1317,7 +1332,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q : provider->requestPixmap(imageId(url), &readSize, requestSize); if (!pixmap.isNull()) { *ok = true; - return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()), readSize, requestSize, providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform); + return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()), readSize, requestSize, providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame); } break; } @@ -1347,7 +1362,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q QQuickTextureFactory *factory = texReader.read(); if (factory) { *ok = true; - return new QQuickPixmapData(declarativePixmap, url, factory, factory->textureSize(), requestSize, providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform); + return new QQuickPixmapData(declarativePixmap, url, factory, factory->textureSize(), requestSize, providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame); } else { errorString = QQuickPixmap::tr("Error decoding: %1").arg(url.toString()); if (f.fileName() != localFile) @@ -1356,9 +1371,10 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q } else { QImage image; QQuickImageProviderOptions::AutoTransform appliedTransform = providerOptions.autoTransform(); - if (readImage(url, &f, &image, &errorString, &readSize, requestSize, providerOptions, &appliedTransform)) { + int frameCount; + if (readImage(url, &f, &image, &errorString, &readSize, &frameCount, requestSize, providerOptions, &appliedTransform, frame)) { *ok = true; - return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestSize, providerOptions, appliedTransform); + return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestSize, providerOptions, appliedTransform, frame, frameCount); } else if (f.fileName() != localFile) { errorString += QString::fromLatin1(" (%1)").arg(f.fileName()); } @@ -1476,6 +1492,13 @@ QQuickImageProviderOptions::AutoTransform QQuickPixmap::autoTransform() const return QQuickImageProviderOptions::UsePluginDefaultTransform; } +int QQuickPixmap::frameCount() const +{ + if (d) + return d->frameCount; + return 0; +} + QQuickTextureFactory *QQuickPixmap::textureFactory() const { if (d) @@ -1554,7 +1577,7 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &reques load(engine, url, requestSize, options, QQuickImageProviderOptions()); } -void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &requestSize, QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions) +void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &requestSize, QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions, int frame, int frameCount) { if (d) { d->declarativePixmaps.remove(this); @@ -1562,7 +1585,7 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &reques d = nullptr; } - QQuickPixmapKey key = { &url, &requestSize, providerOptions }; + QQuickPixmapKey key = { &url, &requestSize, frame, providerOptions }; QQuickPixmapStore *store = pixmapStore(); QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end(); @@ -1574,7 +1597,7 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &reques QSize dummy; if (requestSize != dummy) qWarning() << "Ignoring sourceSize request for image url that came from grabToImage. Use the targetSize parameter of the grabToImage() function instead."; - const QQuickPixmapKey grabberKey = { &url, &dummy, QQuickImageProviderOptions() }; + const QQuickPixmapKey grabberKey = { &url, &dummy, 0, QQuickImageProviderOptions() }; iter = store->m_cache.find(grabberKey); } else if (options & QQuickPixmap::Cache) iter = store->m_cache.find(key); @@ -1596,7 +1619,7 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &reques if (!(options & QQuickPixmap::Asynchronous)) { bool ok = false; PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingStarted>(url)); - d = createPixmapDataSync(this, engine, url, requestSize, providerOptions, &ok); + d = createPixmapDataSync(this, engine, url, requestSize, providerOptions, frame, &ok); if (ok) { PIXMAP_PROFILE(pixmapLoadingFinished(url, QSize(width(), height()))); if (options & QQuickPixmap::Cache) @@ -1613,7 +1636,7 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &reques return; - d = new QQuickPixmapData(this, url, requestSize, providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform); + d = new QQuickPixmapData(this, url, requestSize, providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame, frameCount); if (options & QQuickPixmap::Cache) d->addToCache(); @@ -1647,9 +1670,9 @@ void QQuickPixmap::clear(QObject *obj) } } -bool QQuickPixmap::isCached(const QUrl &url, const QSize &requestSize, const QQuickImageProviderOptions &options) +bool QQuickPixmap::isCached(const QUrl &url, const QSize &requestSize, const int frame, const QQuickImageProviderOptions &options) { - QQuickPixmapKey key = { &url, &requestSize, options }; + QQuickPixmapKey key = { &url, &requestSize, frame, options }; QQuickPixmapStore *store = pixmapStore(); return store->m_cache.contains(key); diff --git a/src/quick/util/qquickpixmapcache_p.h b/src/quick/util/qquickpixmapcache_p.h index 91fb1ed3bb..ab5d391fa2 100644 --- a/src/quick/util/qquickpixmapcache_p.h +++ b/src/quick/util/qquickpixmapcache_p.h @@ -150,6 +150,7 @@ public: const QSize &implicitSize() const; const QSize &requestSize() const; QQuickImageProviderOptions::AutoTransform autoTransform() const; + int frameCount() const; QImage image() const; void setImage(const QImage &); void setPixmap(const QQuickPixmap &other); @@ -164,7 +165,7 @@ public: void load(QQmlEngine *, const QUrl &, QQuickPixmap::Options options); void load(QQmlEngine *, const QUrl &, const QSize &); void load(QQmlEngine *, const QUrl &, const QSize &, QQuickPixmap::Options options); - void load(QQmlEngine *, const QUrl &, const QSize &, QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions); + void load(QQmlEngine *, const QUrl &, const QSize &, QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions, int frame = 0, int frameCount = 1); void clear(); void clear(QObject *); @@ -175,7 +176,7 @@ public: bool connectDownloadProgress(QObject *, int); static void purgeCache(); - static bool isCached(const QUrl &url, const QSize &requestSize, const QQuickImageProviderOptions &options); + static bool isCached(const QUrl &url, const QSize &requestSize, const int frame, const QQuickImageProviderOptions &options); static const QLatin1String itemGrabberScheme; diff --git a/tests/auto/quick/qquickborderimage/data/multi.ico b/tests/auto/quick/qquickborderimage/data/multi.ico Binary files differnew file mode 100644 index 0000000000..b748ceaa29 --- /dev/null +++ b/tests/auto/quick/qquickborderimage/data/multi.ico diff --git a/tests/auto/quick/qquickborderimage/data/multiframe.qml b/tests/auto/quick/qquickborderimage/data/multiframe.qml new file mode 100644 index 0000000000..8bd32da5a6 --- /dev/null +++ b/tests/auto/quick/qquickborderimage/data/multiframe.qml @@ -0,0 +1,8 @@ +import QtQuick 2.14 + +BorderImage { + source: "multi.ico" + border { left: 19; top: 19; right: 19; bottom: 19 } + width: 160; height: 160 + horizontalTileMode: BorderImage.Stretch +} diff --git a/tests/auto/quick/qquickborderimage/data/multiframeAsync.qml b/tests/auto/quick/qquickborderimage/data/multiframeAsync.qml new file mode 100644 index 0000000000..059e4becf3 --- /dev/null +++ b/tests/auto/quick/qquickborderimage/data/multiframeAsync.qml @@ -0,0 +1,9 @@ +import QtQuick 2.14 + +BorderImage { + source: "multi.ico" + asynchronous: true + border { left: 19; top: 19; right: 19; bottom: 19 } + width: 160; height: 160 + horizontalTileMode: BorderImage.Stretch +} diff --git a/tests/auto/quick/qquickborderimage/tst_qquickborderimage.cpp b/tests/auto/quick/qquickborderimage/tst_qquickborderimage.cpp index 9292e1886a..4181f46551 100644 --- a/tests/auto/quick/qquickborderimage/tst_qquickborderimage.cpp +++ b/tests/auto/quick/qquickborderimage/tst_qquickborderimage.cpp @@ -79,6 +79,8 @@ private slots: #if QT_CONFIG(opengl) void borderImageMesh(); #endif + void multiFrame_data(); + void multiFrame(); private: QQmlEngine engine; @@ -601,6 +603,57 @@ void tst_qquickborderimage::borderImageMesh() qPrintable(errorMessage)); } #endif + +void tst_qquickborderimage::multiFrame_data() +{ + QTest::addColumn<QString>("qmlfile"); + QTest::addColumn<bool>("asynchronous"); + + QTest::addRow("default") << "multiframe.qml" << false; + QTest::addRow("async") << "multiframeAsync.qml" << true; +} + +void tst_qquickborderimage::multiFrame() +{ + QFETCH(QString, qmlfile); + QFETCH(bool, asynchronous); + Q_UNUSED(asynchronous) + + QQuickView view(testFileUrl(qmlfile)); + QQuickBorderImage *image = qobject_cast<QQuickBorderImage*>(view.rootObject()); + QVERIFY(image); + QSignalSpy countSpy(image, SIGNAL(frameCountChanged())); + QSignalSpy currentSpy(image, SIGNAL(currentFrameChanged())); + if (asynchronous) { + QCOMPARE(image->frameCount(), 0); + QTRY_COMPARE(image->frameCount(), 4); + QCOMPARE(countSpy.count(), 1); + } else { + QCOMPARE(image->frameCount(), 4); + } + QCOMPARE(image->currentFrame(), 0); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + QImage contents = view.grabWindow(); + // The first frame looks blue, approximately qRgba(0x43, 0x7e, 0xd6, 0xff) + QRgb color = contents.pixel(60, 60); + QVERIFY(qRed(color) < 0xc0); + QVERIFY(qGreen(color) < 0xc0); + QVERIFY(qBlue(color) > 0xc0); + + image->setCurrentFrame(1); + QTRY_COMPARE(image->status(), QQuickImageBase::Ready); + QCOMPARE(currentSpy.count(), 1); + QCOMPARE(image->currentFrame(), 1); + contents = view.grabWindow(); + // The second frame looks green, approximately qRgba(0x3a, 0xd2, 0x31, 0xff) + color = contents.pixel(60, 60); + QVERIFY(qRed(color) < 0xc0); + QVERIFY(qGreen(color) > 0xc0); + QVERIFY(qBlue(color) < 0xc0); +} + QTEST_MAIN(tst_qquickborderimage) #include "tst_qquickborderimage.moc" diff --git a/tests/auto/quick/qquickimage/data/multi.ico b/tests/auto/quick/qquickimage/data/multi.ico Binary files differnew file mode 100644 index 0000000000..b748ceaa29 --- /dev/null +++ b/tests/auto/quick/qquickimage/data/multi.ico diff --git a/tests/auto/quick/qquickimage/data/multiframe.qml b/tests/auto/quick/qquickimage/data/multiframe.qml new file mode 100644 index 0000000000..df70bc784c --- /dev/null +++ b/tests/auto/quick/qquickimage/data/multiframe.qml @@ -0,0 +1,5 @@ +import QtQuick 2.14 + +Image { + source: "multi.ico" +} diff --git a/tests/auto/quick/qquickimage/data/multiframeAsync.qml b/tests/auto/quick/qquickimage/data/multiframeAsync.qml new file mode 100644 index 0000000000..167b4a3e57 --- /dev/null +++ b/tests/auto/quick/qquickimage/data/multiframeAsync.qml @@ -0,0 +1,6 @@ +import QtQuick 2.14 + +Image { + source: "multi.ico" + asynchronous: true +} diff --git a/tests/auto/quick/qquickimage/tst_qquickimage.cpp b/tests/auto/quick/qquickimage/tst_qquickimage.cpp index 34c18aa64b..23635590e4 100644 --- a/tests/auto/quick/qquickimage/tst_qquickimage.cpp +++ b/tests/auto/quick/qquickimage/tst_qquickimage.cpp @@ -97,6 +97,8 @@ private slots: void highDpiFillModesAndSizes(); void hugeImages(); void urlInterceptor(); + void multiFrame_data(); + void multiFrame(); private: QQmlEngine engine; @@ -1132,6 +1134,56 @@ void tst_qquickimage::urlInterceptor() QTRY_COMPARE(object->progress(), 1.0); } +void tst_qquickimage::multiFrame_data() +{ + QTest::addColumn<QString>("qmlfile"); + QTest::addColumn<bool>("asynchronous"); + + QTest::addRow("default") << "multiframe.qml" << false; + QTest::addRow("async") << "multiframeAsync.qml" << true; +} + +void tst_qquickimage::multiFrame() +{ + QFETCH(QString, qmlfile); + QFETCH(bool, asynchronous); + Q_UNUSED(asynchronous) + + QQuickView view(testFileUrl(qmlfile)); + QQuickImage *image = qobject_cast<QQuickImage*>(view.rootObject()); + QVERIFY(image); + QSignalSpy countSpy(image, SIGNAL(frameCountChanged())); + QSignalSpy currentSpy(image, SIGNAL(currentFrameChanged())); + if (asynchronous) { + QCOMPARE(image->frameCount(), 0); + QTRY_COMPARE(image->frameCount(), 4); + QCOMPARE(countSpy.count(), 1); + } else { + QCOMPARE(image->frameCount(), 4); + } + QCOMPARE(image->currentFrame(), 0); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + QImage contents = view.grabWindow(); + // The first frame is a blue ball, approximately qRgba(0x33, 0x6d, 0xcc, 0xff) + QRgb color = contents.pixel(16, 16); + QVERIFY(qRed(color) < 0xc0); + QVERIFY(qGreen(color) < 0xc0); + QVERIFY(qBlue(color) > 0xc0); + + image->setCurrentFrame(1); + QTRY_COMPARE(image->status(), QQuickImageBase::Ready); + QCOMPARE(currentSpy.count(), 1); + QCOMPARE(image->currentFrame(), 1); + contents = view.grabWindow(); + // The second frame is a green ball, approximately qRgba(0x27, 0xc8, 0x22, 0xff) + color = contents.pixel(16, 16); + QVERIFY(qRed(color) < 0xc0); + QVERIFY(qGreen(color) > 0xc0); + QVERIFY(qBlue(color) < 0xc0); +} + QTEST_MAIN(tst_qquickimage) #include "tst_qquickimage.moc" |