canvas.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. # Copyright (c) <2015-Present> Tzutalin
  2. # Copyright (C) 2013 MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
  3. # William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
  4. # associated documentation files (the "Software"), to deal in the Software without restriction, including without
  5. # limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
  6. # Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
  7. # The above copyright notice and this permission notice shall be included in all copies or substantial portions of
  8. # the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
  9. # NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
  10. # SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
  11. # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  12. # THE SOFTWARE.
  13. try:
  14. from PyQt5.QtGui import *
  15. from PyQt5.QtCore import *
  16. from PyQt5.QtWidgets import *
  17. except ImportError:
  18. from PyQt4.QtGui import *
  19. from PyQt4.QtCore import *
  20. #from PyQt4.QtOpenGL import *
  21. from libs.shape import Shape
  22. from libs.utils import distance
  23. CURSOR_DEFAULT = Qt.ArrowCursor
  24. CURSOR_POINT = Qt.PointingHandCursor
  25. CURSOR_DRAW = Qt.CrossCursor
  26. CURSOR_MOVE = Qt.ClosedHandCursor
  27. CURSOR_GRAB = Qt.OpenHandCursor
  28. # class Canvas(QGLWidget):
  29. class Canvas(QWidget):
  30. zoomRequest = pyqtSignal(int)
  31. scrollRequest = pyqtSignal(int, int)
  32. newShape = pyqtSignal()
  33. selectionChanged = pyqtSignal(bool)
  34. shapeMoved = pyqtSignal()
  35. drawingPolygon = pyqtSignal(bool)
  36. CREATE, EDIT = list(range(2))
  37. _fill_drawing = False # draw shadows
  38. epsilon = 11.0
  39. def __init__(self, *args, **kwargs):
  40. super(Canvas, self).__init__(*args, **kwargs)
  41. # Initialise local state.
  42. self.mode = self.EDIT
  43. self.shapes = []
  44. self.current = None
  45. self.selectedShape = None # save the selected shape here
  46. self.selectedShapeCopy = None
  47. self.drawingLineColor = QColor(0, 0, 255)
  48. self.drawingRectColor = QColor(0, 0, 255)
  49. self.line = Shape(line_color=self.drawingLineColor)
  50. self.prevPoint = QPointF()
  51. self.offsets = QPointF(), QPointF()
  52. self.scale = 1.0
  53. self.pixmap = QPixmap()
  54. self.visible = {}
  55. self._hideBackround = False
  56. self.hideBackround = False
  57. self.hShape = None
  58. self.hVertex = None
  59. self._painter = QPainter()
  60. self._cursor = CURSOR_DEFAULT
  61. # Menus:
  62. self.menus = (QMenu(), QMenu())
  63. # Set widget options.
  64. self.setMouseTracking(True)
  65. self.setFocusPolicy(Qt.WheelFocus)
  66. self.verified = False
  67. self.drawSquare = False
  68. self.fourpoint = True # ADD
  69. self.pointnum = 0
  70. #initialisation for panning
  71. self.pan_initial_pos = QPoint()
  72. def setDrawingColor(self, qColor):
  73. self.drawingLineColor = qColor
  74. self.drawingRectColor = qColor
  75. def enterEvent(self, ev):
  76. self.overrideCursor(self._cursor)
  77. def leaveEvent(self, ev):
  78. self.restoreCursor()
  79. def focusOutEvent(self, ev):
  80. self.restoreCursor()
  81. def isVisible(self, shape):
  82. return self.visible.get(shape, True)
  83. def drawing(self):
  84. return self.mode == self.CREATE
  85. def editing(self):
  86. return self.mode == self.EDIT
  87. def setEditing(self, value=True):
  88. self.mode = self.EDIT if value else self.CREATE
  89. if not value: # Create
  90. self.unHighlight()
  91. self.deSelectShape()
  92. self.prevPoint = QPointF()
  93. self.repaint()
  94. def unHighlight(self):
  95. if self.hShape:
  96. self.hShape.highlightClear()
  97. self.hVertex = self.hShape = None
  98. def selectedVertex(self):
  99. return self.hVertex is not None
  100. def mouseMoveEvent(self, ev):
  101. """Update line with last point and current coordinates."""
  102. pos = self.transformPos(ev.pos())
  103. # Update coordinates in status bar if image is opened
  104. window = self.parent().window()
  105. if window.filePath is not None:
  106. self.parent().window().labelCoordinates.setText(
  107. 'X: %d; Y: %d' % (pos.x(), pos.y()))
  108. # Polygon drawing.
  109. if self.drawing():
  110. self.overrideCursor(CURSOR_DRAW) # ?
  111. if self.current:
  112. # Display annotation width and height while drawing
  113. currentWidth = abs(self.current[0].x() - pos.x())
  114. currentHeight = abs(self.current[0].y() - pos.y())
  115. self.parent().window().labelCoordinates.setText(
  116. 'Width: %d, Height: %d / X: %d; Y: %d' % (currentWidth, currentHeight, pos.x(), pos.y()))
  117. color = self.drawingLineColor
  118. if self.outOfPixmap(pos):
  119. # Don't allow the user to draw outside the pixmap.
  120. # Clip the coordinates to 0 or max,
  121. # if they are outside the range [0, max]
  122. size = self.pixmap.size()
  123. clipped_x = min(max(0, pos.x()), size.width())
  124. clipped_y = min(max(0, pos.y()), size.height())
  125. pos = QPointF(clipped_x, clipped_y)
  126. elif len(self.current) > 1 and self.closeEnough(pos, self.current[0]) and not self.fourpoint:
  127. # Attract line to starting point and colorise to alert the
  128. # user:
  129. pos = self.current[0]
  130. color = self.current.line_color
  131. self.overrideCursor(CURSOR_POINT)
  132. self.current.highlightVertex(0, Shape.NEAR_VERTEX)
  133. elif ( # ADD
  134. len(self.current) > 1
  135. and self.fourpoint
  136. and self.closeEnough(pos, self.current[0])
  137. ):
  138. # Attract line to starting point and
  139. # colorise to alert the user.
  140. pos = self.current[0]
  141. self.overrideCursor(CURSOR_POINT)
  142. self.current.highlightVertex(0, Shape.NEAR_VERTEX)
  143. if self.drawSquare:
  144. initPos = self.current[0]
  145. minX = initPos.x()
  146. minY = initPos.y()
  147. min_size = min(abs(pos.x() - minX), abs(pos.y() - minY))
  148. directionX = -1 if pos.x() - minX < 0 else 1
  149. directionY = -1 if pos.y() - minY < 0 else 1
  150. self.line[1] = QPointF(minX + directionX * min_size, minY + directionY * min_size)
  151. elif self.fourpoint:
  152. # self.line[self.pointnum] = pos # OLD
  153. self.line[0] = self.current[-1]
  154. self.line[1] = pos
  155. else:
  156. self.line[1] = pos # pos is the mouse's current position
  157. self.line.line_color = color
  158. self.prevPoint = QPointF() # ?
  159. self.current.highlightClear()
  160. else:
  161. self.prevPoint = pos
  162. self.repaint()
  163. return
  164. # Polygon copy moving.
  165. if Qt.RightButton & ev.buttons():
  166. if self.selectedShapeCopy and self.prevPoint:
  167. self.overrideCursor(CURSOR_MOVE)
  168. self.boundedMoveShape(self.selectedShapeCopy, pos)
  169. self.repaint()
  170. elif self.selectedShape:
  171. self.selectedShapeCopy = self.selectedShape.copy()
  172. self.repaint()
  173. return
  174. # Polygon/Vertex moving.
  175. if Qt.LeftButton & ev.buttons():
  176. if self.selectedVertex():
  177. self.boundedMoveVertex(pos)
  178. self.shapeMoved.emit()
  179. self.repaint()
  180. elif self.selectedShape and self.prevPoint:
  181. self.overrideCursor(CURSOR_MOVE)
  182. self.boundedMoveShape(self.selectedShape, pos)
  183. self.shapeMoved.emit()
  184. self.repaint()
  185. else:
  186. #pan
  187. delta_x = pos.x() - self.pan_initial_pos.x()
  188. delta_y = pos.y() - self.pan_initial_pos.y()
  189. self.scrollRequest.emit(delta_x, Qt.Horizontal)
  190. self.scrollRequest.emit(delta_y, Qt.Vertical)
  191. self.update()
  192. return
  193. # Just hovering over the canvas, 2 posibilities:
  194. # - Highlight shapes
  195. # - Highlight vertex
  196. # Update shape/vertex fill and tooltip value accordingly.
  197. self.setToolTip("Image")
  198. for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
  199. # Look for a nearby vertex to highlight. If that fails,
  200. # check if we happen to be inside a shape.
  201. index = shape.nearestVertex(pos, self.epsilon)
  202. if index is not None:
  203. if self.selectedVertex():
  204. self.hShape.highlightClear()
  205. self.hVertex, self.hShape = index, shape
  206. shape.highlightVertex(index, shape.MOVE_VERTEX)
  207. self.overrideCursor(CURSOR_POINT)
  208. self.setToolTip("Click & drag to move point")
  209. self.setStatusTip(self.toolTip())
  210. self.update()
  211. break
  212. elif shape.containsPoint(pos):
  213. if self.selectedVertex():
  214. self.hShape.highlightClear()
  215. self.hVertex, self.hShape = None, shape
  216. self.setToolTip(
  217. "Click & drag to move shape '%s'" % shape.label)
  218. self.setStatusTip(self.toolTip())
  219. self.overrideCursor(CURSOR_GRAB)
  220. self.update()
  221. break
  222. else: # Nothing found, clear highlights, reset state.
  223. if self.hShape:
  224. self.hShape.highlightClear()
  225. self.update()
  226. self.hVertex, self.hShape = None, None
  227. self.overrideCursor(CURSOR_DEFAULT)
  228. def mousePressEvent(self, ev):
  229. pos = self.transformPos(ev.pos())
  230. if ev.button() == Qt.LeftButton:
  231. if self.drawing():
  232. # self.handleDrawing(pos) # OLD
  233. if self.current and self.fourpoint: # ADD IF
  234. # Add point to existing shape.
  235. print('Adding points in mousePressEvent is ', self.line[1])
  236. self.current.addPoint(self.line[1])
  237. self.line[0] = self.current[-1]
  238. if self.current.isClosed():
  239. # print('1111')
  240. self.finalise()
  241. elif not self.outOfPixmap(pos):
  242. # Create new shape.
  243. self.current = Shape()# self.current = Shape(shape_type=self.createMode)
  244. self.current.addPoint(pos)
  245. # if self.createMode == "point":
  246. # self.finalise()
  247. # else:
  248. # if self.createMode == "circle":
  249. # self.current.shape_type = "circle"
  250. self.line.points = [pos, pos]
  251. self.setHiding()
  252. self.drawingPolygon.emit(True)
  253. self.update()
  254. else:
  255. selection = self.selectShapePoint(pos)
  256. self.prevPoint = pos
  257. if selection is None:
  258. #pan
  259. QApplication.setOverrideCursor(QCursor(Qt.OpenHandCursor))
  260. self.pan_initial_pos = pos
  261. elif ev.button() == Qt.RightButton and self.editing():
  262. self.selectShapePoint(pos)
  263. self.prevPoint = pos
  264. self.update()
  265. def mouseReleaseEvent(self, ev):
  266. if ev.button() == Qt.RightButton:
  267. menu = self.menus[bool(self.selectedShapeCopy)]
  268. self.restoreCursor()
  269. if not menu.exec_(self.mapToGlobal(ev.pos()))\
  270. and self.selectedShapeCopy:
  271. # Cancel the move by deleting the shadow copy.
  272. self.selectedShapeCopy = None
  273. self.repaint()
  274. elif ev.button() == Qt.LeftButton and self.selectedShape: # OLD
  275. if self.selectedVertex():
  276. self.overrideCursor(CURSOR_POINT)
  277. else:
  278. self.overrideCursor(CURSOR_GRAB)
  279. elif ev.button() == Qt.LeftButton and not self.fourpoint:
  280. pos = self.transformPos(ev.pos())
  281. if self.drawing():
  282. self.handleDrawing(pos)
  283. else:
  284. #pan
  285. QApplication.restoreOverrideCursor() # ?
  286. def endMove(self, copy=False):
  287. assert self.selectedShape and self.selectedShapeCopy
  288. shape = self.selectedShapeCopy
  289. #del shape.fill_color
  290. #del shape.line_color
  291. if copy:
  292. self.shapes.append(shape)
  293. self.selectedShape.selected = False
  294. self.selectedShape = shape
  295. self.repaint()
  296. else:
  297. self.selectedShape.points = [p for p in shape.points]
  298. self.selectedShapeCopy = None
  299. def hideBackroundShapes(self, value):
  300. self.hideBackround = value
  301. if self.selectedShape:
  302. # Only hide other shapes if there is a current selection.
  303. # Otherwise the user will not be able to select a shape.
  304. self.setHiding(True)
  305. self.repaint()
  306. def handleDrawing(self, pos):
  307. if self.current and self.current.reachMaxPoints() is False:
  308. if self.fourpoint:
  309. targetPos = self.line[self.pointnum]
  310. self.current.addPoint(targetPos)
  311. print('current points in handleDrawing is ', self.line[self.pointnum])
  312. self.update()
  313. if self.pointnum == 3:
  314. self.finalise()
  315. else: # 按住送掉后跳到这里
  316. initPos = self.current[0]
  317. print('initPos', self.current[0])
  318. minX = initPos.x()
  319. minY = initPos.y()
  320. targetPos = self.line[1]
  321. maxX = targetPos.x()
  322. maxY = targetPos.y()
  323. self.current.addPoint(QPointF(maxX, minY))
  324. self.current.addPoint(targetPos)
  325. self.current.addPoint(QPointF(minX, maxY))
  326. self.finalise()
  327. elif not self.outOfPixmap(pos):
  328. print('release')
  329. self.current = Shape()
  330. self.current.addPoint(pos)
  331. self.line.points = [pos, pos]
  332. self.setHiding()
  333. self.drawingPolygon.emit(True)
  334. self.update()
  335. def setHiding(self, enable=True):
  336. self._hideBackround = self.hideBackround if enable else False
  337. def canCloseShape(self):
  338. return self.drawing() and self.current and len(self.current) > 2
  339. def mouseDoubleClickEvent(self, ev):
  340. # We need at least 4 points here, since the mousePress handler
  341. # adds an extra one before this handler is called.
  342. if self.canCloseShape() and len(self.current) > 3:
  343. if not self.fourpoint:
  344. self.current.popPoint()
  345. self.finalise()
  346. def selectShape(self, shape):
  347. self.deSelectShape()
  348. shape.selected = True
  349. self.selectedShape = shape
  350. self.setHiding()
  351. self.selectionChanged.emit(True)
  352. self.update()
  353. def selectShapePoint(self, point):
  354. """Select the first shape created which contains this point."""
  355. self.deSelectShape()
  356. if self.selectedVertex(): # A vertex is marked for selection.
  357. index, shape = self.hVertex, self.hShape
  358. shape.highlightVertex(index, shape.MOVE_VERTEX)
  359. self.selectShape(shape)
  360. return self.hVertex
  361. for shape in reversed(self.shapes):
  362. if self.isVisible(shape) and shape.containsPoint(point):
  363. self.selectShape(shape)
  364. self.calculateOffsets(shape, point)
  365. return self.selectedShape
  366. return None
  367. def calculateOffsets(self, shape, point):
  368. rect = shape.boundingRect()
  369. x1 = rect.x() - point.x()
  370. y1 = rect.y() - point.y()
  371. x2 = (rect.x() + rect.width()) - point.x()
  372. y2 = (rect.y() + rect.height()) - point.y()
  373. self.offsets = QPointF(x1, y1), QPointF(x2, y2)
  374. def snapPointToCanvas(self, x, y):
  375. """
  376. Moves a point x,y to within the boundaries of the canvas.
  377. :return: (x,y,snapped) where snapped is True if x or y were changed, False if not.
  378. """
  379. if x < 0 or x > self.pixmap.width() or y < 0 or y > self.pixmap.height():
  380. x = max(x, 0)
  381. y = max(y, 0)
  382. x = min(x, self.pixmap.width())
  383. y = min(y, self.pixmap.height())
  384. return x, y, True
  385. return x, y, False
  386. def boundedMoveVertex(self, pos):
  387. index, shape = self.hVertex, self.hShape
  388. point = shape[index]
  389. if self.outOfPixmap(pos):
  390. size = self.pixmap.size()
  391. clipped_x = min(max(0, pos.x()), size.width())
  392. clipped_y = min(max(0, pos.y()), size.height())
  393. pos = QPointF(clipped_x, clipped_y)
  394. if self.drawSquare:
  395. opposite_point_index = (index + 2) % 4
  396. opposite_point = shape[opposite_point_index]
  397. min_size = min(abs(pos.x() - opposite_point.x()), abs(pos.y() - opposite_point.y()))
  398. directionX = -1 if pos.x() - opposite_point.x() < 0 else 1
  399. directionY = -1 if pos.y() - opposite_point.y() < 0 else 1
  400. shiftPos = QPointF(opposite_point.x() + directionX * min_size - point.x(),
  401. opposite_point.y() + directionY * min_size - point.y())
  402. else:
  403. shiftPos = pos - point
  404. shape.moveVertexBy(index, shiftPos)
  405. lindex = (index + 1) % 4
  406. rindex = (index + 3) % 4
  407. lshift = None
  408. rshift = None
  409. if index % 2 == 0:
  410. rshift = QPointF(shiftPos.x(), 0)
  411. lshift = QPointF(0, shiftPos.y())
  412. else:
  413. lshift = QPointF(shiftPos.x(), 0)
  414. rshift = QPointF(0, shiftPos.y())
  415. shape.moveVertexBy(rindex, rshift)
  416. shape.moveVertexBy(lindex, lshift)
  417. def boundedMoveShape(self, shape, pos):
  418. if self.outOfPixmap(pos):
  419. return False # No need to move
  420. o1 = pos + self.offsets[0]
  421. if self.outOfPixmap(o1):
  422. pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
  423. o2 = pos + self.offsets[1]
  424. if self.outOfPixmap(o2):
  425. pos += QPointF(min(0, self.pixmap.width() - o2.x()),
  426. min(0, self.pixmap.height() - o2.y()))
  427. # The next line tracks the new position of the cursor
  428. # relative to the shape, but also results in making it
  429. # a bit "shaky" when nearing the border and allows it to
  430. # go outside of the shape's area for some reason. XXX
  431. #self.calculateOffsets(self.selectedShape, pos)
  432. dp = pos - self.prevPoint
  433. if dp:
  434. shape.moveBy(dp)
  435. self.prevPoint = pos
  436. return True
  437. return False
  438. def deSelectShape(self):
  439. if self.selectedShape:
  440. self.selectedShape.selected = False
  441. self.selectedShape = None
  442. self.setHiding(False)
  443. self.selectionChanged.emit(False)
  444. self.update()
  445. def deleteSelected(self):
  446. if self.selectedShape:
  447. shape = self.selectedShape
  448. self.shapes.remove(self.selectedShape)
  449. self.selectedShape = None
  450. self.update()
  451. return shape
  452. def copySelectedShape(self):
  453. if self.selectedShape:
  454. shape = self.selectedShape.copy()
  455. self.deSelectShape()
  456. self.shapes.append(shape)
  457. shape.selected = True
  458. self.selectedShape = shape
  459. self.boundedShiftShape(shape)
  460. return shape
  461. def boundedShiftShape(self, shape):
  462. # Try to move in one direction, and if it fails in another.
  463. # Give up if both fail.
  464. point = shape[0]
  465. offset = QPointF(2.0, 2.0)
  466. self.calculateOffsets(shape, point)
  467. self.prevPoint = point
  468. if not self.boundedMoveShape(shape, point - offset):
  469. self.boundedMoveShape(shape, point + offset)
  470. def paintEvent(self, event):
  471. if not self.pixmap:
  472. return super(Canvas, self).paintEvent(event)
  473. p = self._painter
  474. p.begin(self)
  475. p.setRenderHint(QPainter.Antialiasing)
  476. p.setRenderHint(QPainter.HighQualityAntialiasing)
  477. p.setRenderHint(QPainter.SmoothPixmapTransform)
  478. p.scale(self.scale, self.scale)
  479. p.translate(self.offsetToCenter())
  480. p.drawPixmap(0, 0, self.pixmap)
  481. Shape.scale = self.scale
  482. for shape in self.shapes:
  483. if (shape.selected or not self._hideBackround) and self.isVisible(shape):
  484. shape.fill = shape.selected or shape == self.hShape
  485. shape.paint(p)
  486. if self.current:
  487. self.current.paint(p)
  488. self.line.paint(p)
  489. if self.selectedShapeCopy:
  490. self.selectedShapeCopy.paint(p)
  491. # Paint rect
  492. if self.current is not None and len(self.line) == 2 and not self.fourpoint:
  493. # print('Drawing rect')
  494. leftTop = self.line[0]
  495. rightBottom = self.line[1]
  496. rectWidth = rightBottom.x() - leftTop.x()
  497. rectHeight = rightBottom.y() - leftTop.y()
  498. p.setPen(self.drawingRectColor)
  499. brush = QBrush(Qt.BDiagPattern)
  500. p.setBrush(brush)
  501. p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)
  502. # ADD:
  503. if (
  504. self.fillDrawing()
  505. and self.fourpoint
  506. and self.current is not None
  507. and len(self.current.points) >= 2
  508. ):
  509. print('paint event')
  510. drawing_shape = self.current.copy()
  511. drawing_shape.addPoint(self.line[1])
  512. drawing_shape.fill = True
  513. drawing_shape.paint(p)
  514. if self.drawing() and not self.prevPoint.isNull() and not self.outOfPixmap(self.prevPoint):
  515. p.setPen(QColor(0, 0, 0))
  516. p.drawLine(self.prevPoint.x(), 0, self.prevPoint.x(), self.pixmap.height())
  517. p.drawLine(0, self.prevPoint.y(), self.pixmap.width(), self.prevPoint.y())
  518. self.setAutoFillBackground(True)
  519. if self.verified:
  520. pal = self.palette()
  521. pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
  522. self.setPalette(pal)
  523. else:
  524. pal = self.palette()
  525. pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
  526. self.setPalette(pal)
  527. p.end()
  528. def fillDrawing(self):
  529. return self._fill_drawing
  530. def transformPos(self, point):
  531. """Convert from widget-logical coordinates to painter-logical coordinates."""
  532. return point / self.scale - self.offsetToCenter()
  533. def offsetToCenter(self):
  534. s = self.scale
  535. area = super(Canvas, self).size()
  536. w, h = self.pixmap.width() * s, self.pixmap.height() * s
  537. aw, ah = area.width(), area.height()
  538. x = (aw - w) / (2 * s) if aw > w else 0
  539. y = (ah - h) / (2 * s) if ah > h else 0
  540. return QPointF(x, y)
  541. def outOfPixmap(self, p):
  542. w, h = self.pixmap.width(), self.pixmap.height()
  543. return not (0 <= p.x() <= w and 0 <= p.y() <= h)
  544. def finalise(self):
  545. assert self.current
  546. if self.current.points[0] == self.current.points[-1]:
  547. # print('finalse')
  548. self.current = None
  549. self.drawingPolygon.emit(False)
  550. self.update()
  551. return
  552. self.current.close()
  553. self.shapes.append(self.current)
  554. self.current = None
  555. self.setHiding(False)
  556. self.newShape.emit()
  557. self.update()
  558. def closeEnough(self, p1, p2):
  559. #d = distance(p1 - p2)
  560. #m = (p1-p2).manhattanLength()
  561. # print "d %.2f, m %d, %.2f" % (d, m, d - m)
  562. return distance(p1 - p2) < self.epsilon
  563. # These two, along with a call to adjustSize are required for the
  564. # scroll area.
  565. def sizeHint(self):
  566. return self.minimumSizeHint()
  567. def minimumSizeHint(self):
  568. if self.pixmap:
  569. return self.scale * self.pixmap.size()
  570. return super(Canvas, self).minimumSizeHint()
  571. def wheelEvent(self, ev):
  572. qt_version = 4 if hasattr(ev, "delta") else 5
  573. if qt_version == 4:
  574. if ev.orientation() == Qt.Vertical:
  575. v_delta = ev.delta()
  576. h_delta = 0
  577. else:
  578. h_delta = ev.delta()
  579. v_delta = 0
  580. else:
  581. delta = ev.angleDelta()
  582. h_delta = delta.x()
  583. v_delta = delta.y()
  584. mods = ev.modifiers()
  585. if Qt.ControlModifier == int(mods) and v_delta:
  586. self.zoomRequest.emit(v_delta)
  587. else:
  588. v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)
  589. h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal)
  590. ev.accept()
  591. def keyPressEvent(self, ev):
  592. key = ev.key()
  593. if key == Qt.Key_Escape and self.current:
  594. print('ESC press')
  595. self.current = None
  596. self.drawingPolygon.emit(False)
  597. self.update()
  598. elif key == Qt.Key_Return and self.canCloseShape():
  599. self.finalise()
  600. elif key == Qt.Key_Left and self.selectedShape:
  601. self.moveOnePixel('Left')
  602. elif key == Qt.Key_Right and self.selectedShape:
  603. self.moveOnePixel('Right')
  604. elif key == Qt.Key_Up and self.selectedShape:
  605. self.moveOnePixel('Up')
  606. elif key == Qt.Key_Down and self.selectedShape:
  607. self.moveOnePixel('Down')
  608. def moveOnePixel(self, direction):
  609. # print(self.selectedShape.points)
  610. if direction == 'Left' and not self.moveOutOfBound(QPointF(-1.0, 0)):
  611. # print("move Left one pixel")
  612. self.selectedShape.points[0] += QPointF(-1.0, 0)
  613. self.selectedShape.points[1] += QPointF(-1.0, 0)
  614. self.selectedShape.points[2] += QPointF(-1.0, 0)
  615. self.selectedShape.points[3] += QPointF(-1.0, 0)
  616. elif direction == 'Right' and not self.moveOutOfBound(QPointF(1.0, 0)):
  617. # print("move Right one pixel")
  618. self.selectedShape.points[0] += QPointF(1.0, 0)
  619. self.selectedShape.points[1] += QPointF(1.0, 0)
  620. self.selectedShape.points[2] += QPointF(1.0, 0)
  621. self.selectedShape.points[3] += QPointF(1.0, 0)
  622. elif direction == 'Up' and not self.moveOutOfBound(QPointF(0, -1.0)):
  623. # print("move Up one pixel")
  624. self.selectedShape.points[0] += QPointF(0, -1.0)
  625. self.selectedShape.points[1] += QPointF(0, -1.0)
  626. self.selectedShape.points[2] += QPointF(0, -1.0)
  627. self.selectedShape.points[3] += QPointF(0, -1.0)
  628. elif direction == 'Down' and not self.moveOutOfBound(QPointF(0, 1.0)):
  629. # print("move Down one pixel")
  630. self.selectedShape.points[0] += QPointF(0, 1.0)
  631. self.selectedShape.points[1] += QPointF(0, 1.0)
  632. self.selectedShape.points[2] += QPointF(0, 1.0)
  633. self.selectedShape.points[3] += QPointF(0, 1.0)
  634. self.shapeMoved.emit()
  635. self.repaint()
  636. def moveOutOfBound(self, step):
  637. points = [p1+p2 for p1, p2 in zip(self.selectedShape.points, [step]*4)]
  638. return True in map(self.outOfPixmap, points)
  639. def setLastLabel(self, text, line_color = None, fill_color = None):
  640. assert text
  641. self.shapes[-1].label = text
  642. if line_color:
  643. self.shapes[-1].line_color = line_color
  644. if fill_color:
  645. self.shapes[-1].fill_color = fill_color
  646. return self.shapes[-1]
  647. def undoLastLine(self):
  648. assert self.shapes
  649. self.current = self.shapes.pop()
  650. self.current.setOpen()
  651. self.line.points = [self.current[-1], self.current[0]]
  652. self.drawingPolygon.emit(True)
  653. def resetAllLines(self):
  654. assert self.shapes
  655. self.current = self.shapes.pop()
  656. self.current.setOpen()
  657. self.line.points = [self.current[-1], self.current[0]]
  658. self.drawingPolygon.emit(True)
  659. self.current = None
  660. self.drawingPolygon.emit(False)
  661. self.update()
  662. def loadPixmap(self, pixmap):
  663. self.pixmap = pixmap
  664. self.shapes = []
  665. self.repaint() # 这函数在哪
  666. def loadShapes(self, shapes):
  667. self.shapes = list(shapes)
  668. self.current = None
  669. self.repaint()
  670. def setShapeVisible(self, shape, value):
  671. self.visible[shape] = value
  672. self.repaint()
  673. def currentCursor(self):
  674. cursor = QApplication.overrideCursor()
  675. if cursor is not None:
  676. cursor = cursor.shape()
  677. return cursor
  678. def overrideCursor(self, cursor):
  679. self._cursor = cursor
  680. if self.currentCursor() is None:
  681. QApplication.setOverrideCursor(cursor)
  682. else:
  683. QApplication.changeOverrideCursor(cursor)
  684. def restoreCursor(self):
  685. QApplication.restoreOverrideCursor()
  686. def resetState(self):
  687. self.restoreCursor()
  688. self.pixmap = None
  689. self.update()
  690. def setDrawingShapeToSquare(self, status):
  691. self.drawSquare = status