Animated GIF in QGraphicsScene/QGraphicsView
-
wrote on 13 Feb 2021, 13:19 last edited by
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 -
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.
Arturwrote on 13 Feb 2021, 14:28 last edited by JonB@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. -
@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; };
-
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; };
wrote on 13 Feb 2021, 15:22 last edited by@Chris-Kawa That's smart! I'll definitely try this approach out, thank you!
-
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; };
wrote on 13 Feb 2021, 17:56 last edited by@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. -
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.wrote on 13 Feb 2021, 18:17 last edited by@Chris-Kawa Noted thank you. Now it compiles without errors. I think I need to read a bit about newer c++...
1/8