How to load and svg using QGraphicsSvgItem.renderer().load? - python

I'm struggling to load an SVG image using PyQt5 QGraphicsSvgItem.renderer().load. MWE:
from PyQt5.QtCore import QByteArray
from PyQt5.QtSvg import QGraphicsSvgItem, QSvgRenderer
from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView, QMainWindow, QApplication
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
scene = QGraphicsScene(self)
view = QGraphicsView(scene)
renderer = QSvgRenderer()
self.setCentralWidget(view)
with open('test.svg') as fh:
self.svg_data = fh.read()
self.svg_data = QByteArray(self.svg_data.encode())
self.svg_item = QGraphicsSvgItem()
self.svg_item.setSharedRenderer(renderer)
self.svg_item.renderer().load(self.svg_data)
scene.addItem(self.svg_item)
self.svg_item.setPos(-50, -50)
self.svg_item2 = QGraphicsSvgItem('test2.svg')
scene.addItem(self.svg_item2)
self.svg_item2.setPos(50, 50)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
While test.svg won't be loaded, test2.svg will. See here: Window with a single svg image test.svg is a blue rectangle, test2.svg is a black rectangle.
What am I missing?
(NOTE: I am aware that I can load a svg using either QGraphicsSvgItem('myfile.svg') or QGraphicsSvgItem(QSvgRenderer(my_svg_data)), but I need to update the svg image in an existing object, so these methods wouldn't work for me.)

The problem is that when using the QSvgRenderer to load the .svg then the boundingRect of the QGraphicsSvgItem is not updated so nothing will be drawn. A possible solution is to use passing an empty string to the setElementId method to recalculate the geometry.
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
scene = QGraphicsScene(self)
view = QGraphicsView(scene)
self.setCentralWidget(view)
self.svg_item = QGraphicsSvgItem()
renderer = QSvgRenderer()
self.svg_item.setSharedRenderer(renderer)
with open("test.svg", "rb") as f:
self.svg_item.renderer().load(f.read())
self.svg_item.setElementId("")
scene.addItem(self.svg_item)
self.svg_item.setPos(-50, -50)
self.svg_item2 = QGraphicsSvgItem("test2.svg")
scene.addItem(self.svg_item2)
self.svg_item2.setPos(50, 50)

Related

MediaPlayer not showing in Layout PyQt5 [duplicate]

I am trying to overlay some graphics(QtGraphicsView) on top of video player(QVideoWidget). i have already tried setting QtGraphicsView subclass stylesheets to transparent and background brush and none is working.
#self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(30, 30, 30, 3)))
#self.setStyleSheet("background-color:rgba(0,0,0,0)")
#self.setStyleSheet("background:rgba(0,0,0,0)")
self.setStyleSheet("background:transparent")
self.setStyleSheet("background-color:transparent")
self.setStyleSheet("background-color:rgba(30,30,30,3)")
self.setStyleSheet("background:rgba(30,30,30,3)")
Actual Intentions is to easily crop a video (visual way). All other work is done regarding capturing events , doing math etc. this image explains situation pretty well. . At this point it feels, surely i am doing it wrong way, there must be some easier way in QtMultiMedia Components to draw on top of them. Any Ideas really appreciated.
One possible solution is to use QGraphicsVideoItem instead of QVideoWidget and embed it in the QGraphicsView, then the other items can be made child of the QGraphicsVideoItem so that it is on top, besides the position of the new items will be related to the QGraphicsVideoItem.
import os
from PyQt5 import QtCore, QtGui, QtWidgets, QtMultimedia, QtMultimediaWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self._scene = QtWidgets.QGraphicsScene(self)
self._gv = QtWidgets.QGraphicsView(self._scene)
self._videoitem = QtMultimediaWidgets.QGraphicsVideoItem()
self._scene.addItem(self._videoitem)
self._ellipse_item = QtWidgets.QGraphicsEllipseItem(QtCore.QRectF(50, 50, 40, 40), self._videoitem)
self._ellipse_item.setBrush(QtGui.QBrush(QtCore.Qt.green))
self._ellipse_item.setPen(QtGui.QPen(QtCore.Qt.red))
self._player = QtMultimedia.QMediaPlayer(self, QtMultimedia.QMediaPlayer.VideoSurface)
self._player.stateChanged.connect(self.on_stateChanged)
self._player.setVideoOutput(self._videoitem)
file = os.path.join(os.path.dirname(__file__), "small.mp4")
self._player.setMedia(QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(file)))
button = QtWidgets.QPushButton("Play")
button.clicked.connect(self._player.play)
self.resize(640, 480)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self._gv)
lay.addWidget(button)
#QtCore.pyqtSlot(QtMultimedia.QMediaPlayer.State)
def on_stateChanged(self, state):
if state == QtMultimedia.QMediaPlayer.PlayingState:
self._gv.fitInView(self._videoitem, QtCore.Qt.KeepAspectRatio)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())

`QPixmap` and `QLabel` size slightly increases when reloading

When I'm trying to make my app, I stumbled upon this unexpected behavior where when I re-display a new QPixmap in a QLabel. I tried to simplify the code and ended up with the code below. I also attached the video of the behavior.
I provided here a replicable example (It just needs some .jpg file in the same directory):
import sys
import os
import random
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLabel, QSizePolicy
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
class AppDemo(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(200, 200, 400, 400)
current_working_dir = os.path.abspath('')
dir_files = os.listdir(current_working_dir)
# Saving .jpg from the dir
self.picture = []
for file in dir_files:
if file.endswith(".jpg"):
self.picture.append(file)
self.label = QLabel()
self.label.setStyleSheet("border: 1px solid black;") # <- for the debugging
self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Expanding)
self.label.setPixmap(self.random_picture_selector())
button = QPushButton("Reload Picture")
button.clicked.connect(self.reload_picture)
layout = QVBoxLayout(self)
layout.addWidget(button)
layout.addWidget(self.label)
def reload_picture(self):
self.label.setPixmap(self.random_picture_selector())
def random_picture_selector(self):
rnd_picture = random.choice(self.picture)
pixmap = QPixmap(rnd_picture)
pixmap = pixmap.scaledToWidth(self.label.width(), Qt.SmoothTransformation)
# pixmap = pixmap.scaled(self.label.width(), self.label.height(), Qt.KeepAspectRatio) # <- even this is not working
return pixmap
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = AppDemo()
demo.show()
sys.exit(app.exec_())
Additional Infos:
When simplifying the code I realized that the problem disappears when I removed these following lines. (although I'm not very sure that these part of the code really causes the problem)
pixmap = pixmap.scaledToWidth(self.label.width(), Qt.SmoothTransformation)
# pixmap = pixmap.scaled(self.label.width(), self.label.height(), Qt.KeepAspectRatio) # <- even this is not working
I really have no idea what causes the problem even after looking for the Docs of QPixmap and QLabel.
The problem is caused by the stylesheet border. If you just print the pixmap and label size after setting the pixmap, you'll see that the label width is increased by 2 pixels, which is the sum of the left and right border.
You either remove the border, or you use the contentsRect():
width = self.label.contentsRect().width()
pixmap = pixmap.scaledToWidth(width, Qt.SmoothTransformation)
Read more about the Box Model in the Qt style sheet documentation.

Background picture in QMainwindow PyQt5

I'm trying to get a background image to my mainwindow but i can't get it to work properly.
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QLabel
from PyQt5.QtGui import QIcon
from PyQt5 import QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtSvg import *
from PyQt5.QtWidgets import *
from abc import abstractmethod
class App(QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent=parent)
self.title = 'Title'
self.left = 500
self.top = 500
self.width = 440
self.height = 280
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
# ...
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
#view = TableScene(ex)
ex.show()
sys.exit(app.exec_())
I've tried different kinds of methods but none of them works as it should.
I found the following code as a solution from another topic but it just gives me a black background and the rest of the widgets get laggy.
oImage = QImage("table.png")
sImage = oImage.scaled(QSize(440, 280))
palette = QPalette()
palette.setBrush(QPalette.Window, QBrush(sImage))
self.setPalette(palette)
I don't know if the whole window gets laggy or what really happens but the picture below is a screenshot of a part of the window using the code above, and as you can see it gets all black and the slider shows all the previous position it has been on, sort of laggy anyways.
I've also tried the setStyleSheet but I don't know if it's my syntax that's wrong or if it's a faulty way of doing it. Does anyone know a way of doing it correctly?
EDIT
This is my current window:
This is the picture I'm trying to implement as a background to my current window, the picture called "table.png" :
This is a visualization of what I'm trying to do, and this is made in paint since I don't know how to do it correctly:
And this is what I get if i use the code from the other topic:
One of the possible reasons why a black background appears is that QImage is null. And a QImage is null because the image is invalid or because the image path is incorrect. In this case I think it is the second case since the OP uses a relative path that is prone to errors. The solution is to build the absolute path using the script information such as its location:
import os
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class App(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent=parent)
self.initUI()
def initUI(self):
self.setWindowTitle("Title")
self.setGeometry(500, 500, 440, 280)
oImage = QtGui.QImage(os.path.join(CURRENT_DIR, "table.png"))
sImage = oImage.scaled(QtCore.QSize(440, 280))
palette = QtGui.QPalette()
palette.setBrush(QtGui.QPalette.Window, QtGui.QBrush(sImage))
self.setPalette(palette)
pushbutton = QtWidgets.QPushButton("test", self)
pushbutton.move(100, 100)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ex = App()
ex.show()
sys.exit(app.exec_())
Note: The image provided by the OP has extension .jpg but the one indicated by code is .png, maybe "imgur" has changed the extension.
Note: If the window is resized manually, the following behavior will be observed:
So for this there are 2 possible solutions depending on the developer's criteria:
Set a fixed size: self.setFixedSize(440, 280)
Adapt the image to the size of the window:
import os
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class App(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent=parent)
self.initUI()
def initUI(self):
self.setWindowTitle("Title")
self.setGeometry(500, 500, 440, 280)
pushbutton = QtWidgets.QPushButton("test", self)
pushbutton.move(100, 100)
self.oImage = QtGui.QImage(os.path.join(CURRENT_DIR, "table.png"))
# or QPixmap
# self.oPixmap = QtGui.QPixmap(os.path.join(CURRENT_DIR, "table.png"))
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.drawImage(self.rect(), self.oImage)
# or QPixmap
# painter.drawPixmap(self.rect(), self.oPixmap)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ex = App()
ex.show()
sys.exit(app.exec_())
or
import os
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class App(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent=parent)
self.initUI()
def initUI(self):
self.setWindowTitle("Title")
self.setGeometry(500, 500, 440, 280)
pushbutton = QtWidgets.QPushButton("test", self)
pushbutton.move(100, 100)
self.setStyleSheet(
"""
QMainWindow{
border-image: url(%s) 0 0 0 0 stretch stretch
}
"""
% os.path.join(CURRENT_DIR, "table.png")
)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ex = App()
ex.show()
sys.exit(app.exec_())

PyQt5: How to place a QPixmap on top of another QPixmap?

I would like to place a QPixmap on another QPixmap. Both have the same size, so I would just like to make an overlay. The overlay image has a transparent elipse in the middle. I figure they should be QPixmap format, however I dont know how to place them on top of each other and keep them in place when resizing the window. This is my code displaying how my background images are placed. I have attached a image explaining what i want.
import sys
from PyQt5 import QtGui ,QtWidgets, uic
from PyQt5.QtCore import Qt
class Ergolab(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(Ergolab, self).__init__(*args, **kwargs)
# Load the UI Page
self.ui = uic.loadUi("mainwindow.ui",self)
self.pixmap1 = QtGui.QPixmap('C:/Users/Frede/Desktop/img1.jpg')
self.shoflexLLabel.setPixmap(self.pixmap1.scaled(self.shoflexLLabel.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
self.shoflexLLabel.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.shoflexLLabel.setMinimumSize(150, 150)
self.shoflexLLabel.resize(800, 600)
self.pixmap2 = QtGui.QPixmap('C:/Users/Frede/Desktop/img2.jpg')
self.shoflexRLabel.setPixmap(self.pixmap2.scaled(self.shoflexRLabel.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
self.shoflexRLabel.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.shoflexRLabel.setMinimumSize(150, 150)
self.shoflexRLabel.resize(800, 600)
def resizeEvent(self, event):
scaledSize = self.shoflexLLabel.size()
if not self.shoflexLLabel.pixmap() or scaledSize != self.shoflexLLabel.pixmap().size():
self.updateLabel()
def updateLabel(self):
self.shoflexLLabel.setPixmap(self.pixmap1.scaled(
self.shoflexLLabel.size(), Qt.KeepAspectRatio,
Qt.SmoothTransformation))
self.shoflexRLabel.setPixmap(self.pixmap2.scaled(
self.shoflexRLabel.size(), Qt.KeepAspectRatio,
Qt.SmoothTransformation))
def main():
app = QtWidgets.QApplication(sys.argv)
main = Ergolab()
main.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
This is the result I would like:
You must use QPainter by setting the circle as a clip path:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
label = QtWidgets.QLabel()
self.setCentralWidget(label)
base_pixmap = QtGui.QPixmap("background.png")
overlay_pixmap = QtGui.QPixmap("overlay.png")
radius = 300
r = QtCore.QRectF()
r.setSize(radius * QtCore.QSizeF(1, 1))
r.moveCenter(base_pixmap.rect().center())
path = QtGui.QPainterPath()
path.addEllipse(r)
painter = QtGui.QPainter(base_pixmap)
painter.setRenderHints(
QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform
)
painter.setClipPath(path, QtCore.Qt.IntersectClip)
painter.drawPixmap(QtCore.QPoint(), overlay_pixmap)
painter.end()
label.setPixmap(base_pixmap)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

PyQt video frame update signal (Trigger function after each video frame)

I am creating a video player and I need to draw some polygons on top of it. I am using a QGraphicsScene to create this and I need to update the polygons on screen after each frame. I am currently using the QMediaPlayer paired up with a QGraphicsVideoItem to create this. The problem I am having is that the QMediaPlayer doesn't have a signal that activates on each frame. It has positionChanged(), but this only seems to trigger once every second.
I tried using QMovie since it does send updates on every frame, but it did not display anything. This is the code I used to implement this.
video_view = QGraphicsView()#view to hold video
video_item = QGraphicsVideoItem()#video item for scene
video_scene = QGraphicsScene()#scene for Qgraphics view
video_view.setScene(video_scene)
label = QLabel()
movie = QMovie(self.video_paths[index]) #contains file path
label.setMovie(movie)
video_scene.addWidget(label)
self.vlayout_main_video.addWidget(video_view)
The video file I am using is a .avi file and it is 72Mb large.
I would really appreciate it if somebody could point me in the right direction on how I could do this. I am currently using PyQt5.
Thank you
There are 2 options:
positionChanged is emited every second because the notifyInterval property of QMediaPlayer is set in that period. So you can change that property, for example to 60 ms.
from PyQt5 import QtCore, QtGui, QtWidgets, QtMultimedia, QtMultimediaWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
scene = QtWidgets.QGraphicsScene(self)
self.video_view = QtWidgets.QGraphicsView(scene)
self.setCentralWidget(self.video_view)
self.player = QtMultimedia.QMediaPlayer(self, QtMultimedia.QMediaPlayer.VideoSurface)
self.video_item = QtMultimediaWidgets.QGraphicsVideoItem()
self.player.setVideoOutput(self.video_item)
scene.addItem(self.video_item)
file = "/path/of/video"
self.player.setMedia(QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(file)))
self.player.positionChanged.connect(self.on_positionChanged)
self.player.setNotifyInterval(60)
self.player.play()
#QtCore.pyqtSlot('qint64')
def on_positionChanged(self, p):
print(p, QtCore.QTime.currentTime().toString("hh:mm:ss.zzz"))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
Use the VideoFrameProbed signal from QVideoProbe:
from PyQt5 import QtCore, QtGui, QtWidgets, QtMultimedia, QtMultimediaWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
scene = QtWidgets.QGraphicsScene(self)
self.video_view = QtWidgets.QGraphicsView(scene)
self.setCentralWidget(self.video_view)
self.player = QtMultimedia.QMediaPlayer(self, QtMultimedia.QMediaPlayer.VideoSurface)
self.video_item = QtMultimediaWidgets.QGraphicsVideoItem()
self.player.setVideoOutput(self.video_item)
scene.addItem(self.video_item)
file = "/path/of/video"
self.player.setMedia(QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(file)))
self.player.play()
probe = QtMultimedia.QVideoProbe(self)
probe.videoFrameProbed.connect(self.on_videoFrameProbed)
probe.setSource(self.player)
#QtCore.pyqtSlot()
def on_videoFrameProbed(self):
print(QtCore.QTime.currentTime().toString("hh:mm:ss.zzz"))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

Categories

Resources