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:

Is there a way to get crisp corners for a 1px wide pen that also work for partially transparent lines and arbitrary QPainterPaths?
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.
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))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With