Not Rotating Image Correctly - python

I'm trying to rotate the background of my QGraphicsView object an I am getting in trouble. The red pen describes the problem what I'm getting.
What I want:
I want to keep the arrow centered in the middle of my screen.
I want the background image to rotate, taking the center of my screen as reference
I want to define a rectangle inside my QGraphicsView. that will be defined as the region where it will delimit what will be shown. Thus, every time I rotate my screen. The region "not covered" by the image will always be outside the boundary of the rectangle.
I want define the reference point of rotation, in the middle of my screen (x=VIEW_WIDTH/2, y=VIEW_HEIGHT/2)
And here is my code:
First, I'll show the arrow class:
from PyQt5 import QtCore, QtGui, QtWidgets # importation of some libraries
# Construnction of an arrow item, it'll be used in the QGraphicsView
class ArrowItem(QtWidgets.QGraphicsPathItem): # it inherit QgraphicsPathItem, which allows to handle it
# in the QgraphicsView
def __init__(self, parent=None): # The init method
super().__init__(parent)
self._length = -1
self._angle = 0
self._points = QtCore.QPointF(), QtCore.QPointF(), QtCore.QPointF() # with three points we
# construct a triangle.
self.length = 40.0 # the basic triangle length
self.rotate(180) # the triangle was built upside down, though I've just ran the function 'rotate'
#property
def angle(self):
"""
angle of the arrow
:return:
"""
return self._angle
#angle.setter
def angle(self, angle):
self._angle = angle
#property
def length(self):
return self._length
#length.setter
def length(self, l):
self._length = l
pos_top = QtCore.QPointF(0, l * 4 / 5)
pos_left = QtCore.QPointF(-l * 3 / 5, -l / 5)
pos_right = QtCore.QPointF(
l * 3 / 5,
-l / 5,
)
path = QtGui.QPainterPath()
path.moveTo(pos_top)
path.lineTo(pos_right)
path.lineTo(pos_left)
self.setPath(path)
self._points = pos_top, pos_left, pos_right
def paint(self, painter, option, widget):
pos_top, pos_left, pos_right = self._points
left_color = QtGui.QColor("#cc0000")
right_color = QtGui.QColor("#ff0000")
bottom_color = QtGui.QColor("#661900")
path_left = QtGui.QPainterPath()
path_left.lineTo(pos_top)
path_left.lineTo(pos_left)
path_right = QtGui.QPainterPath()
path_right.lineTo(pos_top)
path_right.lineTo(pos_right)
path_bottom = QtGui.QPainterPath()
path_bottom.lineTo(pos_left)
path_bottom.lineTo(pos_right)
painter.setPen(QtGui.QColor("black"))
painter.setBrush(left_color)
painter.drawPath(path_left)
painter.setBrush(right_color)
painter.drawPath(path_right)
painter.setBrush(bottom_color)
painter.drawPath(path_bottom)
def rotate(self, value):
"""
:param value:
"""
self._angle += value
transform = QtGui.QTransform()
transform.translate(20, 100)
transform.rotate(self._angle)
self.setTransform(transform)
# Just a function to translate the arrow, it'll not be used now!
def moveTo(self, next_position, duration=100):
self._animation = QtCore.QVariantAnimation()
self._animation.setStartValue(self.pos())
self._animation.setEndValue(next_position)
self._animation.setDuration(duration)
self._animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
self._animation.valueChanged.connect(self.setPos)
Here follows the main window code:
# Just setting up the main Window
class WorkScreen(QtWidgets.QMainWindow):
# Some constant variable to keep things easy to change in the future
VIEW_HEIGHT = 600
VIEW_WIDTH = 900
FLAT_BUTTON = True
"""
Attempt to paint the work screen.
"""
def __init__(self, **kwargs):
# initializing the parent class, using a 2.7 pythonic style
super(WorkScreen, self).__init__(**kwargs)
# customizing the main window.
self.setStyleSheet("""
QMainWindow{
background: rgb(180, 180, 180)}
QPushButton{
background-color: rgb(0, 200, 0);
}
""")
# the attribute 'rotation' must be zero.
self.rotation = 0
# defining some attributes
self.pixmap = QtGui.QPixmap()
self.scene = QtWidgets.QGraphicsScene()
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
self.verticalLayout_3 = QtWidgets.QVBoxLayout()
self.pushButton_status = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_settings = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_analyse = QtWidgets.QPushButton(self.centralwidget)
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
self.view = QtWidgets.QGraphicsView(self.centralwidget)
self.verticalLayout = QtWidgets.QVBoxLayout()
self.pushButton_field = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_guide = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_mapping = QtWidgets.QPushButton(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.arrowItem = ArrowItem()
self.set_up()
self.view.setScene(self.scene)
self.view.scale(-1, 1)
self.image = QtGui.QImage("ola.png")
self.image = self.image.scaled(self.VIEW_WIDTH+300, self.VIEW_HEIGHT+300)
self.pixmap.convertFromImage(self.image)
self.scene.addPixmap(self.pixmap)
self.add_arrow()
def set_up(self):
"""
build the main window.
"""
self.pushButton_guide.setFlat(self.FLAT_BUTTON)
self.pushButton_mapping.setFlat(self.FLAT_BUTTON)
self.pushButton_field.setFlat(self.FLAT_BUTTON)
self.pushButton_status.setFlat(self.FLAT_BUTTON)
self.pushButton_settings.setFlat(self.FLAT_BUTTON)
self.pushButton_analyse.setFlat(self.FLAT_BUTTON)
self.setObjectName("MainWindow")
self.setMaximumWidth(1024)
self.setMaximumHeight(600)
self.resize(1024, 600)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout.setObjectName("horizontalLayout")
self.verticalLayout_3.setContentsMargins(-1, -1, 0, -1)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.pushButton_status.setObjectName("pushButton_status")
self.verticalLayout_3.addWidget(self.pushButton_status)
self.pushButton_settings.setObjectName("pushButton_settings")
self.verticalLayout_3.addWidget(self.pushButton_settings)
self.pushButton_analyse.setObjectName("pushButton_analyse")
self.verticalLayout_3.addWidget(self.pushButton_analyse)
self.horizontalLayout.addLayout(self.verticalLayout_3)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.view.setRenderHints(QtGui.QPainter.HighQualityAntialiasing | QtGui.QPainter.TextAntialiasing)
self.view.setCacheMode(QtWidgets.QGraphicsView.CacheBackground)
self.view.scale(-1, 1)
self.view.setFixedHeight(self.VIEW_HEIGHT)
self.view.setFixedWidth(self.VIEW_WIDTH)
self.view.setObjectName("view")
# self.view.setFixedWidth(self.VIEW_WIDTH)
# self.view.setFixedHeight(self.VIEW_HEIGHT)
self.verticalLayout_2.addWidget(self.view)
self.horizontalLayout.addLayout(self.verticalLayout_2)
self.verticalLayout.setContentsMargins(-1, -1, 0, -1)
self.verticalLayout.setObjectName("verticalLayout")
self.pushButton_field.setObjectName("pushButton_field")
self.verticalLayout.addWidget(self.pushButton_field)
self.pushButton_guide.setObjectName("pushButton_guide")
self.verticalLayout.addWidget(self.pushButton_guide)
self.pushButton_mapping.setObjectName("pushButton_mapping")
self.verticalLayout.addWidget(self.pushButton_mapping)
self.horizontalLayout.addLayout(self.verticalLayout)
self.setCentralWidget(self.centralwidget)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 26))
self.menubar.setObjectName("menubar")
self.setMenuBar(self.menubar)
self.statusbar.setObjectName("statusbar")
self.setStatusBar(self.statusbar)
self.retranslateUi()
QtCore.QMetaObject.connectSlotsByName(self)
# Just to test
self.pushButton_status.clicked.connect(self.rotate_arrow)
self.pushButton_guide.clicked.connect(self.rotate_pixmap)
# Created just to emit a signal
def rotate_arrow(self):
"""
It'll not be used after.
"""
self.arrowItem.rotate(value=30)
def rotate_pixmap(self):
pixmap = self.pixmap
self.rotation += 15
transform = QtGui.QTransform().rotate(self.rotation, axis=QtCore.Qt.ZAxis)
pixmap = pixmap.transformed(transform, QtCore.Qt.SmoothTransformation)
self.scene.addPixmap(pixmap)
self.arrowItem.setPos(self.arrowItem.pos())
self.arrowItem.rotate(self.rotation)
self.arrowItem.setZValue(self.arrowItem.zValue()+1)
def add_arrow(self):
"""
:cvar
"""
self.scene.addItem(self.arrowItem)
self.arrowItem.setPos(QtCore.QPointF(self.VIEW_WIDTH / 2 - 50, self.VIEW_HEIGHT / 2 - 100))
def retranslateUi(self):
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton_status.setText(_translate("MainWindow", "Situação"))
self.pushButton_settings.setText(_translate("MainWindow", "Configurações"))
self.pushButton_analyse.setText(_translate("MainWindow", "Analisar"))
self.pushButton_field.setText(_translate("MainWindow", "Campo"))
self.pushButton_guide.setText(_translate("MainWindow", "Guia"))
self.pushButton_mapping.setText(_translate("MainWindow", "Mapeamento"))
# calling the application
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = WorkScreen()
ui.show()
sys.exit(app.exec_())
Here is the image that I am using to display on the background:

If you want to rotate a QGraphicsItem it is not necessary to use QTransform, just use the setRotation method. On the other hand, the image must be scaled so that the rotated area intersected by the visible area is the same as the visible area and that minimum area is calculated as: sqrt(2) x max(width, height). Considering the above, the solution is:
from PyQt5 import QtCore, QtGui, QtWidgets
class ArrowItem(QtWidgets.QGraphicsPathItem):
def __init__(self, parent=None):
super().__init__(parent)
self._length = -1
self._points = (
QtCore.QPointF(),
QtCore.QPointF(),
QtCore.QPointF(),
)
self.length = 40.0
#property
def length(self):
return self._length
#length.setter
def length(self, l):
self._length = l
pos_top = QtCore.QPointF(0, l * 4 / 5)
pos_left = QtCore.QPointF(-l * 3 / 5, -l / 5)
pos_right = QtCore.QPointF(
l * 3 / 5,
-l / 5,
)
path = QtGui.QPainterPath()
path.moveTo(pos_top)
path.lineTo(pos_right)
path.lineTo(pos_left)
self.setPath(path)
self._points = pos_top, pos_left, pos_right
def paint(self, painter, option, widget):
pos_top, pos_left, pos_right = self._points
left_color = QtGui.QColor("#cc0000")
right_color = QtGui.QColor("#ff0000")
bottom_color = QtGui.QColor("#661900")
path_left = QtGui.QPainterPath()
path_left.lineTo(pos_top)
path_left.lineTo(pos_left)
path_right = QtGui.QPainterPath()
path_right.lineTo(pos_top)
path_right.lineTo(pos_right)
path_bottom = QtGui.QPainterPath()
path_bottom.lineTo(pos_left)
path_bottom.lineTo(pos_right)
painter.setPen(QtGui.QColor("black"))
painter.setBrush(left_color)
painter.drawPath(path_left)
painter.setBrush(right_color)
painter.drawPath(path_right)
painter.setBrush(bottom_color)
painter.drawPath(path_bottom)
def moveTo(self, next_position, duration=100):
self._animation = QtCore.QVariantAnimation()
self._animation.setStartValue(self.pos())
self._animation.setEndValue(next_position)
self._animation.setDuration(duration)
self._animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
self._animation.valueChanged.connect(self.setPos)
class MainWindow(QtWidgets.QMainWindow):
VIEW_HEIGHT = 600
VIEW_WIDTH = 900
def __init__(self, parent=None):
super().__init__(parent)
self.scene = QtWidgets.QGraphicsScene(self)
self.view = QtWidgets.QGraphicsView(self.scene)
self.status_btn = QtWidgets.QPushButton("Status")
self.settings_btn = QtWidgets.QPushButton("Settings")
self.analyze_btn = QtWidgets.QPushButton("Analyze")
self.field_btn = QtWidgets.QPushButton("Field")
self.guide_btn = QtWidgets.QPushButton("Guide")
self.mapping_btn = QtWidgets.QPushButton("Mapping")
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
vlayl = QtWidgets.QVBoxLayout()
vlayl.addWidget(self.status_btn)
vlayl.addWidget(self.settings_btn)
vlayl.addWidget(self.analyze_btn)
vlayr = QtWidgets.QVBoxLayout()
vlayr.addWidget(self.field_btn)
vlayr.addWidget(self.guide_btn)
vlayr.addWidget(self.mapping_btn)
hlay = QtWidgets.QHBoxLayout(central_widget)
hlay.addLayout(vlayl)
hlay.addWidget(self.view)
hlay.addLayout(vlayr)
self.status_btn.clicked.connect(self.rotate_arrow)
self.guide_btn.clicked.connect(self.rotate_pixmap)
self.view.setFixedSize(self.VIEW_WIDTH, self.VIEW_HEIGHT)
r = self.view.mapToScene(self.view.viewport().rect()).boundingRect()
self.view.setSceneRect(r)
factor = 1.5 * max(self.VIEW_WIDTH, self.VIEW_HEIGHT)
pixmap = QtGui.QPixmap("ola.png").scaled(factor, factor)
self.pixmap_item = self.scene.addPixmap(pixmap)
center = self.pixmap_item.boundingRect().center()
self.pixmap_item.setPos(-center)
self.pixmap_item.setTransformOriginPoint(center)
self.arrow_item = ArrowItem()
self.scene.addItem(self.arrow_item)
def rotate_arrow(self):
delta = 30.0
self.arrow_item.setRotation(self.arrow_item.rotation() + delta)
def rotate_pixmap(self):
delta = 15.0
self.pixmap_item.setRotation(self.pixmap_item.rotation() + delta)
self.arrow_item.setRotation(self.arrow_item.rotation() + delta)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

Related

How do I stop the small spinning circles along the scanning line in QPainter?

I'm trying to rotate a line in a circle like clock's second arm. When rotating the line, I also want to draw small circles here and there inside the circle. The problem is that the small circles I drew is rotating along the line. I want these circles stay at their location and move with my code. Can someone help me?
import sys
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
from PySide2.QtOpenGL import *
from OpenGL import GL
class Helper:
def __init__(self):
gradient = QLinearGradient(QPointF(50, -20), QPointF(80, 20))
gradient.setColorAt(0.0, Qt.green)
gradient.setColorAt(1.0, Qt.blue)
self.background = QBrush(QColor(0, 0, 0))
self.circlePen = QPen(Qt.green)
self.circlePen.setWidth(2)
self.textPen = QPen(Qt.green,8,Qt.SolidLine)
self.textFont = QFont()
self.textFont.setPixelSize(100)
def paint(self, painter, event, elapsed):
painter.fillRect(event.rect(), self.background)
painter.setPen(self.circlePen)
painter.translate(400, 400)
painter.drawEllipse(-250,-250,500,500)
painter.rotate(elapsed * 0.35)
painter.setBrush(QColor(Qt.green))
dx = 100
dy = 100
for i in range(10):
painter.drawEllipse(dx,dy,8,8)
dx += 10
dy += 10
painter.save()
painter.drawLine(0,-250,0,0)
painter.restore()
class GLWidget(QGLWidget):
def __init__(self, helper, parent = None):
QGLWidget.__init__(self, QGLFormat(QGL.SampleBuffers), parent)
self.helper = helper
self.elapsed = 0
self.setFixedSize(800, 800)
def animate(self):
self.elapsed = (self.elapsed + self.sender().interval()) % 1000
self.repaint()
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
self.helper.paint(painter, event, self.elapsed)
painter.end()
class Window(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
helper = Helper()
openGL = GLWidget(helper, self)
openGLLabel = QLabel(self.tr("OpenGL"))
openGLLabel.setAlignment(Qt.AlignHCenter)
layout = QGridLayout()
layout.addWidget(openGL, 100, 100)
layout.addWidget(openGLLabel, 1, 0)
self.setLayout(layout)
timer = QTimer(self)
self.connect(timer, SIGNAL("timeout()"), openGL.animate)
timer.start(1)
self.setWindowTitle(self.tr("2D Painting on Native and OpenGL Widgets"))
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
small green circles are rotating with line
small green circles are rotating with line
1: The results of above code
The result I have
The result I want like this

PyQt5 Curved QSlider

I've been playing around with customising PyQt widgets using paint events. I've been trying to customise the QSlider widget and have had some success, mostly with CSS styling. However, I'm having difficulty making it curved with a QPainterPath as it always seems flat. Is this something that is beyond the capability of this widget (which would surprise me)? The below is my most recent attempt of many with no success. I tried path points instead of cubicTo() with the same. Can anyone help or point me in the right direction?
from PyQt5 import QtGui, QtWidgets, QtCore
class slider(QtWidgets.QSlider):
def __init__(self, parent=None):
super(slider, self).__init__(parent)
self.parent = parent
self.setMinimum(10)
self.setMaximum(30)
self.setValue(20)
self.setTickPosition(QtWidgets.QSlider.TicksBelow)
self.setTickInterval(5)
def paintEvent(self, event):
qp = QtGui.QPainter()
qp.begin(self)
qp.setRenderHint(QtGui.QPainter.Antialiasing)
self.drawCurve(qp)
qp.end()
def drawCurve(self, qp):
path = QtGui.QPainterPath()
path.moveTo(30, 30)
path.cubicTo(30, 30, 200, 350, 200, 30)
qp.drawPath(path)
To have the sensation of depth it is only to choose the correct colors, for this QPainterPathStroker is also used. On the other hand I added the functionality that the QPainterPath is scaled:
from PyQt5 import QtCore, QtGui, QtWidgets
class PathSlider(QtWidgets.QAbstractSlider):
def __init__(self, path=QtGui.QPainterPath(), *args, **kwargs):
super(PathSlider, self).__init__(*args, **kwargs)
self._path = path
self.stroke_path = self._path
self.scale_path = self._path
def setPath(self, path):
path.translate(-path.boundingRect().topLeft())
self._path = path
self.update()
def path(self):
return self._path
path = QtCore.pyqtProperty(QtGui.QPainterPath, fget=path, fset=setPath)
def paintEvent(self, event):
border = 10
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
sx, sy = (self.rect().width() -2*border)/self.path.boundingRect().width(), \
(self.rect().height() -2*border)/self.path.boundingRect().height()
tr = QtGui.QTransform()
tr.translate(border, border)
tr.scale(sx, sy)
self.scale_path = tr.map(self.path)
stroker = QtGui.QPainterPathStroker()
stroker.setCapStyle(QtCore.Qt.RoundCap)
stroker.setWidth(4)
stroke_path = stroker.createStroke(self.scale_path).simplified()
painter.setPen(QtGui.QPen(self.palette().color(QtGui.QPalette.Shadow), 1))
painter.setBrush(QtGui.QBrush(self.palette().color(QtGui.QPalette.Midlight)))
painter.drawPath(stroke_path)
stroker.setWidth(20)
self.stroke_path = stroker.createStroke(self.scale_path).simplified()
percentage = (self.value() - self.minimum())/(self.maximum() - self.minimum())
highlight_path = QtGui.QPainterPath()
highlight_path.moveTo(self.scale_path.pointAtPercent(0))
n_p = int((self.maximum() + 1 - self.minimum())/self.singleStep())
for i in range(n_p+1):
d = i*percentage/n_p
p = self.scale_path.pointAtPercent(d)
highlight_path.lineTo(p)
stroker.setWidth(3)
new_phighlight_path = stroker.createStroke(highlight_path).simplified()
activeHighlight = self.palette().color(QtGui.QPalette.Highlight)
painter.setPen(activeHighlight)
painter.setBrush(QtGui.QBrush(QtGui.QColor(activeHighlight)))
painter.drawPath(new_phighlight_path)
opt = QtWidgets.QStyleOptionSlider()
r = self.style().subControlRect(QtWidgets.QStyle.CC_Slider, opt, QtWidgets.QStyle.SC_SliderHandle, self)
pixmap = QtGui.QPixmap(r.width() + 2*2, r.height() + 2*2)
pixmap.fill(QtCore.Qt.transparent)
r = pixmap.rect().adjusted(2, 2, -2, -2)
pixmap_painter = QtGui.QPainter(pixmap)
pixmap_painter.setRenderHint(QtGui.QPainter.Antialiasing)
pixmap_painter.setPen(QtGui.QPen(self.palette().color(QtGui.QPalette.Shadow), 2))
pixmap_painter.setBrush(self.palette().color(QtGui.QPalette.Base))
pixmap_painter.drawRoundedRect(r, 4, 4)
pixmap_painter.end()
r.moveCenter(p.toPoint())
painter.drawPixmap(r, pixmap)
def minimumSizeHint(self):
return QtCore.QSize(15, 15)
def sizeHint(self):
return QtCore.QSize(336, 336)
def mousePressEvent(self, event):
self.update_pos(event.pos())
super(PathSlider, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
self.update_pos(event.pos())
super(PathSlider, self).mouseMoveEvent(event)
def update_pos(self, point):
if self.stroke_path.contains(point):
n_p = int((self.maximum() + 1 - self.minimum())/self.singleStep())
ls = []
for i in range(n_p):
p = self.scale_path.pointAtPercent(i*1.0/n_p)
ls.append(QtCore.QLineF(point, p).length())
j = ls.index(min(ls))
val = int(j*(self.maximum() + 1 - self.minimum())/n_p)
self.setValue(val)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QWidget()
s1 = PathSlider(minimum=0, maximum=100)
s2 = PathSlider(minimum=0, maximum=100)
s = QtWidgets.QSlider(minimum=0, maximum=100)
s.valueChanged.connect(s1.setValue)
s.valueChanged.connect(s2.setValue)
s1.valueChanged.connect(s.setValue)
s2.valueChanged.connect(s.setValue)
c1 = QtCore.QPointF(5, -15)
c2 = QtCore.QPointF(220, -15)
path = QtGui.QPainterPath(QtCore.QPointF(5, 100))
path.cubicTo(c1, c2, QtCore.QPointF(235, 100))
s1.setPath(path)
c1 = QtCore.QPointF(5, 15)
c2 = QtCore.QPointF(220, 15)
path = QtGui.QPainterPath(QtCore.QPointF(5, -100))
path.cubicTo(c1, c2, QtCore.QPointF(235, -100))
s2.setPath(path)
lay = QtWidgets.QHBoxLayout(w)
lay.addWidget(s1)
lay.addWidget(s2)
lay.addWidget(s)
w.show()
sys.exit(app.exec_())

QPaint draw lines with offset

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)

Pyqt take screenshot of certain screen area

I have this app written in pyqt. You can view the source at http://www.pasteall.org/62739/python It takes screen shots of the desktop. what I want to know is how to make it also take a screen shot of a certain area on the screen like window's snipping tool can do (http://sjcblogs.sanjac.edu/its/files/2011/09/Snipping-Tool-selection-types1.jpg and gilsmethod.wpengine.netdna-cdn.com/images/snipping-tool-windows-73.png). How do I write this?
Thank you in advance!
You should use
img = QPixmap.grabWindow(QApplication.desktop().winId(), x, y, width, height)
The pyqt distro has a very good python example of screeshoting with pyqt. The C++ code of the example, easily converted to Python, is at http://doc.qt.io/qt-4.8/qt-desktop-screenshot-example.html
Install following packages:
pip install pillow opencv-python pyside2
Run the following code:
import sys
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
import numpy as np
import cv2
from PIL import ImageGrab
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow")
MainWindow.resize(1000, 1000)
sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
MainWindow.setSizePolicy(sizePolicy)
MainWindow.setMinimumSize(QSize(1000, 1000))
MainWindow.setMaximumSize(QSize(16777215, 16777215))
MainWindow.setTabShape(QTabWidget.Rounded)
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget")
sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth())
self.centralwidget.setSizePolicy(sizePolicy1)
self.centralwidget.setMinimumSize(QSize(1000, 1000))
self.gridLayout = QGridLayout(self.centralwidget)
self.gridLayout.setObjectName(u"gridLayout")
self.pushButton_area = QPushButton(self.centralwidget)
self.pushButton_area.setObjectName(u"pushButton_area")
self.pushButton_area.setMinimumSize(QSize(800, 0))
self.gridLayout.addWidget(self.pushButton_area, 0, 0, 1, 1)
self.label = QLabel(self.centralwidget)
self.label.setObjectName(u"label")
sizePolicy2 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
sizePolicy2.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0)
sizePolicy2.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
self.label.setSizePolicy(sizePolicy2)
self.label.setMinimumSize(QSize(800, 800))
self.label.setMaximumSize(QSize(4096, 4096))
self.gridLayout.addWidget(self.label, 2, 0, 2, 1)
self.pushButton_full = QPushButton(self.centralwidget)
self.pushButton_full.setObjectName(u"pushButton_full")
self.pushButton_full.setMinimumSize(QSize(800, 0))
self.gridLayout.addWidget(self.pushButton_full, 1, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Snipping Tool", None))
MainWindow.setAccessibleName(QCoreApplication.translate("MainWindow", u"Snipping Tool", None))
self.pushButton_area.setText(QCoreApplication.translate("MainWindow", u"Select Area", None))
self.pushButton_full.setText(QCoreApplication.translate("MainWindow", u"Fullscreen", None))
# Refer to https://github.com/harupy/snipping-tool
class SnippingWidget(QWidget):
is_snipping = False
def __init__(self, parent=None, app=None):
super(SnippingWidget, self).__init__()
self.parent = parent
self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.screen = app.primaryScreen()
self.setGeometry(0, 0, self.screen.size().width(), self.screen.size().height())
self.begin = QPoint()
self.end = QPoint()
self.onSnippingCompleted = None
def fullscreen(self):
img = ImageGrab.grab(bbox=(0, 0, self.screen.size().width(), self.screen.size().height()))
try:
img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
except:
img = None
if self.onSnippingCompleted is not None:
self.onSnippingCompleted(img)
def start(self):
SnippingWidget.is_snipping = True
self.setWindowOpacity(0.3)
QApplication.setOverrideCursor(QCursor(Qt.CrossCursor))
self.show()
def paintEvent(self, event):
if SnippingWidget.is_snipping:
brush_color = (128, 128, 255, 100)
lw = 3
opacity = 0.3
else:
self.begin = QPoint()
self.end = QPoint()
brush_color = (0, 0, 0, 0)
lw = 0
opacity = 0
self.setWindowOpacity(opacity)
qp = QPainter(self)
qp.setPen(QPen(QColor('black'), lw))
qp.setBrush(QColor(*brush_color))
rect = QRectF(self.begin, self.end)
qp.drawRect(rect)
def mousePressEvent(self, event):
self.begin = event.pos()
self.end = self.begin
self.update()
def mouseMoveEvent(self, event):
self.end = event.pos()
self.update()
def mouseReleaseEvent(self, event):
SnippingWidget.is_snipping = False
QApplication.restoreOverrideCursor()
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())
self.repaint()
QApplication.processEvents()
img = ImageGrab.grab(bbox=(x1, y1, x2, y2))
try:
img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
except:
img = None
if self.onSnippingCompleted is not None:
self.onSnippingCompleted(img)
self.close()
class MainWindow(QMainWindow):
useQThread = True
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.setAcceptDrops(True)
self.snippingWidget = SnippingWidget(app=QApplication.instance())
self.snippingWidget.onSnippingCompleted = self.onSnippingCompleted
self.ui.pushButton_area.clicked.connect(self.snipArea)
self.ui.pushButton_full.clicked.connect(self.snipFull)
self._pixmap = None
def onSnippingCompleted(self, frame):
self.setWindowState(Qt.WindowActive)
if frame is None:
return
image = QImage(frame, frame.shape[1], frame.shape[0], frame.strides[0], QImage.Format_RGB888)
pixmap = QPixmap.fromImage(image)
self._pixmap = self.resizeImage(pixmap)
self.ui.label.setPixmap(self._pixmap)
def snipArea(self):
self.setWindowState(Qt.WindowMinimized)
self.snippingWidget.start()
def snipFull(self):
self.setWindowState(Qt.WindowMinimized)
self.snippingWidget.fullscreen()
def dragEnterEvent(self, event):
event.acceptProposedAction()
def dropEvent(self, event):
urls = event.mimeData().urls()
filename = urls[0].toLocalFile()
self.loadFile(filename)
self.decodeFile(filename)
event.acceptProposedAction()
def resizeImage(self, pixmap):
lwidth = self.ui.label.width()
pwidth = pixmap.width()
lheight = self.ui.label.height()
pheight = pixmap.height()
wratio = pwidth * 1.0 / lwidth
hratio = pheight * 1.0 / lheight
if pwidth > lwidth or pheight > lheight:
if wratio > hratio:
lheight = pheight / wratio
else:
lwidth = pwidth / hratio
scaled_pixmap = pixmap.scaled(lwidth, lheight)
return scaled_pixmap
else:
return pixmap
def showMessageBox(self, title, content):
msgBox = QMessageBox()
msgBox.setWindowTitle(title)
msgBox.setText(content)
msgBox.exec_()
def closeEvent(self, event):
msg = "Close the app?"
reply = QMessageBox.question(self, 'Message',
msg, QMessageBox.Yes, QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

PyQt is slow in handling a large number of objects

I present here a simple pyqt code, but I developed my whole design and algo upon this. The problem, is that PyQt is unable to manage a large number of objects when created. This is simple code for drawing many rectangles inside a big one. And it has Zoom and pan feature too.
import sys
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import *
from PyQt4.QtGui import *
item = None
class Rectangle(QGraphicsItem):
def __init__(self, parent, x, y, width, height, scene = None, fill=None):
self.parent = parent
super(Rectangle, self).__init__()
self.setFlags(QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
rect = QRectF(x,y,width,height)
self.dimen = [x,y,width,height]
self.rect = rect
scene.addItem(self)
self.show_its_ui()
#Function to get relative position
def get_relative_location(self,loc):
self.loc = QPointF(loc[0],loc[1])
global item
self.loc = self.mapFromItem(item, self.loc)
self.loc = (self.loc.x(),self.loc.y())
self.loc = list(self.loc)
self.dimen[0] = self.loc[0]
self.dimen[1] = self.loc[1]
#Showing its UI in the form of a rectangle
def show_its_ui(self):
#First gets its relative location and set it.
if(item is not None):
self.get_relative_location([self.dimen[0],self.dimen[1]])
#Make a Final rectangle
rect = QRectF(self.dimen[0],self.dimen[1],self.dimen[2],self.dimen[3])
self.rect = rect
def boundingRect(self):
return self.rect.adjusted(-2, -2, 2, 2)
def parentWidget(self):
return self.scene().views()[0]
def paint(self, painter, option, widget):
pen = QPen(Qt.SolidLine)
pen.setColor(Qt.black)
pen.setWidth(1)
painter.setBrush(QtGui.QColor(255, 50, 90, 200))
painter.drawRect(self.rect)
class GraphicsView(QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setDragMode(QGraphicsView.RubberBandDrag)
self.setRenderHint(QPainter.Antialiasing)
self.setRenderHint(QPainter.TextAntialiasing)
def wheelEvent(self, event):
factor = 1.41 ** (event.delta() / 240.0)
self.scale(factor, factor)
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self, parent)
self.Width = 800
self.Height = 500
self.view = GraphicsView()
self.scene = QGraphicsScene(self)
self.scene.setSceneRect(0, 0, self.Width, self.Height)
self.view.setScene(self.scene)
self.initUI()
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.view)
mainWidget = QtGui.QWidget()
mainWidget.setLayout(hbox)
self.setCentralWidget(mainWidget)
def initUI(self):
self.group = QGraphicsItemGroup()
self.group.setFlags(QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
global item
item = self.group
self.group.setFiltersChildEvents(True)
self.group.setHandlesChildEvents(False)
self._link1 = Rectangle(self, 10, 10, self.Width, self.Height,self.scene)
self._link1.setFiltersChildEvents(True)
self._link1.setHandlesChildEvents(False)
self.group.addToGroup(self._link1)
self.scene.addItem(self.group)
self.addObjects()
#Here I added objects in the big canvas
def addObjects(self):
xpos = 20
ypos = 20
#Change the below to 5 each
width = 50
height = 50
#Generate many rectangles
while(ypos < 450):
xpos = 20
while(xpos < 750):
t = Rectangle(self._link1,xpos,ypos,width,height,self.scene,True)
self.group.addToGroup(t)
xpos += (width+2)
ypos += (height+2)
app = QtGui.QApplication(sys.argv)
mainWindow = MainWindow()
method = mainWindow
mainWindow.show()
sys.exit(app.exec_())
Try changing, the width and height values present in addObjects function of the mainwindow class, say to 5 and 5 respectively from 50. Then the whole application run at very slow speed, when you pan/zoom. Please share your suggestions in rectifying this.

Categories

Resources