Software and hardware environment
- Windows 10 64bit
- Anaconda3 with python 3.8
- PyQt5 5.15
Introduction
To display a large picture in a small window, sometimes you need to view the details of the picture, and at this time you need to zoom in on the picture. This requirement is very common in map applications. This article will achieve such an effect.
Practical
This is implemented based on QGraphicsView
, setting the left mouse button to delineate the zoom area, the right button to restore the original state, and the up and down scroll wheel to zoom out. The main knowledge point is the operation of mouse events
Look at the specific code
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())
Execute the above code, you can get
Source code download
https://github.com/xugaoxiang/learningPyQt5
PyQt5 series of tutorials
For more PyQt5
tutorials, please move to
https://xugaoxiang.com/category/python/pyqt5/
This article is transferred from https://xugaoxiang.com/2022/12/06/pyqt5-37-image-zoom/
This site is only for collection, and the copyright belongs to the original author.