1+ import os .path
2+ import sys
3+
4+ from PyQt5 .QtCore import Qt , QRectF , QPoint , QPointF , pyqtSignal , QEvent , QSize
5+ from PyQt5 .QtGui import QImage , QPixmap , QPainterPath , QMouseEvent , QPainter , QPen
6+ from PyQt5 .QtWidgets import QGraphicsView , QGraphicsScene , QFileDialog , QSizePolicy
7+ from PyQt5 .QtWidgets import QApplication
8+
9+ class QtImageViewer (QGraphicsView ):
10+
11+ # 定义使用到的鼠标操作信号
12+ leftMouseButtonPressed = pyqtSignal (float , float )
13+ leftMouseButtonReleased = pyqtSignal (float , float )
14+ middleMouseButtonPressed = pyqtSignal (float , float )
15+ middleMouseButtonReleased = pyqtSignal (float , float )
16+ rightMouseButtonPressed = pyqtSignal (float , float )
17+ rightMouseButtonReleased = pyqtSignal (float , float )
18+ leftMouseButtonDoubleClicked = pyqtSignal (float , float )
19+ rightMouseButtonDoubleClicked = pyqtSignal (float , float )
20+ viewChanged = pyqtSignal ()
21+ mousePositionOnImageChanged = pyqtSignal (QPoint )
22+ roiSelected = pyqtSignal (int )
23+
24+ def __init__ (self ):
25+ QGraphicsView .__init__ (self )
26+
27+ self .scene = QGraphicsScene ()
28+ self .setScene (self .scene )
29+
30+ # 显示的图片
31+ self ._image = None
32+ self .aspectRatioMode = Qt .AspectRatioMode .KeepAspectRatio
33+
34+ # 默认没有滚动条
35+ self .setHorizontalScrollBarPolicy (Qt .ScrollBarPolicy .ScrollBarAlwaysOff )
36+ self .setVerticalScrollBarPolicy (Qt .ScrollBarPolicy .ScrollBarAlwaysOff )
37+
38+ # 左键区域放大
39+ self .regionZoomButton = Qt .MouseButton .LeftButton
40+ # 右键缩小
41+ self .zoomOutButton = Qt .MouseButton .RightButton
42+ # 中键拖动
43+ self .panButton = Qt .MouseButton .MiddleButton
44+ # 放大或缩小的因子
45+ self .wheelZoomFactor = 1.25
46+
47+ self .zoomStack = []
48+
49+ # Flags for active zooming/panning.
50+ self ._isZooming = False
51+ self ._isPanning = False
52+
53+ self ._pixelPosition = QPoint ()
54+ self ._scenePosition = QPointF ()
55+
56+ self .ROIs = []
57+
58+ self .setSizePolicy (QSizePolicy .Policy .Expanding , QSizePolicy .Policy .Expanding )
59+
60+ def hasImage (self ):
61+ return self ._image is not None
62+
63+ def setImage (self , image ):
64+
65+ # 同时支持 QPixmap 和 QImage
66+ if type (image ) is QPixmap :
67+ pixmap = image
68+ elif type (image ) is QImage :
69+ pixmap = QPixmap .fromImage (image )
70+ else :
71+ raise RuntimeError ("ImageViewer.setImage: Argument must be a QImage, QPixmap." )
72+ if self .hasImage ():
73+ self ._image .setPixmap (pixmap )
74+ else :
75+ self ._image = self .scene .addPixmap (pixmap )
76+
77+ self .setSceneRect (QRectF (pixmap .rect ())) # Set scene size to image size.
78+ self .updateViewer ()
79+
80+ def open (self , filepath = None ):
81+
82+ # 不带参数,默认打开文件选择器选择文件
83+ if filepath is None :
84+ filepath , dummy = QFileDialog .getOpenFileName (self , "Open image file." )
85+ if len (filepath ) and os .path .isfile (filepath ):
86+ image = QImage (filepath )
87+ self .setImage (image )
88+
89+ def updateViewer (self ):
90+
91+ if not self .hasImage ():
92+ return
93+ if len (self .zoomStack ):
94+ self .fitInView (self .zoomStack [- 1 ], self .aspectRatioMode )
95+ else :
96+ self .fitInView (self .sceneRect (), self .aspectRatioMode )
97+
98+ def clearZoom (self ):
99+ if len (self .zoomStack ) > 0 :
100+ self .zoomStack = []
101+ self .updateViewer ()
102+ self .viewChanged .emit ()
103+
104+ def resizeEvent (self , event ):
105+ # 事件响应
106+ self .updateViewer ()
107+
108+ def mousePressEvent (self , event ):
109+ dummyModifiers = Qt .KeyboardModifier (Qt .KeyboardModifier .ShiftModifier | Qt .KeyboardModifier .ControlModifier
110+ | Qt .KeyboardModifier .AltModifier | Qt .KeyboardModifier .MetaModifier )
111+ if event .modifiers () == dummyModifiers :
112+ QGraphicsView .mousePressEvent (self , event )
113+ event .accept ()
114+ return
115+
116+ if (self .regionZoomButton is not None ) and (event .button () == self .regionZoomButton ):
117+ self ._pixelPosition = event .pos ()
118+ self .setDragMode (QGraphicsView .DragMode .RubberBandDrag )
119+ QGraphicsView .mousePressEvent (self , event )
120+ event .accept ()
121+ self ._isZooming = True
122+ return
123+
124+ if (self .zoomOutButton is not None ) and (event .button () == self .zoomOutButton ):
125+ if len (self .zoomStack ):
126+ self .zoomStack .pop ()
127+ self .updateViewer ()
128+ self .viewChanged .emit ()
129+ event .accept ()
130+ return
131+
132+ if (self .panButton is not None ) and (event .button () == self .panButton ):
133+ self ._pixelPosition = event .pos () # store pixel position
134+ self .setDragMode (QGraphicsView .DragMode .ScrollHandDrag )
135+ if self .panButton == Qt .MouseButton .LeftButton :
136+ QGraphicsView .mousePressEvent (self , event )
137+ else :
138+ self .viewport ().setCursor (Qt .CursorShape .ClosedHandCursor )
139+ dummyModifiers = Qt .KeyboardModifier (Qt .KeyboardModifier .ShiftModifier
140+ | Qt .KeyboardModifier .ControlModifier
141+ | Qt .KeyboardModifier .AltModifier
142+ | Qt .KeyboardModifier .MetaModifier )
143+ dummyEvent = QMouseEvent (QEvent .Type .MouseButtonPress , QPointF (event .pos ()), Qt .MouseButton .LeftButton ,
144+ event .buttons (), dummyModifiers )
145+ self .mousePressEvent (dummyEvent )
146+ sceneViewport = self .mapToScene (self .viewport ().rect ()).boundingRect ().intersected (self .sceneRect ())
147+ self ._scenePosition = sceneViewport .topLeft ()
148+ event .accept ()
149+ self ._isPanning = True
150+ return
151+
152+ scenePos = self .mapToScene (event .pos ())
153+ if event .button () == Qt .MouseButton .LeftButton :
154+ self .leftMouseButtonPressed .emit (scenePos .x (), scenePos .y ())
155+ elif event .button () == Qt .MouseButton .MiddleButton :
156+ self .middleMouseButtonPressed .emit (scenePos .x (), scenePos .y ())
157+ elif event .button () == Qt .MouseButton .RightButton :
158+ self .rightMouseButtonPressed .emit (scenePos .x (), scenePos .y ())
159+
160+ QGraphicsView .mousePressEvent (self , event )
161+
162+ def mouseReleaseEvent (self , event ):
163+ dummyModifiers = Qt .KeyboardModifier (Qt .KeyboardModifier .ShiftModifier | Qt .KeyboardModifier .ControlModifier
164+ | Qt .KeyboardModifier .AltModifier | Qt .KeyboardModifier .MetaModifier )
165+ if event .modifiers () == dummyModifiers :
166+ QGraphicsView .mouseReleaseEvent (self , event )
167+ event .accept ()
168+ return
169+
170+ if (self .regionZoomButton is not None ) and (event .button () == self .regionZoomButton ):
171+ QGraphicsView .mouseReleaseEvent (self , event )
172+ zoomRect = self .scene .selectionArea ().boundingRect ().intersected (self .sceneRect ())
173+ self .scene .setSelectionArea (QPainterPath ())
174+ self .setDragMode (QGraphicsView .DragMode .NoDrag )
175+ zoomPixelWidth = abs (event .pos ().x () - self ._pixelPosition .x ())
176+ zoomPixelHeight = abs (event .pos ().y () - self ._pixelPosition .y ())
177+ if zoomPixelWidth > 3 and zoomPixelHeight > 3 :
178+ if zoomRect .isValid () and (zoomRect != self .sceneRect ()):
179+ self .zoomStack .append (zoomRect )
180+ self .updateViewer ()
181+ self .viewChanged .emit ()
182+ event .accept ()
183+ self ._isZooming = False
184+ return
185+
186+ if (self .panButton is not None ) and (event .button () == self .panButton ):
187+ if self .panButton == Qt .MouseButton .LeftButton :
188+ QGraphicsView .mouseReleaseEvent (self , event )
189+ else :
190+ self .viewport ().setCursor (Qt .CursorShape .ArrowCursor )
191+ dummyModifiers = Qt .KeyboardModifier (Qt .KeyboardModifier .ShiftModifier
192+ | Qt .KeyboardModifier .ControlModifier
193+ | Qt .KeyboardModifier .AltModifier
194+ | Qt .KeyboardModifier .MetaModifier )
195+ dummyEvent = QMouseEvent (QEvent .Type .MouseButtonRelease , QPointF (event .pos ()),
196+ Qt .MouseButton .LeftButton , event .buttons (), dummyModifiers )
197+ self .mouseReleaseEvent (dummyEvent )
198+ self .setDragMode (QGraphicsView .DragMode .NoDrag )
199+ if len (self .zoomStack ) > 0 :
200+ sceneViewport = self .mapToScene (self .viewport ().rect ()).boundingRect ().intersected (self .sceneRect ())
201+ delta = sceneViewport .topLeft () - self ._scenePosition
202+ self .zoomStack [- 1 ].translate (delta )
203+ self .zoomStack [- 1 ] = self .zoomStack [- 1 ].intersected (self .sceneRect ())
204+ self .viewChanged .emit ()
205+ event .accept ()
206+ self ._isPanning = False
207+ return
208+
209+ scenePos = self .mapToScene (event .pos ())
210+ if event .button () == Qt .MouseButton .LeftButton :
211+ self .leftMouseButtonReleased .emit (scenePos .x (), scenePos .y ())
212+ elif event .button () == Qt .MouseButton .MiddleButton :
213+ self .middleMouseButtonReleased .emit (scenePos .x (), scenePos .y ())
214+ elif event .button () == Qt .MouseButton .RightButton :
215+ self .rightMouseButtonReleased .emit (scenePos .x (), scenePos .y ())
216+
217+ QGraphicsView .mouseReleaseEvent (self , event )
218+
219+ def mouseDoubleClickEvent (self , event ):
220+ if (self .zoomOutButton is not None ) and (event .button () == self .zoomOutButton ):
221+ self .clearZoom ()
222+ event .accept ()
223+ return
224+
225+ scenePos = self .mapToScene (event .pos ())
226+ if event .button () == Qt .MouseButton .LeftButton :
227+ self .leftMouseButtonDoubleClicked .emit (scenePos .x (), scenePos .y ())
228+ elif event .button () == Qt .MouseButton .RightButton :
229+ self .rightMouseButtonDoubleClicked .emit (scenePos .x (), scenePos .y ())
230+
231+ QGraphicsView .mouseDoubleClickEvent (self , event )
232+
233+ def wheelEvent (self , event ):
234+ if self .wheelZoomFactor is not None :
235+ if self .wheelZoomFactor == 1 :
236+ return
237+ if event .angleDelta ().y () < 0 :
238+ # zoom in
239+ if len (self .zoomStack ) == 0 :
240+ self .zoomStack .append (self .sceneRect ())
241+ elif len (self .zoomStack ) > 1 :
242+ del self .zoomStack [:- 1 ]
243+ zoomRect = self .zoomStack [- 1 ]
244+ center = zoomRect .center ()
245+ zoomRect .setWidth (zoomRect .width () / self .wheelZoomFactor )
246+ zoomRect .setHeight (zoomRect .height () / self .wheelZoomFactor )
247+ zoomRect .moveCenter (center )
248+ self .zoomStack [- 1 ] = zoomRect .intersected (self .sceneRect ())
249+ self .updateViewer ()
250+ self .viewChanged .emit ()
251+ else :
252+ if len (self .zoomStack ) == 0 :
253+ return
254+
255+ if len (self .zoomStack ) > 1 :
256+ del self .zoomStack [:- 1 ]
257+ zoomRect = self .zoomStack [- 1 ]
258+ center = zoomRect .center ()
259+ zoomRect .setWidth (zoomRect .width () * self .wheelZoomFactor )
260+ zoomRect .setHeight (zoomRect .height () * self .wheelZoomFactor )
261+ zoomRect .moveCenter (center )
262+ self .zoomStack [- 1 ] = zoomRect .intersected (self .sceneRect ())
263+ if self .zoomStack [- 1 ] == self .sceneRect ():
264+ self .zoomStack = []
265+ self .updateViewer ()
266+ self .viewChanged .emit ()
267+ event .accept ()
268+ return
269+
270+ QGraphicsView .wheelEvent (self , event )
271+
272+ def mouseMoveEvent (self , event ):
273+ if self ._isPanning :
274+ QGraphicsView .mouseMoveEvent (self , event )
275+ if len (self .zoomStack ) > 0 :
276+ sceneViewport = self .mapToScene (self .viewport ().rect ()).boundingRect ().intersected (self .sceneRect ())
277+ delta = sceneViewport .topLeft () - self ._scenePosition
278+ self ._scenePosition = sceneViewport .topLeft ()
279+ self .zoomStack [- 1 ].translate (delta )
280+ self .zoomStack [- 1 ] = self .zoomStack [- 1 ].intersected (self .sceneRect ())
281+ self .updateViewer ()
282+ self .viewChanged .emit ()
283+
284+ scenePos = self .mapToScene (event .pos ())
285+ if self .sceneRect ().contains (scenePos ):
286+ x = int (round (scenePos .x () - 0.5 ))
287+ y = int (round (scenePos .y () - 0.5 ))
288+ imagePos = QPoint (x , y )
289+ else :
290+ imagePos = QPoint (- 1 , - 1 )
291+ self .mousePositionOnImageChanged .emit (imagePos )
292+
293+ QGraphicsView .mouseMoveEvent (self , event )
294+
295+ def enterEvent (self , event ):
296+ self .setCursor (Qt .CursorShape .CrossCursor )
297+
298+ def leaveEvent (self , event ):
299+ self .setCursor (Qt .CursorShape .ArrowCursor )
300+
301+ if __name__ == '__main__' :
302+
303+ app = QApplication (sys .argv )
304+ viewer = QtImageViewer ()
305+ # viewer.open(filepath='test.jpg')
306+ viewer .open ()
307+ viewer .show ()
308+ sys .exit (app .exec ())
0 commit comments