Muhammad Umer
Muhammad Umer

Reputation: 33

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

Upvotes: 1

Views: 4111

Answers (1)

musicamante
musicamante

Reputation: 48374

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_())

Upvotes: 4

Related Questions