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.