class Texture: """A class describing the interface to be used for texture objects. This interface should be implemented by OpenGL implementations to handle texture objects. """ def __init__(self, open_gl_binding_object: QAbstractOpenGLFunctions) -> None: super().__init__() self._qt_texture = QOpenGLTexture(QOpenGLTexture.Target.Target2D) self._gl = open_gl_binding_object self._file_name = None self._image = None def getTextureId(self) -> int: """Get the OpenGL ID of the texture.""" return self._qt_texture.textureId() def bind(self, texture_unit): """Bind the texture to a certain texture unit. :param texture_unit: The texture unit to bind to. """ if not self._qt_texture.isCreated(): if self._file_name != None: self._image = QImage(self._file_name).mirrored() elif self._image is None: # No filename or image set. self._image = QImage(1, 1, QImage.Format.Format_ARGB32) self._image.fill(0) self._qt_texture.setData(self._image) self._qt_texture.setMinMagFilters(QOpenGLTexture.Filter.Linear, QOpenGLTexture.Filter.Linear) self._qt_texture.bind(texture_unit) def release(self, texture_unit): """Release the texture from a certain texture unit. :param texture_unit: The texture unit to release from. """ self._qt_texture.release(texture_unit) def load(self, file_name): """Load an image and upload it to the texture. :param file_name: The file name of the image to load. """ self._file_name = file_name # Actually loading the texture is postponed until the next bind() call. # This makes sure we are on the right thread and have a current context when trying to upload. def setImage(self, image): self._image = image
def get_masked_image(path, size=64, overlay_text=""): """ Returns a pixmap from an image file masked with a smooth circle. The returned pixmap will have a size of *size* × *size* pixels. :param str path: Path to image file. :param int size: Target size. Will be the diameter of the masked image. :param str overlay_text: Overlay text. This will be shown in white sans-serif on top of the image. :return: Masked image with overlay text. :rtype: QPixmap """ with open(path, "rb") as f: imgdata = f.read() imgtype = path.split(".")[-1] # Load image and convert to 32-bit ARGB (adds an alpha channel): image = QImage.fromData(imgdata, imgtype) image.convertToFormat(QImage.Format.Format_ARGB32) # Crop image to a square: imgsize = min(image.width(), image.height()) width = (image.width() - imgsize) / 2 height = (image.height() - imgsize) / 2 rect = QRect( round(width), round(height), 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.Format_ARGB32) out_img.fill(Qt.GlobalColor.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.PenStyle.NoPen) # Don't draw an outline painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) # Use AA painter.drawEllipse(0, 0, imgsize, imgsize) # Actually draw the circle if overlay_text: # draw text font = QtGui.QFont("Arial Rounded MT Bold") font.setPointSize(imgsize * 0.4) painter.setFont(font) painter.setPen(Qt.GlobalColor.white) painter.drawText(QRect(0, 0, imgsize, imgsize), Qt.AlignmentFlag.AlignCenter, overlay_text) 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 = QtWidgets.QApplication.instance().devicePixelRatio() pm = QPixmap.fromImage(out_img) pm.setDevicePixelRatio(pr) size = int(pr * size) pm = pm.scaled( size, size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation, ) return pm