This question already has answers here:
How to draw a line from mouse to a point in PyQt5?
(3 answers)
PyQt5 triggering a paintEvent() with keyPressEvent()
(1 answer)
PyQt5: painting using events
(2 answers)
Closed 2 years ago.
I'm learning PyQt5, and I'd like my GUI to have kind of a drawing surface, on which the user can draw anything, so the app can then get this drawing as an image (the goal is to perform classification on that drawing).
How can I do that ? All I found is Qpainter, which allow me to draw when coding the app, but it won't let the user dynamically draw when using the app.
QPainter is a surface for drawing. Here is an example of drawing a rectangular flag:
import sys
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 200, 200)
self.setWindowTitle('Drawing')
self.show()
def paintEvent(self, event):
qp = QPainter()
qp.begin(self)
self.drawFlag(qp)
qp.end()
def drawFlag(self,qp):
qp.setBrush(QColor(255, 0, 0))
qp.drawRect(30, 30, 120, 30)
qp.setBrush(QColor(0, 255, 0))
qp.drawRect(30, 60, 120, 30)
qp.setBrush(QColor(0, 0, 255))
qp.drawRect(30, 90, 120, 30)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
If you want to draw a line use:
qp.drawLine(x1, y1, x2, y2)
Mouse position:
def mouseMoveEvent(self, event):
print(event.x, event.y)
Related
I'm trying to make an application in python, with the visible window a pyglet one. The problem is, I need graphics capability AND interactions with HTML pages at the same time. I'm going to use a PyQt6 to communicate with the HTML. So the question is, how do I get a PyQT6 window to render INSIDE a Pyglet window?
My current code:
import pyglet
from pyglet.gl import *
import sys
from PyQt6.QtWidgets import QApplication, QWidget
app = QApplication(sys.argv)
w = QWidget()
w.resize(250, 200)
w.move(300, 300)
w.setWindowTitle('Simple')
w.show()
window = pyglet.window.Window(800, 600, "Radium")
#window.event
def on_draw():
window.clear()
# Render window with OpenGL
# ...
pyglet.app.run()
sys.exit(app.exec())
I am not sure if you can render PyQT inside a Pyglet Window. It has it's own rendering and drawing system that can't be accessed at a low level required to integrate it into a pyglet Window.
You can however do the reverse, OpenGL (and by extension, use pyglet) in QT. However this also means you need to be experienced in both to really make it work well.
I have made a runnable example using PyQt6:
import sys
import pyglet
#from PyQt5 import QtGui
#from PyQt5 import QtCore, QtWidgets
#from PyQt5.QtOpenGL import QGLWidget as OpenGLWidget
from PyQt6 import QtGui
from PyQt6 import QtCore, QtWidgets
from PyQt6.QtOpenGLWidgets import QOpenGLWidget as OpenGLWidget
from pyglet.gl import glClear, GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT
import random
"""An example showing how to use pyglet in QT, utilizing the OGLWidget.
Since this relies on the QT Window, any events called on Pyglet Window
will NOT be called.
This includes mouse, keyboard, tablet, and anything else relating to the Window
itself. These must be handled by QT itself.
This just allows user to create and use pyglet related things such as sprites, shapes,
batches, clock scheduling, sound, etc.
"""
class MainWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__()
self.setWindowTitle("Pyglet and QT Example")
self.shapes = []
width, height = 640, 480
self.opengl = PygletWidget(width, height)
self.sprite_button = QtWidgets.QPushButton('Create Rectangle', self)
self.sprite_button.clicked.connect(self.create_sprite_click)
self.clear_sprite_button = QtWidgets.QPushButton('Clear Shapes', self)
self.clear_sprite_button.clicked.connect(self.clear_sprite_click)
mainLayout = QtWidgets.QVBoxLayout()
mainLayout.addWidget(self.opengl)
mainLayout.addWidget(self.sprite_button)
mainLayout.addWidget(self.clear_sprite_button)
self.setLayout(mainLayout)
def create_sprite_click(self):
gl_width, gl_height = self.opengl.size().width(), self.opengl.size().height()
width = random.randint(50, 100)
height = random.randint(50, 100)
x = random.randint(0, gl_width-width)
y = random.randint(0, gl_height-height)
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
shape = pyglet.shapes.Rectangle(x, y, width, height, color=color, batch=self.opengl.batch)
shape.opacity = random.randint(100, 255)
self.shapes.append(shape)
def clear_sprite_click(self):
for shape in self.shapes:
shape.delete()
self.shapes.clear()
class PygletWidget(OpenGLWidget):
def __init__(self, width, height, parent=None):
super().__init__(parent)
self.setMinimumSize(width, height)
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self._pyglet_update)
self.timer.setInterval(0)
self.timer.start()
def _pyglet_update(self):
# Tick the pyglet clock, so scheduled events can work.
pyglet.clock.tick()
# Force widget to update, otherwise paintGL will not be called.
self.update() # self.updateGL() for pyqt5
def paintGL(self):
"""Pyglet equivalent of on_draw event for window"""
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
self.batch.draw()
def initializeGL(self):
"""Call anything that needs a context to be created."""
self.batch = pyglet.graphics.Batch()
size = self.size()
w, h = size.width(), size.height()
self.projection = pyglet.window.Projection2D()
self.projection.set(w, h, w, h)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()
ui = MainWidget(window)
ui.show() # Calls initializeGL. Do not do any GL stuff before this is called.
app.exec() # exec_ in 5.
If the top module is a pg.GraphicsScene, it can work properly, when a rect is added to the scene, it can be selected by the itemAt() and removed from the scene. But when the top module is a pg.GraphicsLayoutWidget, with a PlotItem is added by addPlot, then the rect is added to the PlotItem, now the itemAt() returns a QGraphicsRectItem seems to be some of background things but not point to the added rect, if perform the deletion, this QGraphicsRectItem is to be deleted first, then the added rect can be selected, the reason why and what is the QGraphicsRectItem background at the top selection level? Any help will be appreciated very much!
import sys
import pyqtgraph as pg
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtWidgets import QGraphicsRectItem
pg.setConfigOption('background', 'k')
pg.setConfigOption('foreground', 'w')
rect1 = QGraphicsRectItem(120, 120, 60, 60)
rect1.setPen(pg.mkPen((51, 51, 153), width=2))
rect1.setBrush(pg.mkBrush(51, 51, 153, 50))
rect2 = QGraphicsRectItem(200, 200, 80, 80)
rect2.setPen(pg.mkPen((51, 51, 153), width=2))
rect2.setBrush(pg.mkBrush(51, 51, 153, 50))
rect1.setFlag(rect1.ItemIsFocusable)
rect1.setFlag(rect1.ItemIsSelectable)
rect2.setFlag(rect2.ItemIsFocusable)
rect2.setFlag(rect2.ItemIsSelectable)
rect1.setZValue(1000)
rect2.setZValue(1000)
class win(pg.GraphicsLayoutWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('pyqtgraph GraphicsLayoutWidget')
self.plt1 = self.addPlot(title='pyqtgraph PlotItem')
self.plt1.setEnabled(False)
self.plt1.addItem(rect1)
self.plt1.addItem(rect2)
self.plt1.disableAutoRange()
def mousePressEvent(self, event):
#item = self.scene().itemAt(event.pos(), QtGui.QTransform())
item = self.itemAt(event.pos())
print(item)
#self.plt1.removeItem(item)
self.scene().removeItem(item)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = win()
w.show()
sys.exit(app.exec_())
In the end, You want to remove rectangle, but nothing else.
In Your current implementation, You are removing everything You click on and You would have to implement some logic to detect "is this the item I want to remove" ?
Instead You can create new Class RemovableRect, which indicates that it can be removed from the Scene onMousePressEvent.
And You can follow same logic for other items with same behavior.
Here is modified code using such a removable rectangle:
import sys
import pyqtgraph as pg
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QGraphicsRectItem
pg.setConfigOption('background', 'k')
pg.setConfigOption('foreground', 'w')
class RemovableRect(QGraphicsRectItem):
def mousePressEvent(self, event: 'QGraphicsSceneMouseEvent') -> None:
"""Remove me from the Scene"""
self.scene().removeItem(self)
rect1 = RemovableRect(120, 120, 60, 60)
rect1.setPen(pg.mkPen((51, 51, 153), width=2))
rect1.setBrush(pg.mkBrush(51, 51, 153, 50))
rect2 = RemovableRect(200, 200, 80, 80)
rect2.setPen(pg.mkPen((51, 51, 153), width=2))
rect2.setBrush(pg.mkBrush(51, 51, 153, 50))
rect1.setFlag(rect1.ItemIsFocusable)
rect1.setFlag(rect1.ItemIsSelectable)
rect2.setFlag(rect2.ItemIsFocusable)
rect2.setFlag(rect2.ItemIsSelectable)
class win(pg.GraphicsLayoutWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('pyqtgraph GraphicsLayoutWidget')
self.plt1 = self.addPlot(title='pyqtgraph PlotItem')
self.plt1.addItem(rect1)
self.plt1.addItem(rect2)
self.plt1.disableAutoRange()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = win()
w.show()
sys.exit(app.exec_())
I removed few lines from Your code, especially self.plt1.setEnabled(False) which would disable event propagation to Your rectangles.
Am using pyqt5 and Python in my program which is drawing shapes.
My problem is after am drawing my shapes when I zoom in they look in a really bad way like the image below.
I want to get sharpness like in the SVG image.
So how to get these results in pyqt5?
This what I get:
And this what I want:
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtCore import Qt
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 900, 900)
self.setWindowTitle('Pen styles')
self.show()
def paintEvent(self, e):
qp = QPainter()
qp.begin(self)
self.drawLines(qp)
qp.end()
def drawLines(self, qp):
pen = QPen(Qt.black, 2, Qt.SolidLine)
qp.setPen(pen)
qp.drawLine(20, 40, 800, 800)
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
By default QPainter only uses the TextAntialiasing render hint, so shapes are always aliased.
In order to draw "smooth" lines, you need to activate the Antialiasing hint.
Note that you don't need to call begin() if you provide the paint device in the QPainter constructor, and since it's a local variable in the scope of paintEvent() you don't need to call end() either, as it will be called anyway when the painter is destroyed by the garbage collector when the function returns.
def paintEvent(self, e):
qp = QPainter(self)
qp.setRenderHints(qp.Antialiasing)
self.drawLines(qp)
I have created a square at random position in canvas, but I don't know how do I move it to somewhere else in canvas by dragging it to desired position, please suggest some edits or a new method to achieve the proposed task, I am learning while doing so.
P.S. Attached a screenshot of the output window.
import sys
from random import randint
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow,QPushButton,QWidget
from PyQt5 import QtGui
from PyQt5.QtCore import QRect,Qt
from PyQt5.QtGui import QPainter,QBrush, QPen
from PyQt5 import QtCore
class Window(QMainWindow):
def __init__(self):
super(Window,self).__init__()
title="TeeSquare"
left=500
top=200
width=500
height=400
iconName="square.jpg"
self.setWindowTitle(title)
self.setWindowIcon(QtGui.QIcon(iconName))
self.setGeometry(left, top, width, height)
self.should_paint_Rect = False
self.windowcomponents()
self.initUI()
self.show()
def initUI(self):
if self.should_paint_Rect:
self.label=QtWidgets.QLabel(self)
self.label.setText("circle")
def windowcomponents(self):
button=QPushButton("Add", self)
button.setGeometry(QRect(0, 0, 50, 28))
button.setIcon(QtGui.QIcon("Add.png"))
button.setToolTip("Create Square")
button.clicked.connect(self.paintRect)
def paintEvent(self, event):
super().paintEvent(event)
if self.should_paint_Rect:
painter = QtGui.QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QPen(Qt.black, 5, Qt.SolidLine))
painter.drawRect(randint(0,500), randint(0,500), 100, 100)
self.initUI()
self.label.move(60,100)
def paintRect(self, painter):
self.should_paint_Rect = True
self.update()
app = QApplication(sys.argv)
Rect=Window()
Rect.show()
sys.exit(app.exec_())
The logic of creating a dynamic element is to indicate a set of specific characteristics that by modifying these, the element is modified.
In this case you could use the center of the square, the dimensions of the square, etc. and that data must be implemented through a data structure that can be created from scratch for example by creating a class that has the information of the rectangle, but in Qt it is not necessary to create that element since it already exists and is QRect.
Now that that element has been identified, you can create a QRect whose top-left is random when the button is pressed, and use that QRect to paint it.
For dragging the procedure is:
Get the mouse click position.
Verify that the click is inside the rectangle.
Calculate the position relative to the rectangle.
When moving the mouse, the position of the rectangle must be updated based on the position of the mouse press.
Considering all of the above, the solution is:
import random
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.rect = QtCore.QRect()
self.drag_position = QtCore.QPoint()
button = QtWidgets.QPushButton("Add", self)
button.clicked.connect(self.on_clicked)
self.resize(640, 480)
#QtCore.pyqtSlot()
def on_clicked(self):
if self.rect.isNull():
self.rect = QtCore.QRect(
QtCore.QPoint(*random.sample(range(200), 2)), QtCore.QSize(100, 100)
)
self.update()
def paintEvent(self, event):
super().paintEvent(event)
if not self.rect.isNull():
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(QtGui.QPen(QtCore.Qt.black, 5, QtCore.Qt.SolidLine))
painter.drawRect(self.rect)
def mousePressEvent(self, event):
if self.rect.contains(event.pos()):
self.drag_position = event.pos() - self.rect.topLeft()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if not self.drag_position.isNull():
self.rect.moveTopLeft(event.pos() - self.drag_position)
self.update()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.drag_position = QtCore.QPoint()
super().mouseReleaseEvent(event)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
Rect = Window()
Rect.show()
sys.exit(app.exec_())
I am developing a GUI based on PyQt5, and I am wondering if I can get the handles of individual elements that have been created using the QPainter class.
I mean, supose that we have three rectangles that have been painted using that class with the following code:
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor, QBrush
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 350, 100)
self.setWindowTitle('Colours')
self.show()
def paintEvent(self, e):
qp = QPainter()
qp.begin(self)
self.drawRectangles(qp)
qp.end()
def drawRectangles(self, qp):
col = QColor(0, 0, 0)
col.setNamedColor('#d4d4d4')
qp.setPen(col)
qp.setBrush(QColor(200, 0, 0))
qp.drawRect(10, 15, 90, 60)
qp.setBrush(QColor(255, 80, 0, 160))
qp.drawRect(130, 15, 90, 60)
qp.setBrush(QColor(25, 0, 90, 200))
qp.drawRect(250, 15, 90, 60)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
I want to retrieve a kind of handle of each rectangle, for instance, handle_1, handle_2 and handle_3, in order to modify the properties of one of them in another thread without plotting the rest of them.
handle_3.setColor(...)
If this is not possible, I was wondering if I could create a kind of transparent containter with Qt (which effectively has a handle to modify the stylesheet) and put a QLabel inside it. If so, what container would be the best choice?
As #ekumoro says, the paint-event does not create new elements, so updating one of the rectangles without re-painting the rest of them is impossible.
The only way to achieve this is using containers. Attending to the second question, I found that the best one is the QFrame.