def __init__(self, parentItem, segments, colour): QGraphicsItem.__init__(self, parent=parentItem) self.colour_name = colour self.shape = QPainterPath() self.labels = QGraphicsItemGroup(self) self.bbox = QRectF(0, 0, 0, 0) for (p1, p2), label in segments: lvect = QVector2D(p2 - p1) lpath = QPainterPath() m = TWY_line_margin l = lvect.length() plst = [QPointF(-m, 0), QPointF(-m/3, -m), QPointF(l + m/3, -m), QPointF(l + m, 0), QPointF(l + m/3, m), QPointF(-m/3, m)] lpath.addPolygon(QPolygonF(plst)) lrot = QTransform() lrot.rotateRadians(atan2(lvect.y(), lvect.x())) lpath = lrot.map(lpath) lpath.translate(p1) self.shape.addPath(lpath) rect = QRectF(p1, p2).normalized() if label != None: self.labels.addToGroup(TaxiwayLabelItem(label, rect.center(), self)) self.bbox |= rect self.shape.setFillRule(Qt.WindingFill) self.mouse_highlight = False self.labels.setVisible(False)
def drawString(self, content, fontSize=256, color=QColor("red")): icon = self.icon() pixmap = icon.pixmap(512, 512) pixSize = pixmap.rect() painter = QPainter(pixmap) font = painter.font() font.setPixelSize(fontSize) painter.setFont(font) path = QPainterPath() path.addText(0, 0, font, content) pathBBox = path.boundingRect() xOffset = (pixSize.width() - pathBBox.width()) / 2 - pathBBox.left() yOffset = (pixSize.height() + pathBBox.height()) / 2 path.translate(xOffset, yOffset) ## paint shadow pathPen = QPen(QColor(0, 0, 0, 200)) pathPen.setWidth(180) painter.strokePath(path, pathPen) painter.fillPath(path, QBrush(color)) ## make number bolder pathPen = QPen(color) pathPen.setWidth(20) painter.strokePath(path, pathPen) painter.end() self.setIcon(QIcon(pixmap))
def createResizeArrow(straight): if straight: arrowLength = 14 else: arrowLength = 16 arrowHeadLength = 4.5 arrowHeadWidth = 5 bodyWidth = 1.5 path = QPainterPath() path.lineTo(arrowHeadWidth, arrowHeadLength) path.lineTo(0 + bodyWidth, arrowHeadLength) path.lineTo(0 + bodyWidth, arrowLength - arrowHeadLength) path.lineTo(arrowHeadWidth, arrowLength - arrowHeadLength) path.lineTo(0, arrowLength) path.lineTo(-arrowHeadWidth, arrowLength - arrowHeadLength) path.lineTo(0 - bodyWidth, arrowLength - arrowHeadLength) path.lineTo(0 - bodyWidth, arrowHeadLength) path.lineTo(-arrowHeadWidth, arrowHeadLength) path.closeSubpath() if straight: x = 2 else: x =3 path.translate(0, x) return path
def createResizeArrow(straight): if straight: arrowLength = 14 else: arrowLength = 16 arrowHeadLength = 4.5 arrowHeadWidth = 5 bodyWidth = 1.5 path = QPainterPath() path.lineTo(arrowHeadWidth, arrowHeadLength) path.lineTo(0 + bodyWidth, arrowHeadLength) path.lineTo(0 + bodyWidth, arrowLength - arrowHeadLength) path.lineTo(arrowHeadWidth, arrowLength - arrowHeadLength) path.lineTo(0, arrowLength) path.lineTo(-arrowHeadWidth, arrowLength - arrowHeadLength) path.lineTo(0 - bodyWidth, arrowLength - arrowHeadLength) path.lineTo(0 - bodyWidth, arrowHeadLength) path.lineTo(-arrowHeadWidth, arrowHeadLength) path.closeSubpath() if straight: x = 2 else: x = 3 path.translate(0, x) return path
def _updateSequenceText(self): seq_item = self._seq_item is_on_top = self._is_on_top index = self._insertion.idx() base_text = self._seq_text font = styles.SEQUENCEFONT seq_font_h = styles.SEQUENCEFONTH insert_w = styles.INSERTWIDTH seq_font_char_w = styles.SEQUENCEFONTCHARWIDTH # draw sequence on the insert if base_text: # only draw sequences if they exist i.e. not None! len_BT = len(base_text) if is_on_top: angle_offset = 0 else: angle_offset = 180 if len_BT > 20: base_text = base_text[:17] + '...' len_BT = len(base_text) fraction_arc_len_per_char = ( 1.0 - 2.0 * _FRACTION_INSERT_TO_PAD) / (len_BT + 1) seq_item.setPen(QPen(Qt.NoPen)) seq_item.setBrush(QBrush(Qt.black)) seq_path = QPainterPath() loop_path = self.path() for i in range(len_BT): frac = _FRACTION_INSERT_TO_PAD + ( i + 1) * fraction_arc_len_per_char pt = loop_path.pointAtPercent(frac) tang_ang = loop_path.angleAtPercent(frac) temp_path = QPainterPath() # 1. draw the text temp_path.addText(0, 0, font, base_text[i if is_on_top else -i - 1]) # 2. center it at the zero point different for top and bottom # strands if not is_on_top: temp_path.translate(0, -seq_font_h - insert_w) temp_path.translate( QPointF(-seq_font_char_w / 2., -2 if is_on_top else seq_font_h)) mat = QTransform() # 3. rotate it mat.rotate(-tang_ang + angle_offset) rotated_path = mat.map(temp_path) # 4. translate the rotate object to it's position on the part rotated_path.translate(pt) seq_path.addPath(rotated_path) # end for seq_item.setPath(seq_path)
def _updateSequenceText(self): seq_item = self._seq_item is_on_top = self._is_on_top index = self._insertion.idx() base_text = self._seq_text font = styles.SEQUENCEFONT seq_font_h = styles.SEQUENCEFONTH insert_w = styles.INSERTWIDTH seq_font_char_w = styles.SEQUENCEFONTCHARWIDTH # draw sequence on the insert if base_text: # only draw sequences if they exist i.e. not None! len_BT = len(base_text) if is_on_top: angle_offset = 0 else: angle_offset = 180 if len_BT > 20: base_text = base_text[:17] + '...' len_BT = len(base_text) fraction_arc_len_per_char = (1.0 - 2.0*_FRACTION_INSERT_TO_PAD) / (len_BT + 1) seq_item.setPen(QPen(Qt.NoPen)) seq_item.setBrush(QBrush(Qt.black)) seq_path = QPainterPath() loop_path = self.path() for i in range(len_BT): frac = _FRACTION_INSERT_TO_PAD + (i+1)*fraction_arc_len_per_char pt = loop_path.pointAtPercent(frac) tang_ang = loop_path.angleAtPercent(frac) temp_path = QPainterPath() # 1. draw the text temp_path.addText(0, 0, font, base_text[i if is_on_top else -i-1]) # 2. center it at the zero point different for top and bottom # strands if not is_on_top: temp_path.translate(0, -seq_font_h - insert_w) temp_path.translate(QPointF(-seq_font_char_w / 2., -2 if is_on_top else seq_font_h)) mat = QTransform() # 3. rotate it mat.rotate(-tang_ang + angle_offset) rotated_path = mat.map(temp_path) # 4. translate the rotate object to it's position on the part rotated_path.translate(pt) seq_path.addPath(rotated_path) # end for seq_item.setPath(seq_path)
def _updateSequenceText(self): seqItem = self._seqItem isOnTop = self._isOnTop index = self._insertion.idx() baseText = self._seqText font = styles.SEQUENCEFONT seqFontH = styles.SEQUENCEFONTH insertW = styles.INSERTWIDTH seqFontCharW = styles.SEQUENCEFONTCHARWIDTH # draw sequence on the insert if baseText: # only draw sequences if they exist i.e. not None! lenBT = len(baseText) if isOnTop: angleOffset = 0 else: angleOffset = 180 if lenBT > 20: baseText = baseText[:17] + '...' lenBT = len(baseText) fractionArclenPerChar = (1.0 - 2.0 * _fractionInsertToPad) / (lenBT + 1) seqItem.setPen(QPen(Qt.NoPen)) seqItem.setBrush(QBrush(Qt.black)) seqPath = QPainterPath() loopPath = self.path() for i in range(lenBT): frac = _fractionInsertToPad + (i + 1) * fractionArclenPerChar pt = loopPath.pointAtPercent(frac) tangAng = loopPath.angleAtPercent(frac) tempPath = QPainterPath() # 1. draw the text tempPath.addText(0, 0, font, baseText[i if isOnTop else -i - 1]) # 2. center it at the zero point different for top and bottom # strands if not isOnTop: tempPath.translate(0, -seqFontH - insertW) tempPath.translate( QPointF(-seqFontCharW / 2., -2 if isOnTop else seqFontH)) mat = QMatrix3x3() # 3. rotate it mat.rotate(-tangAng + angleOffset) rotatedPath = mat.map(tempPath) # 4. translate the rotate object to it's position on the part rotatedPath.translate(pt) seqPath.addPath(rotatedPath) # end for seqItem.setPath(seqPath)
def _updateSequenceText(self): seqItem = self._seqItem isOnTop = self._isOnTop index = self._insertion.idx() baseText = self._seqText font = styles.SEQUENCEFONT seqFontH = styles.SEQUENCEFONTH insertW = styles.INSERTWIDTH seqFontCharW = styles.SEQUENCEFONTCHARWIDTH # draw sequence on the insert if baseText: # only draw sequences if they exist i.e. not None! lenBT = len(baseText) if isOnTop: angleOffset = 0 else: angleOffset = 180 if lenBT > 20: baseText = baseText[:17] + '...' lenBT = len(baseText) fractionArclenPerChar = (1.0-2.0*_fractionInsertToPad)/(lenBT+1) seqItem.setPen(QPen(Qt.NoPen)) seqItem.setBrush(QBrush(Qt.black)) seqPath = QPainterPath() loopPath = self.path() for i in range(lenBT): frac = _fractionInsertToPad + (i+1)*fractionArclenPerChar pt = loopPath.pointAtPercent(frac) tangAng = loopPath.angleAtPercent(frac) tempPath = QPainterPath() # 1. draw the text tempPath.addText(0,0, font, baseText[i if isOnTop else -i-1]) # 2. center it at the zero point different for top and bottom # strands if not isOnTop: tempPath.translate(0, -seqFontH - insertW) tempPath.translate(QPointF(-seqFontCharW/2., -2 if isOnTop else seqFontH)) mat = QMatrix3x3() # 3. rotate it mat.rotate(-tangAng + angleOffset) rotatedPath = mat.map(tempPath) # 4. translate the rotate object to it's position on the part rotatedPath.translate(pt) seqPath.addPath(rotatedPath) # end for seqItem.setPath(seqPath)
def xtooltippath(point): print(point) path = QPainterPath() width = 100 height = 50 pointer = 5 path.lineTo(width, 0), path.lineTo(width, height) path.lineTo(width / 2 + pointer, height) path.lineTo(width / 2, height + pointer) path.lineTo(width / 2 - pointer, height) path.lineTo(0, height) path.lineTo(0, 0) path.closeSubpath() path.translate(point.x() - width / 2, point.y() - height - pointer) return path
def draw_path(self): l, s, d = 45, 10, 10 proj = self.plan.projection.dot orig = np.array([0, 0]) axes = QPainterPath() axes.addPath(self.axis_arrow("x", orig, orig+l*proj([1, 0, 0]), s)) axes.addPath(self.axis_arrow("y", orig, orig+l*proj([0, 1, 0]), s)) axes.addPath(self.axis_arrow("z", orig, orig+l*proj([0, 0, 1]), s)) tran = self.deviceTransform(self.plan.viewportTransform()).inverted()[0] view = tran.mapRect(QRectF(self.plan.viewport().rect())) rect = axes.boundingRect() axes.translate(view.left() + view.width()/15 - rect.left(), view.bottom() - view.height()/15 - rect.bottom()) path = QPainterPath() path.addPath(axes) path.addEllipse(-d/2, -d/2, d, d) return path
def draw_path(self): l, s, d = 45, 10, 10 proj = self.plan.projection.dot orig = np.array([0, 0]) axes = QPainterPath() axes.addPath(self.axis_arrow("x", orig, orig + l * proj([1, 0, 0]), s)) axes.addPath(self.axis_arrow("y", orig, orig + l * proj([0, 1, 0]), s)) axes.addPath(self.axis_arrow("z", orig, orig + l * proj([0, 0, 1]), s)) tran = self.deviceTransform( self.plan.viewportTransform()).inverted()[0] view = tran.mapRect(QRectF(self.plan.viewport().rect())) rect = axes.boundingRect() axes.translate(view.left() + view.width() / 15 - rect.left(), view.bottom() - view.height() / 15 - rect.bottom()) path = QPainterPath() path.addPath(axes) path.addEllipse(-d / 2, -d / 2, d, d) return path
def paint(self, painter, style, widget=None): assert isinstance(painter, QPainter) if self.isSelected(): brush = QBrush(Qt.yellow) else: brush = QBrush(Qt.white) pen = QPen(Qt.black) circle_path = QPainterPath() circle_path.addEllipse(self.boundingRect()) painter.fillPath(circle_path, brush) painter.strokePath(circle_path, pen) text_path = QPainterPath() text_path.addText(0, 0, QFont(), str(self.node)) box = text_path.boundingRect() text_path.translate(-box.center()) painter.fillPath(text_path, QBrush(Qt.black))
from PyQt5.QtGui import QColor, QPainter, QPainterPath from PyQt5.QtWidgets import (QHBoxLayout, QLabel, QSpinBox, QSizePolicy, QWidget) from trufont.controls.pathButton import PathButton __all__ = ["StatusBar"] _minPath = QPainterPath() _minPath.moveTo(1, 6) _minPath.lineTo(11, 6) _plusPath = QPainterPath(_minPath) _plusPath.moveTo(6, 1) _plusPath.lineTo(6, 11) _minPath.translate(5, 7) _plusPath.translate(5, 7) def Button(): btn = PathButton() btn.setIsDownColor(QColor(210, 210, 210)) btn.setIsFlipped(True) btn.setSize(QSize(23, 25)) return btn class StatusBar(QWidget): """ Use the *sizeChanged* signal for size changes.
def updateGeometry(self): # Calculate path absolute branch_abs = QPainterPath() branch_abs.moveTo(self.__start) branch_abs.cubicTo(self.__spline1, self.__spline2, self.__end) branch_middle = self.__get_bezier_middle(branch_abs) middle_angle = self.__get_bezier_middle_angle(branch_abs) # Calculate arrow absolute arrow_abs = self.__calculate_arrow( branch_middle, middle_angle, self.__arrow_height, self.__arrow_length, self.__arrow_side_spline_depth, self.__arrow_back_spline_depth) # Calculate arrow mask absolute arrow_mask_abs = self.__calculate_arrow( branch_middle, middle_angle, self.__arrow_height + self.__arrow_mask_height_offset, self.__arrow_length + self.__arrow_mask_length_offset, self.__arrow_side_spline_depth, self.__arrow_back_spline_depth) # Add some margin to avoid cutting off anti aliasing pixels of branch branch_aliasing_margin = 4 geo_branch = branch_abs.boundingRect() \ .toRect().marginsAdded(QMargins( branch_aliasing_margin, branch_aliasing_margin, branch_aliasing_margin, branch_aliasing_margin)) # Get bounding rect of arrow mask # The mask already has a margin to avoid cuttin of anti aliasing pixels geo_arrow_mask = arrow_mask_abs.boundingRect().toRect() # Unite arrow mask bounds and branch bounds and set as geometry united = geo_branch.united(geo_arrow_mask) self.setGeometry(united) super().updateGeometry() # Translate absolute paths to relative paths for use in widget offset = self.mapFromParent(QPoint()) path_rel = QPainterPath(branch_abs) path_rel.translate(offset) self.__branch = path_rel arrow_rel = QPainterPath(arrow_abs) arrow_rel.translate(offset) self.__arrow = arrow_rel arrow_mask_rel = QPainterPath(arrow_mask_abs) arrow_mask_rel.translate(offset) self.__arrow_mask = arrow_mask_rel stroker = QPainterPathStroker() stroker.setWidth(8) wide_stroke = stroker.createStroke(self.__branch) stroker.setWidth(self.__pen_width) narrow_stroke = stroker.createStroke(self.__branch) branch_outlet = wide_stroke.united(narrow_stroke).toFillPolygon() mask_poly_f = self.__arrow_mask.toFillPolygon().united(branch_outlet) region = QRegion(mask_poly_f.toPolygon()) self.setMask(region) self.update()
path.quadTo(c1, p1) path.quadTo(c2, start) # end def _PATH_START = QPointF(_HBW, _HBW) _PATH_MID_UP = QPointF(_HBW, -_BW) _PATH_UP_UP_CTRL_PT = QPointF(-_HBW, -_BW) _PATH_UP_DOWN_CTRL_PT = QPointF(1.5 * _BW, -_BW) _PATH_MID_DOWN = QPointF(_HBW, 2 * _BW) _PATH_DOWN_DOWN_CTRL_PT = QPointF(-_HBW, 2 * _BW) _PATH_DOWN_UP_CTRL_PT = QPointF(1.5 * _BW, 2 * _BW) _INSERT_PATH_UP = QPainterPath() _insertGen(_INSERT_PATH_UP, _PATH_START, _PATH_UP_UP_CTRL_PT,\ _PATH_MID_UP, _PATH_UP_DOWN_CTRL_PT) _INSERT_PATH_UP.translate(_OFFSET1, 0) _INSERT_PATH_UP_RECT = _INSERT_PATH_UP.boundingRect() _INSERT_PATH_DOWN = QPainterPath() _insertGen(_INSERT_PATH_DOWN, _PATH_START, _PATH_DOWN_DOWN_CTRL_PT,\ _PATH_MID_DOWN, _PATH_DOWN_UP_CTRL_PT) _INSERT_PATH_DOWN.translate(_OFFSET1, 0) _INSERT_PATH_DOWNRect = _INSERT_PATH_DOWN.boundingRect() _BIG_RECT = _DEFAULT_RECT.united(_INSERT_PATH_UP_RECT) _BIG_RECT = _BIG_RECT.united(_INSERT_PATH_DOWNRect) _B_PEN2 = getPenObj(styles.BLUE_STROKE, 2) _OFFSET2 = _BW*0.75 _FONT = QFont(styles.THE_FONT, styles.THE_FONT_SIZE/2, QFont.Bold) _BIG_RECT.adjust(-15, -15, 30, 30) # Bases are drawn along and above the insert path.
path.quadTo(c1, p1) path.quadTo(c2, start) # end def _pathStart = QPointF(_hbw, _hbw) _pathMidUp = QPointF(_hbw, -_bw) _pathUpUpCtrlPt = QPointF(-_hbw, -_bw) _pathUpDownCtrlPt = QPointF(1.5 * _bw, -_bw) _pathMidDown = QPointF(_hbw, 2 * _bw) _pathDownDownCtrlPt = QPointF(-_hbw, 2 * _bw) _pathDownUpCtrlPt = QPointF(1.5 * _bw, 2 * _bw) _insertPathUp = QPainterPath() _insertGen(_insertPathUp, _pathStart, _pathUpUpCtrlPt,\ _pathMidUp, _pathUpDownCtrlPt) _insertPathUp.translate(_offset1, 0) _insertPathUpRect = _insertPathUp.boundingRect() _insertPathDown = QPainterPath() _insertGen(_insertPathDown, _pathStart, _pathDownDownCtrlPt,\ _pathMidDown, _pathDownUpCtrlPt) _insertPathDown.translate(_offset1, 0) _insertPathDownRect = _insertPathDown.boundingRect() _bigRect = _defaultRect.united(_insertPathUpRect) _bigRect = _bigRect.united(_insertPathDownRect) _bpen2 = QPen(styles.BLUE_STROKE, 2) _offset2 = _bw*0.75 _font = QFont(styles.THE_FONT, 10, QFont.Bold) _bigRect.adjust(-15, -15, 30, 30) # Bases are drawn along and above the insert path.
def svgToPath(filename): """Return QPainterPath instance from an svg file. No colors will be included. If the file contains multiple paths, they will be connected to form one single path. It will also return a boolean indicates if the path is closed or not.""" path = QPainterPath() info = findPath(filename) start = QPointF(0, 0) last_cp = start # last control point, for S cubic bezier curve. last_qp = start # last control point, for T quadratic bezier curve. for idx in range(len(info)): line = info[idx] cmd = line[0] if (cmd.upper() == 'Z'): path.closeSubpath() continue coords = re.split(r'\s+|,|(?<=\d)(?=-)', line[1]) if (cmd.upper() == 'V'): # only last coordinate matters. coord = eval(coords[-1]) verticalLineTo(path, coord, cmd) continue if (cmd.upper() == 'H'): # only last coordinate matters. coord = eval(coords[-1]) horizontalLineTo(path, coord, cmd) continue # pair two values into one coords = [x+','+y for x, y in zip(coords[::2], coords[1::2])] coords = list(map(getPoint, coords)) if (cmd.upper() == 'M'): # if m is at the start of the path if (line == info[0]): start = coords[0] moveTo(path, start, 'M') # m is not at the start of the path else: path.closeSubpath() lineTo(path, coords[0], cmd) for i in range(1, len(coords)): lineTo(path, coords[i], cmd) continue if (cmd.upper() == 'L'): for coord in coords: lineTo(path, coord, cmd) continue if (cmd.upper() == 'C'): for i in range(len(coords)//3): # Saving coordinates for smoothcurve command last_cp = cubicTo(path, *coords[i*3:i*3+3], absolute=cmd) continue if (cmd.upper() == 'S'): if not (info[idx-1][0].upper() in 'SC'): last_cp = path.currentPoint() for i in range(len(coords)//2): last_cp = smoothCubicTo(path, last_cp, *coords[i*2:i*2+2], absolute=cmd) continue if (cmd.upper() == 'Q'): for i in range(len(coords)//2): # Saving coordinates for T smooth curve command last_qp = quadTo(path, *coords[i*2:i*2+2], absolute=cmd) continue if (cmd.upper() == 'T'): if not (info[idx-1][0].upper() in 'QT'): last_qp = path.currentPoint() for coord in coords: last_qp = smoothQuadTo(path, last_qp, coord, absolute=cmd) continue raise Exception('svg file contains command {}, which is not supported' .format(cmd)) closed = True if ((abs((path.currentPosition() - start).x()) > 1) and (abs((path.currentPosition() - start).y()) > 1)): closed = False path.translate(-start) path.translate(-path.boundingRect().center()) return path, closed
def paintEvent(self,event): global monster_data global dmg painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.drawPixmap(event.rect(),self.pixmap) if self.card is not None and self.card.ID is not 0: card = self.card # Draw card level at the bottom centered pen = QPen() if np.floor(card.lv) == monster_data[card.ID]['max_level']: lvstr = 'Lv.Max' brush = QBrush(QColor(252,232,131)) else: lvstr = 'Lv.%d' % np.floor(card.lv) brush = QBrush(Qt.white) path = QPainterPath() pen.setWidth(0); pen.setBrush(Qt.black) font = QFont() font.setPointSize(11) font.setWeight(QFont.Black) path.addText(event.rect().x(),event.rect().y()+48,font,lvstr) rect = path.boundingRect() target = (event.rect().x()+event.rect().width())/2 # center the rect in event.rect() path.translate(target-rect.center().x(), 0) painter.setPen(pen) painter.setBrush(QBrush(Qt.black)) painter.drawPath(path.translated(.5,.5)) painter.setPen(pen) painter.setBrush(brush) painter.drawPath(path) # Draw +eggs at the top right eggs = card.plus_atk+card.plus_hp+card.plus_rcv if eggs > 0: eggstr = '+%d' % eggs pen.setBrush(Qt.yellow) brush = QBrush(Qt.yellow) path = QPainterPath() pen.setWidth(0) pen.setBrush(Qt.black) font = QFont() font.setPointSize(11) font.setWeight(QFont.Black) path.addText(event.rect().x(),event.rect().y()+12,font,eggstr) path.translate(50-path.boundingRect().right()-3,0) #painter.setFont(font) painter.setPen(pen) painter.setBrush(QBrush(Qt.black)) painter.drawPath(path.translated(.5,.5)) painter.setPen(pen) painter.setBrush(brush) painter.drawPath(path) #painter.drawText(event.rect().adjusted(0,0,0,0),Qt.AlignRight, eggstr) # Draw awakenings at the top left in a green circle if card.current_awakening > 0: path = QPainterPath() rect = QRectF(event.rect()).adjusted(4,4,-36,-36) path.addEllipse(rect) painter.setBrush(QBrush(QColor(34,139,34))) pen.setBrush(Qt.white) pen.setWidth(1) painter.setPen(pen) painter.drawPath(path) path = QPainterPath() font.setPointSize(9) awkstr = ('%d' % card.current_awakening if card.current_awakening < card.max_awakening else '★') path.addText(rect.x(),rect.bottom(),font,awkstr) br = path.boundingRect() path.translate(rect.center().x()-br.center().x(), rect.center().y()-br.center().y()) pen.setBrush(QColor(0,0,0,0)) pen.setWidth(0) painter.setPen(pen) painter.setBrush(QBrush(Qt.yellow)) painter.drawPath(path) # Draw main attack damage #print(self.main_attack) if self.main_attack > 0: matkstr = '%d' % self.main_attack painter.setBrush(QBrush(COLORS[self.card.element[0]])) path = QPainterPath() font = QFont() font.setFamily('Helvetica') font.setWeight(QFont.Black) #font.setStretch(25) font.setPointSize(13) path.addText(rect.x(),rect.bottom(),font,matkstr) rect = QRectF(event.rect()) br = path.boundingRect() path.translate(rect.center().x()-br.center().x(), rect.center().y()-br.bottom()-1) # pen.setBrush(Qt.black) pen.setWidthF(.75) painter.setPen(pen) painter.drawPath(path) # Draw sub attack damage #print(self.main_attack) if self.sub_attack > 0: satkstr = '%d' % self.sub_attack painter.setBrush(QBrush(COLORS[self.card.element[1]])) path = QPainterPath() font = QFont() font.setFamily('Helvetica') font.setWeight(QFont.Black) #font.setStretch(25) font.setPointSize(12) path.addText(rect.x(),rect.bottom(),font,satkstr) rect = QRectF(event.rect()) br = path.boundingRect() path.translate(rect.center().x()-br.center().x(), rect.center().y()-br.top()+1) # pen.setBrush(Qt.black) pen.setWidthF(.75) painter.setPen(pen) painter.drawPath(path)
path.quadTo(c1, p1) path.quadTo(c2, start) # end def _PATH_START = QPointF(_HBW, _HBW) _PATH_MID_UP = QPointF(_HBW, -_BW) _PATH_UP_UP_CTRL_PT = QPointF(-_HBW, -_BW) _PATH_UP_DOWN_CTRL_PT = QPointF(1.5 * _BW, -_BW) _PATH_MID_DOWN = QPointF(_HBW, 2 * _BW) _PATH_DOWN_DOWN_CTRL_PT = QPointF(-_HBW, 2 * _BW) _PATH_DOWN_UP_CTRL_PT = QPointF(1.5 * _BW, 2 * _BW) _INSERT_PATH_UP = QPainterPath() _insertGen(_INSERT_PATH_UP, _PATH_START, _PATH_UP_UP_CTRL_PT, _PATH_MID_UP, _PATH_UP_DOWN_CTRL_PT) _INSERT_PATH_UP.translate(_OFFSET1, 0) _INSERT_PATH_UP_RECT = _INSERT_PATH_UP.boundingRect() _INSERT_PATH_DOWN = QPainterPath() _insertGen(_INSERT_PATH_DOWN, _PATH_START, _PATH_DOWN_DOWN_CTRL_PT, _PATH_MID_DOWN, _PATH_DOWN_UP_CTRL_PT) _INSERT_PATH_DOWN.translate(_OFFSET1, 0) _INSERT_PATH_DOWNRect = _INSERT_PATH_DOWN.boundingRect() _BIG_RECT = _DEFAULT_RECT.united(_INSERT_PATH_UP_RECT) _BIG_RECT = _BIG_RECT.united(_INSERT_PATH_DOWNRect) _B_PEN2 = getPenObj(styles.BLUE_STROKE, 2) _OFFSET2 = _BW*0.75 _FONT = QFont(styles.THE_FONT, styles.THE_FONT_SIZE/2, QFont.Bold) _BIG_RECT.adjust(-15, -15, 30, 30) # Bases are drawn along and above the insert path.
path.quadTo(c2, start) # end def _pathStart = QPointF(_hbw, _hbw) _pathMidUp = QPointF(_hbw, -_bw) _pathUpUpCtrlPt = QPointF(-_hbw, -_bw) _pathUpDownCtrlPt = QPointF(1.5 * _bw, -_bw) _pathMidDown = QPointF(_hbw, 2 * _bw) _pathDownDownCtrlPt = QPointF(-_hbw, 2 * _bw) _pathDownUpCtrlPt = QPointF(1.5 * _bw, 2 * _bw) _insertPathUp = QPainterPath() _insertGen(_insertPathUp, _pathStart, _pathUpUpCtrlPt,\ _pathMidUp, _pathUpDownCtrlPt) _insertPathUp.translate(_offset1, 0) _insertPathUpRect = _insertPathUp.boundingRect() _insertPathDown = QPainterPath() _insertGen(_insertPathDown, _pathStart, _pathDownDownCtrlPt,\ _pathMidDown, _pathDownUpCtrlPt) _insertPathDown.translate(_offset1, 0) _insertPathDownRect = _insertPathDown.boundingRect() _bigRect = _defaultRect.united(_insertPathUpRect) _bigRect = _bigRect.united(_insertPathDownRect) _bpen2 = QPen(styles.BLUE_STROKE, 2) _offset2 = _bw * 0.75 _font = QFont(styles.THE_FONT, 10, QFont.Bold) _bigRect.adjust(-15, -15, 30, 30) # Bases are drawn along and above the insert path. # These calculations revolve around fixing the characters at a certain
class ViewFisheye(QWidget): # sample selection SelectionType = Enum('SelectType', 'Exact Closest Rect') SelectionMode = Enum('SelectMode', 'Select Add Remove') SelectionRectMin = 10 # pixels, width and height, scales as photo scales SampleRadius = 10 # pixels, scales as photo scales SelectedPixelBox = 64 # pixels, width and height def __init__(self, parent): super().__init__() # members self.parent = parent self.myPhoto = QImage() self.myPhotoPixels = np.zeros(shape=(1, 1, 4)) self.myPhotoPath = "" self.myPhotoTime = datetime(1,1,1) self.myPhotoSrcRect = QRect() self.myPhotoDestRect = QRect() self.myPhotoRadius = 0 self.myPhotoRotation = 0 self.rawAvailable = False self.coordsMouse = (0, 0) self.viewCenter = (0, 0) self.dragSelectRect = QRect(0, 0, 0, 0) self.sunPosition = (0, 0) # (azimuth (theta), altitude (phi)(90-zenith)) self.sunPositionVisible = (0,0) # point (x,y) of sun location rendered on screen (scaled) self.sunPathPoints = [] # [(azimuth (theta), altitude (phi)(90-zenith), datetime)] self.compassTicks = [] # [[x1, y1, x2, y2, x1lbl, y1lbl, angle]] self.lensIdealRadii = [] # list of radii for ideal lens latitudes to draw self.lensRealRadii = [] # list of radii for real/warped lens latitudes to draw self.samplePoints = [] # (x,y) coords of all samples on the photo rendered on screen (scaled) self.sampleAreaVisible = [] # area of 4 points for each sample rendered on screen (scaled) self.samplePointsInFile = [] # points (x,y) of all samples in the photo on file self.samplesSelected = [] # indices of selected samples self.skyCover = common.SkyCover.UNK # members - preloaded graphics self.painter = QPainter() self.mask = QImage() self.pathSun = QPainterPath() self.penText = QPen(Qt.white, 1, Qt.SolidLine) self.penLens = QPen(Qt.magenta, 1, Qt.SolidLine) self.penSun = QPen(QColor(255, 165, 0), 2, Qt.SolidLine) self.penSelected = [] # list of pens, one for each sampling pattern location self.penSelectRect = QPen(Qt.white, 1, Qt.DashLine) self.penShadowText = QPen(Qt.black, 1, Qt.SolidLine) self.penShadowSun = QPen(Qt.black, 2, Qt.SolidLine) self.penShadowSelected = QPen(Qt.black, 3, Qt.SolidLine) self.brushGrid = QBrush(Qt.white, Qt.SolidPattern) self.fontFixed = QFont('Courier New', 8) self.fontScaled = QFont('Courier New', 8) self.fontMetrics = QFontMetrics(self.fontScaled) self.iconWarning = self.style().standardIcon(QStyle.SP_MessageBoxWarning).pixmap(ViewFisheye.SelectedPixelBox / 2) def dataLoaded(self): # Note - this function only runs once the data directory has been loaded self.setMouseTracking(True) color = QColor(255, 255, 255) self.samplesSelected.clear() self.samplePoints.clear() self.sampleAreaVisible.clear() self.samplePointsInFile.clear() self.penSelected.clear() for t, p in common.SamplingPattern: self.samplePoints.append((0, 0)) # these will need to be recomputed as photo scales self.samplePointsInFile.append((0, 0)) # these only need to be computed once per photo self.sampleAreaVisible.append([]) color.setHsv(t, int(utility.normalize(p, 0, 90) * 127 + 128), 255) self.penSelected.append(QPen(color, 3, Qt.SolidLine)) def setPhoto(self, path, exif=None): # if photo is valid if path is not None and os.path.exists(path): self.myPhotoPath = path self.myPhoto = QImage(path) self.myPhotoSrcRect = QRect(0, 0, self.myPhoto.width(), self.myPhoto.height()) self.myPhotoDestRect = QRect(0, 0, self.width(), self.height()) self.rawAvailable = utility_data.isHDRRawAvailable(path) if exif is not None: self.myPhotoTime = datetime.strptime(str(exif["EXIF DateTimeOriginal"]), '%Y:%m:%d %H:%M:%S') else: self.myPhotoTime = utility_data.imageEXIFDateTime(path) # cache each sample's coordinate in the photo # note: technically doesn't need to be recalculated if all photos have same resolution! self.samplePointsInFile = utility_data.computePointsInImage(path, common.SamplingPattern) # keep a copy the image's pixels in memory (used later for exporting, etc.) ptr = self.myPhoto.bits() ptr.setsize(self.myPhoto.byteCount()) pixbgr = np.asarray(ptr).reshape(self.myPhoto.height(), self.myPhoto.width(), 4) # HACKAROONIE: byte order is not the same as image format, so swapped it around :/ # TODO: should handle this better self.myPhotoPixels = np.copy(pixbgr) red = np.copy(self.myPhotoPixels[:, :, 0]) self.myPhotoPixels[:, :, 0] = self.myPhotoPixels[:, :, 2] self.myPhotoPixels[:, :, 2] = red # rgba = self.myPhoto.pixelColor(center[0], center[1]) # print((rgba.red(), rgba.green(), rgba.blue())) # rgba = pixrgb[center[1], center[0]] # print(rgba) # photo is null or missing else: self.myPhoto = QImage() self.myPhotoPixels = np.zeros(shape=(1,1,4)) self.myPhotoPath = "" self.myPhotoTime = datetime(1, 1, 1) self.myPhotoSrcRect = QRect() self.myPhotoDestRect = QRect() self.rawAvailable = False # precompute as much as we can before any drawing self.computeBounds() def setSunPath(self, sunpath): self.sunPathPoints = sunpath def setSunPosition(self, pos): self.sunPosition = pos def setSkycover(self, sc): self.skyCover = sc def getSamplePatternRGB(self, index): if index < 0 or index >= len(common.SamplingPattern): return (0,0,0) color = self.penSelected[index].color() return (color.red(), color.green(), color.blue()) def resetRotation(self, angles=0): self.myPhotoRotation = angles def selectSamples(self, message="none"): # nothing to do if no photo loaded if self.myPhoto.isNull(): return # handle selection message if message == "none": self.samplesSelected.clear() elif message == "all": self.samplesSelected[:] = [i for i in range(0, len(common.SamplingPattern))] elif message == "inverse": allidx = set([i for i in range(0, len(common.SamplingPattern))]) selidx = set(self.samplesSelected) self.samplesSelected[:] = list(allidx - selidx) # remove samples in circumsolar avoidance region if necessary sunAvoid = common.AppSettings["AvoidSunAngle"] if sunAvoid > 0: sunAvoidRads = math.radians(common.AppSettings["AvoidSunAngle"]) sunPosRads = (math.radians(self.sunPosition[0]), math.radians(self.sunPosition[1])) self.samplesSelected[:] = [idx for idx in self.samplesSelected if utility_angles.CentralAngle(sunPosRads, common.SamplingPatternRads[idx], inRadians=True) > sunAvoidRads] # update self.repaint() self.parent.graphSamples(self.samplesSelected) def mouseMoveEvent(self, event): # nothing to do if no photo loaded if self.myPhoto.isNull(): return # detect primary mouse button drag for sample selection if event.buttons() == Qt.LeftButton: # update drag selection bounds self.dragSelectRect.setWidth(event.x() - self.dragSelectRect.x()) self.dragSelectRect.setHeight(event.y() - self.dragSelectRect.y()) # detect middle mouse button drag for image rotation elif (event.buttons() == Qt.MidButton): old = (self.coordsMouse[0] - self.viewCenter[0], self.coordsMouse[1] - self.viewCenter[1]) new = (event.x() - self.viewCenter[0], event.y() - self.viewCenter[1]) # clockwise drag decreases rotation if old[1]*new[0] < old[0]*new[1]: self.myPhotoRotation -= 1 # counter-clockwise drag increases rotation else: self.myPhotoRotation += 1 # rotation if self.myPhotoRotation >= 0: self.myPhotoRotation %= 360 else: self.myPhotoRotation %= -360 # lastly, cache mouse coordinates and update self.coordsMouse = (event.x(), event.y()) self.repaint() def mousePressEvent(self, event): # nothing to do if no photo loaded if self.myPhoto.isNull(): return # we only care about a left click for point and drag selection # right click is for context menu - handled elsewhere # middle click is for rotation - handled elsewhere if event.buttons() != Qt.LeftButton: return # start logging drag selection (whether user drags or not) self.dragSelectRect.setX(event.x()) self.dragSelectRect.setY(event.y()) self.dragSelectRect.setWidth(0) self.dragSelectRect.setHeight(0) def mouseReleaseEvent(self, event): # nothing to do if no photo loaded if self.myPhoto.isNull(): return # detect primary mouse button release for stopping sample selection if event.button() == Qt.LeftButton: # read modifier keys for user desired selection mode mode = ViewFisheye.SelectionMode.Select if event.modifiers() == Qt.ControlModifier: mode = ViewFisheye.SelectionMode.Add elif event.modifiers() == Qt.ShiftModifier: mode = ViewFisheye.SelectionMode.Remove # unflip coordinates of rect so that width and height are always positive r = self.dragSelectRect r = utility.rectForwardFacing([r.x(), r.y(), r.right(), r.bottom()]) self.dragSelectRect.setCoords(r[0], r[1], r[2], r[3]) # select samples prevSelected = list(self.samplesSelected) if self.dragSelectRect.width() < ViewFisheye.SelectionRectMin and self.dragSelectRect.height() < ViewFisheye.SelectionRectMin: self.computeSelectedSamples(ViewFisheye.SelectionType.Closest, mode) else: self.computeSelectedSamples(ViewFisheye.SelectionType.Rect, mode) # reset drag selection self.dragSelectRect.setX(event.x()) self.dragSelectRect.setY(event.y()) self.dragSelectRect.setWidth(0) self.dragSelectRect.setHeight(0) # update self.repaint() if self.samplesSelected != prevSelected: self.parent.graphSamples(self.samplesSelected) def wheelEvent(self, event): # nothing to do if no photo loaded if self.myPhoto.isNull(): return self.parent.timeChangeWheelEvent(event) def leaveEvent(self, event): self.coordsMouse = (-1, -1) self.repaint() def resizeEvent(self, event): self.computeBounds() def contextMenuEvent(self, event): # nothing to do if no photo loaded if self.myPhoto.isNull(): return self.parent.triggerContextMenu(self, event) def computeSelectedSamples(self, type, mode): px = 0 py = 0 x1 = 0 y1 = 0 x2 = 0 y2 = 0 # in select mode, clear current selection if mode == ViewFisheye.SelectionMode.Select: self.samplesSelected = [] # these are the samples we will be adding or removing sampleAdjustments = [] # which single sample did user select by point if type == ViewFisheye.SelectionType.Exact: px = self.coordsMouse[0] py = self.coordsMouse[1] for i in range(0, len(self.samplePoints)): x, y = self.samplePoints[i] x1 = x - ViewFisheye.SampleRadius y1 = y - ViewFisheye.SampleRadius x2 = x + ViewFisheye.SampleRadius y2 = y + ViewFisheye.SampleRadius if px >= x1 and px <= x2 and py >= y1 and py <= y2: sampleAdjustments.append(i) break # which single sample is the closest to the mouse coordinate elif type == ViewFisheye.SelectionType.Closest: px = self.coordsMouse[0] py = self.coordsMouse[1] dist = math.sqrt((py-self.viewCenter[1])*(py-self.viewCenter[1]) + (px-self.viewCenter[0])*(px-self.viewCenter[0])) if dist <= self.myPhotoRadius: close = math.inf closest = -1 for i in range(0, len(self.samplePoints)): x, y = self.samplePoints[i] dist = math.sqrt((y-py)*(y-py) + (x-px)*(x-px)) if dist < close: close = dist closest = i if closest >= 0: sampleAdjustments.append(closest) # which samples are in the drag selection rect elif type == ViewFisheye.SelectionType.Rect: x1 = self.dragSelectRect.x() y1 = self.dragSelectRect.y() x2 = self.dragSelectRect.x() + self.dragSelectRect.width() y2 = self.dragSelectRect.y() + self.dragSelectRect.height() for i in range(0, len(self.samplePoints)): x, y = self.samplePoints[i] if x >= x1 and x <= x2 and y >= y1 and y <= y2: sampleAdjustments.append(i) # remove samples in circumsolar avoidance region sunAvoid = common.AppSettings["AvoidSunAngle"] if sunAvoid > 0: sunAvoidRads = math.radians(common.AppSettings["AvoidSunAngle"]) sunPosRads = (math.radians(self.sunPosition[0]), math.radians(self.sunPosition[1])) sampleAdjustments[:] = [idx for idx in sampleAdjustments if utility_angles.CentralAngle(sunPosRads, common.SamplingPatternRads[idx], inRadians=True) > sunAvoidRads] # no changes to be made if len(sampleAdjustments) <= 0: return # finally modify sample selection and return difference if mode == ViewFisheye.SelectionMode.Select or mode == ViewFisheye.SelectionMode.Add: for i in range(0, len(sampleAdjustments)): if sampleAdjustments[i] not in self.samplesSelected: # don't readd existing indices self.samplesSelected.append(sampleAdjustments[i]) elif mode == ViewFisheye.SelectionMode.Remove: for i in range(0, len(sampleAdjustments)): try: self.samplesSelected.remove(sampleAdjustments[i]) except: pass # ignore trying to remove indices that aren't currently selected # sort selection for easier searching later self.samplesSelected.sort() def computeBounds(self): if self.myPhoto.isNull(): self.myPhotoDestRect = QRect(0, 0, self.width(), self.height()) self.viewCenter = (self.width() / 2, self.height() / 2) self.myPhotoRadius = 0 self.myPhotoDiameter = 0 for i in range(0, len(common.SamplingPattern)): self.samplePoints[i] = (0, 0) self.sampleAreaVisible[i] = [] return # scale photo destination rect to fit photo on screen # scale by the scaling factor that requires the most scaling ( - 2 to fit in border ) wRatio = self.width() / self.myPhoto.width() hRatio = self.height() / self.myPhoto.height() if wRatio <= hRatio: self.myPhotoDestRect.setWidth(self.myPhotoSrcRect.width() * wRatio - 2) self.myPhotoDestRect.setHeight(self.myPhotoSrcRect.height() * wRatio - 2) else: self.myPhotoDestRect.setWidth(self.myPhotoSrcRect.width() * hRatio - 2) self.myPhotoDestRect.setHeight(self.myPhotoSrcRect.height() * hRatio - 2) # center the photo dest rect self.myPhotoDestRect.moveTo(self.width() / 2 - self.myPhotoDestRect.width() / 2, self.height() / 2 - self.myPhotoDestRect.height() / 2) # NOTE - THESE ARE THE MOST IMPORTANT COMPUTATIONS FROM WHICH EVERYTHING ELSE IS PLOTTED self.viewCenter = (self.width() / 2, self.height() / 2) self.myPhotoRadius = self.myPhotoDestRect.height() / 2 self.myPhotoDiameter = self.myPhotoRadius * 2 self.myPhotoTopLeft = ((self.viewCenter[0] - self.myPhotoRadius), (self.viewCenter[1] - self.myPhotoRadius)) # compute new scaled font size self.fontScaled = QFont('Courier New', self.myPhotoRadius * (1/(101-common.AppSettings["HUDTextScale"]))) self.fontMetrics = QFontMetrics(self.fontScaled) # compute sampling pattern collision bounds ViewFisheye.SampleRadius = self.myPhotoRadius / 50 hFOV = common.DataConfig["RadianceFOV"] / 2 for i in range(0, len(common.SamplingPattern)): # compute sample bounds u, v = utility_angles.SkyCoord2FisheyeUV(common.SamplingPattern[i][0], common.SamplingPattern[i][1]) x = self.myPhotoTopLeft[0] + (u * self.myPhotoDiameter) y = self.myPhotoTopLeft[1] + (v * self.myPhotoDiameter) self.samplePoints[i] = (x, y) # compute sampling pattern actual sampling areas (projected differential angle area) p1 = utility_angles.SkyCoord2FisheyeUV(common.SamplingPattern[i][0] - hFOV, common.SamplingPattern[i][1] - hFOV) p2 = utility_angles.SkyCoord2FisheyeUV(common.SamplingPattern[i][0] - hFOV, common.SamplingPattern[i][1] + hFOV) p3 = utility_angles.SkyCoord2FisheyeUV(common.SamplingPattern[i][0] + hFOV, common.SamplingPattern[i][1] + hFOV) p4 = utility_angles.SkyCoord2FisheyeUV(common.SamplingPattern[i][0] + hFOV, common.SamplingPattern[i][1] - hFOV) p1 = QPoint(self.myPhotoTopLeft[0] + (p1[0] * self.myPhotoDiameter), self.myPhotoTopLeft[1] + (p1[1] * self.myPhotoDiameter)) p2 = QPoint(self.myPhotoTopLeft[0] + (p2[0] * self.myPhotoDiameter), self.myPhotoTopLeft[1] + (p2[1] * self.myPhotoDiameter)) p3 = QPoint(self.myPhotoTopLeft[0] + (p3[0] * self.myPhotoDiameter), self.myPhotoTopLeft[1] + (p3[1] * self.myPhotoDiameter)) p4 = QPoint(self.myPhotoTopLeft[0] + (p4[0] * self.myPhotoDiameter), self.myPhotoTopLeft[1] + (p4[1] * self.myPhotoDiameter)) self.sampleAreaVisible[i] = [p1, p2, p3, p4] # compute compass lines self.compassTicks.clear() tickLength = self.myPhotoRadius / 90 for angle in range(0, 360, 10): theta = 360 - ((angle + 270) % 360) # angles eastward from North, North facing down rads = theta * math.pi / 180.0 cx1 = (math.cos(rads) * (self.myPhotoRadius - tickLength)) + self.viewCenter[0] cy1 = (math.sin(rads) * (self.myPhotoRadius - tickLength)) + self.viewCenter[1] cx2 = (math.cos(rads) * self.myPhotoRadius) + self.viewCenter[0] cy2 = (math.sin(rads) * self.myPhotoRadius) + self.viewCenter[1] lx1 = (math.cos(rads) * (self.myPhotoRadius - tickLength*4)) + self.viewCenter[0] - self.fontMetrics.width(str(angle))/2 ly1 = (math.sin(rads) * (self.myPhotoRadius - tickLength*4)) + self.viewCenter[1] - self.fontMetrics.height()/2 self.compassTicks.append([cx1, cy1, cx2, cy2, lx1, ly1, angle]) # x1, y1, x2, y2, x1lbl, y1lbl, angle # compute new grid for debugging coordinates griddivs = 5 gridwidth = int(round(self.myPhotoDiameter / griddivs)) self.gridpoints = [] self.gridUVs = [] self.gridskycoords = [] for r in range(1, griddivs): for c in range(1, griddivs): point = (self.myPhotoTopLeft[0] + (c * gridwidth), self.myPhotoTopLeft[1] + (r * gridwidth)) self.gridpoints.append(point) u = (point[0] - self.myPhotoTopLeft[0]) / self.myPhotoDiameter v = (point[1] - self.myPhotoTopLeft[1]) / self.myPhotoDiameter self.gridUVs.append((u, v)) t, p = utility_angles.FisheyeUV2SkyCoord(u, v) self.gridskycoords.append((t, p)) # compute lens (ideal and actual) radii for drawn latitude ellipses along zenith self.lensIdealRadii.clear() self.lensRealRadii.clear() for alt in common.SamplingPatternAlts: # ideal lens u, v = utility_angles.SkyCoord2FisheyeUV(90, alt, lenswarp=False) x = self.myPhotoTopLeft[0] + (u * self.myPhotoDiameter) r = x - self.viewCenter[0] self.lensIdealRadii.append((r, alt)) # (radius, altitude) # warped lens u, v = utility_angles.SkyCoord2FisheyeUV(90, alt) x = self.myPhotoTopLeft[0] + (u * self.myPhotoDiameter) r = x - self.viewCenter[0] self.lensRealRadii.append((r, alt)) # (radius, altitude) # compute sun path screen points self.pathSun = QPainterPath() if len(self.sunPathPoints) > 0: azi, alt, dt = self.sunPathPoints[0] u, v = utility_angles.SkyCoord2FisheyeUV(azi, alt) x = self.myPhotoTopLeft[0] + (u * self.myPhotoDiameter) y = self.myPhotoTopLeft[1] + (v * self.myPhotoDiameter) self.pathSun.moveTo(x, y) for i in range(1, len(self.sunPathPoints)): azi, alt, dt = self.sunPathPoints[i] u, v = utility_angles.SkyCoord2FisheyeUV(azi, alt) x = self.myPhotoTopLeft[0] + (u * self.myPhotoDiameter) y = self.myPhotoTopLeft[1] + (v * self.myPhotoDiameter) self.pathSun.lineTo(x, y) # compute sun position screen point u, v = utility_angles.SkyCoord2FisheyeUV(self.sunPosition[0], self.sunPosition[1]) x = self.myPhotoTopLeft[0] + (u * self.myPhotoDiameter) y = self.myPhotoTopLeft[1] + (v * self.myPhotoDiameter) self.sunPositionVisible = (x, y) # compute new mask self.mask = QPixmap(self.width(), self.height()).toImage() def paintEvent(self, event): super().paintEvent(event) painter = QPainter() painter.begin(self) # background brushBG = QBrush(Qt.black, Qt.SolidPattern) if not common.AppSettings["ShowMask"]: brushBG.setColor(Qt.darkGray) brushBG.setStyle(Qt.Dense1Pattern) painter.setBackground(Qt.gray) else: brushBG.setColor(Qt.black) brushBG.setStyle(Qt.SolidPattern) painter.setBackground(Qt.black) painter.setBackgroundMode(Qt.OpaqueMode) painter.setBrush(brushBG) painter.setPen(Qt.NoPen) painter.drawRect(0, 0, self.width(), self.height()) # draw photo if not self.myPhoto.isNull(): # rotate and draw photo as specified by user transform = QTransform() transform.translate(self.myPhotoDestRect.center().x(), self.myPhotoDestRect.center().y()) transform.rotate(-self.myPhotoRotation) transform.translate(-self.myPhotoDestRect.center().x(), -self.myPhotoDestRect.center().y()) painter.setTransform(transform) painter.drawImage(self.myPhotoDestRect, self.myPhoto, self.myPhotoSrcRect) # draw it painter.resetTransform() # useful local vars centerPoint = QPoint(self.viewCenter[0], self.viewCenter[1]) destRect = QRect(0, 0, self.myPhotoDestRect.width(), self.myPhotoDestRect.height()) fontWidth = self.fontMetrics.width("X") # mask if common.AppSettings["ShowMask"]: maskPainter = QPainter() maskPainter.begin(self.mask) maskPainter.setBrush(QBrush(Qt.magenta, Qt.SolidPattern)) maskPainter.drawEllipse(self.viewCenter[0] - self.myPhotoRadius, self.viewCenter[1] - self.myPhotoRadius, self.myPhotoDiameter, self.myPhotoDiameter) maskPainter.end() painter.setCompositionMode(QPainter.CompositionMode_DestinationIn) painter.drawImage(0, 0, self.mask) painter.setCompositionMode(QPainter.CompositionMode_SourceOver) # HUD if common.AppSettings["ShowHUD"]: painter.setBackgroundMode(Qt.TransparentMode) #painter.setBackground(Qt.black) painter.setBrush(Qt.NoBrush) painter.setFont(self.fontScaled) # draw UV grid if common.AppSettings["ShowUVGrid"]: painter.setPen(self.penText) # box tl = self.myPhotoTopLeft tr = (self.viewCenter[0] + self.myPhotoRadius, self.viewCenter[1] - self.myPhotoRadius) bl = (self.viewCenter[0] - self.myPhotoRadius, self.viewCenter[1] + self.myPhotoRadius) br = (self.viewCenter[0] + self.myPhotoRadius, self.viewCenter[1] + self.myPhotoRadius) painter.drawLine(tl[0], tl[1], tr[0], tr[1]) painter.drawLine(bl[0], bl[1], br[0], br[1]) painter.drawLine(tl[0], tl[1], bl[0], bl[1]) painter.drawLine(tr[0], tr[1], br[0], br[1]) # crosshairs painter.drawLine(tl[0], self.viewCenter[1], tr[0], self.viewCenter[1]) painter.drawLine(self.viewCenter[0], tr[1], self.viewCenter[0], br[1]) # labels destRect.setCoords(tl[0] + 4, tl[1] + 4, self.width(), self.height()) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "0") destRect.setCoords(tr[0] - (fontWidth+4), tr[1] + 4, self.width(), self.height()) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "1") destRect.setCoords(bl[0] + 3, bl[1] - (self.fontMetrics.height()+3), self.width(), self.height()) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "1") destRect.setCoords(br[0] - (fontWidth+3), br[1] - (self.fontMetrics.height()+3), self.width(), self.height()) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "1") # grid coordinates gpntrad = self.myPhotoRadius * 0.005 painter.setPen(self.penText) painter.setBrush(self.brushGrid) painter.setFont(self.fontScaled) for i in range(0, len(self.gridpoints)): point = self.gridpoints[i] u, v = self.gridUVs[i] t, p = self.gridskycoords[i] painter.drawEllipse(QPoint(point[0], point[1]), gpntrad, gpntrad) destRect.setCoords(point[0]+fontWidth/2, point[1]-self.fontMetrics.height(), self.width(), self.height()) textuv = "{0:.1f}u, {1:.1f}v".format(round(u,1), round(v,1)) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, textuv) destRect.setCoords(point[0]+fontWidth/2, point[1], self.width(), self.height()) textuv = "{0:d}°, {1:d}°".format(int(round(t)), int(round(p))) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, textuv) painter.setBrush(Qt.NoBrush) # draw lens warp if common.AppSettings["ShowLensWarp"]: # ideal lens longitudes along azimuth painter.setPen(self.penText) for i in range(0, int(len(self.compassTicks)/2), 3): p1 = QPoint(self.compassTicks[i][2], self.compassTicks[i][3]) p2 = QPoint(self.compassTicks[i+18][2], self.compassTicks[i+18][3]) # tick opposite 180 degrees painter.drawLine(p1, p2) # ideal lens latitudes along zenith for r, alt in self.lensIdealRadii: painter.drawEllipse(centerPoint, r, r) # actual/warped lens latitudes along zenith painter.setPen(self.penLens) for r, alt in self.lensRealRadii: painter.drawEllipse(centerPoint, r, r) destRect.setCoords(self.viewCenter[0] + r + 3, self.viewCenter[1] - (self.fontMetrics.height() + 3), self.width(), self.height()) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "{0:d}°".format(int(alt))) # draw compass if common.AppSettings["ShowCompass"]: # compass ticks text shadows if common.AppSettings["ShowShadows"]: painter.setPen(self.penShadowText) for tick in self.compassTicks: destRect.setCoords(tick[4] + 1, tick[5] + 1, self.width(), self.height()) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, str(tick[6])+"°") # compass ticks text painter.setPen(self.penText) for tick in self.compassTicks: painter.drawLine(tick[0], tick[1], tick[2], tick[3]) destRect.setCoords(tick[4], tick[5], self.width(), self.height()) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, str(tick[6])+"°") # photo radius #painter.drawEllipse(self.viewCenter[0] - self.myPhotoRadius, self.viewCenter[1] - self.myPhotoRadius, self.myPhotoDiameter, self.myPhotoDiameter) painter.drawEllipse(centerPoint, self.myPhotoRadius, self.myPhotoRadius) # cardinal directions destRect.setCoords(self.viewCenter[0] - self.myPhotoRadius - (fontWidth+4), self.viewCenter[1] - self.fontMetrics.height()/2, self.width(), self.height()) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "W") destRect.setCoords(self.viewCenter[0] + self.myPhotoRadius + 4, self.viewCenter[1] - self.fontMetrics.height()/2, self.width(), self.height()) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "E") destRect.setCoords(self.viewCenter[0] - fontWidth/2, self.viewCenter[1] - self.myPhotoRadius - (self.fontMetrics.height()+3), self.width(), self.height()) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "S") destRect.setCoords(self.viewCenter[0] - fontWidth/2, self.viewCenter[1] + self.myPhotoRadius + 3, self.width(), self.height()) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "N") # draw sampling pattern if common.AppSettings["ShowSamples"]: painter.setPen(self.penText) for i, points in enumerate(self.sampleAreaVisible): painter.drawLine(QLine(points[0], points[1])) painter.drawLine(QLine(points[1], points[2])) painter.drawLine(QLine(points[2], points[3])) painter.drawLine(QLine(points[3], points[0])) for i in range(0, len(self.samplePoints)): p = self.samplePoints[i] painter.drawEllipse(QPoint(p[0],p[1]), ViewFisheye.SampleRadius, ViewFisheye.SampleRadius) painter.drawText(p[0] + ViewFisheye.SampleRadius, p[1], str(i)) # draw sun path if common.AppSettings["ShowSunPath"]: sunradius = self.myPhotoRadius * 0.1 # shadows painter.setPen(self.penShadowSun) if common.AppSettings["ShowShadows"]: painter.drawEllipse(QPoint(self.sunPositionVisible[0]+1, self.sunPositionVisible[1]+1), sunradius, sunradius) self.pathSun.translate(1.0, 1.0) painter.drawPath(self.pathSun) self.pathSun.translate(-1.0, -1.0) for i in range(0, self.pathSun.elementCount()): e = self.pathSun.elementAt(i) destRect.setCoords(e.x, e.y + self.fontMetrics.height()/2 + 1, self.width(), self.height()) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, str(self.sunPathPoints[i][2].hour)) # sun, path, hours painter.setPen(self.penSun) painter.drawEllipse(QPoint(self.sunPositionVisible[0], self.sunPositionVisible[1]), sunradius, sunradius) painter.drawPath(self.pathSun) for i in range(0, self.pathSun.elementCount()): e = self.pathSun.elementAt(i) destRect.setCoords(e.x, e.y + self.fontMetrics.height() / 2, self.width(), self.height()) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, str(self.sunPathPoints[i][2].hour)) # draw selected samples (ALWAYS) r = QRect() # shadows if common.AppSettings["ShowShadows"]: painter.setPen(self.penShadowSelected) for i in self.samplesSelected: x, y = self.samplePoints[i] painter.drawEllipse(QPoint(x+1, y+1), ViewFisheye.SampleRadius, ViewFisheye.SampleRadius) # samples for i in self.samplesSelected: painter.setPen(self.penSelected[i]) x, y = self.samplePoints[i] painter.drawEllipse(QPoint(x, y), ViewFisheye.SampleRadius, ViewFisheye.SampleRadius) # draw user's selection bounds if (abs(self.dragSelectRect.right()-self.dragSelectRect.left()) >= ViewFisheye.SelectionRectMin and abs(self.dragSelectRect.bottom()-self.dragSelectRect.top()) >= ViewFisheye.SelectionRectMin): painter.setPen(self.penSelectRect) painter.drawRect(self.dragSelectRect) # draw timestamp painter.setPen(self.penText) painter.setFont(self.fontFixed) destRect.setCoords(10, 10, self.width() / 2, 50) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, str(self.myPhotoTime)) # draw sky cover assessment destRect.setCoords(10, 25, self.width(), self.height()) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, self.skyCover.name + "/" + common.SkyCoverDesc[self.skyCover]) # draw photo rotation if self.myPhotoRotation != 0: destRect.setCoords(10, self.height()-25, self.width(), self.height()) painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "Rotation: " + str(self.myPhotoRotation) + "°") # where is the mouse relative to the center? # this is used as an optimization to only display information when mouse is in fisheye portion dx = self.coordsMouse[0] - self.viewCenter[0] dy = self.coordsMouse[1] - self.viewCenter[1] distance = math.sqrt((dx * dx) + (dy * dy)) # distance from mouse to view center # coordinates we are interested in #self.coordsMouse # x,y of this widget coordsxy = (-1, -1) # x,y over photo as scaled/rendered on this widget coordsXY = (-1, -1) # x,y over actual original photo on disk coordsUV = (-1, -1) # u,v coords of fisheye portion of photo w/ 0,0 top left and 1,1 bottom right coordsTP = (-1, -1) # theta,phi polar coordinates # text textxy = "-1, -1 xy" textXY = "-1, -1 xy" textUV = "-1, -1 uv" textTP = "-1, -1 θφ" textPX = "0 0 0 px" # compute all relevant information only when mouse is within fisheye portion of photo if distance < self.myPhotoRadius: coordsxy = (self.coordsMouse[0] - self.myPhotoDestRect.x(), self.coordsMouse[1] - self.myPhotoDestRect.y()) coordsXY = (int(coordsxy[0] / self.myPhotoDestRect.width() * self.myPhoto.width()), int(coordsxy[1] / self.myPhotoDestRect.height() * self.myPhoto.height())) coordsUV = ((self.coordsMouse[0] - self.myPhotoTopLeft[0]) / self.myPhotoDiameter, (self.coordsMouse[1] - self.myPhotoTopLeft[1]) / self.myPhotoDiameter) coordsTP = utility_angles.FisheyeUV2SkyCoord(coordsUV[0], coordsUV[1]) # text textxy = str(coordsxy[0]) + ", " + str(coordsxy[1]) + " xy" textXY = str(coordsXY[0]) + ", " + str(coordsXY[1]) + " xy" textUV = "{:.2f}".format(coordsUV[0]) + ", " + "{:.2f}".format(coordsUV[1]) + " uv" textTP = "{:.2f}".format(coordsTP[0]) + ", " + "{:.2f}".format(coordsTP[1]) + " θφ" # pixels colors pixreg = common.AppSettings["PixelRegion"] colorsRegion = np.zeros((pixreg, pixreg, 4)) colorFinal = colorsRegion[0,0] # RGBA of pixel under mouse of photo on disk # colorFinal = self.myPhoto.pixelColor(coordsXY[0], coordsXY[1]) if distance < self.myPhotoRadius: halfdim = int(pixreg / 2) rstart = coordsXY[1]-halfdim rstop = coordsXY[1]+halfdim+1 cstart = coordsXY[0]-halfdim cstop = coordsXY[0]+halfdim+1 if (rstart >= 0 and rstop<=self.myPhotoPixels.shape[0] and cstart >= 0 and cstop<=self.myPhotoPixels.shape[1]): colorsRegion = self.myPhotoPixels[rstart:rstop, cstart:cstop] colorFinal = colorsRegion[halfdim, halfdim] if pixreg > 1: # with pixel weighting colorFinal = utility_data.collectPixels([coordsXY], [pixreg], pixels=self.myPhotoPixels, weighting=common.PixelWeighting(common.AppSettings["PixelWeighting"]))[0] textPX = str(colorFinal[0]) + " " + str(colorFinal[1]) + " " + str(colorFinal[2]) + " px" # draw HUD text strings # x,y coords destRect.setCoords(0, 0, self.width() - 10, self.height()- 124) painter.drawText(destRect, Qt.AlignBottom | Qt.AlignRight, textxy) # X,Y coords destRect.setCoords(0, 0, self.width() - 10, self.height() - 114) painter.drawText(destRect, Qt.AlignBottom | Qt.AlignRight, textXY) # u,v coords destRect.setCoords(0, 0, self.width() - 10, self.height() - 104) painter.drawText(destRect, Qt.AlignBottom | Qt.AlignRight, textUV) # t,p coords destRect.setCoords(0, 0, self.width() - 10, self.height() - 94) painter.drawText(destRect, Qt.AlignBottom | Qt.AlignRight, textTP) # pixel color destRect.setCoords(0, 0, self.width() - 10, self.height() - 84) painter.drawText(destRect, Qt.AlignBottom | Qt.AlignRight, textPX) # compute pixel visualization coordinates circleX = self.width() - 10 - ViewFisheye.SelectedPixelBox - 10 - ViewFisheye.SelectedPixelBox - 10 - ViewFisheye.SelectedPixelBox circleY = self.height() - 10 - ViewFisheye.SelectedPixelBox pixelsX = self.width() - 10 - ViewFisheye.SelectedPixelBox - 10 - ViewFisheye.SelectedPixelBox pixelsY = self.height() - 10 - ViewFisheye.SelectedPixelBox pixelsWeightedX = self.width() - ViewFisheye.SelectedPixelBox - 10 pixelsWeightedY = self.height() - 10 - ViewFisheye.SelectedPixelBox # draw pixel visualization - fills pixreg = common.AppSettings["PixelRegion"] if distance < self.myPhotoRadius: painter.setPen(Qt.NoPen) # pixel region pixdim = ViewFisheye.SelectedPixelBox / pixreg for row in range(0, pixreg): for col in range(0, pixreg): color = colorsRegion[row, col] color = QColor(color[0], color[1], color[2]) painter.setBrush(QBrush(color, Qt.SolidPattern)) painter.drawRect(pixelsX + (col * pixdim), pixelsY + (row * pixdim), math.ceil(pixdim), math.ceil(pixdim)) # final pixel color color = QColor(colorFinal[0], colorFinal[1], colorFinal[2]) painter.setBrush(QBrush(color, Qt.SolidPattern)) cx = circleX + (coordsUV[0] * ViewFisheye.SelectedPixelBox) cy = circleY + (coordsUV[1] * ViewFisheye.SelectedPixelBox) painter.drawEllipse(cx - 5, cy - 5, 10, 10) painter.drawRect(pixelsWeightedX, pixelsWeightedY, ViewFisheye.SelectedPixelBox, ViewFisheye.SelectedPixelBox) # draw pixel visualization - outlines painter.setPen(self.penText) painter.setBrush(Qt.NoBrush) painter.drawEllipse(circleX, circleY, ViewFisheye.SelectedPixelBox, ViewFisheye.SelectedPixelBox) painter.drawRect(pixelsX, pixelsY, ViewFisheye.SelectedPixelBox, ViewFisheye.SelectedPixelBox) painter.drawRect(pixelsWeightedX, pixelsWeightedY, ViewFisheye.SelectedPixelBox, ViewFisheye.SelectedPixelBox) # raw data missing indicator # if (not self.rawAvailable): # painter.drawPixmap(pixelX + ViewFisheye.SelectedPixelBox / 2, # pixelY + ViewFisheye.SelectedPixelBox / 2, # self.iconWarning) # end draw painter.end()