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/489865I 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. -
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?
-
QGraphicsItem
does not inheritQObject
so don't useQ_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 writeusing 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++...