I want to display an image in a QGraphicsView, actually in QGraphicsScene, this is the easy part, bu, when I move the cursor over the image, I want to see the X and Y coordinates lines (the yellow lines), like in this image, can anyone explain me how to do this?
To implement what you want there are 2 tasks:
Obtain the position of the cursor, for this case the flag mouseTracking is enabled so that mouseMoveEvent() is called where the position is obtained.
Paint on the top layer, for this we use the drawForeground() function.
from PyQt5 import QtCore, QtGui, QtWidgets
class GraphicsScene(QtWidgets.QGraphicsScene):
def drawForeground(self, painter, rect):
super(GraphicsScene, self).drawForeground(painter, rect)
if not hasattr(self, "cursor_position"):
return
painter.save()
pen = QtGui.QPen(QtGui.QColor("yellow"))
pen.setWidth(4)
painter.setPen(pen)
linex = QtCore.QLineF(
rect.left(),
self.cursor_position.y(),
rect.right(),
self.cursor_position.y(),
)
liney = QtCore.QLineF(
self.cursor_position.x(),
rect.top(),
self.cursor_position.x(),
rect.bottom(),
)
for line in (linex, liney):
painter.drawLine(line)
painter.restore()
def mouseMoveEvent(self, event):
self.cursor_position = event.scenePos()
self.update()
super(GraphicsScene, self).mouseMoveEvent(event)
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setMouseTracking(True)
scene = GraphicsScene(QtCore.QRectF(-200, -200, 400, 400), self)
self.setScene(scene)
if __name__ == "__main__":
import sys
import random
app = QtWidgets.QApplication(sys.argv)
w = GraphicsView()
for _ in range(4):
r = QtCore.QRectF(
*random.sample(range(-200, 200), 2),
*random.sample(range(50, 150), 2)
)
it = w.scene().addRect(r)
it.setBrush(QtGui.QColor(*random.sample(range(255), 3)))
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
Related
I am trying to build a paint app that has a grid, but I don't want to grid to be included in the saved photo.
I tried to draw on a transparent image, but I got a black background instead!
Here is the full code:
import sys
from PyQt5 import QtGui, QtWidgets, QtCore
from PyQt5.QtCore import QPoint, Qt
from PyQt5.QtGui import QBrush, QGuiApplication, QImage, QPainter, QPen, QIcon, QColor
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QTextEdit, QAction, QFileDialog,
QGraphicsScene, QGraphicsProxyWidget, QGraphicsView
import pyautogui
import matplotlib.pyplot as plt
class Drawer(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self._drawing = False
self.last_point = QPoint()
self.image_layer = QImage(self.size(), QImage.Format_RGB32)
self.image_layer.fill(Qt.gray)
self.brushSize = 2
self.brushColor = Qt.black
#paint = QPainter(self.image_layer)
#paint.setCompositionMode(QtGui.QPainter.CompositionMode_Clear)
# paint.drawLine(0,0,self.size().width(),0)
# paint.drawLine(0,10,200,10)
#paint.drawLine(0,0,0,200)
#paint.drawLine(10,0,10,200)
self.update()
def mousePressEvent(self, event):
self._drawing = True
self.last_point = event.pos()
def mouseMoveEvent(self, event):
if self._drawing and event.buttons() & Qt.LeftButton:
painter = QPainter(self.image_layer)
painter.setPen(
QPen(
self.brushColor,
self.brushSize,
Qt.SolidLine,
Qt.RoundCap,
Qt.RoundJoin,
)
)
painter.drawLine(self.last_point, event.pos())
self.last_point = event.pos()
self.update()
def paintEvent(self, event):
painter = QPainter(self)
painter.drawImage(QPoint(), self.image_layer)
painter.end()
def resizeEvent(self, event):
if (
self.size().width() > self.image_layer.width()
or self.size().height() > self.image_layer.height()
):
qimg = QImage(
max(self.size().width(), self.image_layer.width()),
max(self.size().height(), self.image_layer.height()),
QImage.Format_RGB32,
)
qimg.fill(Qt.gray)
painter = QPainter(qimg)
painter.drawImage(QPoint(), self.image_layer)
painter.drawLine(0, 0, qimg.size().width(), 0)
painter.drawLine(0, 10, qimg.size().width(), 10)
painter.drawLine(0, 600, qimg.size().width(), 600)
print(qimg.size().height())
painter.end()
self.image_layer = qimg
self.update()
class Window(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
[x, y] = pyautogui.size()
self.setGeometry(0, 0, x, y)
self.drawer = Drawer()
textbox = QTextEdit("Converted text will show here")
central_widget = QWidget()
self.setCentralWidget(central_widget)
vlay = QVBoxLayout(central_widget)
vlay.addWidget(textbox)
vlay.addWidget(self.drawer, stretch=1)
mainMenu = self.menuBar()
fileMenu = mainMenu.addMenu("File")
saveAction = QAction(QIcon("icons/save.png"), "Save", self)
saveAction.setShortcut("Ctrl+S")
fileMenu.addAction(saveAction)
saveAction.triggered.connect(self.save)
def save(self):
filePath, _ = QFileDialog.getSaveFileName(self.drawer, "Save Image", "",
"PNG(*.png);;JPEG(*.jpg *.jpeg);;All Files(*.*) ")
if filePath == "":
return
self.drawer.image_layer.save(filePath)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec())
Note: there are only two lines that mimics the grid, I can draw it later, but for now I want the saved image to not include the grind lines.
You should paint the grid in the paintEvent, instead of continuously painting it onto the image.
def paintEvent(self, event):
painter = QPainter(self)
painter.drawImage(QPoint(), self.image_layer)
gridSize = 10
x = y = 0
width = self.width()
height = self.height()
while y <= height:
# draw horizontal lines
painter.drawLine(0, y, width, y)
y += gridSize
while x <= width:
# draw vertical lines
painter.drawLine(x, 0, x, height)
x += gridSize
i'm loading an image on a label supposed after that
a mouse click event that draws dots on the label but dots are drawn on the main window ( behind the label )
GUI Image
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super(ApplicationWindow, self).__init__()
uic.loadUi('MainWindow.ui', self)
self.setFixedSize(self.size())
self.show()
self.points = QtGui.QPolygon()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
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.red, 5)
brush = QtGui.QBrush(QtCore.Qt.red)
qp.setPen(pen)
qp.setBrush(brush)
for i in range(self.points.count()):
# qp.drawEllipse(self.points.point(i), 5, 5)
# or
qp.drawPoints(self.points)
def main():
app = QtWidgets.QApplication(sys.argv)
application = ApplicationWindow()
application.show()
sys.exit(app.exec_())
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = ApplicationWindow()
sys.exit(app.exec_())
main()
You are calling QPainter(self) so the paint device is the MainWindow. Instead call QPainter on the QPixmap. Here is an example.
class Template(QWidget):
def __init__(self):
super().__init__()
self.lbl = QLabel()
self.pix = QPixmap('photo.jpeg')
grid = QGridLayout(self)
grid.addWidget(self.lbl, 0, 0)
self.points = QPolygon()
def mousePressEvent(self, event):
self.points << QPoint(event.x() - self.lbl.x(), event.y() - self.lbl.y())
def paintEvent(self, event):
qp = QPainter(self.pix)
qp.setRenderHint(QPainter.Antialiasing)
pen = QPen(Qt.red, 5)
brush = QBrush(Qt.red)
qp.setPen(pen)
qp.setBrush(brush)
qp.drawPoints(self.points)
self.lbl.setPixmap(self.pix)
However a better way is to use QImage in a custom widget that will act as a canvas.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Canvas(QWidget):
def __init__(self, photo, *args, **kwargs):
super().__init__(*args, **kwargs)
self.image = QImage(photo)
self.setFixedSize(self.image.width(), self.image.height())
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
qp = QPainter(self.image)
qp.setRenderHint(QPainter.Antialiasing)
qp.setPen(QPen(Qt.red, 5))
qp.setBrush(Qt.red)
qp.drawPoint(event.pos())
self.update()
def paintEvent(self, event):
qp = QPainter(self)
rect = event.rect()
qp.drawImage(rect, self.image, rect)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
w = QWidget()
self.setCentralWidget(w)
grid = QGridLayout(w)
grid.addWidget(Canvas('photo.jpeg'))
if __name__ == '__main__':
app = QApplication(sys.argv)
gui = MainWindow()
gui.show()
sys.exit(app.exec_())
Output:
I want to move the QLabel with the mouse movement (not like Drag&drop, 'object' disappears while moving). Clicked - moved - released. I did it to some extent, but I ran into a problem. QLabel shrinks as I move it or even disappears (like shrinks to 0 width). How to fix it or what more correct approach to do it?
(self.label_pos is needed to keep the mouse position relative inside self.label)
Or its just monitor's refresh rate issue? But in photoshop's gradient editor, that little color stop isn't shrikns. It's choppy because of refresh rate, but always the same size.
This is what I want to see, recorded using a screen capture program. The same thing I see in Photoshop
This is what I see, recorded on my phone. The quality is poor, but the difference is clearly visible anyway.
This Photoshop is also captured on my phone, here the “object” remains the same size, as in the example made using screen capture
Here is code from eyllanesc's answer, 'object' still shrinks :(
self.label = QLabel(self)
self.label.move(100, 100)
self.label.mousePressEvent = self.mouse_on
self.label.mouseReleaseEvent = self.mouse_off
def mouse_on(self, event):
self.bool = True
self.label_pos = event.pos()
def mouse_off(self, event):
self.bool = False
def mouseMoveEvent(self, event):
if self.bool:
self.label.move(event.x()-self.label_pos.x(), event.y()-self.label_pos.y())
Instead of using a QLabel I recommend using QGraphicsRectItem with a QGraphicsView since it is specialized in this type of tasks:
from PyQt5 import QtCore, QtGui, QtWidgets
class Widget(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.setScene(QtWidgets.QGraphicsScene(self))
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
brush = QtWidgets.QApplication.palette().brush(QtGui.QPalette.Window)
self.setBackgroundBrush(brush)
rect_item = self.scene().addRect(
QtCore.QRectF(QtCore.QPointF(), QtCore.QSizeF(40, 80))
)
rect_item.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
rect_item.setBrush(QtGui.QBrush(QtGui.QColor("red")))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.setFixedSize(640, 480)
w.show()
sys.exit(app.exec_())
If you want to just scroll horizontally then overwrite the itemChange method of QGraphicsItem:
from PyQt5 import QtCore, QtGui, QtWidgets
class HorizontalItem(QtWidgets.QGraphicsRectItem):
def __init__(self, rect, parent=None):
super(HorizontalItem, self).__init__(rect, parent)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
def itemChange(self, change, value):
if (
change == QtWidgets.QGraphicsItem.ItemPositionChange
and self.scene()
):
return QtCore.QPointF(value.x(), self.pos().y())
return super(HorizontalItem, self).itemChange(change, value)
class Widget(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.setScene(QtWidgets.QGraphicsScene(self))
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
brush = QtWidgets.QApplication.palette().brush(QtGui.QPalette.Window)
self.setBackgroundBrush(brush)
rect_item = HorizontalItem(
QtCore.QRectF(QtCore.QPointF(), QtCore.QSizeF(40, 80))
)
rect_item.setBrush(QtGui.QBrush(QtGui.QColor("red")))
self.scene().addItem(rect_item)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.setFixedSize(640, 480)
w.show()
sys.exit(app.exec_())
In the following code there is an example similar to what you want:
from PyQt5 import QtCore, QtGui, QtWidgets
class HorizontalItem(QtWidgets.QGraphicsRectItem):
def __init__(self, rect, parent=None):
super(HorizontalItem, self).__init__(rect, parent)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
def itemChange(self, change, value):
if (
change == QtWidgets.QGraphicsItem.ItemPositionChange
and self.scene()
):
return QtCore.QPointF(value.x(), self.pos().y())
return super(HorizontalItem, self).itemChange(change, value)
class Widget(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.setScene(QtWidgets.QGraphicsScene(self))
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
brush = QtWidgets.QApplication.palette().brush(QtGui.QPalette.Window)
self.setBackgroundBrush(brush)
self.setFixedSize(640, 480)
size = self.mapToScene(self.viewport().rect()).boundingRect().size()
r = QtCore.QRectF(QtCore.QPointF(), size)
self.setSceneRect(r)
rect = QtCore.QRectF(
QtCore.QPointF(), QtCore.QSizeF(0.8 * r.width(), 80)
)
rect.moveCenter(r.center())
rect_item = self.scene().addRect(rect)
rect_item.setBrush(QtGui.QBrush(QtGui.QColor("salmon")))
item = HorizontalItem(
QtCore.QRectF(
rect.bottomLeft() + QtCore.QPointF(0, 20), QtCore.QSizeF(20, 40)
)
)
item.setBrush(QtGui.QBrush(QtGui.QColor("red")))
self.scene().addItem(item)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
I have some trouble using QVariantAnimation. I've been searching exactly how to use it, but I just don't understand what to do. The documentation and the examples that I read are confusing me more.
I have a list of png images, and I want them to move from point A to point B and change the image depending on the step that I define, within a certain time.
An example would be, Point A is (0, 0) - Point B is (6, 0) and step is (2, 0).
From (0, 0) to (2, 0) display Image1
From (2, 0) to (4, 0) display Image2
From (4, 0) to (6, 0) display Image3
Giving this example as I don't understand QVariantAnimation past giving them the start and the end points.
There are several ways to implement what you point out(the distance is very small so I have changed the dimensions).
This method for each section is a QVariantAnimation that establishes a new QPixmap.
from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets
import random
def create_pixmap():
pixmap = QtGui.QPixmap(QtCore.QSize(20, 20))
pixmap.fill(QtGui.QColor(*random.sample(range(255), 3)))
return pixmap
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.m_scene = QtWidgets.QGraphicsScene(
QtCore.QRectF(-200, -200, 400, 400), self
)
view = QtWidgets.QGraphicsView(self.m_scene)
self.setCentralWidget(view)
self.m_pixmap_item = QtWidgets.QGraphicsPixmapItem()
self.m_scene.addItem(self.m_pixmap_item)
datas = [
(QtCore.QPointF(0, 40), create_pixmap()),
(QtCore.QPointF(0, 80), create_pixmap()),
(QtCore.QPointF(0, 120), create_pixmap()),
]
self.data_iter = iter(datas)
self.move()
def move(self):
try:
end_point, pixmap = next(self.data_iter)
self.m_pixmap_item.setPixmap(pixmap)
animation = QtCore.QVariantAnimation(
duration=500,
valueChanged=self.m_pixmap_item.setPos,
finished=self.move,
startValue=self.m_pixmap_item.pos(),
endValue=end_point,
parent=self,
)
animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
except StopIteration:
pass
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
In this method, the itemChange method is overridden so that it changes from QPixmap to each section.
from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets
import random
def create_pixmap():
pixmap = QtGui.QPixmap(QtCore.QSize(20, 20))
pixmap.fill(QtGui.QColor(*random.sample(range(255), 3)))
return pixmap
class GraphicsPixmapItem(QtWidgets.QGraphicsPixmapItem):
def __init__(self, parent=None):
super(GraphicsPixmapItem, self).__init__(parent)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
self.m_pixmaps = [create_pixmap() for _ in range(3)]
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionChange:
y = self.pos().y()
if 0 <= y < 40:
self.change_pixmap(self.m_pixmaps[0])
elif 40 <= y < 80:
self.change_pixmap(self.m_pixmaps[1])
elif 80 <= y < 120:
self.change_pixmap(self.m_pixmaps[2])
return super(GraphicsPixmapItem, self).itemChange(change, value)
def change_pixmap(self, pixmap):
if self.pixmap() != pixmap:
self.setPixmap(pixmap)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.m_scene = QtWidgets.QGraphicsScene(
QtCore.QRectF(-200, -200, 400, 400), self
)
view = QtWidgets.QGraphicsView(self.m_scene)
self.setCentralWidget(view)
self.m_pixmap_item = GraphicsPixmapItem()
self.m_scene.addItem(self.m_pixmap_item)
animation = QtCore.QVariantAnimation(
duration=3000,
valueChanged=self.m_pixmap_item.setPos,
startValue=self.m_pixmap_item.pos(),
endValue=QtCore.QPointF(0, 120),
parent=self,
)
animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
import sys, csv
from PySide import QtGui, QtCore
from mainStrato import *
X_START = 10
Y_START = 15
MAX_WIDTH = 1350
MAX_LENGH = 1650
ZOOM = 2.5
WIDTH_PEZZO = 150
LENGH_PEZZO = 600
CENTER_OFFSET_X = 15
CENTER_OFFSET_Y = 0
class Strato(QtGui.QMainWindow, Ui_MainWindow):
#Apre il file CSV e copia le singole righe in una lista
def __init__(self, parent=None):
super(Strato, self).__init__(parent)
self.setupUi(self)
def paintEvent(centralwidget, e):
qp = QtGui.QPainter()
qp.begin(centralwidget)
print "paint event"
qp.end()
self.drawRectangles(qp)
def drawRectangles(self, qp):
color = QtGui.QColor(0, 0, 0)
color.setNamedColor('#d4d4d4')
qp.setPen(color)
qp.setBrush(QtGui.QColor(200, 0, 0))
coordCarro = QtCore.QRectF(X_START, Y_START, MAX_WIDTH/ZOOM, MAX_LENGH/ZOOM)
qp.drawRect(coordCarro)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = Strato()
window.show()
sys.exit(app.exec_())
mainstrato is a file generated from pyside-uic.
I got an error Object not defined on calling self.drawRectangles or any other function in Strato called inside paint event?
If I copy the drawRectangles code in paint event it works!
Suggestion? What's wrong?
You are missing self in the paintEvent definition - and I assume that centralwidget is part of your UI class, so that should be accessible through self.centralwidget (since you are inheriting from your UI class). Besides self, paintEvent has only one parameter, the event object. Also, you must not call qp.end() before you have drawn your rectangles. Finally, you need to properly indent your code - but that could also be a copy&paste issue when posting the question. Try
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self.centralwidget)
print "paint event"
self.drawRectangles(qp)
qp.end()
And, finally, you should not paint on other widgets from within one Widget's paintEvent. Instead, subclass QWidget and overide its paint event. The following sscce works (all non-relevant code removed):
#!/usr/bin/python
import sys
from PySide import QtGui, QtCore
X_START = 10
Y_START = 15
MAX_WIDTH = 1350
MAX_LENGH = 1650
ZOOM = 2.5
WIDTH_PEZZO = 150
LENGH_PEZZO = 600
CENTER_OFFSET_X = 15
CENTER_OFFSET_Y = 0
class PaintWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(PaintWidget, self).__init__(parent)
def paintEvent(self, e):
qp = QtGui.QPainter(self)
print("paint event")
self.drawRectangles(qp)
def drawRectangles(self, qp):
color = QtGui.QColor(0, 0, 0)
color.setNamedColor('#d4d4d4')
qp.setPen(color)
qp.setBrush(QtGui.QColor(200, 0, 0))
coordCarro = QtCore.QRectF(X_START, Y_START, MAX_WIDTH/ZOOM, MAX_LENGH/ZOOM)
qp.drawRect(coordCarro)
class Strato(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Strato, self).__init__(parent)
self.centralwidget = PaintWidget(self)
self.setCentralWidget(self.centralwidget)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = Strato()
window.show()
sys.exit(app.exec_())