I am using the QGraphicsPixmapItem to show an image on the display. Now, I want to be able to update this image on the fly, but I seem to be running into some issues.
This is the header file:
class Enemy_View : public QGraphicsPixmapItem
{
public:
Enemy_View(QGraphicsScene &myScene);
void defeat();
private:
QGraphicsScene &scene;
QPixmap image;
}
And here is the cpp file
Enemy_View::Enemy_View(QGraphicsScene &myScene):
image{":/images/alive.png"}, scene(myScene)
{
QGraphicsPixmapItem *enemyImage = scene.addPixmap(image.scaledToWidth(20));
enemyImage->setPos(20, 20);
this->defeat();
}
void Enemy_View::defeat(void)
{
image.load(":/images/dead.png");
this->setPixmap(image);
this->update();
}
SO the idea is that I want to be able to call the defeat method on my object, that then edits some attributes and eventually changes the image. However, what I am doing now does not work. The alive.png image does display, but does not get updated to the dead.png one.
Update 1
As mentioned by Marek R I appear to be replicating a lot of built-in functionality. I tried to clean this up but now nothing is showing up on the scene anymore.
.h file
class Enemy_View : public QGraphicsPixmapItem
{
public:
Enemy_View(QGraphicsScene &myScene);
void defeat();
private:
QGraphicsScene &scene;
/* Extra vars */
};
.cpp file
Enemy_View::Enemy_View(QGraphicsScene &myScene):
scene(myScene)
{
/* This part would seem ideal but doesn't work */
this->setPixmap(QPixmap(":/images/alive.png").scaledToWidth(10));
this->setPos(10, 10);
scene.addItem(this);
/* This part does render the images */
auto *thisEl = scene.addPixmap(QPixmap(":/images/Jackskellington.png").scaledToWidth(10));
thisEl->setPos(10, 10);
scene.addItem(this);
this->defeat();
}
void Enemy_View::defeat(void)
{
this->setPixmap(QPixmap(":/images/dead.png"));
}
So I removed the QPixmap, but I'm not sure whether I can remove the QGraphicsScene. In my cpp-file you can see I have two versions of the constructor now. The first part, using the this seems like an ideal solution, but does not display the image on the screen (even though it does compile without errors). The second version with thisEl does render it. What am I doing wrong with the first version?
Why FGS you are subclassing QGraphicsPixmapItem? QGraphicsPixmapItem has all functionality you need. And those new fields you have added does nothing, they only try replicate functionality which is already there (but with this implementation it does nothing).
This suppose to be something like that:
QPixmp image(":/images/alive.png");
QGraphicsPixmapItem *enemyItem = scene.addPixmap(image.scaledToWidth(20));
enemyItem->setPos(20, 20);
// and after something dies
QPixmap dieImage(":/images/dead.png");
enemyItem->setPixmap(dieImage);
Related
I want to write an tool of objective annotation using qt5 MinGw32, which could annotate object in video file and diplay them during playing. So QGraphicsScene is inherited to implementation the function.
Something wrong happens when I change the QGraphicsScene's background frequently(e.g. 30 fps): most of time it works as expected while sometimes the background could not move.
Here is my code:
void MyGraphicsScene::UpdateFrame(QImage image)
{
QPixmap pixmap = QPixmap::fromImage(image);
//fix the view's size and scene's size
views().first()->setFixedSize(pixmap.size());
setSceneRect(0,0, pixmap.width(), pixmap.height());
setBackgroundBrush(QBrush(pixmap));
}
...
//In another thread
void Thread::run()
{
...
myScene.UpdateFrame(newImage);
...
}
I have search through the qt's document and found no answer.
However, there is something strange:
when wrong thing happens, I find the background continues to change, but it didn't show change on the screen unless I move the app to another screen (I have two screen). However, with the app moved, the QGraphicsScene's background just change once and becomes static afterwards.
I guess the background has been changed but doesn't repainted, so I used update(), but it didn't help.
BTW, I couldn't reproduce the occasion, sometiems it happens, somtimes not.
do I need to represented any methods? Or I called the methods in a wrong way? Or is there an alternative approach that would work?
Many thanks for your help in advance.
You should not change QtGui elements from a different thread by calling a method directly.
Use the Qt signal-slot concept.
Calling update() is not necessary.
class MyGraphicsScene{...
....
signals:
void singalFromThread(QImage image);
public:
//CTor
MyGraphicsScene()
{
connect(this, SIGNAL(singalFromThread(QImage)),this,SLOT(UpdateFrame(QImage)));
}
//Call this method from your thread
void updateForwarder(QImage image)
{
//Jump into gui thread
emit singalFromThread(image);
}
public slots:
void UpdateFrame(QImage image)
{
setBackgroundBrush(....);
}
};
I am creating a simple gauge in Qt 4.7.4, and everything is working wonderfully. Except for the fact that, for the life of me, I cannot get the dial shape to paint over the text labels when it passes over them. It always paints it behind the label. I am just using a simple drawpolygon() method.
I'm thinking this has something to do about paint events? I am drawing everything inside a QFrame inside a MainWindow. I am using QFrame's paintEvent.
Edit:
The QLabels are created on start up with new QLabel(this). They are only created once, and never touched again ( Similar to manually adding them on the Ui with Designer). The drawpolygon() is in the QFrame's Paint event.
"myclass.h"
class gauge : public QFrame
{
Q_OBJECT
public:
explicit gauge(QWidget *parent = 0);
~gauge();
void setValues(int req, int Limit, bool extra=false);
private:
void drawDial();
protected:
void paintEvent(QPaintEvent *e);
};
"myclass.cpp"
void gauge::paintEvent(QPaintEvent *e)
{
Q_UNUSED(e);
drawDial();
return;
}
void gauge::drawDial()
{
QPainter Needle(this);
Needle.save();
Needle.setRenderHint(Needle.Antialiasing, true); // Needle was Staggered looking, This will make it smooth
Needle.translate(centrePt); // Center of Widget
Needle.drawEllipse(QPoint(0,0),10,10);
Needle.restore();
Needle.end();
}
If the gauge widget and the QLabels are siblings, then you can move the gauge widget to the front by calling its raise() method.
If the QLabels are children of the gauge widget, on the other hand, then they will always display in front of it. In that case you can either reorganize your widget hierarchy so that they are siblings instead, or you can get rid of the QLabels and simply call drawText() from your paintEvent() method instead (after drawDial() returns)
I'm trying to write a QML plugin that reads frames from a video (using a custom widget to do that task, NOT QtMultimedia/Phonon), and each frame is converted to a QImage RGB888, and then displayed on a QGLWidget (for performance reasons). Right now nothing is draw to the screen and the screen stays white all the time.
It's important to state that I already have all of this working without QGLWidget, so I know the issue is setting up and drawing on QGLWidget.
The plugin is being registered with:
qmlRegisterType<Video>(uri,1,0,"Video");
so Video is the main class of the plugin. On it's constructor we have:
Video::Video(QDeclarativeItem* parent)
: QDeclarativeItem(parent), d_ptr(new VideoPrivate(this))
{
setFlag(QGraphicsItem::ItemHasNoContents, false);
Q_D(Video);
QDeclarativeView* view = new QDeclarativeView;
view->setViewport(&d->canvas()); // canvas() returns a reference to my custom OpenGL Widget
}
Before I jump to the canvas object, let me say that I overloaded Video::paint() so it calls canvas.paint() while passing QImage as parameter, I don't know if this is the right way to do it so I would like some advice on this:
void Video::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
Q_UNUSED(painter);
Q_UNUSED(widget);
Q_UNUSED(option);
Q_D(Video);
// I know for sure at this point "d->image()" is valid, but I'm hiding the code for clarity
d->canvas().paint(painter, option, d->image());
}
The canvas object is declared as GLWidget canvas; and the header of this class is defined as:
class GLWidget : public QGLWidget
{
Q_OBJECT
public:
explicit GLWidget(QWidget* parent = NULL);
~GLWidget();
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QImage* image);
};
Seems pretty simple. Now, the implementation of QGLWidget is the following:
GLWidget::GLWidget(QWidget* parent)
: QGLWidget(QGLFormat(QGL::SampleBuffers), parent)
{
// Should I do something here?
// Maybe setAutoFillBackground(false); ???
}
GLWidget::~GLWidget()
{
}
And finally:
void GLWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QImage* image)
{
// I ignore painter because it comes from Video, so I create a new one:
QPainter gl_painter(this);
// Perform drawing as Qt::KeepAspectRatio
gl_painter.fillRect(QRectF(QPoint(0, 0), QSize(this->width(), this->height())), Qt::black);
QImage scaled_img = image->scaled(QSize(this->width(), this->height()), _ar, Qt::FastTransformation);
gl_painter.drawImage(qRound(this->width()/2) - qRound(scaled_img.size().width()/2),
qRound(this->height()/2) - qRound(scaled_img.size().height()/2),
scaled_img);
}
What am I missing?
I originally asked this question on Qt Forum but got no replies.
Solved. The problem was that I was trying to create a new GL context within my plugin when I should be retrieving the GL context from the application that loaded it.
This code was very helpful to understand how to accomplish that.
By the way, I discovered that the stuff was being draw inside view. It's just that I needed to execute view->show(), but that created another window which was not what I was looking for. The link I shared above has the answer.
I think that you have to draw on your glwidget using the opengl functions.
One possible way is to override your paintGL() method in GLWidget
and then draw your image with glDrawPixels().
glClear(GL_COLOR_BUFFER_BIT);
glDrawPixels(buffer.width(), buffer.height(), GL_RGBA, GL_UNSIGNED_BYTE, buffer.bits());
Where buffer is a QImage object that needs to be converted using QGLWidget::converrtToGLFormat() static method.
Look at this source for reference:
https://github.com/nayyden/ZVector/blob/master/src/GLDebugBufferWidget.cpp
I'm making a custom widget in Qt, and drawing an image as its background. The background image is to be exactly the same for all instances of widgets. I'd like to know if I'm doing it in an appropriate way.
Here's how I'm doing it now:
// QMyWidget.h
class QMyWidget : public QWidget
{
/* some stuff.. and then: */
protected:
static QImage *imgBackground;
}
// QMyWidget.cpp
QImage *QMyWidget::imgDial = NULL;
QMyWidget::QMyWidget(QWidget *parent) : QWidget(parent)
{
if(imgBackground== NULL)
{
imgBackground= new QImage();
imgBackground->load(":/Images/background.png");
}
}
void QMyWidget::paintEvent(QPaintEvent *e)
{
QPainter painter(this);
painter.drawImage(QPoint(), *imgBackground);
}
The code works just fine, but is this considered a good way to do it?
That's one way of doing it, and it's a reasonably good way if you're only dealing with a single image, but if you ever decide to scale and use a couple of custom resources then the Qt Resource System is the better way to do it. It'll save you time in code (don't need to repeatedly do QImage), and has a few other nice features like resource compression.
Please check the following implementation:
BoardView.h
#include <QGraphicsView>
class BoardView : public QGraphicsView
{
protected:
void resizeEvent(QResizeEvent* event);
public:
BoardView();
};
BoardView.cpp
BoardView::BoardView()
{
}
void BoardView::resizeEvent(QResizeEvent* event)
{
QGraphicsView::resizeEvent(event);
double wd = width();
double ht = height();
double min_wh = min(wd, ht);
qDebug() << min_wh;
QTransform transform;
transform.scale(min_wh / Options::getMainBoardMinSize(), min_wh / Options::getMainBoardMinSize());
setTransform(transform);
}
The above two files are directly copied from an existing project, say P1. The purpose of this code is to handle resizeEvent() and scale the view according to widget size.
The problem is, this works exactly as it should in project P1. Everything is scaled as expected. But resizeEvent() is not called from the new project at all, when the view is resized by resizing main window, on which it is a widget.
The BoardView object does everything else as expected. It shows the scene. It captures and passes mouse events to the scene etc. Only the resizeEvent() is not called.
What am I doing wrong?
I'm using Qt Creator 2.0.1, Qt 4.7 32 bit on Windows 7 Ultimate 32 bit.
Hmm... Are you missing some code here?
Or your class declaration is incorrect - you should change it to
class BoardView : public QGraphicsView
{
Q_OBJECT
...
public:
BoardView(QWidget* parent);
};
...
BoardView::BoardView(QWidget* parent):QGraphicsView(parent)
{
}
Sorry if this is omitted by you because of triviality, but I don't know your QT experience level. This might be why QT events works incorrectly with your class...
Why a BoardView::resizeEvent() is not called, yet the QGraphicsView was showing the QGraphicsScene correctly? The reason is a silly mistake, instead of
BoardView _board_view;
I wrote
QGraphicsView _board_view;
Sorry for posting such a silly question!