I am currently working on a PyQt5 based user manager and want to display user images as circle.
User image can have any size and aspect ratio, so I also need to crop them before applying the mask:
You can easily set a
QPixmap to a
QLabel and show it in your widget.
However, it took me hours to find out how to mask an image with a smooth,
If you search for this problem, you’ll find some (relatively old) StackOverflow questions and topics in Qt5 forums, but none of the proposed solutions worked for me.
The following snipped does:
- crop the original image to a square
- mask it with a smooth, anti-aliased circle
- rescale it to the required size
- take care of Retina / high DPI displays
from PyQt5.QtCore import Qt, QRect from PyQt5.QtGui import QBrush, QImage, QPainter, QPixmap, QWindow from PyQt5.QtWidgets import QLabel, QVBoxLayout, QWidget def mask_image(imgdata, imgtype='jpg', size=64): """Return a ``QPixmap`` from *imgdata* masked with a smooth circle. *imgdata* are the raw image bytes, *imgtype* denotes the image type. The returned image will have a size of *size* × *size* pixels. """ # Load image and convert to 32-bit ARGB (adds an alpha channel): image = QImage.fromData(imgdata, imgtype) image.convertToFormat(QImage.Format_ARGB32) # Crop image to a square: imgsize = min(image.width(), image.height()) rect = QRect( (image.width() - imgsize) / 2, (image.height() - imgsize) / 2, imgsize, imgsize, ) image = image.copy(rect) # Create the output image with the same dimensions and an alpha channel # and make it completely transparent: out_img = QImage(imgsize, imgsize, QImage.Format_ARGB32) out_img.fill(Qt.transparent) # Create a texture brush and paint a circle with the original image onto # the output image: brush = QBrush(image) # Create texture brush painter = QPainter(out_img) # Paint the output image painter.setBrush(brush) # Use the image texture brush painter.setPen(Qt.NoPen) # Don't draw an outline painter.setRenderHint(QPainter.Antialiasing, True) # Use AA painter.drawEllipse(0, 0, imgsize, imgsize) # Actually draw the circle painter.end() # We are done (segfault if you forget this) # Convert the image to a pixmap and rescale it. Take pixel ratio into # account to get a sharp image on retina displays: pr = QWindow().devicePixelRatio() pm = QPixmap.fromImage(out_img) pm.setDevicePixelRatio(pr) size *= pr pm = pm.scaled(size, size, Qt.KeepAspectRatio, Qt.SmoothTransformation) return pm class Window(QWidget): """Simple window that shows our masked image and text label.""" def __init__(self, imgpath): super().__init__() imgdata = open(imgpath, 'rb').read() pixmap = mask_image(imgdata) ilabel = QLabel() ilabel.setPixmap(pixmap) tlabel = QLabel('Hello, world!') layout = QVBoxLayout() layout.addWidget(ilabel, 0, Qt.AlignCenter) layout.addWidget(tlabel, 0, Qt.AlignCenter) self.setLayout(layout) if __name__ == '__main__': import sys from PyQt5.QtWidgets import QApplication app = QApplication(sys.argv) w = Window(imgpath=sys.argv) w.show() sys.exit(app.exec_())
And this is how the result looks like:
I also toyed around with
QPixmap.setMask() but both approaches did not lead to the desired results.