如何在 QGraphicsView 中启用平移和缩放
Posted
技术标签:
【中文标题】如何在 QGraphicsView 中启用平移和缩放【英文标题】:How to enable Pan and Zoom in a QGraphicsView 【发布时间】:2016-06-01 05:27:14 【问题描述】:我正在使用 python 和 Qt Designer 来实现加载 tiff 图像并在某些鼠标事件(滚轮 - 缩放,按下滚轮 - 平移)上启用平移和缩放。
我正在研究一些可以处理图像等的选项和类,到目前为止我发现:
QGraphicsScene、QImage、QGraphicsView
我有三个班(只是测试)
ViewerDemo 有 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))
MyForm类,即QDialog,这里我调用类ViewerDemo,加载Image,将图片放入QGraphicsView em>
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) 就是应用程序正在执行的地方:
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_())
我发现了如何通过鼠标单击(左键和滚轮单击)来打印像素坐标(例如,我需要它来获取图片 WGS84 的坐标系中的坐标)。
我更需要的是如何缩放图片(滚轮或双击,等等)和平移图片(按住鼠标左键单击或按住滚轮)。
或者,是否有一些更好的 Qt 类可以做到这一点,以及一些更好的方法 你能帮帮我吗?
这就是我到目前为止的代码
【问题讨论】:
【参考方案1】:使用QGraphicsView
的内置功能做到这一点并不难。
下面的演示脚本具有左键平移和滚轮缩放(包括锚定到当前光标位置)。 fitInView
方法已被重新实现,因为内置版本添加了一个无法删除的奇怪固定边距。
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(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 版本:
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(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_())
【讨论】:
哇。非常感谢。你做了一切。就一个问题。如何删除鼠标光标“手”?我只是想在按住左键时显示手(就在我想平移时)?第二个问题,如何添加一些矢量图形,并在图片上放置一些像素?非常感谢!! 还有一个问题。如何设置我的 QGraphicsView 不大于图片。因为当我单击时,我会在查看器上获得像素位置,有时没有图片。我需要不在查看器上的图片元素的像素位置。帮忙? 在鼠标光标上:我想您可以将拖动模式设置为鼠标向下/鼠标向上之类的。至于其他问题:请提出新问题。 好的。我在 mousePressed 上启用了拖动模式,在双击时,我使用函数 mapToScene 来查找场景的坐标。非常感谢。 再问一个问题。它与缩放选项有关。如何查看图像比例(1:1000、1:2500)等。如果你明白我的意思,【参考方案2】:使用普通 PIL (pillow) 库可以打开高达数 GB 的 TIFF 文件。这并不容易,但它确实有效。
你可以看到example here,在粗体EDIT字符串之后的第二个例子可以打开、移动和缩放TIFF文件。
【讨论】:
以上是关于如何在 QGraphicsView 中启用平移和缩放的主要内容,如果未能解决你的问题,请参考以下文章
如何在自定义 UIView 下方的 MKMapview 中启用 touchEvents(滚动和平移)?
QGraphicsView ensureVisible() 和 centerOn()
如何为 UINavigationController 启用全屏平移
滚动 QGraphicsView 和 QGraphicsScene