# Copyright (c) <2015-Present> Tzutalin # Copyright (C) 2013 MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba, # William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and # associated documentation files (the "Software"), to deal in the Software without restriction, including without # limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the # Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: # The above copyright notice and this permission notice shall be included in all copies or substantial portions of # the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT # NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT # SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # !/usr/bin/python # -*- coding: utf-8 -*- import math import sys from PyQt5.QtCore import QPointF from PyQt5.QtGui import QColor, QPen, QPainterPath, QFont from libs.utils import distance DEFAULT_LINE_COLOR = QColor(0, 255, 0, 128) DEFAULT_FILL_COLOR = QColor(255, 0, 0, 128) DEFAULT_SELECT_LINE_COLOR = QColor(255, 255, 255) DEFAULT_SELECT_FILL_COLOR = QColor(0, 128, 255, 155) DEFAULT_VERTEX_FILL_COLOR = QColor(0, 255, 0, 255) DEFAULT_HVERTEX_FILL_COLOR = QColor(255, 0, 0) DEFAULT_LOCK_COLOR = QColor(255, 0, 255) MIN_Y_LABEL = 10 class Shape(object): P_SQUARE, P_ROUND = range(2) MOVE_VERTEX, NEAR_VERTEX = range(2) # The following class variables influence the drawing # of _all_ shape objects. line_color = DEFAULT_LINE_COLOR fill_color = DEFAULT_FILL_COLOR select_line_color = DEFAULT_SELECT_LINE_COLOR select_fill_color = DEFAULT_SELECT_FILL_COLOR vertex_fill_color = DEFAULT_VERTEX_FILL_COLOR hvertex_fill_color = DEFAULT_HVERTEX_FILL_COLOR point_type = P_ROUND point_size = 8 scale = 1.0 def __init__(self, label=None, line_color=None, difficult=False, key_cls="None", paintLabel=False, paintIdx=False): self.label = label self.idx = None # bbox order, only for table annotation self.points = [] self.fill = False self.selected = False self.difficult = difficult self.key_cls = key_cls self.paintLabel = paintLabel self.paintIdx = paintIdx self.locked = False self.direction = 0 self.center = None self.epsilon = 5 # same as canvas self._highlightIndex = None self._highlightMode = self.NEAR_VERTEX self._highlightSettings = { self.NEAR_VERTEX: (4, self.P_ROUND), self.MOVE_VERTEX: (1.5, self.P_SQUARE), } self._closed = False if line_color is not None: # Override the class line_color attribute # with an object attribute. Currently this # is used for drawing the pending line a different color. self.line_color = line_color def rotate(self, theta): for i, p in enumerate(self.points): self.points[i] = self.rotatePoint(p, theta) self.direction -= theta self.direction = self.direction % (2 * math.pi) def rotatePoint(self, p, theta): order = p - self.center cosTheta = math.cos(theta) sinTheta = math.sin(theta) pResx = cosTheta * order.x() + sinTheta * order.y() pResy = - sinTheta * order.x() + cosTheta * order.y() pRes = QPointF(self.center.x() + pResx, self.center.y() + pResy) return pRes def close(self): self.center = QPointF((self.points[0].x() + self.points[2].x()) / 2, (self.points[0].y() + self.points[2].y()) / 2) self._closed = True def reachMaxPoints(self): if len(self.points) >= 4: return True return False def addPoint(self, point): if self.reachMaxPoints() and self.closeEnough(self.points[0], point): self.close() else: self.points.append(point) def closeEnough(self, p1, p2): return distance(p1 - p2) < self.epsilon def popPoint(self): if self.points: return self.points.pop() return None def isClosed(self): return self._closed def setOpen(self): self._closed = False def paint(self, painter): if self.points: color = self.select_line_color if self.selected else self.line_color pen = QPen(color) # Try using integer sizes for smoother drawing(?) pen.setWidth(max(1, int(round(2.0 / self.scale)))) painter.setPen(pen) line_path = QPainterPath() vrtx_path = QPainterPath() line_path.moveTo(self.points[0]) # Uncommenting the following line will draw 2 paths # for the 1st vertex, and make it non-filled, which # may be desirable. # self.drawVertex(vrtx_path, 0) for i, p in enumerate(self.points): line_path.lineTo(p) self.drawVertex(vrtx_path, i) if self.isClosed(): line_path.lineTo(self.points[0]) painter.drawPath(line_path) painter.drawPath(vrtx_path) painter.fillPath(vrtx_path, self.vertex_fill_color) # Draw text at the top-left if self.paintLabel: min_x = sys.maxsize min_y = sys.maxsize for point in self.points: min_x = min(min_x, point.x()) min_y = min(min_y, point.y()) if min_x != sys.maxsize and min_y != sys.maxsize: font = QFont() font.setPointSize(8) font.setBold(True) painter.setFont(font) if self.label is None: self.label = "" if min_y < MIN_Y_LABEL: min_y += MIN_Y_LABEL painter.drawText(min_x, min_y, self.label) # Draw number at the top-right if self.paintIdx: min_x = sys.maxsize min_y = sys.maxsize for point in self.points: min_x = min(min_x, point.x()) min_y = min(min_y, point.y()) if min_x != sys.maxsize and min_y != sys.maxsize: font = QFont() font.setPointSize(8) font.setBold(True) painter.setFont(font) text = '' if self.idx != None: text = str(self.idx) if min_y < MIN_Y_LABEL: min_y += MIN_Y_LABEL painter.drawText(min_x, min_y, text) if self.fill: color = self.select_fill_color if self.selected else self.fill_color painter.fillPath(line_path, color) def drawVertex(self, path, i): d = self.point_size / self.scale shape = self.point_type point = self.points[i] if i == self._highlightIndex: size, shape = self._highlightSettings[self._highlightMode] d *= size if self._highlightIndex is not None: self.vertex_fill_color = self.hvertex_fill_color else: self.vertex_fill_color = Shape.vertex_fill_color if shape == self.P_SQUARE: path.addRect(point.x() - d / 2, point.y() - d / 2, d, d) elif shape == self.P_ROUND: path.addEllipse(point, d / 2.0, d / 2.0) else: assert False, "unsupported vertex shape" def nearestVertex(self, point, epsilon): for i, p in enumerate(self.points): if distance(p - point) <= epsilon: return i return None def containsPoint(self, point): return self.makePath().contains(point) def makePath(self): path = QPainterPath(self.points[0]) for p in self.points[1:]: path.lineTo(p) return path def boundingRect(self): return self.makePath().boundingRect() def moveBy(self, offset): self.points = [p + offset for p in self.points] def moveVertexBy(self, i, offset): self.points[i] = self.points[i] + offset def highlightVertex(self, i, action): self._highlightIndex = i self._highlightMode = action def highlightClear(self): self._highlightIndex = None def copy(self): shape = Shape("%s" % self.label) shape.points = [p for p in self.points] shape.center = self.center shape.direction = self.direction shape.fill = self.fill shape.selected = self.selected shape._closed = self._closed if self.line_color != Shape.line_color: shape.line_color = self.line_color if self.fill_color != Shape.fill_color: shape.fill_color = self.fill_color shape.difficult = self.difficult shape.key_cls = self.key_cls return shape def __len__(self): return len(self.points) def __getitem__(self, key): return self.points[key] def __setitem__(self, key, value): self.points[key] = value