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

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.

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

Overlaying Images in a QLabel for Onion Skinning

I'm trying to produce an onion skin effect using a QLabel in PyQt. In the simplified example below, three images are loaded in and drawn to the label using QPainter.
from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QLabel
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QImage, QPixmap, QPainter
import sys
from pathlib import Path
class MainWindow(QWidget):
def __init__(self):
super().__init__()
# -------------------------------------------------------------
# Define the display.
self.display = QLabel()
# -------------------------------------------------------------
# Import the frames
frame_1 = QImage(str(Path(f'fixtures/test_onion_skin/frame_{1}.png')))
frame_2 = QImage(str(Path(f'fixtures/test_onion_skin/frame_{2}.png')))
frame_3 = QImage(str(Path(f'fixtures/test_onion_skin/frame_{3}.png')))
# -------------------------------------------------------------
# Populate the display
frame_1_scaled = frame_1.scaled(self.size(), Qt.KeepAspectRatio)
frame_2_scaled = frame_2.scaled(self.size(), Qt.KeepAspectRatio)
frame_3_scaled = frame_3.scaled(self.size(), Qt.KeepAspectRatio)
base_pixmap = QPixmap(frame_1_scaled.size())
painter = QPainter(base_pixmap)
painter.drawImage(QPoint(), frame_3_scaled)
painter.setOpacity(0.5)
painter.drawImage(QPoint(), frame_2_scaled)
painter.setOpacity(0.3)
painter.drawImage(QPoint(), frame_1_scaled)
painter.end()
self.display.setPixmap(base_pixmap)
# -------------------------------------------------------------
# Set the layout.
layout = QGridLayout()
layout.addWidget(self.display, 0, 0)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Ideally, the last image would show as fully opaque, with earlier images having an increasingly higher transparency. Instead, I'm getting an output where all three images blend together equally. This seems like a simple problem to fix, but my 'Google Fu' hasn't yielded much this time around.
Edit
Here are the image files. They seem have automatically been converted to .jpg unfortunately. If there's a better way to include them please let me know.
frame_1
frame_2
frame_3
Edit 2
After some experimentation I've decided to compromise and allow some 'blending' of the base image. I'm working with raw images from a camera device, so the background of each image is always going to be non-transparent.
In case anyone is interested here is the code:
from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QLabel, QSizePolicy
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QImage, QPixmap, QPainter
import sys
from pathlib import Path
class MainWindow(QWidget):
def __init__(self):
super().__init__()
# -------------------------------------------------------------
# Define values used for image painting.
self.first_opacity = 1 # Set first image to fully opaque so it does not blend into background.
self.falloff_value = 0.15 # The opacity of the second image.
self.falloff_rate = 0.5 # A factor used to decrement subsequent image transparencies.
# -------------------------------------------------------------
# Define the display.
self.display = QLabel()
self.display.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
self.display.setMinimumSize(1, 1)
# -------------------------------------------------------------
# Import frames.
self.images = []
for i in range(1, 4, 1):
self.images.append(QImage(str(Path(f'fixtures/test_onion_skin/frame_{i}.png'))))
# -------------------------------------------------------------
# Set the display.
self.update_display()
# -------------------------------------------------------------
# Set the layout.
layout = QGridLayout()
layout.addWidget(self.display, 0, 0)
self.setLayout(layout)
def update_display(self):
# -------------------------------------------------------------
# Define the base pixmap on which to merge images.
base_pixmap = QPixmap(self.display.size())
base_pixmap.fill(Qt.transparent)
# -------------------------------------------------------------
# Preform paint cycle for images.
painter = QPainter(base_pixmap)
for (image, opacity) in zip(reversed(self.images), reversed(self.get_opacities(len(self.images)))):
painter.setOpacity(opacity)
painter.drawImage(QPoint(), image.scaled(base_pixmap.size(), Qt.KeepAspectRatio))
painter.end()
# -------------------------------------------------------------
self.display.setPixmap(base_pixmap)
def get_opacities(self, num_images):
# -------------------------------------------------------------
# Define a list to store image opacity values.
opacities = [self.first_opacity]
value = self.falloff_value
# -------------------------------------------------------------
# Calculate additional opacity values if more than one image is desired.
if num_images > 1:
num_decrements = num_images - 1
for i in range(1, num_decrements + 1, 1):
opacities.insert(0, value)
value *= self.falloff_rate
# -------------------------------------------------------------
return opacities
def resizeEvent(self, event):
self.update_display()
event.accept()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Since the OP does not provide the images then the problem can be caused by:
The order of how opacity is set.
The background color of the image is not transparent.
For my demo I will use this gif and since the background is not transparent (which would be ideal) then I will apply a mask when I paint each image.
import os
from pathlib import Path
import sys
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QColor, QImageReader, QPainter, QPixmap, QRegion
from PyQt5.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget
CURRENT_DIRECTORY = Path(__file__).resolve().parent
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.display = QLabel(alignment=Qt.AlignCenter)
lay = QVBoxLayout(self)
lay.addWidget(self.display)
background_color = QColor("white")
filename = os.fspath(CURRENT_DIRECTORY / "Animhorse.gif")
image_reader = QImageReader(filename)
pixmap = QPixmap(image_reader.size())
pixmap.fill(background_color)
images = []
while image_reader.canRead():
images.append(image_reader.read())
painter = QPainter(pixmap)
for image, opacity in zip(images[3:6], (0.3, 0.7, 1.0)):
painter.setOpacity(opacity)
p = QPixmap.fromImage(image)
mask = p.createMaskFromColor(background_color, Qt.MaskInColor)
painter.setClipRegion(QRegion(mask))
painter.drawImage(QPoint(), image)
painter.end()
self.display.setPixmap(pixmap)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

QPixmap / QPainter showing black window background

I am following an example from the PyQt5 book by Martin Fitzpatrick. When I run the following code, the background is black and the line is not drawn:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtCore import Qt
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.label = QtWidgets.QLabel()
canvas = QtGui.QPixmap(400, 300)
self.label.setPixmap(canvas)
self.setCentralWidget(self.label)
self.draw_something()
def draw_something(self):
painter = QtGui.QPainter(self.label.pixmap())
painter.drawLine(10, 10, 300, 200)
painter.end()
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
The expected result is on the left:
By default the memory that a QPixmap uses is not cleaned by efficiency so it has the bytes not altered as indicated by the docs:
QPixmap::QPixmap(int width, int height) Constructs a pixmap with the
given width and height. If either width or height is zero, a null
pixmap is constructed.
Warning: This will create a QPixmap with uninitialized data. Call
fill() to fill the pixmap with an appropriate color before drawing
onto it with QPainter.
(emphasis mine)
The solution is to use fill to set the background color:
canvas = QtGui.QPixmap(400, 300)
canvas.fill(QtGui.QColor("white"))

Removing focus border from QSlider in PyQt

I am looking for a solution to remove a terrible looking focus rectangle over a QSlider.
In addition that it looks terrible, it covers the ticks and is not entirely drawn.
This is an ancient issue; I remember stumbling into it many years ago. The workarounds
mentioned (e.g. Removing dotted border without setting NoFocus in Windows PyQt) do not work for a slider on my Linux system.
Setting outline to none or using the Qt.WA_MacShowFocusRect does not work on Linux.
slider.setStyleSheet('QSlider { outline:none; }')
slider.setAttribute(Qt.WA_MacShowFocusRect, 0)
The only thing that 'works' is setting the focus policy to Qt.NoFocus, which is not a real
solution, since one might want to work with keyboard.
slider.setFocusPolicy(Qt.NoFocus)
I am on Linux and the issue is on all available themes. Source code:
#!/usr/bin/python
from PyQt5.QtWidgets import (QWidget, QSlider, QHBoxLayout,
QLabel, QApplication)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
hbox = QHBoxLayout()
sld = QSlider(Qt.Horizontal, self)
# sld.setFocusPolicy(Qt.NoFocus)
# sld.setStyleSheet('QSlider { outline:none; padding: 0 2 0 2;}')
# sld.setAttribute(Qt.WA_MacShowFocusRect, 0)
sld.setTickPosition(QSlider.TicksAbove)
sld.setRange(0, 100)
sld.setPageStep(5)
sld.valueChanged.connect(self.changeValue)
self.label = QLabel("0", self)
self.label.setMinimumWidth(80)
hbox.addWidget(sld)
hbox.addSpacing(15)
hbox.addWidget(self.label)
self.setLayout(hbox)
self.setGeometry(300, 300, 350, 250)
self.setWindowTitle('QSlider')
self.show()
def changeValue(self, value):
self.label.setText(str(value))
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Screenshot:

How to make QTabWidget look transparent?

I want to make a simple GUI desktop application using python.
I've made a simple window with a background image, and I added a tab-widget on the right corner. It worked properly. But the tab-bar and tab contents area are white.
What I want is that the tab-widget's background shows it's parent window's background image (which means it's transparent). But I don't know how to do it.
Here is my working environment, code and screen shot:
Working environment:
Windows 7
Python 3.4
PyQt5 5.5
Source code:
# coding: utf-8
# There are some unnecessary module.
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QTabWidget, QWidget, QLabel, QHBoxLayout, QVBoxLayout, QGridLayout
from PyQt5.QtGui import QPixmap, QPalette, QBrush, QColor
from PyQt5.QtCore import Qt
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
tab1 = QWidget()
tab2 = QWidget()
vbox1 = self.makeTab1()
vbox2 = self.makeTab2()
tab1.setLayout(vbox1)
tab2.setLayout(vbox2)
tabs = QTabWidget()
tabs.addTab(tab1, "firstTab")
tabs.addTab(tab2, "secondTab")
picLabel = QLabel(self)
picFile = 'background_img.jpg'
pixmap = QPixmap(picFile)
palette = QPalette()
palette.setBrush(QPalette.Background, QBrush(pixmap))
hboxEX = QHBoxLayout()
hboxEX.addStretch(2)
hboxEX.addWidget(tabs)
hboxEX.setStretchFactor(tabs, 1)
vboxEX = QVBoxLayout()
vboxEX.addStretch(1)
vboxEX.addLayout(hboxEX)
vboxEX.setStretchFactor(hboxEX, 1)
self.setLayout(vboxEX)
self.setPalette(palette)
self.resize(pixmap.width(), pixmap.height())
self.show()
def makeTab1(self):
lbl1 = QLabel(self)
lbl2 = QLabel(self)
lbl3 = QLabel(self)
lbl1.setText("Google")
lbl2.setText("WikiPedia")
lbl3.setText("StackOverflow")
lbl1.setOpenExternalLinks(True)
lbl2.setOpenExternalLinks(True)
lbl3.setOpenExternalLinks(True)
vbox1 = QVBoxLayout()
vbox1.addWidget(lbl1)
vbox1.addWidget(lbl2)
vbox1.addWidget(lbl3)
return vbox1
def makeTab2(self):
lbl4 = QLabel(self)
lbl5 = QLabel(self)
lbl6 = QLabel(self)
lbl4.setText("Python")
lbl5.setText("CentOS")
lbl6.setText("MariaDB")
lbl4.setOpenExternalLinks(True)
lbl5.setOpenExternalLinks(True)
lbl6.setOpenExternalLinks(True)
vbox2 = QVBoxLayout()
vbox2.addWidget(lbl4)
vbox2.addWidget(lbl5)
vbox2.addWidget(lbl6)
return vbox2
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Screen shot:
What I tried:
I added these codes and it changed the tab's color. But it didn't make the tab area look transparent:
tabPalette = QPalette()
tabPalette.setColor(QPalette.Background, QColor("cyan"))
tab1.setAutoFillBackground(True)
tab1.setPalette(tabPalette)
I think it's a Windows problem. You might want to try to switch your application to a different style.
See this page of PyQt reference http://pyqt.sourceforge.net/Docs/PyQt4/qstyle.html
some sample code:
def changeStyle(self, styleName):
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create(styleName))
self.changePalette()
call it with:
changeStyle("plastique")

Categories

Resources