Paint a QGraphicsItem to a QImage without needing a scene/view - qt

So here's what I'm trying to do - Using a custom QGraphicsItem, I have my QPainter setup to paint into a QImage, which I then save to a file (or just keep the QImage in memory until I need it).
The issue I've found is that QGraphicsItem::paint() is only called if the QGraphcsItem belongs to a scene, the scene belongs to a view, AND the view and scene are not hidden.
Here's the code outside my project for testing purposes:
MyQGfx Class
void MyQGfx::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
qDebug() << "begin of paint function";
QRectF rec = boundingRect();
QImage image(boundingRect().size().toSize(),
QImage::Format_ARGB32_Premultiplied);
image.fill(0);
// construct a dedicated offline painter for this image
QPainter imagePainter(&image);
imagePainter.translate(-boundingRect().topLeft());
// paint the item using imagePainter
imagePainter.setPen(Qt::blue);
imagePainter.setBrush(Qt::green);
imagePainter.drawEllipse(-50, -50, 100, 100);
imagePainter.end();
if(image.save("C://plot.jpg"))
{
qDebug() << "written";
}
else {
qDebug() << "not written";
}
}
MainWindow Class
....
QGraphicsView* view = new QGraphicsView(this);
QGraphicsScene* scene = new QGraphicsScene(this);
view->setScene(scene);
MyQGfx* gfx = new MyQGfx();
scene->addItem(gfx);
gfx->update();
....
This all works fine, but I don't want a view/scene necessary, as it would be displayed on the mainwindow - is there any way around this?

Can't you just create a custom method accepting a QPainter, one painting on a QImage and one on your item?

Related

QGraphicsView paint not called

I'm working on a tree view for a user-defined class called Family_tree with nodes having a pointer to their father, mother, spouse and a vector of children. I created a TreeViewNode class derived from QGraphicsItem with a node* m_node member and overrides for the boundingRect and paint methods like so:
QRectF TreeViewNode::boundingRect() const {
return QRectF(-50, -50, 100, 100);
}
void TreeViewNode::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
qDebug() << "Paint function called";
painter->drawRect(boundingRect());
painter->drawText(boundingRect(), Qt::AlignCenter, QString::fromStdString(m_node->getPatient().get_Name()));
for (auto child : m_node->getChildren()) {
TreeViewNode* childNode = new TreeViewNode(child, this);
childNode->setPos(0, 100);
QLineF line(0, 50, 0, 150);
painter->drawLine(line);
}
}
I've set up a Family_tree with just one node for simplicity (the root) and I'm trying to get it to show in the view in MainWindow but I don't see paint getting called at all despite me adding the root to the scene (scene.item().count() returns 1..) and the view is simply blank. Am I doing something blatantly wrong here? Thank you for your time!
//MainWindow.cpp
TreeViewNode* root = new TreeViewNode(family->get_root());
view = new QGraphicsView(); QGraphicsScene scene; view->setScene(&scene);
scene.addItem(root); root->setPos(100, 100); view->update();

Qt Button Group over OpenGLWidget

I am designing a user interface with Qt Creator. I have a openGLwidget which covers all the form and I want to place some buttons aligned to bottom and some kind of notification frames over the widget. Is there any default way to do that?
When I try to design that on creator, I could not get rid of layout limits and don't allow me to place a widget over openglwidget.
When reading the question first, I was reading this as “How to make a QOpenGLWidget with Head-Up Display with buttons?”.
How to make a QOpenGLWidget with Head-Up Display (painted with QPainter), I once answered in SO: Paint a rect on qglwidget at specifit times. However, there was only painting – no interactive widgets.
Hence, I prepared a new sample testQGLWidgetHUDButtons.cc:
#include <QtWidgets>
class OpenGLWidget: public QOpenGLWidget, public QOpenGLFunctions {
private:
struct ClearColor {
float r, g, b;
ClearColor(): r(0.6f), g(0.8f), b(1.0f) { }
} _clearColor;
public:
OpenGLWidget(QWidget *pQParent = nullptr):
QOpenGLWidget(pQParent),
QOpenGLFunctions()
{ }
virtual ~OpenGLWidget() = default;
OpenGLWidget(const OpenGLWidget&) = delete;
OpenGLWidget& operator=(const OpenGLWidget&) = delete;
void setClearColor(float r, float g, float b);
protected:
virtual void initializeGL() override;
virtual void paintGL() override;
};
void OpenGLWidget::setClearColor(float r, float g, float b)
{
_clearColor.r = r; _clearColor.g = g; _clearColor.b = b;
update(); // force update of widget
}
void OpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
}
void OpenGLWidget::paintGL()
{
glClearColor(
_clearColor.r, _clearColor.g, _clearColor.b, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
OpenGLWidget qWin;
QGridLayout qGrid;
QPushButton qBtn1(QString::fromUtf8("Black BG"));
qGrid.addWidget(&qBtn1, 1, 1);
QPushButton qBtn2(QString::fromUtf8("White BG"));
qGrid.addWidget(&qBtn2, 1, 2);
QPushButton qBtn3(QString::fromUtf8("Blue BG"));
qGrid.addWidget(&qBtn3, 1, 3);
qGrid.setRowStretch(0, 1);
qGrid.setColumnStretch(0, 1);
qWin.setLayout(&qGrid);
qWin.show();
// install signal handlers
QObject::connect(&qBtn1, &QPushButton::clicked,
[&qWin](bool) { qWin.setClearColor(0.0f, 0.0f, 0.0f); });
QObject::connect(&qBtn2, &QPushButton::clicked,
[&qWin](bool) { qWin.setClearColor(1.0f, 1.0f, 1.0f); });
QObject::connect(&qBtn3, &QPushButton::clicked,
[&qWin](bool) { qWin.setClearColor(0.6f, 0.8f, 1.0f); });
// runtime loop
return app.exec();
}
and the project file testQGLWidgetHUDButtons.pro:
SOURCES = testQGLWidgetHUDButtons.cc
QT += widgets opengl
Compiled and tested in VS2013 on Windows 10:
It's actually quite easy – QOpenGLWidget is derived from QWidget. QWidget provides the method QWidget::setLayout(). To add child widgets, a QLayout should be used which manages layout of children in parent widget (and takes part in size negotiation when parent widget is layouted).
To keep it simple, I used a QGridLayout where buttons are placed in cells (1, 1), (1, 2), and (1, 3). Row 0 and column 0 are left empty but enabled for stetching. This results effectively in attaching the buttons in lower right corner.
When I was about to publish this answer, I took a second look onto the question and realized the Qt Button Group in title. I would've ignore it (multiple buttons are a button group?) but there is also the qbuttongroup which made me stumbling.
A QButtonGroup:
The QButtonGroup class provides a container to organize groups of button widgets.
QButtonGroup provides an abstract container into which button widgets can be placed. It does not provide a visual representation of this container (see QGroupBox for a container widget), but instead manages the states of each of the buttons in the group.
(Emphasize mine.)
So, a button group can be used e.g. to keep radio buttons in sync. It is derived from QObject and hence, neither a QWidget nor a QLayout. Thus, it's not suitable to add visible child widgets to the QOpenGLWidget.
Though I've no experiences with QCreator, I assume it should work there as well as it does in my sample:
Assign a layout to the QOpenGLWidget (e.g. QGridLayout as I did in my sample).
Add QPushButtons to this layout.
Btw. IMHO this is no specific problem about the QOpenGLWidget – it should've happend with any other QWidget as well.

QGraphicsItem::itemChange notified for position change but not for size change

I've got a class derived from QGraphicsEllipseItem in which I need to know when its position or size changes in any way. I handle resizing with a mouse and calls to QGraphicsEllipse::setRect.
OK, so I dutifully overrode the itemChange() method in the class and then was careful to set the ItemSendsGeometryChanges flag after creating it
// Returns a human readable string for any GraphicsItemChange enum value
inline std::string EnumName(QGraphicsItem::GraphicsItemChange e);
// Simple test ellipse class
class MyEllipse : public QGraphicsEllipseItem
{
public:
MyEllipse(int x, int y, int w, int h) : QGraphicsEllipseItem(x, y, w, h)
{
setFlags(
QGraphicsItem::ItemIsSelectable
| QGraphicsItem::ItemIsMovable
| QGraphicsItem::ItemSendsGeometryChanges);
}
// QGraphicItem overrides
virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override
{
std::stringstream oss;
oss << "ItemChange " << EnumName(change) << std::endl;
OutputDebugString(oss.str().c_str());
return __super::itemChange(change, value);
}
};
My main code creates one of these, adds it to the scene and then tries moving/resizing it.
And while I do always receive notifications after calling setPos() on the ellipse, I get NO notification after calling setRect(). I can use setRect to completely change the ellipse's geometry but my itemChange override is never called. Not with any flags.
Now obviously changing the item's rect is changing its geometry, so what am I missing?
Is there some other flag I should set? Some other way to change the size of the ellipse I should use? Some other notification virtual I can override?
The problem is that QGraphicsItem's position is not related with QGraphicsEllipseItem's rectangle. The first one is a position of the item relative to it's parent item or, if it is NULL, to it's scene. The last one is a rectangle relative to the item position where an ellipse should be drawn. The scene and QGraphicsItem's core don't know about any changes of it.
Let's take a look at this test:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsScene scene;
QGraphicsView view(&scene);
QGraphicsEllipseItem item(10, 20, 30, 40);
scene.addItem(&item);
qDebug() << item.pos() << item.scenePos() << item.boundingRect();
item.setRect(110, 120, 30, 40);
qDebug() << item.pos() << item.scenePos() << item.boundingRect();
view.resize(500, 500);
view.show();
return app.exec();
}
Output is:
QPointF(0,0) QPointF(0,0) QRectF(9.5,19.5 31x41)
QPointF(0,0) QPointF(0,0) QRectF(109.5,119.5 31x41)
Possible ways out:
use setTransform. Transform matrix changes are tracked by standard QGraphicsitems, and itemChange will receive corresponding change. But I guess that non-ident matrices can decrease performance (didn't check).
implement your own function like setRect in which you will track geometry changes manually.
subclass QGraphicsItem, not QGraphicsEllipseItem. In this case you can prevent untrackable geometry changes as they are performed through your rules. It looks like this:
class EllipseItem: public QGraphicsItem
{
public:
// Here are constructors and a lot of standard things for
// QGraphicsItem subclassing, see Qt Assistant.
...
// This method is not related with QGraphicsEllipseItem at all.
void setRect(const QRectF &newRect)
{
setPos(newRect.topLeft());
_width = newRect.width();
_height = newRect.height();
update();
}
QRectF boundingRect() const override
{
return bRect();
}
void paint(QPainter * painter, const QStyleOptionGraphicsItem * option,
QWidget * widget = nullptr) override
{
painter->drawRect(bRect());
}
private:
qreal _width;
qreal _height;
QRectF bRect() const
{
return QRectF(0, 0, _width, _height);
}
};
You also should track item transformations and moves through QGraphicsItem::itemChange.

Efficient way of displaying a continuous stream of QImages

I am currently using a QLabel to do this, but this seems to be rather slow:
void Widget::sl_updateLiveStreamLabel(spImageHolder_t _imageHolderShPtr) //slot
{
QImage * imgPtr = _imageHolderShPtr->getImagePtr();
m_liveStreamLabel.setPixmap( QPixmap::fromImage(*imgPtr).scaled(this->size(), Qt::KeepAspectRatio, Qt::FastTransformation) );
m_liveStreamLabel.adjustSize();
}
Here I am generating a new QPixmap object for each new image that arrives. Since QPixmap operations are restricted to the GUI Thread, this also makes the GUI feel poorly responsive.
I've seen there are already some discussions on this, most of them advising to use QGraphicsView or QGLWidget, but I have not been able to find a quick example how to properly use those, which would be what I am looking for.
I'd appreciate any help.
QPixmap::fromImage is not the only problem. Using QPixmap::scaled or QImage::scaled also should be avoided. However you can't display QImage directly in QLabel or QGraphicsView. Here is my class that display QImage directly and scales it to the size of the widget:
Header:
class ImageDisplay : public QWidget {
Q_OBJECT
public:
ImageDisplay(QWidget* parent = 0);
void setImage(QImage* image);
private:
QImage* m_image;
protected:
void paintEvent(QPaintEvent* event);
};
Source:
ImageDisplay::ImageDisplay(QWidget *parent) : QWidget(parent) {
m_image = 0;
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
}
void ImageDisplay::setImage(QImage *image) {
m_image = image;
repaint();
}
void ImageDisplay::paintEvent(QPaintEvent*) {
if (!m_image) { return; }
QPainter painter(this);
painter.drawImage(rect(), *m_image, m_image->rect());
}
I tested it on 3000x3000 image scaled down to 600x600 size. It gives 40 FPS, while QLabel and QGraphicsView (even with fast image transformation enabled) gives 15 FPS.
Setting up a QGraphicsView and QGraphicsScene is quite straight-forward: -
int main( int argc, char **argv )
{
QApplication app(argc, argv);
// Create the scene and set its dimensions
QGraphicsScene scene;
scene.setSceneRect( 0.0, 0.0, 400.0, 400.0 );
// create an item that will hold an image
QGraphicsPixmapItem *item = new QGraphicsPixmapItem(0);
// load an image and set it to the pixmapItem
QPixmap pixmap("pathToImage.png") // example filename pathToImage.png
item->setPixmap(pixmap);
// add the item to the scene
scene.addItem(item);
item->setPos(200,200); // set the item's position in the scene
// create a view to look into the scene
QGraphicsView view( &scene );
view.setRenderHints( QPainter::Antialiasing );
view.show();
return app.exec();
}
I recommend not use QLabel but write own class. Every call of setPixmap causes layout system to recalculate sizes of items and this can propagate to topmost parent (QMainWindow) and this is quite big overhead.
Conversion and scaling also is a bit costly.
Finally best approach is to use profiler to detect where is the biggest problem.

How to create a draggable (borderless and titleless) top level window in QT

I'd appreciate help creating a top-level window in Qt with the following characteristics. The window must be:
Borderless, titleless and lie on top of all other windows on the desktop (easy)
Draggable by clicking and dragging anywhere inside it (this what I need help with)
Constrained to the top border of the desktop while dragging (relatively easy)
Basically, I'm trying to collapse our QT application to a top-level icon on the top border of the desktop.
You'll find the answer to the first part in: Making a borderless window with for Qt, and the answer to the second part in Select & moving Qwidget in the screen.
Combining the two, and adding the last part is straightforward.
Here's how you could do it:
#include <QtGui>
class W: public QWidget
{
Q_OBJECT
Set up a borderless widget with a few buttons to lock/unlock and quit:
public:
W(QWidget *parent=0)
: QWidget(parent, Qt::FramelessWindowHint), locked(false)
{
QPushButton *lock = new QPushButton("Lock");
QPushButton *unlock = new QPushButton("Unlock");
QPushButton *quit = new QPushButton("&Quit");
connect(lock, SIGNAL(clicked()), this, SLOT(lock()));
connect(unlock, SIGNAL(clicked()), this, SLOT(unlock()));
connect(quit, SIGNAL(clicked()),
QApplication::instance(), SLOT(quit()));
QHBoxLayout *l = new QHBoxLayout;
l->addWidget(lock);
l->addWidget(unlock);
l->addWidget(quit);
setLayout(l);
}
public slots:
void lock() {
locked = true;
move(x(), 0); // move window to the top of the screen
}
void unlock() { locked = false; }
Do the mouse handling:
protected:
void mousePressEvent(QMouseEvent *evt)
{
oldPos = evt->globalPos();
}
void mouseMoveEvent(QMouseEvent *evt)
{
const QPoint delta = evt->globalPos() - oldPos;
if (locked)
// if locked, ignore delta on y axis, stay at the top
move(x()+delta.x(), y());
else
move(x()+delta.x(), y()+delta.y());
oldPos = evt->globalPos();
}
private:
bool locked;
QPoint oldPos;
};

Resources