软硬件环境
- Windows 10 64bit
- Anaconda3 with python 3.8
- PyQt5 5.15
简介
在一个较小窗口中去显示一张大图片,某些时候需要查看图片的细节,这时候就需要将图片放大,这个需求在地图应用中非常常见。本篇就来实现一个这样的效果。
实操
这里基于 QGraphicsView
来实现,设置鼠标左键划定缩放区域、右键恢复原始状态、上下滚轮进行方法缩小,主要的知识点是鼠标事件的操作
来看具体的代码
import os.path
import sys
from PyQt5.QtCore import Qt, QRectF, QPoint, QPointF, pyqtSignal, QEvent, QSize
from PyQt5.QtGui import QImage, QPixmap, QPainterPath, QMouseEvent, QPainter, QPen
from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QFileDialog, QSizePolicy
from PyQt5.QtWidgets import QApplication
class QtImageViewer(QGraphicsView):
# 定义使用到的鼠标操作信号
leftMouseButtonPressed = pyqtSignal(float, float)
leftMouseButtonReleased = pyqtSignal(float, float)
middleMouseButtonPressed = pyqtSignal(float, float)
middleMouseButtonReleased = pyqtSignal(float, float)
rightMouseButtonPressed = pyqtSignal(float, float)
rightMouseButtonReleased = pyqtSignal(float, float)
leftMouseButtonDoubleClicked = pyqtSignal(float, float)
rightMouseButtonDoubleClicked = pyqtSignal(float, float)
viewChanged = pyqtSignal()
mousePositionOnImageChanged = pyqtSignal(QPoint)
roiSelected = pyqtSignal(int)
def __init__(self):
QGraphicsView.__init__(self)
self.scene = QGraphicsScene()
self.setScene(self.scene)
# 显示的图片
self._image = None
self.aspectRatioMode = Qt.AspectRatioMode.KeepAspectRatio
# 默认没有滚动条
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
# 左键区域放大
self.regionZoomButton = Qt.MouseButton.LeftButton
# 右键缩小
self.zoomOutButton = Qt.MouseButton.RightButton
# 中键拖动
self.panButton = Qt.MouseButton.MiddleButton
# 放大或缩小的因子
self.wheelZoomFactor = 1.25
self.zoomStack = []
# Flags for active zooming/panning.
self._isZooming = False
self._isPanning = False
self._pixelPosition = QPoint()
self._scenePosition = QPointF()
self.ROIs = []
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
def hasImage(self):
return self._image is not None
def setImage(self, image):
# 同时支持 QPixmap 和 QImage
if type(image) is QPixmap:
pixmap = image
elif type(image) is QImage:
pixmap = QPixmap.fromImage(image)
else:
raise RuntimeError("ImageViewer.setImage: Argument must be a QImage, QPixmap.")
if self.hasImage():
self._image.setPixmap(pixmap)
else:
self._image = self.scene.addPixmap(pixmap)
self.setSceneRect(QRectF(pixmap.rect())) # Set scene size to image size.
self.updateViewer()
def open(self, filepath=None):
# 不带参数,默认打开文件选择器选择文件
if filepath is None:
filepath, dummy = QFileDialog.getOpenFileName(self, "Open image file.")
if len(filepath) and os.path.isfile(filepath):
image = QImage(filepath)
self.setImage(image)
def updateViewer(self):
if not self.hasImage():
return
if len(self.zoomStack):
self.fitInView(self.zoomStack[-1], self.aspectRatioMode)
else:
self.fitInView(self.sceneRect(), self.aspectRatioMode)
def clearZoom(self):
if len(self.zoomStack) > 0:
self.zoomStack = []
self.updateViewer()
self.viewChanged.emit()
def resizeEvent(self, event):
# 事件响应
self.updateViewer()
def mousePressEvent(self, event):
dummyModifiers = Qt.KeyboardModifier(Qt.KeyboardModifier.ShiftModifier | Qt.KeyboardModifier.ControlModifier
| Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.MetaModifier)
if event.modifiers() == dummyModifiers:
QGraphicsView.mousePressEvent(self, event)
event.accept()
return
if (self.regionZoomButton is not None) and (event.button() == self.regionZoomButton):
self._pixelPosition = event.pos()
self.setDragMode(QGraphicsView.DragMode.RubberBandDrag)
QGraphicsView.mousePressEvent(self, event)
event.accept()
self._isZooming = True
return
if (self.zoomOutButton is not None) and (event.button() == self.zoomOutButton):
if len(self.zoomStack):
self.zoomStack.pop()
self.updateViewer()
self.viewChanged.emit()
event.accept()
return
if (self.panButton is not None) and (event.button() == self.panButton):
self._pixelPosition = event.pos() # store pixel position
self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
if self.panButton == Qt.MouseButton.LeftButton:
QGraphicsView.mousePressEvent(self, event)
else:
self.viewport().setCursor(Qt.CursorShape.ClosedHandCursor)
dummyModifiers = Qt.KeyboardModifier(Qt.KeyboardModifier.ShiftModifier
| Qt.KeyboardModifier.ControlModifier
| Qt.KeyboardModifier.AltModifier
| Qt.KeyboardModifier.MetaModifier)
dummyEvent = QMouseEvent(QEvent.Type.MouseButtonPress, QPointF(event.pos()), Qt.MouseButton.LeftButton,
event.buttons(), dummyModifiers)
self.mousePressEvent(dummyEvent)
sceneViewport = self.mapToScene(self.viewport().rect()).boundingRect().intersected(self.sceneRect())
self._scenePosition = sceneViewport.topLeft()
event.accept()
self._isPanning = True
return
scenePos = self.mapToScene(event.pos())
if event.button() == Qt.MouseButton.LeftButton:
self.leftMouseButtonPressed.emit(scenePos.x(), scenePos.y())
elif event.button() == Qt.MouseButton.MiddleButton:
self.middleMouseButtonPressed.emit(scenePos.x(), scenePos.y())
elif event.button() == Qt.MouseButton.RightButton:
self.rightMouseButtonPressed.emit(scenePos.x(), scenePos.y())
QGraphicsView.mousePressEvent(self, event)
def mouseReleaseEvent(self, event):
dummyModifiers = Qt.KeyboardModifier(Qt.KeyboardModifier.ShiftModifier | Qt.KeyboardModifier.ControlModifier
| Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.MetaModifier)
if event.modifiers() == dummyModifiers:
QGraphicsView.mouseReleaseEvent(self, event)
event.accept()
return
if (self.regionZoomButton is not None) and (event.button() == self.regionZoomButton):
QGraphicsView.mouseReleaseEvent(self, event)
zoomRect = self.scene.selectionArea().boundingRect().intersected(self.sceneRect())
self.scene.setSelectionArea(QPainterPath())
self.setDragMode(QGraphicsView.DragMode.NoDrag)
zoomPixelWidth = abs(event.pos().x() - self._pixelPosition.x())
zoomPixelHeight = abs(event.pos().y() - self._pixelPosition.y())
if zoomPixelWidth > 3 and zoomPixelHeight > 3:
if zoomRect.isValid() and (zoomRect != self.sceneRect()):
self.zoomStack.append(zoomRect)
self.updateViewer()
self.viewChanged.emit()
event.accept()
self._isZooming = False
return
if (self.panButton is not None) and (event.button() == self.panButton):
if self.panButton == Qt.MouseButton.LeftButton:
QGraphicsView.mouseReleaseEvent(self, event)
else:
self.viewport().setCursor(Qt.CursorShape.ArrowCursor)
dummyModifiers = Qt.KeyboardModifier(Qt.KeyboardModifier.ShiftModifier
| Qt.KeyboardModifier.ControlModifier
| Qt.KeyboardModifier.AltModifier
| Qt.KeyboardModifier.MetaModifier)
dummyEvent = QMouseEvent(QEvent.Type.MouseButtonRelease, QPointF(event.pos()),
Qt.MouseButton.LeftButton, event.buttons(), dummyModifiers)
self.mouseReleaseEvent(dummyEvent)
self.setDragMode(QGraphicsView.DragMode.NoDrag)
if len(self.zoomStack) > 0:
sceneViewport = self.mapToScene(self.viewport().rect()).boundingRect().intersected(self.sceneRect())
delta = sceneViewport.topLeft() - self._scenePosition
self.zoomStack[-1].translate(delta)
self.zoomStack[-1] = self.zoomStack[-1].intersected(self.sceneRect())
self.viewChanged.emit()
event.accept()
self._isPanning = False
return
scenePos = self.mapToScene(event.pos())
if event.button() == Qt.MouseButton.LeftButton:
self.leftMouseButtonReleased.emit(scenePos.x(), scenePos.y())
elif event.button() == Qt.MouseButton.MiddleButton:
self.middleMouseButtonReleased.emit(scenePos.x(), scenePos.y())
elif event.button() == Qt.MouseButton.RightButton:
self.rightMouseButtonReleased.emit(scenePos.x(), scenePos.y())
QGraphicsView.mouseReleaseEvent(self, event)
def mouseDoubleClickEvent(self, event):
if (self.zoomOutButton is not None) and (event.button() == self.zoomOutButton):
self.clearZoom()
event.accept()
return
scenePos = self.mapToScene(event.pos())
if event.button() == Qt.MouseButton.LeftButton:
self.leftMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y())
elif event.button() == Qt.MouseButton.RightButton:
self.rightMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y())
QGraphicsView.mouseDoubleClickEvent(self, event)
def wheelEvent(self, event):
if self.wheelZoomFactor is not None:
if self.wheelZoomFactor == 1:
return
if event.angleDelta().y() < 0:
# zoom in
if len(self.zoomStack) == 0:
self.zoomStack.append(self.sceneRect())
elif len(self.zoomStack) > 1:
del self.zoomStack[:-1]
zoomRect = self.zoomStack[-1]
center = zoomRect.center()
zoomRect.setWidth(zoomRect.width() / self.wheelZoomFactor)
zoomRect.setHeight(zoomRect.height() / self.wheelZoomFactor)
zoomRect.moveCenter(center)
self.zoomStack[-1] = zoomRect.intersected(self.sceneRect())
self.updateViewer()
self.viewChanged.emit()
else:
if len(self.zoomStack) == 0:
return
if len(self.zoomStack) > 1:
del self.zoomStack[:-1]
zoomRect = self.zoomStack[-1]
center = zoomRect.center()
zoomRect.setWidth(zoomRect.width() * self.wheelZoomFactor)
zoomRect.setHeight(zoomRect.height() * self.wheelZoomFactor)
zoomRect.moveCenter(center)
self.zoomStack[-1] = zoomRect.intersected(self.sceneRect())
if self.zoomStack[-1] == self.sceneRect():
self.zoomStack = []
self.updateViewer()
self.viewChanged.emit()
event.accept()
return
QGraphicsView.wheelEvent(self, event)
def mouseMoveEvent(self, event):
if self._isPanning:
QGraphicsView.mouseMoveEvent(self, event)
if len(self.zoomStack) > 0:
sceneViewport = self.mapToScene(self.viewport().rect()).boundingRect().intersected(self.sceneRect())
delta = sceneViewport.topLeft() - self._scenePosition
self._scenePosition = sceneViewport.topLeft()
self.zoomStack[-1].translate(delta)
self.zoomStack[-1] = self.zoomStack[-1].intersected(self.sceneRect())
self.updateViewer()
self.viewChanged.emit()
scenePos = self.mapToScene(event.pos())
if self.sceneRect().contains(scenePos):
x = int(round(scenePos.x() - 0.5))
y = int(round(scenePos.y() - 0.5))
imagePos = QPoint(x, y)
else:
imagePos = QPoint(-1, -1)
self.mousePositionOnImageChanged.emit(imagePos)
QGraphicsView.mouseMoveEvent(self, event)
def enterEvent(self, event):
self.setCursor(Qt.CursorShape.CrossCursor)
def leaveEvent(self, event):
self.setCursor(Qt.CursorShape.ArrowCursor)
if __name__ == '__main__':
app = QApplication(sys.argv)
viewer = QtImageViewer()
# viewer.open(filepath='test.jpg')
viewer.open()
viewer.show()
sys.exit(app.exec())
执行上述代码,可以得到
源码下载
https://github.com/xugaoxiang/learningPyQt5
PyQt5系列教程
更多PyQt5
教程,请移步