Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MiterJoin is ignored for single-pixel width pen

Tags:

qt

qt5

pyqt5

Problem

When drawing a rectangle with a line width of 1px on a QPainter with antialiasing enabled results in slightly transparent corners regardless of what JoinStyle is set. The same can be observed for polylines and QPainterPaths

def paintEvent(self, event):
    with QPainter(self) as p:
        p.setRenderHint(QPainter.Antialiasing, True)

        p.fillRect(self.rect(), QColor('black'))
        p.setBrush(Qt.NoBrush)

        # 1px width border, corner is partially transparent
        p.setPen(QPen(QColor("orange"), 1, cap=Qt.SquareCap, join=Qt.MiterJoin))
        p.drawRect(QRectF(10.5, 10.5, 50., 25.))

        # 3px width border, corner is crisp
        p.setPen(QPen(QColor("orange"), 3, cap=Qt.SquareCap, join=Qt.MiterJoin))
        p.drawRect(QRectF(15.5, 15.5, 50., 25.))

produces this output:

both corners use Qt.MiterJoin, but the 1px corner is not as crisp

Question

Is there a way to get crisp corners for a 1px wide pen that also work for partially transparent lines and arbitrary QPainterPaths?

Solution tried so far

  • Drawing each line individually:
    Works fine, but there are artifacts with partially transparent lines because the overlap at the corners.

  • Drawing each line individually, but shifting start of each line by 1:
    Solves issue with partially transparent lines right angles, but not other angles or curves.

  • Temporarily disabling antialiasing:
    Diagonal lines and curved become jagged.

like image 730
J. Coenen Avatar asked Oct 30 '25 10:10

J. Coenen


1 Answers

This is caused by the private QCosmeticStroker, which is what QPainter uses when drawing paths that have a pen width between 0 and 1 (included), in the paint device scale.

Drawing "pixel perfect" lines in antialiased mode has always been a bit complex, and some level of compromise (and possible fault) always exists.

Similarly, that stroker, while normally accurate, fast and efficient, can fail under certain circumstances (to realize how complex all that actually is, see its current implementation), and while one would assume that drawing a simple squared angle might not be that an issue, it can be when dealing with modular painting of "poly-lines" (which may also include curves) and closed paths.

Also consider that, for 1 pixel wide pens, the join style difference is almost negligible: the default bevel join would fill 87.5% of the pixel area, and with round join it's ~94.6%.

It may be still worth a bug report, though.

In any case, a simple work around for this is to prevent that stroker to come in play: just use a pen width that is slightly larger than 1. It may be a bit less efficient, since the painter will need to check if the line goes out of the "pixel boundary" even for straight orthogonal lines and therefore try to apply antialiasing almost unnecessarily, but it's an acceptable cost if image accuracy is more important:

# no need to set the cap, which already defaults to square
p.setPen(QPen(QColor("orange"), 1.0001))
like image 134
musicamante Avatar answered Nov 01 '25 12:11

musicamante