Is there any easy way to copy QPainter state? - qt

I want to implment push_group/pop_group of cairo with QPainter, but QPainter resets all its state while begin() with a new painterDevice, so I have to save/revert all state manually.

Yes, just check out QPainter::save() and QPainter::restore().
If you want to save/restore between the lifespan of multiple QPainters, you have to do it manually. You could just create a class PainterState that encapsulates the painter state (pen, brush, transform, etc.), and then store a QStack<PainterState>.
There is a QPainterState class, but it is for internal use only, and I think it's only for use with a single QPainter. See the source ("qpainter_p.h") if you're interested in the QPainterState members (too many to copy here).

When constructing the QPainter object, you can draw it to a QPicture. Then it can be reloaded when needed and painted out to the real QPaintDevice.
QPicture picture;
QPainter painterQueued;
painterQueued.begin(&picture); // paint in picture
painterQueued.drawEllipse(10,20, 80,70); // draw an ellipse
painterQueued.end(); // painting done
QImage myImage;
QPainter painterTarget;
painterTarget.begin(&myImage); // paint in myImage
painterTarget.drawPicture(0, 0, picture); // draw the picture at (0,0)
painterTarget.end(); // painting done
You could queue up many QPicture objects in a list, stack, etc, and replay them when needed.

Related

OpenGL / QT : Need help in Converting from QImage to Opengl format and render the pixels

We are migrating from Old OpenGl to Modern OpenGL. I am trying to port two functions which uses QT/OpenGL and want to convert to Modern OpenGL. QImage content should be converted to OpenGL Format. Then I want to read the pixels of QImage and render in OpenGL. How to do this in Modern OpenGL. I know glcopypixels() / glDrawPixels() is deprecated. Any pointers? I have the following code but it is in old OpenGL. Basically the whole idea is writing to back buffer and restoring the back buffer and render pixels to avoid redraw. I am using QOpenglWidget Class given by QT Framework (QT 5.1). I have tried many things converting to OpenGL format from QImage. But it did not work. Need your help. Thanks in Advance.
QImage _savedBackBuffer;
void SaveBackBuffer()
{
glReadBuffer(GL_BACK);
QImage buf = this->grabFramebuffer();
_savedBackBuffer = convertToGLFormat(buf); // convertToGLFormat is not available in
QOpenGLWidget class
}
void restoreBackBuffer()
{
glDrawBuffer(GL_BACK);
**glDrawPixels**( _savedBackBuffer.width(), _savedBackBuffer.height(),
GL_RGBA, GL_UNSIGNED_BYTE, _savedBackBuffer.bits() ); ---> glDrawPixels is Deprecated. How to handle this call.
}
flush () {
glReadBuffer (GL_BACK);
glDrawBuffer(GL_FRONT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
**glCopyPixels**(0, 0, _scrWidth, _scrHeight, GL_COLOR); // glCopyPixels Deprecated
...
glflush();
}
I have added below code to grab the Framebuffer. But still I am getting an Empty QImage. Anything wrong with my code.
saveBackBuffer()
{
_bSavingBackBuffer = true;
QString fileName("C:\\Users\\ey617e\\Desktop\\yourFile.png");
QFile file(fileName);
file.open(QIODevice::WriteOnly);
glReadBuffer(GL_BACK);
makeCurrent();
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
QOpenGLFramebufferObject * fbo = new
QOpenGLFramebufferObject(_scrWidth, _scrHeight, format);
fbo->bind();
paintGL();
_savedBackBuffer = fbo->toImage();
_savedBackBuffer.save(file.fileName(), "PNG");
fbo->release();
}
paintGL()
{
QOpenGLPaintDevice fboPaintDev(_scrWidth, _scrHeight);
QPainter painter(&fboPaintDev);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
painter.beginNativePainting();
drawDisplayLists(_underIllusDisplayLists);
drawDisplayLists(_illusDisplayLists);
painter.endNativePainting();
painter.drawText(20, 40, "Foo");
painter.end();
}
You can create a QOpenGLTexture object directly from a QImage: https://doc.qt.io/qt-5/qopengltexture.html#QOpenGLTexture-1
You can then use that texture directly for any image related OpenGL operations.
Basically the whole idea is writing to back buffer and restoring the back buffer and render pixels to avoid redraw.
Don't do that! It will actually impair performance, since drawing on top of previously rendered content introduces implicit synchronization points, thereby eliminating options to render new contents in parallel to advancing the presentation swap chain.
As "counterintuitive" as it may sound, just redraw the whole thing, each and every frame. If your codebase is that old, then the complexity of what you're drawing very likely is going to be so low, that you could easily render thousands of frames per second.
On the other hand retaining the contents of the backbuffer constitutes a cache and thus introduces the complexity of deciding upon cache invalidation.
I bet, that just redrawing using modern methods (geometry in buffer objects, index buffers, untangling of sync points) and simplifying the rendering code path by mere elimination the code that's responsible for determining when to actually redraw portions of the picture will actually vastly outperform anything what you had before.

How can I mirror a movie and play it in reverse?

I have been playing with QMovie a bit trying to mirror a movie as well as play it in reverse.
For the mirroring bit I tried assigning a negative width to no avail. Since QImage does offer facilities for this I had hoped QMovie would do the same.
There don't seem to be any facilities in the QMovie object for those things, so I'm wondering if I could manipulate the QIODevice to the QMovie object instead to achieve this, but this is completely new territory for me and I don't see anything obvious in the documentation that would achieve mirroring or playing in reverse.
The code example to start with would be the same as on the PySide page:
label = QLabel()
movie = QMovie("animations/fire.gif")
label.setMovie(movie)
movie.start()
Any ideas would be greatly appreciated.
Thanks,
Frank
QMovie doesn't provide the methods to set the current image, so you have to use directly QImageReader to play it in reverse (using QImageReader::jumpToImage()). This is not very easy bacause the delay between a frame and the next can change, but however you can get it calling QImageReader::nextImageDelay().
To display the movie, you can implement your own widget to draw the movie as you want.
In the paintEvent() you can set a transformation to the painter to get the mirror effect and than draw the current image of the movie.
Example:
void MyWidget::paintEvent(QPaintEvent * event)
{
QPainter painter(this);
painter.scale(-1, 1); // x-axis mirror
//here maybe you must adjust the transformation to center and scale the movie.
painter.drawImage(0, 0, currentImage);
}
To play the movie you have to set a timer that change the current image.
Example:
//you must create a timer in the constructor and connect it to this slot
void MyWidget::timeoutSlot()
{
int currentImageIndex;
//here you have to compute the image index
imageReader.jumpToImage(currentImageIndex);
currentImage = imageReader.read(); //maybe you want to read it only if the index is changed
update();
}
Here you can find an example of widget subclass, with timer and painter transformation
See also QImageReader, QTimer, QWidget::paintEvent

Efficiently getting from a QPaintDevice to a QSGTexture in QQuickItem

My custom QQuickItem currently does the following
Create a QSGNode that subclasses QSGSimpleTextureNode
In the nodes preprocess function, create a QOpenGLFramebufferObject to draw to
Draw on the QOpenGLFramebufferObject using a QPainter
Display the contents of the QOpenGLFramebufferObject as the node contents
The process I have for converting the FBO to a QSGTexture that I can set on the QSGSimpleTextureNode is the following.
QImage img = m_fbo->toImage();
QSGTexture* tex = m_window->createTextureFromImage(img, QQuickWindow::TextureCanUseAtlas);
setTexture(tex);
This seems very inefficient, and the app starts to get real framey even with relatively reasonable sized FBOs.
My questions are the following.
Is there a simpler way of getting an FBO into a QSGTexture?
Is there a better QPaintDevice compatible item that I should be using rather than a QOpenGLFramebufferObject?
Is there a better subclass I should be extending than QSGSimpleTextureNode to do what I am wanting to do?
Thanks!
1) For non multisample framebuffer objects a texture with the specified texture target is created. You can get the texture id for the texture attached to framebuffer object, using QOpenGLFramebufferObject::takeTexture(). And then
create a new QSGTexture object from an existing GL texture id:
QSize textureSize = m_fbo.size();
GLuint textureId = m_fbo.takeTexture();
QSGTexture* texture = window()->createTextureFromId(textureId, textureSize);
2, 3) The QQuickPaintedItem class provides a way to use the QPainter API in the QML Scene Graph.
The QQuickFramebufferObject class is a convenience class for integrating rendering using a framebuffer object (FBO) with Qt Quick.

Refreshing MainWindow in a loop to simulate a move of QWidget in QT

I am new in QT and my problem is to refresh the page in a loop to make a move on QWidget.
In detail, I have too many points (It is the path which will be followed by an ellipse and they will be drawn as line) and I have an ellipse which will move on the screen according to given two points. During its move, the path is changed. So lines will be drawn again according to new path and the ellipse should follow the new path. What I did as follows:
void MainWindow::paint(...){
painter.drawEllipse(circle) //circle is QRectF
//Also I need to draw lines according to pathPlanned
}
bool MainWindow::replan(){
//it calculates the planned path and if the ellipse does not reached the destination it can change the planned path here
}
void MainWindow::execute(){
while(replan()){
for (it = plannedPath->begin(); it != plannedPath->end(); it++){
//Lines should be redraw according to new pathPlanned
}
circle(...) // new position of ellipse is changed here
// I tried to put QThread::msleep(10) but I learned that it blocks GUI and then deleted it.
}
}
My problem is that loop is working so fast (as usual) and it can not refresh the page until it finishes everything. Then Immediately ellipse is drawn on the destination. I can not see the moves of ellipse.
How can I fix that?
Instead of using QThread::msleep(10), use following
QEventLoop loop;
QTimer::singleShot(100, &loop, SLOT(quit()));
loop.exec();
This will process events after each redraw of ellipse so UI will get updated
You need to use Qt animation framework for that. There are a lot of samples in official documentation. In this case you will not block main event loop and your animations will be smooth.
If you use custom drawing, don't forget to call QWidget::repaint() or QWidget::update() to refresh widget content.
Don't use long time loops in main thread. Use timers + slots.

Best way to create a long line (or cross line) cursor in Qt GraphicsView

The easy way to create the long cross line cursor (as long as viewport) is create a cross line graphicsItem, when mouse moved, set the item's pos property.
But this way will be very slow when the scene is complex, because it should update the whole viewport to update the cursor's pos.
The another easy way is setCursor(QCursor(..)),use a QPixmap to define the long cross line, this way will very fast , but the cursor will exceed the viewport rect.
Is there another way to show a long cross line cursor fastly?
Thanks very much!
If I understand correctly, you want to draw an horizontal line and a vertical line, crossing at the cursor position, and being as large as the viewport is.
A poosible solution would be to reimplement QGraphicsScene::drawForeground() to draw the two lines with the painter.
The problem is that the scene doesn't know about the mouse position. This means the view will have to track it and inform the scene when the mouse position has changed.
To do that, you'll have to create your own GraphicsScene (inheriting QGraphicsScene) and your own GraphicsView (inheriting QGraphicsView).
On your GraphicsView constructor, you'll have to start mouse tracking. This will make your you receive a mouseMoveEvent each time the mouse moves inside the view :
GraphicsViewTrack::GraphicsViewTrack(QWidget* parent) : QGraphicsView(parent) {
setMouseTracking(true);
}
void GraphicsViewTrack::mouseMoveEvent(QMouseEvent* pEvent) {
QPointF MousePos = this->mapToScene(pEvent->pos());
emit mousePosChanged(MousePos.toPoint());
}
As you can see in the code snippet above, the view is emitting a signal (mousePosChanged) to which the scene will be connected. This signal contains the mouse position, converted to the scene's coordinates.
Now, on the scene side, you have to add a slot which will be called when the mouse position changed, store the new mouse position in a member variable and reimplement QGraphicsScene::drawForeground() :
void GraphicsSceneCross::drawForeground(QPainter* painter, const QRectF& rect) {
QRectF SceneRect = this->sceneRect();
painter->setPen(QPen(Qt::black, 1));
painter->drawLine(SceneRect.left(), m_MousePos.y(), SceneRect.right(), m_MousePos.y());
painter->drawLine(m_MousePos.x(), SceneRect.top(), m_MousePos.x(), SceneRect.bottom());
}
void GraphicsSceneCross::onMouseChanged(QPoint NewMousePos) {
m_MousePos = NewMousePos; // Store the mouse position in a member variable
invalidate(); // Tells the scene it should be redrawn
}
The last thing to do is connect the GraphicsView's signal to the GraphicsScene slot.
I'll let you check if this solution is acceptable performance wise.
Based on Jerome answer and using python I created this code in my QGraphicsScene subclass:
def drawForeground(self, painter, rect):
if self.guidesEnabled:
painter.setClipRect(rect)
painter.setPen(self.guidePen)
painter.drawLine(self.coords.x(), rect.top(), self.coords.x(), rect.bottom())
painter.drawLine(rect.left(), self.coords.y(), rect.right(), self.coords.y())
def mouseMoveEvent(self, event):
self.coords = event.scenePos()
self.invalidate()
It should be straighforward for you to write its appropiate C++ code. Note that I take advantage of the rect argument passed by Qt Api framework and I clip the painter to that
area, since it's the visible area to be drawn.
I also cache the pen object, since I realized in other experiments that creating objects while painting will penalty performace and doing it so you also give the user the chance to set a custom pen in your program options.
I have found a way to do this!
I develops under Windows system, so can use lower GDI api jumping out of Qt's painting system.
The detail is get the HDC of the QGraphicsView's viewPort. Then in the QMouseEvent of QGraphicsView use "MoveToEx" and "LineTo" drawing two lines on the viewport,then I should do is erase the "old" cursor, It's easy to do this using "setROP2(HDC dc,R2_NOT)",then draw the old Cursor stored again.
This method doesn't enter the QPainter system, so the GraphicsItems under the cursor will not be repaint.
To solve the filker problem when the mouse moves fast, I don't use "double buffer". I used QTimer to update the cursor at CPU's idle. The detail is in QMouseEvent ,don't update the cursor at time ,but store the position to a list, When CPU idle, draw the cursor at the list of positions
I wish this will help others who meets the same problem with me.
Thanks Jérôme, who gave me useful tip of QGraphicsScene.

Resources