Search code examples
qgraphicsviewpyside6qt6

Background grid in QGraphicsView is rendered incorrectly when zooming


I'm trying to draw a background grid for a QGraphicsView by setting the background brush to a QPixmap showing the upper and right side of a square - this works well and all, but in conjunction with a simple zoom functionality using the wheelEvent and scaleView(), the grid is rendered with parts missing, or not all, when zooming out.

Below a minimal example:

import math

from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene
from PySide6.QtGui import QPixmap, QPainter
from PySide6.QtCore import Qt, QRectF
    
class CanvasView(QGraphicsView):

    class CanvasScene(QGraphicsScene):
        def __init__(self, canvas_view, initial_width, initial_height):
            QGraphicsScene.__init__(self, canvas_view)

    def __init__(self, initial_width, initial_height, grid_step):
        QGraphicsView.__init__(self)
        self.setGeometry(0, 0, initial_width, initial_height)
        self.canvas_scene = self.CanvasScene(self, initial_width, initial_height)
        self.setScene(self.canvas_scene)

        # draw grid

        self.grid_cell = QPixmap(grid_step, grid_step)
        self.grid_cell.fill(Qt.transparent)

        self.cell_painter = QPainter(self.grid_cell)
        self.cell_painter.setPen(Qt.lightGray)
        self.cell_painter.drawLine(0, 0, self.grid_cell.width() - 1, 0)
        self.cell_painter.drawLine(0, 0, 0, self.grid_cell.width() - 1)

        self.setBackgroundBrush(self.grid_cell)

    def wheelEvent(self, event):
        self.scaleView(math.pow(2.0, event.angleDelta().y() / 240.0))

    def scaleView(self, scaleFactor):
        factor = self.transform().scale(scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width()
        if factor < 0.07 or factor > 100:
            return
        self.scale(scaleFactor, scaleFactor)

app = QApplication()
view = CanvasView(1000, 500, 20)
view.show()

app.exec()

And here some screenshots showing the incorrect grid rendering:

enter image description here

enter image description here

enter image description here

Happens on both Linux and Windows. Does anyone have a clue why this happens and how to fix so that the grid scales correctly?


Solution

  • This is caused by the fact that the brush based on a pixmap, which being a raster image, it's pixel-based.

    Scaling raster images that contain straight lines results in aliasing, as shown in the wikipedia examples.

    The source image:

    high resolution image with straight lines

    And the scaled image, without proper anti-aliasing filters:

    small resolution with aliasing

    Considering this, using a pixmap based brush is obviously not a valid option.

    Even the Antialiasing render hint can't do much about this.

    Instead, you can override drawBackground() and paint the grid from there, using QPainter drawing functions.

    class CanvasView(QGraphicsView):
        def __init__(self, initial_width, initial_height, grid_step):
            QGraphicsView.__init__(self)
            self.setGeometry(0, 0, initial_width, initial_height)
            self.canvas_scene = CanvasScene(self, initial_width, initial_height)
            self.setScene(self.canvas_scene)
            self.setRenderHint(QPainter.Antialiasing)
    
            self.grid_step = grid_step
            self.grid_pen = QPen(Qt.lightGray)
    
        def drawBackground(self, qp, rect):
            qp.translate(.5, .5)
            qp.setPen(self.grid_pen)
    
            x, y, right, bottom = rect.toRect().getCoords()
            top = y
            left = x
            step = self.grid_step
    
            yrest = y % step
            if yrest:
                y += step - yrest
            for y in range(y, bottom, step):
                qp.drawLine(left, y, right, y)
    
            xrest = x % step
            if xrest:
                x += step - xrest
            for x in range(x, right, step):
                qp.drawLine(x, top, x, bottom)
    

    On my 10y old computer, with the given window size and at the minimum zoom value, it takes less than 30ms to draw the whole scene (~850 lines).

    If the scene rect is fixed, some further improvement could be achieved by pre-computing the whole grid in a QPicture and just draw it instead of doing it every time.