python-3.xpyqt5360-degrees

Is there any way to display a 360-degree image in PyQt5 Python?


I want to be able to choose a 360-degree image and show it in a PyQt5 window making it interact-able and moveable, is there any way to do this? I am done with the part of choosing the file but can't seem to display it in a Qlabel.

I am done with the part of choosing the file but can't seem to display it in a Qlabel.

I expect the picture to be interactable like the 360-images shown on Facebook


Solution

  • You cannot set the pixmap for the label, as it would show the full image entirely.
    Instead, you will need to create your own QWidget, and provide by yourself "panning" implementation by overriding the paintEvent (which actually draws the image) and mouse events for interaction.

    I made a very basic example, which does not support zooming (which would require much more computation). The source image I used is taken from here

    panoramic image.

    It creates a local pixmap which is repeated 3 times horizontally to ensure that you can pan infinitely, uses mouseMoveEvent to compute the position via a QTimer and limits the vertical position to the image height, while resetting the horizontal position whenever the x coordinate is beyond the half of the image width.

    import sys
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    class Panoramic(QtWidgets.QWidget):
        def __init__(self, imagePath):
            QtWidgets.QWidget.__init__(self)
            self.setCursor(QtCore.Qt.CrossCursor)
            # keep a reference of the original image
            self.source = QtGui.QPixmap(imagePath)
            self.pano = QtGui.QPixmap(self.source.width() * 3, self.source.height())
            self.center = self.pano.rect().center()
            # use a QPointF for precision
            self.delta = QtCore.QPointF()
            self.deltaTimer = QtCore.QTimer(interval=25, timeout=self.moveCenter)
            self.sourceRect = QtCore.QRect()
            # create a pixmap with three copies of the source;
            # this could be avoided by smart repainting and translation of the source
            # but since paintEvent automatically clips the painting, it should be
            # faster then computing the new rectangle each paint cycle, at the cost 
            # of a few megabytes of memory.
            self.setMaximumSize(self.source.size())
            qp = QtGui.QPainter(self.pano)
            qp.drawPixmap(0, 0, self.source)
            qp.drawPixmap(self.source.width(), 0, self.source)
            qp.drawPixmap(self.source.width() * 2, 0, self.source)
            qp.end()
    
        def moveCenter(self):
            if not self.delta:
                return
            self.center += self.delta
            # limit the vertical position
            if self.center.y() < self.sourceRect.height() * .5:
                self.center.setY(self.sourceRect.height() * .5)
            elif self.center.y() > self.source.height() - self.height() * .5:
                self.center.setY(self.source.height() - self.height() * .5)
            # reset the horizontal position if beyond the center of the virtual image
            if self.center.x() < self.source.width() * .5:
                self.center.setX(self.source.width() * 1.5)
            elif self.center.x() > self.source.width() * 2.5:
                self.center.setX(self.source.width() * 1.5)
            self.sourceRect.moveCenter(self.center.toPoint())
            self.update()
    
        def mousePressEvent(self, event):
            if event.button() == QtCore.Qt.LeftButton:
                self.mousePos = event.pos()
    
        def mouseMoveEvent(self, event):
            if event.buttons() != QtCore.Qt.LeftButton:
                return
            delta = event.pos() - self.mousePos
            # use a fraction to get small movements, and ensure we're not too fast
            self.delta.setX(max(-25, min(25, delta.x() * .125)))
            self.delta.setY(max(-25, min(25, delta.y() * .125)))
            if not self.deltaTimer.isActive():
                self.deltaTimer.start()
    
        def mouseReleaseEvent(self, event):
            self.deltaTimer.stop()
    
        def paintEvent(self, event):
            qp = QtGui.QPainter(self)
            qp.drawPixmap(self.rect(), self.pano, self.sourceRect)
    
        # resize and reposition the coordinates whenever the window is resized
        def resizeEvent(self, event):
            self.sourceRect.setSize(self.size())
            self.sourceRect.moveCenter(self.center)
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        w = Panoramic('pano.jpg')
        w.show()
        sys.exit(app.exec_())