diff --git a/src/gui/image/qmovie.cpp b/src/gui/image/qmovie.cpp index 5558f160f3d..56797393544 100644 --- a/src/gui/image/qmovie.cpp +++ b/src/gui/image/qmovie.cpp @@ -149,6 +149,7 @@ #include "qlist.h" #include "qbuffer.h" #include "qdir.h" +#include "qloggingcategory.h" #include "private/qobject_p.h" #include "private/qproperty_p.h" @@ -156,6 +157,8 @@ QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcImageIo) + class QFrameInfo { public: @@ -306,6 +309,19 @@ QFrameInfo QMoviePrivate::infoForFrame(int frameNumber) return QFrameInfo(); // Invalid } + // For an animated image format, the tradition is that QMovie calls read() + // until canRead() == false, because the number of frames may not be known + // in advance; but if we're abusing a multi-frame format as an animation, + // canRead() may remain true, and we need to stop after reading the maximum + // number of frames that the image provides. + const bool supportsAnimation = reader->supportsOption(QImageIOHandler::Animation); + const int stopAtFrame = supportsAnimation ? -1 : frameCount(); + + // For an animated image format, QImageIOHandler::nextImageDelay() should + // provide the time to wait until showing the next frame; but multi-frame + // formats are not expected to provide this value, so use 1000 ms by default. + const int nextFrameDelay = supportsAnimation ? reader->nextImageDelay() : 1000; + if (cacheMode == QMovie::CacheNone) { if (frameNumber != currentFrameNumber+1) { // Non-sequential frame access @@ -335,8 +351,12 @@ QFrameInfo QMoviePrivate::infoForFrame(int frameNumber) } } } - if (reader->canRead()) { + qCDebug(lcImageIo, "CacheNone: read frame %d of %d", frameNumber, stopAtFrame); + if (stopAtFrame > 0 ? (frameNumber < stopAtFrame) : reader->canRead()) { // reader says we can read. Attempt to actually read image + // But if it's a non-animated multi-frame format and we know the frame count, stop there. + if (stopAtFrame > 0) + reader->jumpToImage(frameNumber); QImage anImage = reader->read(); if (anImage.isNull()) { // Reading image failed. @@ -344,7 +364,7 @@ QFrameInfo QMoviePrivate::infoForFrame(int frameNumber) } if (frameNumber > greatestFrameNumber) greatestFrameNumber = frameNumber; - return QFrameInfo(QPixmap::fromImage(std::move(anImage)), reader->nextImageDelay()); + return QFrameInfo(QPixmap::fromImage(std::move(anImage)), nextFrameDelay); } else if (frameNumber != 0) { // We've read all frames now. Return an end marker haveReadAll = true; @@ -360,15 +380,19 @@ QFrameInfo QMoviePrivate::infoForFrame(int frameNumber) if (frameNumber > greatestFrameNumber) { // Frame hasn't been read from file yet. Try to do it for (int i = greatestFrameNumber + 1; i <= frameNumber; ++i) { - if (reader->canRead()) { + qCDebug(lcImageIo, "CacheAll: read frame %d of %d", frameNumber, stopAtFrame); + if (stopAtFrame > 0 ? (frameNumber < stopAtFrame) : reader->canRead()) { // reader says we can read. Attempt to actually read image + // But if it's a non-animated multi-frame format and we know the frame count, stop there. + if (stopAtFrame > 0) + reader->jumpToImage(frameNumber); QImage anImage = reader->read(); if (anImage.isNull()) { // Reading image failed. return QFrameInfo(); // Invalid } greatestFrameNumber = i; - QFrameInfo info(QPixmap::fromImage(std::move(anImage)), reader->nextImageDelay()); + QFrameInfo info(QPixmap::fromImage(std::move(anImage)), nextFrameDelay); // Cache it! frameMap.insert(i, info); if (i == frameNumber) { diff --git a/tests/auto/gui/image/qmovie/CMakeLists.txt b/tests/auto/gui/image/qmovie/CMakeLists.txt index 0130ee270b9..140614a3120 100644 --- a/tests/auto/gui/image/qmovie/CMakeLists.txt +++ b/tests/auto/gui/image/qmovie/CMakeLists.txt @@ -8,7 +8,7 @@ # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} - animations/*) + animations/* multiframe/*) list(APPEND test_data ${test_data_glob}) qt_internal_add_test(tst_qmovie @@ -25,6 +25,7 @@ set(resources_resource_files "animations/comicsecard.gif" "animations/corrupt.gif" "animations/trolltech.gif" + "multiframe/Obj_N2_Internal_Mem.ico" ) qt_internal_add_resource(tst_qmovie "resources" diff --git a/tests/auto/gui/image/qmovie/multiframe/Obj_N2_Internal_Mem.ico b/tests/auto/gui/image/qmovie/multiframe/Obj_N2_Internal_Mem.ico new file mode 100644 index 00000000000..8da119efddf Binary files /dev/null and b/tests/auto/gui/image/qmovie/multiframe/Obj_N2_Internal_Mem.ico differ diff --git a/tests/auto/gui/image/qmovie/tst_qmovie.cpp b/tests/auto/gui/image/qmovie/tst_qmovie.cpp index faa0bee3b9f..4e30fe95d40 100644 --- a/tests/auto/gui/image/qmovie/tst_qmovie.cpp +++ b/tests/auto/gui/image/qmovie/tst_qmovie.cpp @@ -43,6 +43,9 @@ private slots: void emptyMovie(); void bindings(); void automatedBindings(); +#ifndef QT_NO_ICO + void multiFrameImage(); +#endif }; // Testing get/set functions @@ -259,5 +262,32 @@ void tst_QMovie::automatedBindings() } } +#ifndef QT_NO_ICO +/*! \internal + Test behavior of QMovie with image formats that are multi-frame, + but not normally intended as animation formats (such as tiff and ico). +*/ +void tst_QMovie::multiFrameImage() +{ + QMovie movie(QFINDTESTDATA("multiframe/Obj_N2_Internal_Mem.ico")); + const int expectedFrameCount = 9; + + QVERIFY(movie.isValid()); + QCOMPARE(movie.frameCount(), expectedFrameCount); + movie.setSpeed(1000); // speed up the test: play at 10 FPS (1000% of normal) + QElapsedTimer playTimer; + QSignalSpy frameChangedSpy(&movie, &QMovie::frameChanged); + QSignalSpy errorSpy(&movie, &QMovie::error); + QSignalSpy finishedSpy(&movie, &QMovie::finished); + playTimer.start(); + movie.start(); + QTRY_COMPARE(finishedSpy.size(), 1); + QCOMPARE_GE(playTimer.elapsed(), 100 * expectedFrameCount); + QCOMPARE(movie.nextFrameDelay(), 100); + QCOMPARE(errorSpy.size(), 0); + QCOMPARE(frameChangedSpy.size(), expectedFrameCount); +} +#endif + QTEST_MAIN(tst_QMovie) #include "tst_qmovie.moc"