-
Notifications
You must be signed in to change notification settings - Fork 1
/
QtImagePartSelector.py
182 lines (144 loc) · 6.37 KB
/
QtImagePartSelector.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import os.path
try:
from PyQt5.QtCore import Qt, QRectF, pyqtSignal, QT_VERSION_STR, QPoint, QRect, QSize
from PyQt5.QtGui import QImage, QPixmap, QPainterPath
from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QFileDialog, QRubberBand
except ImportError:
raise ImportError("ImportError: Requires PyQt5")
class QtImagePartSelector(QGraphicsView):
"""
Partly based on https://github.com/marcel-goldschen-ohm/PyQtImageViewer
by Marcel Goldschen-Ohm, MIT license
"""
rectSet = pyqtSignal(QRect)
def __init__(self):
QGraphicsView.__init__(self)
# Image is displayed as a QPixmap in a QGraphicsScene attached to this QGraphicsView.
self.scene = QGraphicsScene()
self.setScene(self.scene)
# Store a local handle to the scene's current image pixmap.
self._pixmapHandle = None
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
# rubber band for area selection
self.rubberBand = QRubberBand(QRubberBand.Rectangle, self)
self.rubberBandScenePos = None # so it can be restored after resizing
self.setMouseTracking(True)
self.origin = QPoint()
self.changeRubberBand = False
# previous mouse position during mouse drag action
self.dragPrevMousePos = None
self.setCursor(Qt.CrossCursor)
def hasImage(self):
""" Returns whether or not the scene contains an image pixmap.
"""
return self._pixmapHandle is not None
def clearImage(self):
""" Removes the current image pixmap from the scene if it exists.
"""
if self.hasImage():
self.scene.removeItem(self._pixmapHandle)
self._pixmapHandle = None
def pixmap(self):
""" Returns the scene's current image pixmap as a QPixmap, or else None if no image exists.
:rtype: QPixmap | None
"""
if self.hasImage():
return self._pixmapHandle.pixmap()
return None
def image(self):
""" Returns the scene's current image pixmap as a QImage, or else None if no image exists.
:rtype: QImage | None
"""
if self.hasImage():
return self._pixmapHandle.pixmap().toImage()
return None
def resizeEvent(self, event):
QGraphicsView.resizeEvent(self, event)
self.updateRubberBandDisplay()
def showEvent(self, event):
self.old_center = self.mapToScene(self.rect().center())
def setImage(self, image):
""" Set the scene's current image pixmap to the input QImage or QPixmap.
Raises a RuntimeError if the input image has type other than QImage or QPixmap.
:type image: QImage | QPixmap
"""
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 or QPixmap.")
if self.hasImage():
self._pixmapHandle.setPixmap(pixmap)
else:
self._pixmapHandle = self.scene.addPixmap(pixmap)
self.setSceneRect(QRectF(pixmap.rect())) # Set scene size to image size.
def loadImageFromFile(self, fileName=""):
""" Load an image from file.
Without any arguments, loadImageFromFile() will popup a file dialog to choose the image file.
With a fileName argument, loadImageFromFile(fileName) will attempt to load the specified image file directly.
"""
if len(fileName) == 0:
fileName, dummy = QFileDialog.getOpenFileName(self, "Open image file.")
if len(fileName) and os.path.isfile(fileName):
image = QImage(fileName)
self.setImage(image)
def mousePressEvent(self, event):
""" Start creation of rubber band
"""
if event.button() == Qt.LeftButton:
self.origin = event.pos()
self.rubberBand.setGeometry(QRect(self.origin, QSize()))
self.rubberBandScenePos = self.mapToScene(self.rubberBand.geometry())
self.rubberBand.show()
self.changeRubberBand = True
elif event.button() == Qt.MidButton:
self.setCursor(Qt.ClosedHandCursor)
self.dragPrevMousePos = event.pos()
QGraphicsView.mousePressEvent(self, event)
def mouseMoveEvent(self, event):
if self.changeRubberBand:
# update rubber
self.rubberBand.setGeometry(QRect(self.origin, event.pos()).normalized())
self.rubberBandScenePos = self.mapToScene(self.rubberBand.geometry())
if event.buttons() & Qt.MidButton:
# drag image
offset = self.dragPrevMousePos - event.pos()
self.dragPrevMousePos = event.pos()
self.verticalScrollBar().setValue(self.verticalScrollBar().value() + offset.y())
self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() + offset.x())
self.updateRubberBandDisplay()
QGraphicsView.mouseMoveEvent(self, event)
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
# Emit rubber band size
self.changeRubberBand = False
self.rectSet.emit(self.rubberBandScenePos.boundingRect().toAlignedRect())
elif event.button() == Qt.MiddleButton:
self.setCursor(Qt.CrossCursor)
QGraphicsView.mouseReleaseEvent(self, event)
def updateRubberBandDisplay(self):
if self.rubberBandScenePos is not None:
self.rubberBand.setGeometry(self.mapFromScene(self.rubberBandScenePos).boundingRect())
def wheelEvent(self, event):
# Zoom Factor
zoomInFactor = 1.1
zoomOutFactor = 1 / zoomInFactor
# Set Anchors
self.setTransformationAnchor(QGraphicsView.NoAnchor)
self.setResizeAnchor(QGraphicsView.NoAnchor)
# Save the scene pos
oldPos = self.mapToScene(event.pos())
# Zoom
if event.angleDelta().y() > 0:
zoomFactor = zoomInFactor
else:
zoomFactor = zoomOutFactor
self.scale(zoomFactor, zoomFactor)
# Get the new position
newPos = self.mapToScene(event.pos())
# Move scene to old position
delta = newPos - oldPos
self.translate(delta.x(), delta.y())
self.updateRubberBandDisplay()