Cómo habilitar Pan y Zoom en un QGraphicsView

Estoy usando Python y Qt Designer para implementar la carga de imágenes tiff y para habilitar Pan y Zoom en algún evento del mouse (rueda – zoom, presionar rueda – pan).

Estaba buscando algunas opciones y clases que pueden funcionar con imágenes, etc., y hasta ahora he encontrado:

QGraphicsScene, QImage, QGraphicsView

Tengo tres clases (solo pruebas)

  1. ViewerDemo que tiene elemento QGraphicsView :

      """description of class""" # Form implementation generated from reading ui file 'GraphicsViewdemo.ui' try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName(("Dialog")) Dialog.resize(500, 500) self.graphicsView = QtGui.QGraphicsView(Dialog) self.graphicsView.setGeometry(QtCore.QRect(0, 0, 500, 500)) self.graphicsView.setObjectName(("graphicsView")) self.retranslateUi(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslateUi(self, Dialog): Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "Dialog", None, QtGui.QApplication.UnicodeUTF8)) 
    • Clase MyForm , es decir , QDialog , donde llamo a la clase ViewerDemo , carga la imagen y pongo la imagen en QGraphicsView

        import sys from ViewerDemo import * from PyQt4 import QtGui class MyForm(QtGui.QDialog): def __init__(self, url, parent=None): QtGui.QWidget.__init__(self, parent) self.ui = Ui_Dialog() self.ui.setupUi(self) self.scene = QtGui.QGraphicsScene(self) self.image = QtGui.QImage(url) pixmap= QtGui.QPixmap.fromImage(self.image) item=QtGui.QGraphicsPixmapItem(pixmap) self.scene.addItem(item) self.ui.graphicsView.setScene(self.scene) self.scale = 1 QtCore.QObject.connect(self.scene, QtCore.SIGNAL('mousePressEvent()'),self.mousePressEvent) def mousePressEvent(self, event): print ('PRESSED : ',event.pos()) 

    (3) es justo donde se está ejecutando la aplicación:

      from PyQt4 import QtGui, QtCore import sys from MyForm import MyForm if __name__ == "__main__": app = QtGui.QApplication(sys.argv) url = "D:/probaTiff" myapp = MyForm(url) myapp.show() sys.exit(app.exec_()) 

    Descubrí cómo hacer algo con el clic del mouse (clic izquierdo y con el botón de la rueda), para imprimir las coordenadas de los píxeles (lo necesitaré para obtener las coordenadas en el Sistema de coordenadas de la imagen WGS84, por ejemplo).

    Lo que necesito más es cómo hacer zoom en la imagen (rueda o doble clic, lo que sea) y hacer una panorámica de la imagen (manteniendo pulsado el botón izquierdo del ratón o sosteniendo la rueda).

    O, ¿hay algunas mejores clases de Qt para hacer esto, y alguna mejor manera?

    Esto es lo que tengo hasta ahora con este código.

    Esto no es demasiado difícil de hacer usando las capacidades QGraphicsView de QGraphicsView .

    La siguiente secuencia de comandos tiene una panorámica del botón izquierdo y el zoom de la rueda (incluido el anclaje a la posición actual del cursor). El método fitInView se ha vuelto a implementar porque la versión incorporada agrega un margen fijo extraño que no se puede eliminar.

    Versión de PyQt4:

     from PyQt4 import QtCore, QtGui class 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(QtCore.QPoint(event.pos())) 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_()) 

    Versión de PyQt5:

     from PyQt5 import QtCore, QtGui, QtWidgets class 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(QtCore.QPoint(event.pos())) 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_()) 

    Es posible abrir archivos TIFF de hasta varios gigabytes con la biblioteca PIL (almohada) ordinaria. No es del todo fácil, pero funciona.

    Puede ver el ejemplo aquí , segundo ejemplo después de que la cadena EDIT en negrita pueda abrir, mover y ampliar archivos TIFF.