I can put dots on screen by clicking and I want to connect them with lines after every point selection. How can I add this part?
import sys
from PyQt5 import QtWidgets, QtGui, QtCore, uic
class GUI(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setFixedSize(self.size())
self.show()
self.points = QtGui.QPolygon()
def mousePressEvent(self, e):
self.points << e.pos()
self.update()
def paintEvent(self, ev):
qp = QtGui.QPainter(self)
qp.setRenderHint(QtGui.QPainter.Antialiasing)
pen = QtGui.QPen(QtCore.Qt.black, 5)
brush = QtGui.QBrush(QtCore.Qt.black)
qp.setPen(pen)
qp.setBrush(brush)
for i in range(self.points.count()):
print(self.points.point(i))
qp.drawEllipse(self.points.point(i), 5, 5)
# qp.drawPoints(self.points)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = GUI()
sys.exit(app.exec_())
You have to draw a line between the previous point with the current point:
def paintEvent(self, ev):
qp = QtGui.QPainter(self)
qp.setRenderHint(QtGui.QPainter.Antialiasing)
pen = QtGui.QPen(QtCore.Qt.black, 5)
brush = QtGui.QBrush(QtCore.Qt.black)
qp.setPen(pen)
qp.setBrush(brush)
lp = QtCore.QPoint()
for i in range(self.points.count()):
cp = self.points.point(i)
qp.drawEllipse(cp, 5, 5)
if not lp.isNull():
qp.drawLine(lp, cp)
lp = cp
Another similar solution can be done with QPainterPath:
class GUI(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.path = QtGui.QPainterPath()
self.setFixedSize(self.size())
self.show()
def mousePressEvent(self, e):
if self.path.elementCount() == 0:
self.path.moveTo(e.pos())
else:
self.path.lineTo(e.pos())
self.update()
super().mousePressEvent(e)
def paintEvent(self, ev):
qp = QtGui.QPainter(self)
qp.setRenderHint(QtGui.QPainter.Antialiasing)
pen = QtGui.QPen(QtCore.Qt.black, 5)
qp.setPen(pen)
qp.drawPath(self.path)
brush = QtGui.QBrush(QtCore.Qt.black)
qp.setBrush(brush)
for i in range(self.path.elementCount()):
e = self.path.elementAt(i)
qp.drawEllipse(QtCore.QPoint(e.x, e.y), 5, 5)
Related
I'm in the process of converting my successful screen snipping program from Tkinter to PYQT5. My question is how to create a fully transparent snipping area (a dynamically updating square region would be nice). The outside of the square will be semi-opaque. I've looked all over stack overflow and the internet and could not find an example of this (others are not a fully transparent drawing window). I've attached my code and a picture example of what I am looking for. The "SnippingWidget" is the class that does the snipping logic.
import sys
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QMainWindow, QApplication, QAction, QWidget
from PyQt5.QtGui import QIcon, QKeySequence
from PIL import ImageGrab
class App(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
self.topMenu()
def initUI(self):
self.setWindowTitle('Lil Snippy')
self.setWindowIcon(QIcon('assets/lilSnippyIcon.png'))
self.setGeometry(400, 300, 400, 300)
self.show()
def topMenu(self):
menubar = self.menuBar()
fileMenu = menubar.addMenu('File')
saveAct = QAction(QIcon('assets/saveIcon.png'), 'Save', self)
saveAsAct = QAction(QIcon('assets/saveAsIcon.png'), 'Save As', self)
modeMenu = menubar.addMenu('Mode')
snipAct = QAction(QIcon('assets/cameraIcon.png'), 'Snip', self)
snipAct.setShortcut(QKeySequence('F1'))
snipAct.triggered.connect(self.activateSnipping)
videoAct = QAction(QIcon('assets/videoIcon.png'), 'Video', self)
videoAct.setShortcut('F2')
soundAct = QAction(QIcon('assets/audioIcon.png'), 'Sound', self)
soundAct.setShortcut('F3')
autoAct = QAction(QIcon('assets/automationIcon.png'), 'Automation', self)
autoAct.setShortcut('F4')
helpMenu = menubar.addMenu('Help')
helpAct = QAction(QIcon('assets/helpIcon.png'), 'Help', self)
aboutAct = QAction(QIcon('assets/aboutIcon.png'), 'About', self)
fileMenu.addAction(saveAct)
fileMenu.addAction(saveAsAct)
modeMenu.addAction(snipAct)
modeMenu.addAction(videoAct)
modeMenu.addAction(soundAct)
modeMenu.addAction(autoAct)
helpMenu.addAction(helpAct)
helpMenu.addAction(aboutAct)
def activateSnipping(self):
print("yes")
self.Snipper = SnippingWidget()
application.hide()
class SnippingWidget(QMainWindow):
def __init__(self, parent = None):
super(SnippingWidget, self).__init__(parent)
self.setStyleSheet("background-color: transparent;")
self.setWindowOpacity(.2)
self.showFullScreen()
self.outsideSquareColor = 'red'
self.squareThickness = 4
self.startX = None
self.startY = None
self.endX = None
self.endY = None
self.begin = QtCore.QPoint()
self.end = QtCore.QPoint()
def mousePressEvent(self, event):
self.begin = event.pos()
self.end = self.begin
self.startX = event.x()
self.startY = event.y()
self.update()
def mouseReleaseEvent(self, QMouseEvent):
self.destroy()
x1 = min(self.begin.x(), self.end.x())
y1 = min(self.begin.y(), self.end.y())
x2 = max(self.begin.x(), self.end.x())
y2 = max(self.begin.y(), self.end.y())
img = ImageGrab.grab(bbox=(x1, y1, x2, y2))
img.save('snips/testImage.png')
application.show()
def mouseMoveEvent(self, event):
self.end = event.pos()
self.update()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setPen(QtGui.QPen(QtGui.QColor('red'), self.squareThickness))
trans = QtGui.QColor(255,255,255,255)
qp.setBrush(trans)
rect = QtCore.QRectF(self.begin, self.end)
qp.drawRect(rect)
if __name__ == '__main__':
app = QApplication(sys.argv)
application = App()
sys.exit(app.exec_())
You have to use QPainterPath to subtract the rectangle from the window with the selected rectangle:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PIL import ImageGrab
class App(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
self.topMenu()
def initUI(self):
self.setWindowTitle("Lil Snippy")
self.setWindowIcon(QtGui.QIcon("assets/lilSnippyIcon.png"))
self.setGeometry(400, 300, 400, 300)
def topMenu(self):
menubar = self.menuBar()
fileMenu = menubar.addMenu("File")
saveAct = QtWidgets.QAction(QtGui.QIcon("assets/saveIcon.png"), "Save", self)
saveAsAct = QtWidgets.QAction(
QtGui.QIcon("assets/saveAsIcon.png"), "Save As", self
)
modeMenu = menubar.addMenu("Mode")
snipAct = QtWidgets.QAction(QtGui.QIcon("assets/cameraIcon.png"), "Snip", self)
snipAct.setShortcut(QtGui.QKeySequence("F1"))
snipAct.triggered.connect(self.activateSnipping)
videoAct = QtWidgets.QAction(QtGui.QIcon("assets/videoIcon.png"), "Video", self)
videoAct.setShortcut("F2")
soundAct = QtWidgets.QAction(QtGui.QIcon("assets/audioIcon.png"), "Sound", self)
soundAct.setShortcut("F3")
autoAct = QtWidgets.QAction(
QtGui.QIcon("assets/automationIcon.png"), "Automation", self
)
autoAct.setShortcut("F4")
helpMenu = menubar.addMenu("Help")
helpAct = QtWidgets.QAction(QtGui.QIcon("assets/helpIcon.png"), "Help", self)
aboutAct = QtWidgets.QAction(QtGui.QIcon("assets/aboutIcon.png"), "About", self)
fileMenu.addAction(saveAct)
fileMenu.addAction(saveAsAct)
modeMenu.addAction(snipAct)
modeMenu.addAction(videoAct)
modeMenu.addAction(soundAct)
modeMenu.addAction(autoAct)
helpMenu.addAction(helpAct)
helpMenu.addAction(aboutAct)
self.snipper = SnippingWidget()
self.snipper.closed.connect(self.on_closed)
def activateSnipping(self):
self.snipper.showFullScreen()
self.hide()
def on_closed(self):
self.snipper.hide()
self.show()
class SnippingWidget(QtWidgets.QMainWindow):
closed = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(SnippingWidget, self).__init__(parent)
self.setStyleSheet("background-color: transparent;")
self.setWindowOpacity(0.2)
self.outsideSquareColor = "red"
self.squareThickness = 4
self.start_point = QtCore.QPoint()
self.end_point = QtCore.QPoint()
def mousePressEvent(self, event):
self.start_point = event.pos()
self.end_point = event.pos()
self.update()
def mouseMoveEvent(self, event):
self.end_point = event.pos()
self.update()
def mouseReleaseEvent(self, QMouseEvent):
r = QtCore.QRect(self.start_point, self.end_point).normalized()
img = ImageGrab.grab(bbox=r.getCoords())
img.save("snips/testImage.png")
self.closed.emit()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setPen(
QtGui.QPen(QtGui.QColor(self.outsideSquareColor), self.squareThickness)
)
trans = QtGui.QColor(255, 255, 255, 255)
qp.setBrush(trans)
outer = QtGui.QPainterPath()
outer.addRect(QtCore.QRectF(self.rect()))
inner = QtGui.QPainterPath()
inner.addRect(QtCore.QRectF(self.start_point, self.end_point).normalized())
r = outer - inner
qp.drawPath(r)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
application = App()
application.show()
sys.exit(app.exec_())
Update:
class SnippingWidget(QtWidgets.QMainWindow):
closed = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(SnippingWidget, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_NoSystemBackground, True)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
self.setStyleSheet("background:transparent;")
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.outsideSquareColor = "red"
self.squareThickness = 4
self.start_point = QtCore.QPoint()
self.end_point = QtCore.QPoint()
def mousePressEvent(self, event):
self.start_point = event.pos()
self.end_point = event.pos()
self.update()
def mouseMoveEvent(self, event):
self.end_point = event.pos()
self.update()
def mouseReleaseEvent(self, QMouseEvent):
r = QtCore.QRect(self.start_point, self.end_point).normalized()
img = ImageGrab.grab(bbox=r.getCoords())
img.save("snips/testImage.png")
self.closed.emit()
def paintEvent(self, event):
trans = QtGui.QColor(255, 255, 255)
r = QtCore.QRectF(self.start_point, self.end_point).normalized()
qp = QtGui.QPainter(self)
trans.setAlphaF(0.2)
qp.setBrush(trans)
outer = QtGui.QPainterPath()
outer.addRect(QtCore.QRectF(self.rect()))
inner = QtGui.QPainterPath()
inner.addRect(r)
r_path = outer - inner
qp.drawPath(r_path)
qp.setPen(
QtGui.QPen(QtGui.QColor(self.outsideSquareColor), self.squareThickness)
)
trans.setAlphaF(0)
qp.setBrush(trans)
qp.drawRect(r)
I want to draw polyline with mouse event. But I can't set endpoints by clicking, or choose pen type. I want to draw linear lines, but when i write this code it only shows dots instead of drawing a line. Here is my code:
import sys
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtGui import QPainter, QBrush, QColor, QPen, QPainterPath
from PyQt5.QtWidgets import QLabel, QGraphicsScene, QGraphicsView
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.begin = QtCore.QPoint()
self.end = QtCore.QPoint()
self.beginList = []
self.endList = []
self.initUI()
def initUI(self):
self.setGeometry(200, 200, 1000, 500)
self.label = QLabel(self)
self.label.resize(500, 40)
self.show()
def paintEvent(self, event):
qp = QPainter(self)
for i,j in zip(self.beginList, self.endList):
qp.drawLines(QtCore.QLineF(i,j))
def mouseMoveEvent(self, event):
self.begin = event.pos()
self.end = event.pos()
self.beginList.append(self.begin)
self.endList.append(self.end)
self.label.setText('Coordinates: ( %d : %d )' % (event.x(), event.y()))
self.update()
def mouseReleaseEvent(self, event):
self.begin = event.pos()
self.end = event.pos()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = MyWidget()
window.resize(800,600)
sys.exit(app.exec_())
If the OP code is analyzed, the starting point and the end point coincide, so when drawing a line between 2 points of the same location, only one point will be drawn. The logic is to join the point obtained in the i-th step with the (i+1)-th point.
To do the above the simplest thing is to use a QPainterPath:
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.paths = []
def initUI(self):
self.setGeometry(200, 200, 1000, 500)
self.label = QtWidgets.QLabel(self)
self.show()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
for path in self.paths:
qp.drawPath(path)
def mousePressEvent(self, event):
path = QtGui.QPainterPath()
path.moveTo(event.pos())
self.paths.append(path)
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
self.paths[-1].lineTo(event.pos())
self.label.setText('Coordinates: ( %d : %d )' % (event.x(), event.y()))
self.label.adjustSize()
self.update()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.paths[-1].lineTo(event.pos())
self.update()
super().mouseReleaseEvent(event)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MyWidget()
window.resize(800, 600)
sys.exit(app.exec_())
I have a PicButton class based on QAbtractButton that has the normal, hover and pressed. However, when clicked the button only changed to the pixmap_pressed briefly and then switch back to standard.
How can I make it behaves like a toggle button so that the pressed pixmap will stay after pressed?
import numpy as np
import time, sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QMainWindow
class PicButton(QAbstractButton):
def __init__(self, pixmap, pixmap_hover, pixmap_pressed, parent=None):
super(PicButton, self).__init__(parent)
self.pixmap = pixmap
self.pixmap_hover = pixmap_hover
self.pixmap_pressed = pixmap_pressed
self.pressed.connect(self.update)
# self.released.connect(self.update)
def paintEvent(self, event):
pix = self.pixmap_hover if self.underMouse() else self.pixmap
if self.isDown():
pix = self.pixmap_pressed
painter = QPainter(self)
painter.drawPixmap(event.rect(), pix)
def enterEvent(self, event):
self.update()
def leaveEvent(self, event):
self.update()
def sizeHint(self):
return self.pixmap.size()
class App(QMainWindow):
def __init__(self):
super().__init__()
self.left = 0
self.top = 0
self.width = 800
self.height = 800
self.initUI()
def initUI(self):
self.setGeometry(self.left, self.top, self.width, self.height)
self.recBtn = PicButton(QPixmap('./img/playrecstop/rec_512.png'),QPixmap('./img/playrecstop/recHL_512.png'),\
QPixmap('./img/playrecstop/recActive_512.png'))
self.recBtn.setText("rec")
self.recBtn.clicked.connect(self.controlButtons)
self.stopBtn = PicButton(QPixmap('./img/playrecstop/stop_512.png'), QPixmap('./img/playrecstop/stopHL_512.png'),\
QPixmap('./img/playrecstop/stopActive_512.png'))
self.stopBtn.setText("stop")
self.stopBtn.clicked.connect(self.controlButtons)
self.leftLayout = QHBoxLayout()
self.rightLayout = QHBoxLayout()
self.rightLayout.addWidget(self.recBtn)
self.rightLayout.addWidget(self.stopBtn)
self.mainLayout = QHBoxLayout()
self.mainLayout.addLayout(self.leftLayout)
self.mainLayout.addLayout(self.rightLayout)
self.setCentralWidget(QWidget(self))
self.centralWidget().setLayout(self.mainLayout)
self.show()
def controlButtons(self):
sender = self.sender()
if (sender.text() == 'stop'):
print ("Stop")
elif (sender.text() == 'rec'):
print ("REC...")
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
Thanks
QAbstractButton has a checkable property that allows you to implement the logic you want:
class PicButton(QAbstractButton):
def __init__(self, pixmap, pixmap_hover, pixmap_pressed, parent=None):
super(PicButton, self).__init__(parent)
self.pixmap = pixmap
self.pixmap_hover = pixmap_hover
self.pixmap_pressed = pixmap_pressed
self.setCheckable(True)
def paintEvent(self, event):
pix = self.pixmap_hover if self.underMouse() else self.pixmap
if self.isChecked():
pix = self.pixmap_pressed
painter = QPainter(self)
painter.drawPixmap(event.rect(), pix)
def enterEvent(self, event):
self.update()
def leaveEvent(self, event):
self.update()
def sizeHint(self):
return self.pixmap.size()
I would like to draw some lines with QPainter, then move the start (x1,y1) coordinates of lines to centerpoint and some other lines which want to offset from center. Those lines should behave programmatically based on value of ellipse or other values. I have tried myself different ways to come around it but does not work.
To QRect can utilize codes such as
moveCenter, moveTopLeft, etc...
But for Qline there are not such methods. According to PyQt doc, a line can be drawn by this:
QLine(int x1, int y1, int x2, int y2)
QLine(const QPoint &p1, const QPoint &p2)
Maybe this line should be used in order to offset it. But no knowlegde to do it.
translated(const QPoint &offset)
On another hand would like to draw some texts and offset them in similar way as Qline.
Look at figures below to see what is that I exactly want to do?
Visualisation
What I have achieved so far.
What I want to achieve.
The code:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Foo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Foo, self).__init__(parent)
self.setGeometry(QtCore.QRect(200, 100, 700, 600))
self.paint = Paint()
self.sizeHint()
self.lay = QtWidgets.QVBoxLayout()
self.lay.addWidget(self.paint)
self.setLayout(self.lay)
class Paint(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Paint, self).__init__(parent)
self.setBackgroundRole(QtGui.QPalette.Base)
self.setAutoFillBackground(True)
xl = self.rect().center().x()
yl = self.rect().center().y()
self.e = QtCore.QRect(QtCore.QPoint(), QtCore.QSize(250, 250))
self.l = QtCore.QLine(QtCore.QPoint(xl, yl) , QtCore.QPoint(self.width(), self.height()/2))
def paintEvent(self, event):
pen = QtGui.QPen()
brush = QtGui.QBrush( QtCore.Qt.darkCyan, QtCore.Qt.Dense7Pattern)
painter = QtGui.QPainter(self)
painter.setBrush(brush)
painter.setPen(pen)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
self.e.moveCenter(self.rect().center())
painter.drawEllipse(self.e)
painter.drawLine(self.l)
# painter.drawText('D')
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Foo()
w.show()
sys.exit(app.exec_())
The Update issue:
I have updated the code, I manage to draw what I want. But having another challenge. You could see on figure below.
When code runs and displays
When Widget minimizes or maximizes, some lines disappears?
The Update code:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Foo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Foo, self).__init__(parent)
self.setGeometry(QtCore.QRect(200, 100, 700, 600))
self.paint = Paint()
self.sizeHint()
self.lay = QtWidgets.QVBoxLayout()
self.lay.addWidget(self.paint)
self.setLayout(self.lay)
class Paint(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Paint, self).__init__(parent)
self.setBackgroundRole(QtGui.QPalette.Base)
self.setAutoFillBackground(True)
self.e = QtCore.QRect(QtCore.QPoint(), QtCore.QSize(250, 250))
self.l1 = QtCore.QLine(QtCore.QPoint(), QtCore.QPoint(200, 0))
self.l2 = QtCore.QLine(QtCore.QPoint(), QtCore.QPoint(0, -200))
self.vl = QtCore.QLine(QtCore.QPoint(), QtCore.QPoint(0, -125))
delta = QtCore.QPoint(20, 0)
self.hl = QtCore.QLine(-delta, self.e.topRight() + delta)
self.al = QtCore.QLine(QtCore.QPoint(), QtCore.QPoint(-20, -20))
self.a2 = QtCore.QLine(QtCore.QPoint(), QtCore.QPoint(-20, 20))
self.a3 = QtCore.QLine(QtCore.QPoint(), QtCore.QPoint(20, 20))
self.a4 = QtCore.QLine(QtCore.QPoint(), QtCore.QPoint(-20, 20))
self._factor = 1.0
def paintEvent(self, event):
pen = QtGui.QPen()
brush = QtGui.QBrush( QtCore.Qt.darkCyan, QtCore.Qt.Dense7Pattern)
painter = QtGui.QPainter(self)
painter.setBrush(brush)
painter.setPen(pen)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
self.e.moveCenter(self.rect().center())
self.l1.translate(self.rect().center() - self.l1.p1())
self.l2.translate(self.rect().center() - self.l2.p1())
self.al.translate(self.l1.p2())
self.a2.translate(self.l1.p2())
painter.translate(self.rect().center())
painter.scale(self._factor, self._factor)
painter.translate(-self.rect().center())
painter.drawEllipse(self.e)
painter.drawLine(self.l1)
painter.drawLine(self.l2)
fnt = painter.font()
fnt.setPointSize(20)
painter.setFont(fnt)
h = QtGui.QFontMetrics(painter.font()).height()
p = QtCore.QPoint(self.rect().center().x(), self.e.top() - 3*h)
u = QtCore.QPoint(self.e.right() + 3*h, self.rect().center().y())
painter.drawText(p, "y")
painter.drawText(u, "x")
r = QtCore.QRect(QtCore.QPoint(self.e.x(), self.e.bottom()), QtCore.QSize(self.e.width(), h))
painter.drawText(r, QtCore.Qt.AlignCenter, "D = 350mm")
offset = QtCore.QPoint(0, 1.5*h)
self.vl.translate(self.e.bottomLeft() - self.vl.p1() + offset)
painter.drawLine(self.vl)
self.vl.translate(self.e.width(), 0)
painter.drawLine(self.vl)
self.hl.translate(self.e.bottomLeft() + offset - QtCore.QPoint(0, 0.4*h))
self.a3.translate(self.l2.p2())
self.a4.translate(self.l2.p2())
painter.drawLine(self.hl)
painter.drawLine(self.al)
painter.drawLine(self.a2)
painter.drawLine(self.a3)
painter.drawLine(self.a4)
def wheelEvent(self, event):
self._factor *= 1.01**(event.angleDelta().y()/15.0)
self.update()
super(Paint, self).wheelEvent(event)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Foo()
w.show()
sys.exit(app.exec_())
I do not know why it behaves like this? any comment or help I appreciate much.
Qt does not know what size the widget will have at the beginning so self.rect() will have any size, for example in my test self.rect() in __init__ returns PyQt5.QtCore.QRect(0, 0, 640, 480) but in paintEvent() it returns PyQt5.QtCore.QRect(0, 0, 678, 578) so that's why the calculation is incorrect.
the solution is to move the line with translate()(do not use translated() because this creates a new QLine and that's what I do not want)
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Foo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Foo, self).__init__(parent)
self.setGeometry(QtCore.QRect(200, 100, 700, 600))
self.paint = Paint()
self.sizeHint()
self.lay = QtWidgets.QVBoxLayout()
self.lay.addWidget(self.paint)
self.setLayout(self.lay)
class Paint(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Paint, self).__init__(parent)
self.setBackgroundRole(QtGui.QPalette.Base)
self.setAutoFillBackground(True)
self.e = QtCore.QRect(QtCore.QPoint(), QtCore.QSize(250, 250))
self.l1 = QtCore.QLine(QtCore.QPoint(), QtCore.QPoint(200, 0))
self.l2 = QtCore.QLine(QtCore.QPoint(), QtCore.QPoint(0, -200))
def paintEvent(self, event):
pen = QtGui.QPen()
brush = QtGui.QBrush( QtCore.Qt.darkCyan, QtCore.Qt.Dense7Pattern)
painter = QtGui.QPainter(self)
painter.setBrush(brush)
painter.setPen(pen)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
self.e.moveCenter(self.rect().center())
self.l1.translate(self.rect().center() - self.l1.p1())
self.l2.translate(self.rect().center() - self.l2.p1())
painter.drawEllipse(self.e)
painter.drawLine(self.l1)
painter.drawLine(self.l2)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Foo()
w.show()
sys.exit(app.exec_())
Update:
class Paint(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Paint, self).__init__(parent)
self.setBackgroundRole(QtGui.QPalette.Base)
self.setAutoFillBackground(True)
self.e = QtCore.QRect(QtCore.QPoint(), QtCore.QSize(250, 250))
self.l1 = QtCore.QLine(QtCore.QPoint(), QtCore.QPoint(200, 0))
self.l2 = QtCore.QLine(QtCore.QPoint(), QtCore.QPoint(0, -200))
self.vl = QtCore.QLine(QtCore.QPoint(), QtCore.QPoint(0, -125))
delta = QtCore.QPoint(20, 0)
self.hl = QtCore.QLine(-delta, self.e.topRight() + delta)
def paintEvent(self, event):
pen = QtGui.QPen()
brush = QtGui.QBrush( QtCore.Qt.darkCyan, QtCore.Qt.Dense7Pattern)
painter = QtGui.QPainter(self)
painter.setBrush(brush)
painter.setPen(pen)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
self.e.moveCenter(self.rect().center())
self.l1.translate(self.rect().center() - self.l1.p1())
self.l2.translate(self.rect().center() - self.l2.p1())
painter.drawEllipse(self.e)
painter.drawLine(self.l1)
painter.drawLine(self.l2)
fnt = painter.font()
fnt.setPointSize(25)
painter.setFont(fnt)
h = QtGui.QFontMetrics(painter.font()).height()
p = QtCore.QPoint(self.rect().center().x(), self.e.bottom() + 0.8*h)
r = QtCore.QRect(QtCore.QPoint(self.e.x(), self.e.bottom()), QtCore.QSize(self.e.width(), h))
painter.drawText(r, QtCore.Qt.AlignCenter, "D")
offset = QtCore.QPoint(0, 1.5*h)
self.vl.translate(self.e.bottomLeft() - self.vl.p1() + offset)
painter.drawLine(self.vl)
self.vl.translate(self.e.width(), 0)
painter.drawLine(self.vl)
self.hl.translate(self.e.bottomLeft() + offset - QtCore.QPoint(0, 20))
painter.drawLine(self.hl)
I have written a code to draw a rectangle on an image in a QGraphicsView but if I resize the image, for example full screen, the position of the rectangle becomes misplaced. Is there any way to fix this?
I think one possible solution is to align the image every time in top left corner but I cannot give 2 arguments in setAlignment().
Here is the code:(the buttons are just for show atm)
class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.b1 = QPushButton('Next Image')
self.b2 = QPushButton('Crop')
self.b3 = QPushButton('Clear')
self.view = GraphicsView()
h_box = QHBoxLayout()
v_box = QVBoxLayout()
v_box.addWidget(self.b1)
v_box.addWidget(self.b2)
v_box.addWidget(self.b3)
h_box.addWidget(self.view)
h_box.addLayout(v_box)
self.setLayout(h_box)
#self.resize(800, 800)
self.setWindowTitle("Super Duper Cropper")
self.show()
class GraphicsView(QGraphicsView):
def __init__(self):
super().__init__()
self.setScene(QGraphicsScene())
self.item = QGraphicsPixmapItem(QPixmap('test.jpg'))
self.scene().addItem(self.item)
def mousePressEvent(self, event):
self.xi = event.x()
self.yi = event.y()
def mouseMoveEvent(self, event):
self.xf = event.x()
self.yf = event.y()
self.draw_rect()
def mouseReleaseEvent(self, event):
self.xf = event.x()
self.yf = event.y()
self.draw_rect()
def draw_rect(self):
self.scene().removeItem(self.item)
self.scene().addItem(self.item)
self.scene().addRect(self.xi, self.yi, self.xf-self.xi, self.yf-self.yi, pen=QPen(QColor(51, 153, 255), 2,
Qt.SolidLine), brush=QBrush(QColor(0, 255, 0, 40)))
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MyWidget()
window.show()
app.aboutToQuit.connect(app.deleteLater)
sys.exit(app.exec_())
You have the following errors:
The coordinates of the scene are different from the coordinates of the view, so you must use the mapToScene() method if you want to establish the right position of the rectangle.
Why do you add and remove the items? the best thing is to reuse
You want the position of the rectangle to be relative to the QGraphicsPixmapItem, so the rectangle has to be a child of the QGraphicsPixmapItem.
Using the above we obtain the following:
class GraphicsView(QGraphicsView):
def __init__(self):
super().__init__()
self.setScene(QGraphicsScene())
self.item = QGraphicsPixmapItem(QPixmap('test.jpg'))
self.scene().addItem(self.item)
self.rect_item = QGraphicsRectItem(QRectF(), self.item)
self.rect_item.setPen(QPen(QColor(51, 153, 255), 2, Qt.SolidLine))
self.rect_item.setBrush(QBrush(QColor(0, 255, 0, 40)))
def mousePressEvent(self, event):
self.pi = self.mapToScene(event.pos())
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
pf = self.mapToScene(event.pos())
if (self.pi - pf).manhattanLength() > QApplication.startDragDistance():
self.pf = pf
self.draw_rect()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
pf = self.mapToScene(event.pos())
if (self.pi - pf).manhattanLength() > QApplication.startDragDistance():
self.pf = pf
self.draw_rect()
super().mouseReleaseEvent(event)
def draw_rect(self):
r = QRectF(self.pi, self.pf).normalized()
r = self.rect_item.mapFromScene(r).boundingRect()
self.rect_item.setRect(r)