pyqt5vispy

Vispy canvas can not be set to transparent background when given a rgba color


I have a pyqt5 app that sets a Vispy widget as top layer and a QGrapchisView as the bottom layer. I draw a sample line in both canvas with similar geometry.

Problem

Even when setting the rgba with an alpha channel as zero or close to zero, the Vispy canvas widget it is not transparent. I try to make the pyqt5 widget native widget transparent as well but it does not make a transparent background.

Expected behavior

The red line from the QgraphicsView should be viewed in its entirety as as well as the black line in from the Vispy canvas. as well as just see the QGraphicsView background color (white) and not the Vispy background color (rgb(86/255,86/255,86/255) ~ gray)

Code

    from PyQt5 import QtWidgets, QtGui, QtCore
    from vispy.scene import SceneCanvas, visuals, transforms
    import numpy as np
    import sys
    
    
    class VispyCanvasWrapper(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.canvas = SceneCanvas(app='pyqt5', bgcolor=(86 / 255, 86/255, 86/255, 0.1))  # Set the background color to almost transparent
            self.grid = self.canvas.central_widget.add_grid()
            self.view_vispy = self.grid.add_view(0, 0, bgcolor=(0, 0, 0, 0.1))  # Set the view's background color to almost transparent
    
            self.view_vispy.camera = "panzoom"
            self.view_vispy.camera.aspect = 1.0
    
            # Set the parent widget of the canvas.native widget to this widget
            self.canvas.native.setParent(self)
            self.geometry = QtCore.QRect(0, 0, 500, 500)
            self.canvas.native.setGeometry(self.geometry)
    
            # self.canvas.native.setGeometry(self.geometry)
            self.setObjectName('background_layer')
    
            # create a transparent background
            self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
            self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
            self.setAutoFillBackground(False)
    
            self.this_style_sheet = """
                                  background-color: transparent;
                                  selection-background-color:transparent;
                              """
            self.setStyleSheet(self.this_style_sheet)
            self.canvas.native.setStyleSheet(self.this_style_sheet)
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            # apply transparent style sheet
            self.this_style_sheet = """
                       background-color: transparent;
                       selection-background-color:transparent;
                   """
    
            self.transparent_viewport_style_sheet = """
                       background-color: transparent;
                   """
    
            # Geometry of the window
            self.geometry = QtCore.QRect(0, 0, 800, 800)
            self.scene_rect_value = QtCore.QRectF(0,0, 200, 200)
            self.setGeometry(self.geometry)
    
            # Create central widget and layout
            central_widget = QtWidgets.QWidget()
            layout = QtWidgets.QGridLayout()
            central_widget.setLayout(layout)
            self.setCentralWidget(central_widget)
    
            # Graphics view widget (bottom layer)
            self.scene = QtWidgets.QGraphicsScene()
            self.graphicsView = QtWidgets.QGraphicsView()
            self.graphicsView.setScene(self.scene)
            self.graphicsView.setSceneRect(self.scene_rect_value)
            layout.addWidget(self.graphicsView, 0, 0)
    
            #vispy widget (top layer)
            self.vispy_canvas = VispyCanvasWrapper(self)
            layout.addWidget(self.vispy_canvas, 0, 0)
    
            # Dummy line
            self.dummy_lines()
    
    
        def dummy_lines(self):
            # add some lines to the line gv
            line_list = [[[0, 0], [250,100] ], [[250, 100], [200,-150]]]
            pen = QtGui.QPen(QtGui.QColor(255, 0, 0))
            for line in line_list:
                x1 = line[0][0]
                y1 = -line[0][1]
                x2 = line[1][0]
                y2 = -line[1][1]
                line = QtWidgets.QGraphicsLineItem(x1, y1, x2, y2)
                line.setPen(pen)
                self.scene.addItem(line)
    
            #add line to vispy
            line_data = np.array([[[0, 0], [250,100] ], [[250, 100], [200,-150]]])
            self.line = visuals.Line(line_data, parent=self.vispy_canvas.view_vispy.scene, color='black')
            # Set the zoom extent
            self.vispy_canvas.view_vispy.camera.set_range(x=(0, 200), y=(0, 200))
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        window = MainWindow()
        window.show()
        sys.exit(app.exec_())


Solution

  • There are some special properties of QOpenGLWidget that make this tricky, but it should be possible (emphasis mine):

    Putting other widgets underneath and making the QOpenGLWidget transparent will not lead to the expected results: The widgets underneath will not be visible. This is because in practice the QOpenGLWidget is drawn before all other regular, non-OpenGL widgets, and so see-through type of solutions are not feasible. Other type of layouts, like having widgets on top of the QOpenGLWidget, will function as expected.

    When absolutely necessary, this limitation can be overcome by setting the Qt::WA_AlwaysStackOnTop attribute on the QOpenGLWidget. Be aware however that this breaks stacking order, for example it will not be possible to have other widgets on top of the QOpenGLWidget, so it should only be used in situations where a semi-transparent QOpenGLWidget with other widgets visible underneath is required.

    So, to take your example (thanks for the well-crafted example!), add self.canvas.native.setAttribute(QtCore.Qt.WA_AlwaysStackOnTop) in your VispyCanvasWrapper.__init__. I put it right next to the other settings:

            # create a transparent background
    +        self.canvas.native.setAttribute(QtCore.Qt.WA_AlwaysStackOnTop)
            self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
            self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
            self.setAutoFillBackground(False)
    

    On my system this seems to produce the desired results and is hopefully enough to at least get you started: output of the sample script from the OP with the suggested change