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:
Happens on both Linux and Windows. Does anyone have a clue why this happens and how to fix so that the grid scales correctly?
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:
And the scaled image, without proper anti-aliasing filters:
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.