Jitter artefacts when reimplementing drawForeground



  • Hi,
    I'm subclassing QGraphicsView to create a simple widget that supports panning and zooming to cursor position (for now).
    I want to create a vignette like border around the view:

    vignette.png
    So I reimplemented drawForeground, but there are two things I can't find a reason for:

    The polygon I use for the left side isn't drawn where it is suposed to:

    init.png
    ... it fixes itself after the first zooming operation or resizing of the window:

    fine.png
    This might be caused by mapToScene, giving unexpected result (see the qDebug further down)
    But the zooming and resizing works really well. The Panning on the other hand does not. It creates some ugly jittering, repeating the vignette and causing both my cpu and gpu to complain:

    gifDebugging.gif

    mydview.h

    class MyView : public QGraphicsView
    {
    	Q_OBJECT
    public:
    	MyView(QWidget *parent = nullptr);
    	~MyView();
    private:
    	const int maxZoomStepsOut = 10;
    	const int maxZoomStepsIn = -15;
    	int actualZoomStep;
    	
    	// panning
    	void shift(qreal dx, qreal dy);
    	void shift(QPointF point);
    	
    	virtual void mouseMoveEvent(QMouseEvent *event) override;
    	virtual void mousePressEvent(QMouseEvent *event) override;
    	virtual void mouseReleaseEvent(QMouseEvent *event) override;
    	
    	bool isPanning;		// wether view is in a panning progress or not
    	QPoint panningStart;	// startpoint of panning
    	
    	// zooming
    	virtual void wheelEvent(QWheelEvent *event) override;
    	const double zoomFactor = 1.2;
    	
    	// reimplement Foreground for Vignette
    	void drawForeground(QPainter *painter, const QRectF &rect) override;
    	const int vignetteSize = 40;
    	
    	// rects to store widget-coords
    	QPoint *vignetteLeftWidgetPolygon = new QPoint[4];
    	
    	// rects mapped to scene-coords
    	QPointF *vignetteLeftPolygon = new QPointF[4];
    	
    	// initalizes first properties of vignette-items, which won't change
    	void initVignette();
    	
    	// update the rects in scene-coords
    	void updateVignetteSceneCoords();
    	
    	// update vignetteGeometry in widget-coords in case of resize event
    	void updateVignetteGeometry();
    	virtual void resizeEvent(QResizeEvent *event) override;
    };
    

    myview.cpp

    MyView::MyView(QWidget *parent)
    {
    	setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    	setRenderHint(QPainter::Antialiasing);
    	setResizeAnchor(AnchorViewCenter);
    	
    	actualZoomStep = 0;
    }
    
    void MyView::shift(qreal dx, qreal dy)
    {
    	horizontalScrollBar()->setValue(  horizontalScrollBar()->value() + dx  );
    	verticalScrollBar()->setValue(  verticalScrollBar()->value() + dy  );
    	
    	// remap Vignette coords to Scene
    	updateVignetteSceneCoords();
    }
    
    void MyView::mouseMoveEvent(QMouseEvent *event)
    {
    	if ( isPanning )
    	{	// shift the view
    		shift( -( event->x() - panningStart.x() ),
    		       -( event->y() - panningStart.y() )	);
    		panningStart.setX( event->x() );
    		panningStart.setY( event->y() );
    		return;
    	}
    }
    
    void MyView::mousePressEvent(QMouseEvent *event)
    {
    	if (event->button() == Qt::MiddleButton)
    	{
    		isPanning = true;
    		panningStart.setX(event->x());
    		panningStart.setY(event->y());
    		setCursor(Qt::ClosedHandCursor);
    		event->accept();
    		return;
    	}
    	event->ignore();
    }
    
    void MyView::mouseReleaseEvent(QMouseEvent *event)
    {
    	if (event->button() == Qt::MiddleButton)
    	{
    		isPanning = false;
    		setCursor(Qt::ArrowCursor);
    		event->accept();
    		return;
    	}
    	event->ignore();
    }
    
    void MyView::wheelEvent(QWheelEvent *event)
    {	// if zooming out
    	if (event->delta() < 0) {
    		// if transform is not already at maxZoomStepsOut
    		if (actualZoomStep < maxZoomStepsOut) {
    				
    				scale(1.0 / zoomFactor, 1.0 / zoomFactor);
    				
    				// shift the view, so that the position under 
    				// the ouse cursor stays the same
    				QPointF viewportCenter = viewport()->rect().center();
    				QPointF mousePos = event->position();
    				QPointF diffNow = mousePos - viewportCenter;
    				QPointF diffNext = diffNow * zoomFactor;
    				QPointF diffNowToNext = diffNow - diffNext;
    				actualZoomStep++;
    				
    				shift(diffNowToNext.x(), diffNowToNext.y());		
    		}
    	// if zooming in
    	} else {
    		// if transform is not already at maxZoomStepsIn
    		if (actualZoomStep > maxZoomStepsIn) {	
    		
    				scale(zoomFactor, zoomFactor);
    				
    				// shift the view, so that the position under 
    				// the ouse cursor stays the same
    				QPointF viewportCenter = viewport()->rect().center();
    				QPointF mousePos = event->position();
    				QPointF diffNow = mousePos - viewportCenter;
    				QPointF diffNext = diffNow / zoomFactor;
    				QPointF diffNowToNext = diffNow - diffNext;
    				actualZoomStep--;
    				
    				shift(diffNowToNext.x(), diffNowToNext.y());
    		}
    	}
    }
    
    void MyView::drawForeground(QPainter *painter, const QRectF &rect)
    {
    	painter->setPen(Qt::NoPen);
    	painter->setBrush( QBrush(Qt::gray) );
    	painter->drawPolygon( vignetteLeftPolygon, 4 );
    }
    
    void MyView::initVignette()
    {
    	// init vignetteRects in widget-coordinates
    	updateVignetteGeometry();
    }
    
    void MyView::updateVignetteGeometry() // in case of resize event
    {
    	// update Rects
    	vignetteLeftWidgetPolygon[0] = QPoint(0, 0);
    	vignetteLeftWidgetPolygon[1] = QPoint(vignetteSize, vignetteSize);
    	vignetteLeftWidgetPolygon[2] = QPoint(vignetteSize, height()-vignetteSize);
    	vignetteLeftWidgetPolygon[3] = QPoint(0, height());
    	
    	qDebug() << "\n";
    	qDebug() << "call inside geometry";
    	qDebug() << "Polygon-Widget:" << vignetteLeftWidgetPolygon[0];
    	qDebug() << "Polygon-Widget:" << vignetteLeftWidgetPolygon[1];
    	qDebug() << "Polygon-Widget:" << vignetteLeftWidgetPolygon[2];
    	qDebug() << "Polygon-Widget:" << vignetteLeftWidgetPolygon[3];	
    	
    	// always remap to scene-coords when updating geometry
    	updateVignetteSceneCoords();
    }
    
    void MyView::updateVignetteSceneCoords()
    {
    	// map gradients
    		
    	// map Polygons
    	vignetteLeftPolygon[0] = mapToScene( vignetteLeftWidgetPolygon[0] );
    	vignetteLeftPolygon[1] = mapToScene( vignetteLeftWidgetPolygon[1] );
    	vignetteLeftPolygon[2] = mapToScene( vignetteLeftWidgetPolygon[2] );
    	vignetteLeftPolygon[3] = mapToScene( vignetteLeftWidgetPolygon[3] );
    	
    	qDebug() << "\n";
    	qDebug() << "call inside sceneCoords";
    	qDebug() << "Polygon:" << vignetteLeftPolygon[0];
    	qDebug() << "Polygon:" << vignetteLeftPolygon[1];
    	qDebug() << "Polygon:" << vignetteLeftPolygon[2];
    	qDebug() << "Polygon:" << vignetteLeftPolygon[3];
    }
    
    void MyView::resizeEvent(QResizeEvent *event)
    {
    	updateVignetteGeometry();
    }
    

    qDebug output:

    call inside geometry
    Polygon-Widget: QPoint(0,0)
    Polygon-Widget: QPoint(40,40)
    Polygon-Widget: QPoint(40,560)
    Polygon-Widget: QPoint(0,600)
    call inside sceneCoords
    Polygon: QPointF(-1178,-119)
    Polygon: QPointF(-1138,-79)
    Polygon: QPointF(-1138,441)
    Polygon: QPointF(-1178,481)
    ------------------------------------------------
    call inside geometry
    Polygon-Widget: QPoint(0,0)
    Polygon-Widget: QPoint(40,40)
    Polygon-Widget: QPoint(40,560)
    Polygon-Widget: QPoint(0,600)
    call inside sceneCoords
    Polygon: QPointF(-1258,-179)
    Polygon: QPointF(-1218,-139)
    Polygon: QPointF(-1218,381)
    Polygon: QPointF(-1258,421)
    

    The Debugs are called first at initialization -> here mapToScene() gives weird results
    The 2nd call is when I manuallly rsize the window -> here mapToScene() works fine

    Some things I realized:

    • The zoom function uses shift() as well (to move the scene back to the mouse cursor), but that doesn't cause artifacts.
    • I guess drawForeground() doesn't delete it's items, drawing them on top of each other. That's why my cpu/gpu is running crazy, creating LOTS of items.
    • calling eraseRect() in drawForeground() first only deletes my scene items, the jittering stays.

    Thank you so much for any clues!

    I am on Windows 10 Education version 1803 OS build 17134.1184
    using Qt 5.14.0 and QtCreator 4.11.0

    PS: I created a complete new Project to isolate the problem, this is actually the second version of this thread. I hope this helps a bit.

    PPS: I reduced he code a little (Destructors, includes) and left out main.cpp, mainwindow.h and mainwindow.cpp as those are really short and straight forward in my case.
    But I will post them as well, if someone is interested.


  • Lifetime Qt Champion

    @Another-Qt-Beginner
    Hi
    Did you try setting
    https://doc.qt.io/qt-5/qgraphicsview.html#ViewportUpdateMode-enum
    to FullViewportUpdate to see if that reduces the issues.


  • Lifetime Qt Champion

    Hi,

    For people to be able to check more effectively, you should post a complete minimal compile example.

    You should also add which version of Windows you are developing for as well as Qt version you are using.



  • @SGaist thanks for the advise


  • Lifetime Qt Champion

    @Another-Qt-Beginner
    Hi
    Did you try setting
    https://doc.qt.io/qt-5/qgraphicsview.html#ViewportUpdateMode-enum
    to FullViewportUpdate to see if that reduces the issues.