labelimgplusplus 2.0.0a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
libs/canvas.py ADDED
@@ -0,0 +1,748 @@
1
+
2
+ try:
3
+ from PyQt5.QtGui import *
4
+ from PyQt5.QtCore import *
5
+ from PyQt5.QtWidgets import *
6
+ except ImportError:
7
+ from PyQt4.QtGui import *
8
+ from PyQt4.QtCore import *
9
+
10
+ # from PyQt4.QtOpenGL import *
11
+
12
+ from libs.shape import Shape
13
+ from libs.utils import distance
14
+
15
+ CURSOR_DEFAULT = Qt.ArrowCursor
16
+ CURSOR_POINT = Qt.PointingHandCursor
17
+ CURSOR_DRAW = Qt.CrossCursor
18
+ CURSOR_MOVE = Qt.ClosedHandCursor
19
+ CURSOR_GRAB = Qt.OpenHandCursor
20
+
21
+ # class Canvas(QGLWidget):
22
+
23
+
24
+ class Canvas(QWidget):
25
+ zoomRequest = pyqtSignal(int)
26
+ lightRequest = pyqtSignal(int)
27
+ scrollRequest = pyqtSignal(int, int)
28
+ newShape = pyqtSignal()
29
+ selectionChanged = pyqtSignal(bool)
30
+ shapeMoved = pyqtSignal()
31
+ drawingPolygon = pyqtSignal(bool)
32
+
33
+ CREATE, EDIT = list(range(2))
34
+
35
+ epsilon = 24.0
36
+
37
+ def __init__(self, *args, **kwargs):
38
+ super(Canvas, self).__init__(*args, **kwargs)
39
+ # Initialise local state.
40
+ self.mode = self.EDIT
41
+ self.shapes = []
42
+ self.current = None
43
+ self.selected_shape = None # save the selected shape here
44
+ self.selected_shape_copy = None
45
+ self.drawing_line_color = QColor(0, 0, 255)
46
+ self.drawing_rect_color = QColor(0, 0, 255)
47
+ self.line = Shape(line_color=self.drawing_line_color)
48
+ self.prev_point = QPointF()
49
+ self.offsets = QPointF(), QPointF()
50
+ self.scale = 1.0
51
+ self.overlay_color = None
52
+ self.label_font_size = 8
53
+ self.pixmap = QPixmap()
54
+ self.visible = {}
55
+ self._hide_background = False
56
+ self.hide_background = False
57
+ self.h_shape = None
58
+ self.h_vertex = 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.draw_square = False
68
+
69
+ # initialisation for panning
70
+ self.pan_initial_pos = QPoint()
71
+
72
+ def set_drawing_color(self, qcolor):
73
+ self.drawing_line_color = qcolor
74
+ self.drawing_rect_color = qcolor
75
+
76
+ def enterEvent(self, ev):
77
+ self.override_cursor(self._cursor)
78
+
79
+ def leaveEvent(self, ev):
80
+ self.restore_cursor()
81
+
82
+ def focusOutEvent(self, ev):
83
+ self.restore_cursor()
84
+
85
+ def isVisible(self, shape):
86
+ return self.visible.get(shape, True)
87
+
88
+ def drawing(self):
89
+ return self.mode == self.CREATE
90
+
91
+ def editing(self):
92
+ return self.mode == self.EDIT
93
+
94
+ def set_editing(self, value=True):
95
+ self.mode = self.EDIT if value else self.CREATE
96
+ if not value: # Create
97
+ self.un_highlight()
98
+ self.de_select_shape()
99
+ self.prev_point = QPointF()
100
+ self.repaint()
101
+
102
+ def un_highlight(self, shape=None):
103
+ if shape == None or shape == self.h_shape:
104
+ if self.h_shape:
105
+ self.h_shape.highlight_clear()
106
+ self.h_vertex = self.h_shape = None
107
+
108
+ def selected_vertex(self):
109
+ return self.h_vertex is not None
110
+
111
+ def mouseMoveEvent(self, ev):
112
+ """Update line with last point and current coordinates."""
113
+ pos = self.transform_pos(ev.pos())
114
+
115
+ # Update coordinates in status bar if image is opened
116
+ window = self.parent().window()
117
+ if window.file_path is not None:
118
+ self.parent().window().label_coordinates.setText(
119
+ 'X: %d; Y: %d' % (pos.x(), pos.y()))
120
+
121
+ # Polygon drawing.
122
+ if self.drawing():
123
+ self.override_cursor(CURSOR_DRAW)
124
+ if self.current:
125
+ # Display annotation width and height while drawing
126
+ current_width = abs(self.current[0].x() - pos.x())
127
+ current_height = abs(self.current[0].y() - pos.y())
128
+ self.parent().window().label_coordinates.setText(
129
+ 'Width: %d, Height: %d / X: %d; Y: %d' % (current_width, current_height, pos.x(), pos.y()))
130
+
131
+ color = self.drawing_line_color
132
+ if self.out_of_pixmap(pos):
133
+ # Don't allow the user to draw outside the pixmap.
134
+ # Clip the coordinates to 0 or max,
135
+ # if they are outside the range [0, max]
136
+ size = self.pixmap.size()
137
+ clipped_x = min(max(0, pos.x()), size.width())
138
+ clipped_y = min(max(0, pos.y()), size.height())
139
+ pos = QPointF(clipped_x, clipped_y)
140
+ elif len(self.current) > 1 and self.close_enough(pos, self.current[0]):
141
+ # Attract line to starting point and colorise to alert the
142
+ # user:
143
+ pos = self.current[0]
144
+ color = self.current.line_color
145
+ self.override_cursor(CURSOR_POINT)
146
+ self.current.highlight_vertex(0, Shape.NEAR_VERTEX)
147
+
148
+ if self.draw_square:
149
+ init_pos = self.current[0]
150
+ min_x = init_pos.x()
151
+ min_y = init_pos.y()
152
+ min_size = min(abs(pos.x() - min_x), abs(pos.y() - min_y))
153
+ direction_x = -1 if pos.x() - min_x < 0 else 1
154
+ direction_y = -1 if pos.y() - min_y < 0 else 1
155
+ self.line[1] = QPointF(min_x + direction_x * min_size, min_y + direction_y * min_size)
156
+ else:
157
+ self.line[1] = pos
158
+
159
+ self.line.line_color = color
160
+ self.prev_point = QPointF()
161
+ self.current.highlight_clear()
162
+ else:
163
+ self.prev_point = pos
164
+ self.repaint()
165
+ return
166
+
167
+ # Polygon copy moving.
168
+ if Qt.RightButton & ev.buttons():
169
+ if self.selected_shape_copy and self.prev_point:
170
+ self.override_cursor(CURSOR_MOVE)
171
+ self.bounded_move_shape(self.selected_shape_copy, pos)
172
+ self.repaint()
173
+ elif self.selected_shape:
174
+ self.selected_shape_copy = self.selected_shape.copy()
175
+ self.repaint()
176
+ return
177
+
178
+ # Polygon/Vertex moving.
179
+ if Qt.LeftButton & ev.buttons():
180
+ if self.selected_vertex():
181
+ self.bounded_move_vertex(pos)
182
+ self.shapeMoved.emit()
183
+ self.repaint()
184
+
185
+ # Display annotation width and height while moving vertex
186
+ point1 = self.h_shape[1]
187
+ point3 = self.h_shape[3]
188
+ current_width = abs(point1.x() - point3.x())
189
+ current_height = abs(point1.y() - point3.y())
190
+ self.parent().window().label_coordinates.setText(
191
+ 'Width: %d, Height: %d / X: %d; Y: %d' % (current_width, current_height, pos.x(), pos.y()))
192
+ elif self.selected_shape and self.prev_point:
193
+ self.override_cursor(CURSOR_MOVE)
194
+ self.bounded_move_shape(self.selected_shape, pos)
195
+ self.shapeMoved.emit()
196
+ self.repaint()
197
+
198
+ # Display annotation width and height while moving shape
199
+ point1 = self.selected_shape[1]
200
+ point3 = self.selected_shape[3]
201
+ current_width = abs(point1.x() - point3.x())
202
+ current_height = abs(point1.y() - point3.y())
203
+ self.parent().window().label_coordinates.setText(
204
+ 'Width: %d, Height: %d / X: %d; Y: %d' % (current_width, current_height, pos.x(), pos.y()))
205
+ else:
206
+ # pan
207
+ delta = ev.pos() - self.pan_initial_pos
208
+ self.scrollRequest.emit(delta.x(), Qt.Horizontal)
209
+ self.scrollRequest.emit(delta.y(), Qt.Vertical)
210
+ self.update()
211
+ return
212
+
213
+ # Just hovering over the canvas, 2 possibilities:
214
+ # - Highlight shapes
215
+ # - Highlight vertex
216
+ # Update shape/vertex fill and tooltip value accordingly.
217
+ self.setToolTip("Image")
218
+ priority_list = self.shapes + ([self.selected_shape] if self.selected_shape else [])
219
+ for shape in reversed([s for s in priority_list if self.isVisible(s)]):
220
+ # Look for a nearby vertex to highlight. If that fails,
221
+ # check if we happen to be inside a shape.
222
+ index = shape.nearest_vertex(pos, self.epsilon)
223
+ if index is not None:
224
+ if self.selected_vertex():
225
+ self.h_shape.highlight_clear()
226
+ self.h_vertex, self.h_shape = index, shape
227
+ shape.highlight_vertex(index, shape.MOVE_VERTEX)
228
+ self.override_cursor(CURSOR_POINT)
229
+ self.setToolTip("Click & drag to move point")
230
+ self.setStatusTip(self.toolTip())
231
+ self.update()
232
+ break
233
+ elif shape.contains_point(pos):
234
+ if self.selected_vertex():
235
+ self.h_shape.highlight_clear()
236
+ self.h_vertex, self.h_shape = None, shape
237
+ self.setToolTip(
238
+ "Click & drag to move shape '%s'" % shape.label)
239
+ self.setStatusTip(self.toolTip())
240
+ self.override_cursor(CURSOR_GRAB)
241
+ self.update()
242
+
243
+ # Display annotation width and height while hovering inside
244
+ point1 = self.h_shape[1]
245
+ point3 = self.h_shape[3]
246
+ current_width = abs(point1.x() - point3.x())
247
+ current_height = abs(point1.y() - point3.y())
248
+ self.parent().window().label_coordinates.setText(
249
+ 'Width: %d, Height: %d / X: %d; Y: %d' % (current_width, current_height, pos.x(), pos.y()))
250
+ break
251
+ else: # Nothing found, clear highlights, reset state.
252
+ if self.h_shape:
253
+ self.h_shape.highlight_clear()
254
+ self.update()
255
+ self.h_vertex, self.h_shape = None, None
256
+ self.override_cursor(CURSOR_DEFAULT)
257
+
258
+ def mousePressEvent(self, ev):
259
+ pos = self.transform_pos(ev.pos())
260
+
261
+ if ev.button() == Qt.LeftButton:
262
+ if self.drawing():
263
+ self.handle_drawing(pos)
264
+ else:
265
+ selection = self.select_shape_point(pos)
266
+ self.prev_point = pos
267
+
268
+ if selection is None:
269
+ # pan
270
+ QApplication.setOverrideCursor(QCursor(Qt.OpenHandCursor))
271
+ self.pan_initial_pos = ev.pos()
272
+
273
+ elif ev.button() == Qt.RightButton and self.editing():
274
+ self.select_shape_point(pos)
275
+ self.prev_point = pos
276
+ self.update()
277
+
278
+ def mouseReleaseEvent(self, ev):
279
+ if ev.button() == Qt.RightButton:
280
+ menu = self.menus[bool(self.selected_shape_copy)]
281
+ self.restore_cursor()
282
+ if not menu.exec_(self.mapToGlobal(ev.pos()))\
283
+ and self.selected_shape_copy:
284
+ # Cancel the move by deleting the shadow copy.
285
+ self.selected_shape_copy = None
286
+ self.repaint()
287
+ elif ev.button() == Qt.LeftButton and self.selected_shape:
288
+ if self.selected_vertex():
289
+ self.override_cursor(CURSOR_POINT)
290
+ else:
291
+ self.override_cursor(CURSOR_GRAB)
292
+ elif ev.button() == Qt.LeftButton:
293
+ pos = self.transform_pos(ev.pos())
294
+ if self.drawing():
295
+ self.handle_drawing(pos)
296
+ else:
297
+ # pan
298
+ QApplication.restoreOverrideCursor()
299
+
300
+ def end_move(self, copy=False):
301
+ assert self.selected_shape and self.selected_shape_copy
302
+ shape = self.selected_shape_copy
303
+ # del shape.fill_color
304
+ # del shape.line_color
305
+ if copy:
306
+ self.shapes.append(shape)
307
+ self.selected_shape.selected = False
308
+ self.selected_shape = shape
309
+ self.repaint()
310
+ else:
311
+ self.selected_shape.points = [p for p in shape.points]
312
+ self.selected_shape_copy = None
313
+
314
+ def hide_background_shapes(self, value):
315
+ self.hide_background = value
316
+ if self.selected_shape:
317
+ # Only hide other shapes if there is a current selection.
318
+ # Otherwise the user will not be able to select a shape.
319
+ self.set_hiding(True)
320
+ self.repaint()
321
+
322
+ def handle_drawing(self, pos):
323
+ if self.current and self.current.reach_max_points() is False:
324
+ init_pos = self.current[0]
325
+ min_x = init_pos.x()
326
+ min_y = init_pos.y()
327
+ target_pos = self.line[1]
328
+ max_x = target_pos.x()
329
+ max_y = target_pos.y()
330
+ self.current.add_point(QPointF(max_x, min_y))
331
+ self.current.add_point(target_pos)
332
+ self.current.add_point(QPointF(min_x, max_y))
333
+ self.finalise()
334
+ elif not self.out_of_pixmap(pos):
335
+ self.current = Shape()
336
+ self.current.add_point(pos)
337
+ self.line.points = [pos, pos]
338
+ self.set_hiding()
339
+ self.drawingPolygon.emit(True)
340
+ self.update()
341
+
342
+ def set_hiding(self, enable=True):
343
+ self._hide_background = self.hide_background if enable else False
344
+
345
+ def can_close_shape(self):
346
+ return self.drawing() and self.current and len(self.current) > 2
347
+
348
+ def mouseDoubleClickEvent(self, ev):
349
+ # We need at least 4 points here, since the mousePress handler
350
+ # adds an extra one before this handler is called.
351
+ if self.can_close_shape() and len(self.current) > 3:
352
+ self.current.pop_point()
353
+ self.finalise()
354
+
355
+ def select_shape(self, shape):
356
+ self.de_select_shape()
357
+ shape.selected = True
358
+ self.selected_shape = shape
359
+ self.set_hiding()
360
+ self.selectionChanged.emit(True)
361
+ self.update()
362
+
363
+ def select_shape_point(self, point):
364
+ """Select the first shape created which contains this point."""
365
+ self.de_select_shape()
366
+ if self.selected_vertex(): # A vertex is marked for selection.
367
+ index, shape = self.h_vertex, self.h_shape
368
+ shape.highlight_vertex(index, shape.MOVE_VERTEX)
369
+ self.select_shape(shape)
370
+ return self.h_vertex
371
+ for shape in reversed(self.shapes):
372
+ if self.isVisible(shape) and shape.contains_point(point):
373
+ self.select_shape(shape)
374
+ self.calculate_offsets(shape, point)
375
+ return self.selected_shape
376
+ return None
377
+
378
+ def calculate_offsets(self, shape, point):
379
+ rect = shape.bounding_rect()
380
+ x1 = rect.x() - point.x()
381
+ y1 = rect.y() - point.y()
382
+ x2 = (rect.x() + rect.width()) - point.x()
383
+ y2 = (rect.y() + rect.height()) - point.y()
384
+ self.offsets = QPointF(x1, y1), QPointF(x2, y2)
385
+
386
+ def snap_point_to_canvas(self, x, y):
387
+ """
388
+ Moves a point x,y to within the boundaries of the canvas.
389
+ :return: (x,y,snapped) where snapped is True if x or y were changed, False if not.
390
+ """
391
+ if x < 0 or x > self.pixmap.width() or y < 0 or y > self.pixmap.height():
392
+ x = max(x, 0)
393
+ y = max(y, 0)
394
+ x = min(x, self.pixmap.width())
395
+ y = min(y, self.pixmap.height())
396
+ return x, y, True
397
+
398
+ return x, y, False
399
+
400
+ def bounded_move_vertex(self, pos):
401
+ index, shape = self.h_vertex, self.h_shape
402
+ point = shape[index]
403
+ if self.out_of_pixmap(pos):
404
+ size = self.pixmap.size()
405
+ clipped_x = min(max(0, pos.x()), size.width())
406
+ clipped_y = min(max(0, pos.y()), size.height())
407
+ pos = QPointF(clipped_x, clipped_y)
408
+
409
+ if self.draw_square:
410
+ opposite_point_index = (index + 2) % 4
411
+ opposite_point = shape[opposite_point_index]
412
+
413
+ min_size = min(abs(pos.x() - opposite_point.x()), abs(pos.y() - opposite_point.y()))
414
+ direction_x = -1 if pos.x() - opposite_point.x() < 0 else 1
415
+ direction_y = -1 if pos.y() - opposite_point.y() < 0 else 1
416
+ shift_pos = QPointF(opposite_point.x() + direction_x * min_size - point.x(),
417
+ opposite_point.y() + direction_y * min_size - point.y())
418
+ else:
419
+ shift_pos = pos - point
420
+
421
+ shape.move_vertex_by(index, shift_pos)
422
+
423
+ left_index = (index + 1) % 4
424
+ right_index = (index + 3) % 4
425
+ left_shift = None
426
+ right_shift = None
427
+ if index % 2 == 0:
428
+ right_shift = QPointF(shift_pos.x(), 0)
429
+ left_shift = QPointF(0, shift_pos.y())
430
+ else:
431
+ left_shift = QPointF(shift_pos.x(), 0)
432
+ right_shift = QPointF(0, shift_pos.y())
433
+ shape.move_vertex_by(right_index, right_shift)
434
+ shape.move_vertex_by(left_index, left_shift)
435
+
436
+ def bounded_move_shape(self, shape, pos):
437
+ if self.out_of_pixmap(pos):
438
+ return False # No need to move
439
+ o1 = pos + self.offsets[0]
440
+ if self.out_of_pixmap(o1):
441
+ pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
442
+ o2 = pos + self.offsets[1]
443
+ if self.out_of_pixmap(o2):
444
+ pos += QPointF(min(0, self.pixmap.width() - o2.x()),
445
+ min(0, self.pixmap.height() - o2.y()))
446
+ # The next line tracks the new position of the cursor
447
+ # relative to the shape, but also results in making it
448
+ # a bit "shaky" when nearing the border and allows it to
449
+ # go outside of the shape's area for some reason. XXX
450
+ # self.calculateOffsets(self.selectedShape, pos)
451
+ dp = pos - self.prev_point
452
+ if dp:
453
+ shape.move_by(dp)
454
+ self.prev_point = pos
455
+ return True
456
+ return False
457
+
458
+ def de_select_shape(self):
459
+ if self.selected_shape:
460
+ self.selected_shape.selected = False
461
+ self.selected_shape = None
462
+ self.set_hiding(False)
463
+ self.selectionChanged.emit(False)
464
+ self.update()
465
+
466
+ def delete_selected(self):
467
+ if self.selected_shape:
468
+ shape = self.selected_shape
469
+ self.un_highlight(shape)
470
+ self.shapes.remove(self.selected_shape)
471
+ self.selected_shape = None
472
+ self.update()
473
+ return shape
474
+
475
+ def copy_selected_shape(self):
476
+ if self.selected_shape:
477
+ shape = self.selected_shape.copy()
478
+ self.de_select_shape()
479
+ self.shapes.append(shape)
480
+ shape.selected = True
481
+ self.selected_shape = shape
482
+ self.bounded_shift_shape(shape)
483
+ return shape
484
+
485
+ def bounded_shift_shape(self, shape):
486
+ # Try to move in one direction, and if it fails in another.
487
+ # Give up if both fail.
488
+ point = shape[0]
489
+ offset = QPointF(2.0, 2.0)
490
+ self.calculate_offsets(shape, point)
491
+ self.prev_point = point
492
+ if not self.bounded_move_shape(shape, point - offset):
493
+ self.bounded_move_shape(shape, point + offset)
494
+
495
+ def paintEvent(self, event):
496
+ if not self.pixmap:
497
+ return super(Canvas, self).paintEvent(event)
498
+
499
+ p = self._painter
500
+ p.begin(self)
501
+ p.setRenderHint(QPainter.Antialiasing)
502
+ p.setRenderHint(QPainter.HighQualityAntialiasing)
503
+ p.setRenderHint(QPainter.SmoothPixmapTransform)
504
+
505
+ p.scale(self.scale, self.scale)
506
+ p.translate(self.offset_to_center())
507
+
508
+ temp = self.pixmap
509
+ if self.overlay_color:
510
+ temp = QPixmap(self.pixmap)
511
+ painter = QPainter(temp)
512
+ painter.setCompositionMode(painter.CompositionMode_Overlay)
513
+ painter.fillRect(temp.rect(), self.overlay_color)
514
+ painter.end()
515
+
516
+ p.drawPixmap(0, 0, temp)
517
+ Shape.scale = self.scale
518
+ Shape.label_font_size = self.label_font_size
519
+ for shape in self.shapes:
520
+ if (shape.selected or not self._hide_background) and self.isVisible(shape):
521
+ shape.fill = shape.selected or shape == self.h_shape
522
+ shape.paint(p)
523
+ if self.current:
524
+ self.current.paint(p)
525
+ self.line.paint(p)
526
+ if self.selected_shape_copy:
527
+ self.selected_shape_copy.paint(p)
528
+
529
+ # Paint rect
530
+ if self.current is not None and len(self.line) == 2:
531
+ left_top = self.line[0]
532
+ right_bottom = self.line[1]
533
+ rect_width = right_bottom.x() - left_top.x()
534
+ rect_height = right_bottom.y() - left_top.y()
535
+ p.setPen(self.drawing_rect_color)
536
+ brush = QBrush(Qt.BDiagPattern)
537
+ p.setBrush(brush)
538
+ p.drawRect(int(left_top.x()), int(left_top.y()), int(rect_width), int(rect_height))
539
+
540
+ if self.drawing() and not self.prev_point.isNull() and not self.out_of_pixmap(self.prev_point):
541
+ p.setPen(QColor(0, 0, 0))
542
+ p.drawLine(int(self.prev_point.x()), 0, int(self.prev_point.x()), int(self.pixmap.height()))
543
+ p.drawLine(0, int(self.prev_point.y()), int(self.pixmap.width()), int(self.prev_point.y()))
544
+
545
+ self.setAutoFillBackground(True)
546
+ if self.verified:
547
+ pal = self.palette()
548
+ pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
549
+ self.setPalette(pal)
550
+ else:
551
+ pal = self.palette()
552
+ pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
553
+ self.setPalette(pal)
554
+
555
+ p.end()
556
+
557
+ def transform_pos(self, point):
558
+ """Convert from widget-logical coordinates to painter-logical coordinates."""
559
+ return point / self.scale - self.offset_to_center()
560
+
561
+ def offset_to_center(self):
562
+ s = self.scale
563
+ area = super(Canvas, self).size()
564
+ w, h = self.pixmap.width() * s, self.pixmap.height() * s
565
+ aw, ah = area.width(), area.height()
566
+ x = (aw - w) / (2 * s) if aw > w else 0
567
+ y = (ah - h) / (2 * s) if ah > h else 0
568
+ return QPointF(x, y)
569
+
570
+ def out_of_pixmap(self, p):
571
+ w, h = self.pixmap.width(), self.pixmap.height()
572
+ return not (0 <= p.x() <= w and 0 <= p.y() <= h)
573
+
574
+ def finalise(self):
575
+ assert self.current
576
+ if self.current.points[0] == self.current.points[-1]:
577
+ self.current = None
578
+ self.drawingPolygon.emit(False)
579
+ self.update()
580
+ return
581
+
582
+ self.current.close()
583
+ self.shapes.append(self.current)
584
+ self.current = None
585
+ self.set_hiding(False)
586
+ self.newShape.emit()
587
+ self.update()
588
+
589
+ def close_enough(self, p1, p2):
590
+ # d = distance(p1 - p2)
591
+ # m = (p1-p2).manhattanLength()
592
+ # print "d %.2f, m %d, %.2f" % (d, m, d - m)
593
+ return distance(p1 - p2) < self.epsilon
594
+
595
+ # These two, along with a call to adjustSize are required for the
596
+ # scroll area.
597
+ def sizeHint(self):
598
+ return self.minimumSizeHint()
599
+
600
+ def minimumSizeHint(self):
601
+ if self.pixmap:
602
+ return self.scale * self.pixmap.size()
603
+ return super(Canvas, self).minimumSizeHint()
604
+
605
+ def wheelEvent(self, ev):
606
+ qt_version = 4 if hasattr(ev, "delta") else 5
607
+ if qt_version == 4:
608
+ if ev.orientation() == Qt.Vertical:
609
+ v_delta = ev.delta()
610
+ h_delta = 0
611
+ else:
612
+ h_delta = ev.delta()
613
+ v_delta = 0
614
+ else:
615
+ delta = ev.angleDelta()
616
+ h_delta = delta.x()
617
+ v_delta = delta.y()
618
+
619
+ mods = ev.modifiers()
620
+ if int(Qt.ControlModifier) | int(Qt.ShiftModifier) == int(mods) and v_delta:
621
+ self.lightRequest.emit(v_delta)
622
+ elif Qt.ControlModifier == int(mods) and v_delta:
623
+ self.zoomRequest.emit(v_delta)
624
+ else:
625
+ v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)
626
+ h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal)
627
+ ev.accept()
628
+
629
+ def keyPressEvent(self, ev):
630
+ key = ev.key()
631
+ if key == Qt.Key_Escape and self.current:
632
+ print('ESC press')
633
+ self.current = None
634
+ self.drawingPolygon.emit(False)
635
+ self.update()
636
+ elif key == Qt.Key_Return and self.can_close_shape():
637
+ self.finalise()
638
+ elif key == Qt.Key_Left and self.selected_shape:
639
+ self.move_one_pixel('Left')
640
+ elif key == Qt.Key_Right and self.selected_shape:
641
+ self.move_one_pixel('Right')
642
+ elif key == Qt.Key_Up and self.selected_shape:
643
+ self.move_one_pixel('Up')
644
+ elif key == Qt.Key_Down and self.selected_shape:
645
+ self.move_one_pixel('Down')
646
+
647
+ def move_one_pixel(self, direction):
648
+ # print(self.selectedShape.points)
649
+ if direction == 'Left' and not self.move_out_of_bound(QPointF(-1.0, 0)):
650
+ # print("move Left one pixel")
651
+ self.selected_shape.points[0] += QPointF(-1.0, 0)
652
+ self.selected_shape.points[1] += QPointF(-1.0, 0)
653
+ self.selected_shape.points[2] += QPointF(-1.0, 0)
654
+ self.selected_shape.points[3] += QPointF(-1.0, 0)
655
+ elif direction == 'Right' and not self.move_out_of_bound(QPointF(1.0, 0)):
656
+ # print("move Right one pixel")
657
+ self.selected_shape.points[0] += QPointF(1.0, 0)
658
+ self.selected_shape.points[1] += QPointF(1.0, 0)
659
+ self.selected_shape.points[2] += QPointF(1.0, 0)
660
+ self.selected_shape.points[3] += QPointF(1.0, 0)
661
+ elif direction == 'Up' and not self.move_out_of_bound(QPointF(0, -1.0)):
662
+ # print("move Up one pixel")
663
+ self.selected_shape.points[0] += QPointF(0, -1.0)
664
+ self.selected_shape.points[1] += QPointF(0, -1.0)
665
+ self.selected_shape.points[2] += QPointF(0, -1.0)
666
+ self.selected_shape.points[3] += QPointF(0, -1.0)
667
+ elif direction == 'Down' and not self.move_out_of_bound(QPointF(0, 1.0)):
668
+ # print("move Down one pixel")
669
+ self.selected_shape.points[0] += QPointF(0, 1.0)
670
+ self.selected_shape.points[1] += QPointF(0, 1.0)
671
+ self.selected_shape.points[2] += QPointF(0, 1.0)
672
+ self.selected_shape.points[3] += QPointF(0, 1.0)
673
+ self.shapeMoved.emit()
674
+ self.repaint()
675
+
676
+ def move_out_of_bound(self, step):
677
+ points = [p1 + p2 for p1, p2 in zip(self.selected_shape.points, [step] * 4)]
678
+ return True in map(self.out_of_pixmap, points)
679
+
680
+ def set_last_label(self, text, line_color=None, fill_color=None):
681
+ assert text
682
+ self.shapes[-1].label = text
683
+ if line_color:
684
+ self.shapes[-1].line_color = line_color
685
+
686
+ if fill_color:
687
+ self.shapes[-1].fill_color = fill_color
688
+
689
+ return self.shapes[-1]
690
+
691
+ def undo_last_line(self):
692
+ assert self.shapes
693
+ self.current = self.shapes.pop()
694
+ self.current.set_open()
695
+ self.line.points = [self.current[-1], self.current[0]]
696
+ self.drawingPolygon.emit(True)
697
+
698
+ def reset_all_lines(self):
699
+ assert self.shapes
700
+ self.current = self.shapes.pop()
701
+ self.current.set_open()
702
+ self.line.points = [self.current[-1], self.current[0]]
703
+ self.drawingPolygon.emit(True)
704
+ self.current = None
705
+ self.drawingPolygon.emit(False)
706
+ self.update()
707
+
708
+ def load_pixmap(self, pixmap):
709
+ self.pixmap = pixmap
710
+ self.shapes = []
711
+ self.repaint()
712
+
713
+ def load_shapes(self, shapes):
714
+ self.shapes = list(shapes)
715
+ self.current = None
716
+ self.repaint()
717
+
718
+ def set_shape_visible(self, shape, value):
719
+ self.visible[shape] = value
720
+ self.repaint()
721
+
722
+ def current_cursor(self):
723
+ cursor = QApplication.overrideCursor()
724
+ if cursor is not None:
725
+ cursor = cursor.shape()
726
+ return cursor
727
+
728
+ def override_cursor(self, cursor):
729
+ self._cursor = cursor
730
+ if self.current_cursor() is None:
731
+ QApplication.setOverrideCursor(cursor)
732
+ else:
733
+ QApplication.changeOverrideCursor(cursor)
734
+
735
+ def restore_cursor(self):
736
+ QApplication.restoreOverrideCursor()
737
+
738
+ def reset_state(self):
739
+ self.de_select_shape()
740
+ self.un_highlight()
741
+ self.selected_shape_copy = None
742
+
743
+ self.restore_cursor()
744
+ self.pixmap = None
745
+ self.update()
746
+
747
+ def set_drawing_shape_to_square(self, status):
748
+ self.draw_square = status