Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Animated GIF in QGraphicsScene/QGraphicsView



  • Good morning,
    I have QGraphicsScene in QGraphicsView to display and manipulate images. It works well, no issues here. It is the first time I tried QGraphicsScene instead if QLabel and I am very fond of how neatly it all works.
    However I stumbled upon the edge case where animated gif is selected for display - as expected, only the first frame is displayed.
    I tried usual way of having QMovie assigned to QLabel etc. - added that to the scene with addWidget() method but label is not visible.
    This project is built using Qt5.15.2 (macOS clang/win mingw8)

    So the question: is there any sane way of making it work without me having to rewrite half of the Qt ;) or should I just let go the idea of displaying animated gifs in the scene?

    As usual, many thanks in advance.
    Artur



  • @artwaw
    I would search for what others have done to get this working. For example
    https://forum.qt.io/topic/15658/solved-how-to-play-gif-animation-in-qgraphicsview-widget
    https://www.qtcentre.org/threads/61528-How-to-place-animated-GIF-on-QGraphicsScene
    https://stackoverflow.com/a/21083803/489865

    I think the last one at least is doing it via QMovie/QLabel/QGraphicsScene::addWidget(). So don't know why you seem to say you can't.



  • @artwaw
    I would search for what others have done to get this working. For example
    https://forum.qt.io/topic/15658/solved-how-to-play-gif-animation-in-qgraphicsview-widget
    https://www.qtcentre.org/threads/61528-How-to-place-animated-GIF-on-QGraphicsScene
    https://stackoverflow.com/a/21083803/489865

    I think the last one at least is doing it via QMovie/QLabel/QGraphicsScene::addWidget(). So don't know why you seem to say you can't.



  • @JonB Thanks. This didn't popup (first result seems the way to go) when I used search in the forum O_O


  • Moderators

    You could certainly use a label, but proxy widgets are a bit heavy. All you really need to do is paint a frame of QMovie, so you can very easily make a custom graphics item that does that. You just need to implement 3 simple methods and you should be good to go, e.g.

    class GraphicsMovieItem : public QGraphicsItem
    {
    public:
        using QGraphicsItem::QGraphicsItem;
    
        void setMovie(QMovie* movie)
        {
            prepareGeometryChange();
            QObject::disconnect(mConnection); // disconnect old object
            mMovie = movie;
            if (mMovie)
                mConnection = QObject::connect(mMovie, &QMovie::frameChanged, [=]{ update(); });
        }
    
        QRectF boundingRect() const override
        {
            if (mMovie)
                return mMovie->frameRect();
            else
                return QRectF();
        }
    
        void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override
        {
            if (mMovie)
                painter->drawPixmap(mMovie->frameRect(), mMovie->currentPixmap(), mMovie->frameRect());
        }
    
    private:
        QPointer<QMovie> mMovie;
        QMetaObject::Connection mConnection;
    };
    


  • @Chris-Kawa That's smart! I'll definitely try this approach out, thank you!



  • @Chris-Kawa I am sorry to bother you but example code provided raises some issues during the compilation:

    /Users/arturwawrowski/cpp/build-TNImageViewer-Desktop_Qt_5_15_2_clang_64bit-Debug/moc_qgraphicsmovieitem.cpp:67: error: no member named 'staticMetaObject' in 'QGraphicsItem'; did you mean simply 'staticMetaObject'?
    moc_qgraphicsmovieitem.cpp:67:34: error: no member named 'staticMetaObject' in 'QGraphicsItem'; did you mean simply 'staticMetaObject'?
        QMetaObject::SuperData::link<QGraphicsItem::staticMetaObject>(),
                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                     staticMetaObject
    moc_qgraphicsmovieitem.cpp:66:58: note: 'staticMetaObject' declared here
    QT_INIT_METAOBJECT const QMetaObject QGraphicsMovieItem::staticMetaObject = { {
                                                             ^
    

    and several of:

    /Users/arturwawrowski/cpp/build-TNImageViewer-Desktop_Qt_5_15_2_clang_64bit-Debug/moc_qgraphicsmovieitem.cpp:78: error: invalid use of non-static data member 'd_ptr'
    moc_qgraphicsmovieitem.cpp:78:21: error: invalid use of non-static data member 'd_ptr'
        return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
               ~~~~~~~~~^~~~~
    

    intertwined with:

    /Users/arturwawrowski/cpp/build-TNImageViewer-Desktop_Qt_5_15_2_clang_64bit-Debug/moc_qgraphicsmovieitem.cpp:78: error: 'd_ptr' is a protected member of 'QObject'
    moc_qgraphicsmovieitem.cpp:78:21: error: 'd_ptr' is a protected member of 'QObject'
    ../../Qt/5.15.2/clang_64/lib/QtCore.framework/Headers/qobject.h:450:33: note: declared protected here
        QScopedPointer<QObjectData> d_ptr;
                                    ^
    

    Code:
    header

    #ifndef QGRAPHICSMOVIEITEM_H
    #define QGRAPHICSMOVIEITEM_H
    
    #include <QGraphicsItem>
    #include <QObject>
    #include <QMovie>
    #include <QPainter>
    
    class QGraphicsMovieItem : public QGraphicsItem
    {
        Q_OBJECT
    public:
        QGraphicsMovieItem(QGraphicsItem *parent = nullptr);
        void setMovie(QMovie* movie);
        QRectF boundingRect() const override;
        void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
    
    private:
        QPointer<QMovie> mMovie;
        QMetaObject::Connection mConnection;
    };
    
    #endif // QGRAPHICSMOVIEITEM_H
    

    implementation:

    #include "qgraphicsmovieitem.h"
    
    QGraphicsMovieItem::QGraphicsMovieItem(QGraphicsItem *parent) : QGraphicsItem(parent) {}
    
    void QGraphicsMovieItem::setMovie(QMovie* movie) {
        prepareGeometryChange();
        QObject::disconnect(mConnection);
        mMovie = movie;
        if (mMovie) {
            mConnection = QObject::connect(mMovie, &QMovie::frameChanged, [=]{ update(); });
        }
    }
    
    QRectF QGraphicsMovieItem::boundingRect() const {
        if (mMovie) { return mMovie->frameRect(); }
        else { return QRectF(); }
    }
    
    void QGraphicsMovieItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
        Q_UNUSED(option)
        Q_UNUSED(widget)
        if (mMovie) {
            painter->drawPixmap(mMovie->frameRect(), mMovie->currentPixmap(), mMovie->frameRect());
    
        }
    }
    

    What I did wrong this time?


  • Moderators

    QGraphicsItem does not inherit QObject so don't use Q_OBJECT for your class.

    Btw. modern C++ has this wonderful thing called inherited constructors, so you don't have to write the boilerplate empty constructors that just pass parameters to base classes. Instead of writing QGraphicsMovieItem(QGraphicsItem *parent = nullptr); and implementing it as an empty method you can just write using QGraphicsItem::QGraphicsItem; and it does exactly the same thing. Super useful when base class has multiple constructors with different parameters as you can inherit them all in just that one line.



  • @Chris-Kawa Noted thank you. Now it compiles without errors. I think I need to read a bit about newer c++...