Draw line and shapes with more sharpness - python

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)

Related

How create a square at any position of canvas that can be moved at any position on the canvas

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_())

Mechanics behind the synchronisation of QPainters

Context
I have a custom Widget which is supposed to make an animation of dots moving in order to make a kind of loading widget. To acheive that goal, I started using QPainter and QVariantAnimation objects, which seemed like a decent tools to do the job. The problem is that I think that the QPainters I initialise when drawing come in conflict with each other.
Technique
To acheive that, I initialize multiple QVariantAnimation, which signal .valueChanged() I connect to a function update(), which is supposed to launch the painEvent(), such as written in the docs
A paint event is a request to repaint all or part of a widget. It can happen for one of the following reasons:repaint() or update() was invoked,
the widget was obscured and has now been uncovered, or
many other reasons.
Since I start different animation at different times, I suppose that the update() is called many times, thus interfering with another QPainter already working. But, as I read in the docs,
When update() is called several times or the window system sends several paint events, Qt merges these events into one event with a larger region.
But it specifies nothing id the QPainter has the same region, which is why I suposse it crashes. It logs messages such as:
QBackingStore::endPaint() called with active painter on backingstore paint device
Minimal Working Example
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QDialog, QPushButton
from PyQt5.QtCore import Qt, pyqtSlot, QVariantAnimation, QVariant, QTimer
from PyQt5.QtGui import QColor, QPainter, QBrush
import time
class Dialog(QDialog):
def __init__(self, *args, **kwargs):
QDialog.__init__(self, *args, **kwargs)
self.resize(500, 500)
self.setLayout(QVBoxLayout())
self.button = QPushButton()
self.layout().addWidget(self.button)
self.paintWidget = PaintWidget()
self.layout().addWidget(self.paintWidget)
self.button.clicked.connect(self.paintWidget.startPainting)
self.button.clicked.connect(self.reverse)
def reverse(self):
if self.paintWidget.isMoving:
self.paintWidget.stopPainting()
class PaintWidget(QWidget):
def __init__(self):
super(PaintWidget, self).__init__()
self.dotRadius = 10
self.dotColor = QColor(255, 100, 100)
self.numberOfDots = 3
self.isMoving = False
self.animation = []
self.createAnimation()
self.dotPosition = [[0, 0], [0, 0], [0, 0]]
def startPainting(self):
for i in range(self.numberOfDots):
self.animation[i].start()
time.sleep(200)
self.isActive = True
def createAnimation(self):
for i in range(self.numberOfDots):
self.animation.append(QVariantAnimation(self, startValue=0, endValue=500, duration=3000))
self.animation[i].valueChanged.connect(self.updatePosition)
#pyqtSlot(QVariant)
def updatePosition(self, position):
self.dotPosition = [position, 0]
self.update()
def paintEvent(self, event):
painter = QPainter(self)
painter.fillRect(self.rect(), Qt.transparent)
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setPen(Qt.NoPen)
for i in range(self.numberOfDots):
painter.save()
painter.translate(0, 0)
position = (self.dotPosition[i][0], self.dotPosition[i][1])
color = self.dotColor
painter.setBrush(QBrush(color, Qt.SolidPattern))
painter.drawEllipse(position[0], position[1], self.dotRadius, self.dotRadius)
painter.restore()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
dial = Dialog()
dial.show()
sys.exit(app.exec_())
Results
I know that for now this code wouldn't work because I can't retreive which dot's animation got updated, but I believe the major problem here is the interference between the painters. Thus, could anyone tell me why this is hapenning and point me in a potential solution? Also, For knowing the dot that got updated and chose the good position, I'm really unsure of how to do this as well.
Your code has the following errors:
Never use time.sleep() in the main GUI thread since it blocks the event loop generating the freezing of the application.
the variable dotPosition that must store all the positions you are replacing it with only one position in the updatePosition method.
You should use QPoint if you are going to store a position instead of a list, use a list is not bad but using QPoint makes your code more readable.
Do not use painter.save() and painter.restore() unnecessarily, neither painter.translate().
Considering the above, the solution is as follows:
from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets
class Dialog(QtWidgets.QDialog):
def __init__(self, *args, **kwargs):
super(Dialog, self).__init__(*args, **kwargs)
self.resize(500, 500)
self.button = QtWidgets.QPushButton()
self.paintWidget = PaintWidget()
self.button.clicked.connect(self.paintWidget.startPainting)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.button)
lay.addWidget(self.paintWidget)
class PaintWidget(QtWidgets.QWidget):
def __init__(self):
super(PaintWidget, self).__init__()
self.dotRadius = 10
self.dotColor = QtGui.QColor(255, 100, 100)
self.animations = []
self.dotPosition = [
QtCore.QPoint(0, 0),
QtCore.QPoint(0, 0),
QtCore.QPoint(0, 0),
]
self.createAnimation()
def startPainting(self):
for i, animation in enumerate(self.animations):
QtCore.QTimer.singleShot(i * 200, animation.start)
def createAnimation(self):
for i, _ in enumerate(self.dotPosition):
wrapper = partial(self.updatePosition, i)
animation = QtCore.QVariantAnimation(
self,
startValue=0,
endValue=500,
duration=3000,
valueChanged=wrapper,
)
self.animations.append(animation)
#QtCore.pyqtSlot(int, QtCore.QVariant)
def updatePosition(self, i, position):
self.dotPosition[i] = QtCore.QPoint(position, 0)
self.update()
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.fillRect(self.rect(), QtCore.Qt.transparent)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(
QtGui.QBrush(self.dotColor, QtCore.Qt.SolidPattern)
)
for position in self.dotPosition:
painter.drawEllipse(position, self.dotRadius, self.dotRadius)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
dial = Dialog()
dial.show()
sys.exit(app.exec_())

Drawing points on QPixmap on QWidget (pyqt5)

I have a QWidget with a QLayout on which there is a QLabel.
I set a QPixmap on the label. Wherever the user clicks on the image, I want to draw a point. I defined mouseReleaseEvent (which works) and paintEvent (but no points are drawn). I've read all similar questions and none of the solutions worked for me. Any help? My relevant code:
class ImageScroller(QtWidgets.QWidget):
def __init__(self, img):
QtWidgets.QWidget.__init__(self)
main_layout = QtWidgets.QVBoxLayout()
self._image_label = QtWidgets.QLabel()
self._set_image(img)
main_layout.addWidget(self._image_label)
main_layout.addStretch()
self.setLayout(main_layout)
def _set_image(self, img):
img = qimage2ndarray.array2qimage(img)
qimg = QtGui.QPixmap.fromImage(img)
self._img_pixmap = QtGui.QPixmap(qimg)
self._image_label.show()
def paintEvent(self, paint_event):
painter = QtGui.QPainter(self)
painter.begin(self)
painter.setPen(QtGui.QPen(QtCore.Qt.red))
pen = QtGui.QPen()
pen.setWidth(20)
painter.setPen(pen)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
painter.drawPoint(300,300)
painter.drawLine(100, 100, 400, 400)
for pos in self.chosen_points:
painter.drawPoint(pos)
painter.end()
def mouseReleaseEvent(self, cursor_event):
self.chosen_points.append(QtGui.QCursor().pos())
self.update()
When you use QtGui.QCursor.pos() is getting the coordinates of the cursor with respect to the screen, but when you want to paint a widget you must be in the coordinates of the widget, for it the widget has the mapToGlobal() method:
self.mapFromGlobal(QtGui.QCursor.pos())
But in this case there is another solution, you must use the event that returns mouseReleaseEvent that has the information in the pos() method:
cursor_event.pos()
Another problem is that the label you created is above the widget so you do not see the points, the easiest thing is to draw the QPixmap directly with the drawPixmap() method.
Complete code:
from PyQt5 import QtWidgets, QtGui, QtCore
class ImageScroller(QtWidgets.QWidget):
def __init__(self):
self.chosen_points = []
QtWidgets.QWidget.__init__(self)
self._image = QtGui.QPixmap("image.png")
def paintEvent(self, paint_event):
painter = QtGui.QPainter(self)
painter.drawPixmap(self.rect(), self._image)
pen = QtGui.QPen()
pen.setWidth(20)
painter.setPen(pen)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
painter.drawPoint(300, 300)
painter.drawLine(100, 100, 400, 400)
for pos in self.chosen_points:
painter.drawPoint(pos)
def mouseReleaseEvent(self, cursor_event):
self.chosen_points.append(cursor_event.pos())
# self.chosen_points.append(self.mapFromGlobal(QtGui.QCursor.pos()))
self.update()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = ImageScroller()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

pyQT equivalent to VB-6 PSet

I have a program I wrote in VB-6 to emulate an old Timex 2068 Z80 micro.
The display is a direct memory write in a specific area of memory and in VB I look for that range and them use VB-6's PSet to set the pixel(s) in question.
I have been trying to understand the example below..
What I am not understanding is how QPainter interacts and how to call drawpoints() from outside the class with x,y and pen color arguments.
Everything I have tried ends up with a QPainter is not active error...
Any assistance would be appreciated as I've spent hours trying this that and the other thing, to no avail.
Thanks
import sys, random
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('Points')
self.show()
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self)
self.drawPoints(qp)
qp.end()
def drawPoints(self, qp):
qp.setPen(QtCore.Qt.red)
size = self.size()
for i in range(1000):
x = random.randint(1, size.width()-1)
y = random.randint(1, size.height()-1)
qp.drawPoint(x, y)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

Returning handles of elements using QPainter

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.

Categories

Resources