How to enable Pan and Zoom in a QGraphicsView How to enable Pan and Zoom in a QGraphicsView python python

How to enable Pan and Zoom in a QGraphicsView


This is not too difficult to do using the built in capabilities of QGraphicsView.

The demo script below has left-button panning and wheel zoom (including anchoring to the current cursor position). The fitInView method has been reimplemented because the built in version adds a weird fixed margin that can't be removed.

PyQt4 version:

from PyQt4 import QtCore, QtGuiclass PhotoViewer(QtGui.QGraphicsView):    photoClicked = QtCore.pyqtSignal(QtCore.QPoint)    def __init__(self, parent):        super(PhotoViewer, self).__init__(parent)        self._zoom = 0        self._empty = True        self._scene = QtGui.QGraphicsScene(self)        self._photo = QtGui.QGraphicsPixmapItem()        self._scene.addItem(self._photo)        self.setScene(self._scene)        self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse)        self.setResizeAnchor(QtGui.QGraphicsView.AnchorUnderMouse)        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)        self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(30, 30, 30)))        self.setFrameShape(QtGui.QFrame.NoFrame)    def hasPhoto(self):        return not self._empty    def fitInView(self, scale=True):        rect = QtCore.QRectF(self._photo.pixmap().rect())        if not rect.isNull():            self.setSceneRect(rect)            if self.hasPhoto():                unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))                self.scale(1 / unity.width(), 1 / unity.height())                viewrect = self.viewport().rect()                scenerect = self.transform().mapRect(rect)                factor = min(viewrect.width() / scenerect.width(),                             viewrect.height() / scenerect.height())                self.scale(factor, factor)            self._zoom = 0    def setPhoto(self, pixmap=None):        self._zoom = 0        if pixmap and not pixmap.isNull():            self._empty = False            self.setDragMode(QtGui.QGraphicsView.ScrollHandDrag)            self._photo.setPixmap(pixmap)        else:            self._empty = True            self.setDragMode(QtGui.QGraphicsView.NoDrag)            self._photo.setPixmap(QtGui.QPixmap())        self.fitInView()    def wheelEvent(self, event):        if self.hasPhoto():            if event.delta() > 0:                factor = 1.25                self._zoom += 1            else:                factor = 0.8                self._zoom -= 1            if self._zoom > 0:                self.scale(factor, factor)            elif self._zoom == 0:                self.fitInView()            else:                self._zoom = 0    def toggleDragMode(self):        if self.dragMode() == QtGui.QGraphicsView.ScrollHandDrag:            self.setDragMode(QtGui.QGraphicsView.NoDrag)        elif not self._photo.pixmap().isNull():            self.setDragMode(QtGui.QGraphicsView.ScrollHandDrag)    def mousePressEvent(self, event):        if self._photo.isUnderMouse():            self.photoClicked.emit(self.mapToScene(event.pos()).toPoint())        super(PhotoViewer, self).mousePressEvent(event)class Window(QtGui.QWidget):    def __init__(self):        super(Window, self).__init__()        self.viewer = PhotoViewer(self)        # 'Load image' button        self.btnLoad = QtGui.QToolButton(self)        self.btnLoad.setText('Load image')        self.btnLoad.clicked.connect(self.loadImage)        # Button to change from drag/pan to getting pixel info        self.btnPixInfo = QtGui.QToolButton(self)        self.btnPixInfo.setText('Enter pixel info mode')        self.btnPixInfo.clicked.connect(self.pixInfo)        self.editPixInfo = QtGui.QLineEdit(self)        self.editPixInfo.setReadOnly(True)        self.viewer.photoClicked.connect(self.photoClicked)        # Arrange layout        VBlayout = QtGui.QVBoxLayout(self)        VBlayout.addWidget(self.viewer)        HBlayout = QtGui.QHBoxLayout()        HBlayout.setAlignment(QtCore.Qt.AlignLeft)        HBlayout.addWidget(self.btnLoad)        HBlayout.addWidget(self.btnPixInfo)        HBlayout.addWidget(self.editPixInfo)        VBlayout.addLayout(HBlayout)    def loadImage(self):        self.viewer.setPhoto(QtGui.QPixmap('image.jpg'))    def pixInfo(self):        self.viewer.toggleDragMode()    def photoClicked(self, pos):        if self.viewer.dragMode()  == QtGui.QGraphicsView.NoDrag:            self.editPixInfo.setText('%d, %d' % (pos.x(), pos.y()))if __name__ == '__main__':    import sys    app = QtGui.QApplication(sys.argv)    window = Window()    window.setGeometry(500, 300, 800, 600)    window.show()    sys.exit(app.exec_())

PyQt5 version:

from PyQt5 import QtCore, QtGui, QtWidgetsclass PhotoViewer(QtWidgets.QGraphicsView):    photoClicked = QtCore.pyqtSignal(QtCore.QPoint)    def __init__(self, parent):        super(PhotoViewer, self).__init__(parent)        self._zoom = 0        self._empty = True        self._scene = QtWidgets.QGraphicsScene(self)        self._photo = QtWidgets.QGraphicsPixmapItem()        self._scene.addItem(self._photo)        self.setScene(self._scene)        self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)        self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)        self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(30, 30, 30)))        self.setFrameShape(QtWidgets.QFrame.NoFrame)    def hasPhoto(self):        return not self._empty    def fitInView(self, scale=True):        rect = QtCore.QRectF(self._photo.pixmap().rect())        if not rect.isNull():            self.setSceneRect(rect)            if self.hasPhoto():                unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))                self.scale(1 / unity.width(), 1 / unity.height())                viewrect = self.viewport().rect()                scenerect = self.transform().mapRect(rect)                factor = min(viewrect.width() / scenerect.width(),                             viewrect.height() / scenerect.height())                self.scale(factor, factor)            self._zoom = 0    def setPhoto(self, pixmap=None):        self._zoom = 0        if pixmap and not pixmap.isNull():            self._empty = False            self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)            self._photo.setPixmap(pixmap)        else:            self._empty = True            self.setDragMode(QtWidgets.QGraphicsView.NoDrag)            self._photo.setPixmap(QtGui.QPixmap())        self.fitInView()    def wheelEvent(self, event):        if self.hasPhoto():            if event.angleDelta().y() > 0:                factor = 1.25                self._zoom += 1            else:                factor = 0.8                self._zoom -= 1            if self._zoom > 0:                self.scale(factor, factor)            elif self._zoom == 0:                self.fitInView()            else:                self._zoom = 0    def toggleDragMode(self):        if self.dragMode() == QtWidgets.QGraphicsView.ScrollHandDrag:            self.setDragMode(QtWidgets.QGraphicsView.NoDrag)        elif not self._photo.pixmap().isNull():            self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)    def mousePressEvent(self, event):        if self._photo.isUnderMouse():            self.photoClicked.emit(self.mapToScene(event.pos()).toPoint())        super(PhotoViewer, self).mousePressEvent(event)class Window(QtWidgets.QWidget):    def __init__(self):        super(Window, self).__init__()        self.viewer = PhotoViewer(self)        # 'Load image' button        self.btnLoad = QtWidgets.QToolButton(self)        self.btnLoad.setText('Load image')        self.btnLoad.clicked.connect(self.loadImage)        # Button to change from drag/pan to getting pixel info        self.btnPixInfo = QtWidgets.QToolButton(self)        self.btnPixInfo.setText('Enter pixel info mode')        self.btnPixInfo.clicked.connect(self.pixInfo)        self.editPixInfo = QtWidgets.QLineEdit(self)        self.editPixInfo.setReadOnly(True)        self.viewer.photoClicked.connect(self.photoClicked)        # Arrange layout        VBlayout = QtWidgets.QVBoxLayout(self)        VBlayout.addWidget(self.viewer)        HBlayout = QtWidgets.QHBoxLayout()        HBlayout.setAlignment(QtCore.Qt.AlignLeft)        HBlayout.addWidget(self.btnLoad)        HBlayout.addWidget(self.btnPixInfo)        HBlayout.addWidget(self.editPixInfo)        VBlayout.addLayout(HBlayout)    def loadImage(self):        self.viewer.setPhoto(QtGui.QPixmap('image.jpg'))    def pixInfo(self):        self.viewer.toggleDragMode()    def photoClicked(self, pos):        if self.viewer.dragMode()  == QtWidgets.QGraphicsView.NoDrag:            self.editPixInfo.setText('%d, %d' % (pos.x(), pos.y()))if __name__ == '__main__':    import sys    app = QtWidgets.QApplication(sys.argv)    window = Window()    window.setGeometry(500, 300, 800, 600)    window.show()    sys.exit(app.exec_())


It is possible to open TIFF files up to several gigabytes with ordinary PIL (pillow) library. It is not quite easy, but it works.

You could see the example here, second example after bold EDIT string could open, move and zoom TIFF files.