I have a window that has six symmetrically placed labels, all showing images (designed using qt-designer with the help of layouts). I would like to resize these images according to the changing window size. I have found some help in previous questions like: PyQt: Detect resizing in Widget-window resized signal
At present, using resizeEvent() in my case does not shrink the images according to the resize function. It is already triggered with the display of my form window thereby making the pushButton useless. Above all, the resulting execution is very slow. My images are of 2058x1536 dimension and displayed transparently.
My qt-designer code is given here: https://pastebin.com/TzM6qiKZ
import Ui_ImageCrop_Test
import sys
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtGui import QPixmap, QImage, QPainter, QColor
from PyQt5.QtCore import Qt
class ImageCrop(Ui_ImageCrop_Test.Ui_MainWindow, QMainWindow):
def __init__(self, parent=None):
super(ImageCrop, self).__init__()
self.setupUi(self)
self.transparency = 220
with open("Img_files.txt") as file:
self.img_files = file.read().splitlines()
self.length = len(self.img_files)
self.pushButton_1.clicked.connect(self.click1)
self.label_1.resizeEvent = self.click1
def click1(self, event):
for i in range(6):
image = QImage(self.img_files[i])
image = image.convertToFormat(QImage.Format_ARGB8565_Premultiplied)
p = QPainter(image)
p.setCompositionMode(QPainter.CompositionMode_DestinationIn)
p.fillRect(image.rect(), QColor(0, 0, 0, self.transparency))
p.end()
pixmap = QPixmap(image)
w = int(self.label_1.width() - 4.0)
h = int(self.label_1.height() - 4.0)
smaller_pixmap = pixmap.scaled(w, h, Qt.IgnoreAspectRatio, Qt.FastTransformation)
if i == 0:
self.label_1.setPixmap(smaller_pixmap)
if i == 1:
self.label_2.setPixmap(smaller_pixmap)
if i == 2:
self.label_3.setPixmap(smaller_pixmap)
if i == 3:
self.label_4.setPixmap(smaller_pixmap)
if i == 4:
self.label_5.setPixmap(smaller_pixmap)
if i == 5:
self.label_6.setPixmap(smaller_pixmap)
def main():
app = QApplication(sys.argv)
form1 = ImageCrop()
form1.show()
app.exec_()
if __name__ == '__main__': main()
Is there any solution to run this code faster? For example, I was thinking to make all my labels turn blank during a mouse click at the edge of my window and then images reappear after the mouse button is released. This does not seem so neat. Also, I am not sure if using paintEvent can reduce my lag. Thank you for your suggestions and comments.
QLabel has the scaledContents property that allows the image to scale automatically:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import Ui_ImageCrop_Test
class ImageCrop(QtWidgets.QMainWindow, Ui_ImageCrop_Test.Ui_MainWindow):
def __init__(self, parent=None):
super(ImageCrop, self).__init__()
self.setupUi(self)
self.pushButton_1.clicked.connect(self.click1)
self.transparency = 220
with open("Img_files.txt") as file:
self.img_files = file.read().splitlines()
#QtCore.pyqtSlot()
def click1(self):
labels = [self.label_1, self.label_2, self.label_3,
self.label_4, self.label_5, self.label_6]
for label, filename in zip(labels, self.img_files):
image = QtGui.QImage(filename)
image = image.convertToFormat(QtGui.QImage.Format_ARGB8565_Premultiplied)
p = QtGui.QPainter(image)
p.setCompositionMode(QtGui.QPainter.CompositionMode_DestinationIn)
p.fillRect(image.rect(), QtGui.QColor(0, 0, 0, self.transparency))
p.end()
pixmap = QtGui.QPixmap(image)
w = int(label.width() - 4.0)
h = int(label.height() - 4.0)
smaller_pixmap = pixmap.scaled(w, h, QtCore.Qt.IgnoreAspectRatio, QtCore.Qt.FastTransformation)
label.setPixmap(smaller_pixmap)
label.setScaledContents(True)
def main():
app = QtWidgets.QApplication(sys.argv)
form1 = ImageCrop()
form1.show()
app.exec_()
if __name__ == '__main__': main()
Related
I made a QGraphicsTextItem and set plain text as its content. After that, I set the QGraphicsTextItem inside the QGraphicsWidget.
My question is, Is it possible to resize the QGraphicsTextItem including its text/contents like in this picture:
This is the video of the resizing that I'm asking for.
If this is possible, how can I apply it to the QGraphicsTextItem?
The first picture is the image of the QGraphicsTextItem but I have no idea how to implement the resizing in the video.
Things I've tried:
I tried using the setTextWidth() and setting it to 0.5 but it's not working.
I also tried using adjustSize() to the QGraphicsTextItem but it's also not working.
Code to reproduce the issue:
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.view = QGraphicsView()
scene = QGraphicsScene()
#before resizing
item = QGraphicsTextItem("Line 1 Line 2 Line 3")
item.setFlags(QGraphicsWidget.ItemIsSelectable)
item.setPos(self.view.mapToScene(2, 2))
scene.addItem(item)
#after resizing
item_1 = QGraphicsTextItem("Line 1\nLine 2\nLine 3")
item_1.setFlags(QGraphicsWidget.ItemIsSelectable)
item_1.setPos(self.view.mapToScene(2, 30))
scene.addItem(item_1)
self.view.setScene(scene)
self.setCentralWidget(self.view)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
I have a PyQt5 GUI application mainwindow that sets geometry based on the screen size. When I call the toogleLogWindow() function, the visibility property of hLayoutWidget_error changes, but window resize does not happen. When I restore the mainwindow manually by clicking the restore button on the right top corner, the resize function works. Can anyone help me understand this behavior? actionToggleLogWindow status is not checked by default.
import sys, os
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUI()
def setupUI(self):
# Set screen size parameters
for i in range(QApplication.desktop().screenCount()):
self.window_size = QApplication.desktop().availableGeometry(i).size()
self.resize(self.window_size)
self.move(QPoint(0, 0))
self._button = QtWidgets.QPushButton(self)
self._button.setText('Test Me')
self._editText = QtWidgets.QComboBox(self)
self._editText.setEditable(True)
self._editText.addItem("")
self._editText.setGeometry(QtCore.QRect(240, 40, 113, 21))
# Connect signal to slot
self._button.clicked.connect(self.toogleLogWindow)
def toogleLogWindow(self):
if self._editText.currentText() == "0":
h = self.window_size.height()
w = int(self.window_size.width()/2)
self.resize(w,h)
elif self._editText.currentText() == "1":
h = self.window_size.height()
w = int(self.window_size.width())
self.resize(w,h)
else:
pass
def get_main_app(argv=[]):
app = QApplication(argv)
win = MainWindow()
win.show()
return app, win
def main():
app, _win = get_main_app(sys.argv)
return app.exec_()
if __name__ == '__main__':
sys.exit(main())
It should be noted that:
It seems that if setting the maximum size of a window before being shown and then displaying it is equivalent to maximizing the window.
When a window is maximized you cannot change its size unless you return it to the previous state, for example if you change the size of the window manually until it is in the normal state then you can just change the size.
So there are several alternatives for this case:
Do not set the full size of the screen:
self.window_size = QApplication.desktop().availableGeometry(i).size() - QSize(10, 10)
Set the size after displaying:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUI()
def setupUI(self):
# Set screen size parameters
for i in range(QApplication.desktop().screenCount()):
self.window_size = QApplication.desktop().availableGeometry(i).size()
self._button = QPushButton(self)
self._button.setText("Test Me")
self._editText = QComboBox(self)
self._editText.setEditable(True)
self._editText.addItem("")
self._editText.setGeometry(QRect(240, 40, 113, 21))
# Connect signal to slot
self._button.clicked.connect(self.toogleLogWindow)
def init_geometry(self):
self.resize(self.window_size)
self.move(QPoint(0, 0))
def toogleLogWindow(self):
if self._editText.currentText() == "0":
h = self.window_size.height()
w = int(self.window_size.width() / 2)
self.resize(w, h)
elif self._editText.currentText() == "1":
h = self.window_size.height()
w = int(self.window_size.width())
self.resize(w, h)
else:
pass
def get_main_app(argv=[]):
app = QApplication(argv)
win = MainWindow()
win.show()
win.init_geometry()
return app, win
I am using a QStackedWidget which has its own enterEvent and leaveEvent. When I move my mouse to the QStackedWidget the enterEvent sets the current index to 1 and on the leaveEvent it sets the current index to 0 so that a different widget is shown on mouse enter and mouse leave in the area of QStackedWidget. It does what I want only if I quickly move my mouse in and out, if I place my mouse too long in the area I get RecursionError: maximum recursion depth exceeded.
Is this because the widgets are changing so fast that the internal stack can't keep up? My question is "How can I make sure this error doesn't occur? I want to display one widget as long as the mouse is over the QStackedWidget and when it is not I want to display the original widget."
The following is the code that I modified (Original Source used buttons to set the index and it is PyQt4)
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QTimeLine
from PyQt5.QtGui import *
class FaderWidget(QWidget):
def __init__(self, old_widget, new_widget):
QWidget.__init__(self, new_widget)
self.old_pixmap = QPixmap(new_widget.size())
old_widget.render(self.old_pixmap)
self.pixmap_opacity = 1.0
self.timeline = QTimeLine()
self.timeline.valueChanged.connect(self.animate)
self.timeline.finished.connect(self.close)
self.timeline.setDuration(333)
self.timeline.start()
self.resize(new_widget.size())
self.show()
def animate(self, value):
self.pixmap_opacity = 1.0 - value
self.repaint()
class StackedWidget(QStackedWidget):
def __init__(self, parent = None):
QStackedWidget.__init__(self, parent)
def setCurrentIndex(self, index):
self.fader_widget = FaderWidget(self.currentWidget(), self.widget(index))
super().setCurrentIndex(index)
def enterEvent(self,event):
self.setCurrentIndex(1)
def leaveEvent(self,event):
self.setCurrentIndex(0)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = QWidget()
stack = StackedWidget()
cal=QCalendarWidget()
stack.addWidget(cal)
editor = QTextEdit()
editor.setPlainText("Hello world! "*100)
stack.addWidget(editor)
layout = QGridLayout(window)
layout.addWidget(stack, 0, 0, 1, 2)
window.show()
sys.exit(app.exec_())
The recursion occurs because when you start the FaderWidget it changes focus and enterEvent is called again which creates a new FaderWidget.
The solution is to verify that the old index is different from the new index to just create the FadeWidget:
import sys
from PyQt5.QtCore import QTimeLine
from PyQt5.QtGui import QPainter, QPixmap
from PyQt5.QtWidgets import (
QApplication,
QCalendarWidget,
QGridLayout,
QStackedWidget,
QTextEdit,
QWidget,
)
class FaderWidget(QWidget):
def __init__(self, old_widget, new_widget):
QWidget.__init__(self, new_widget)
self.pixmap_opacity = 1.0
self.old_pixmap = QPixmap(new_widget.size())
old_widget.render(self.old_pixmap)
self.timeline = QTimeLine()
self.timeline.valueChanged.connect(self.animate)
self.timeline.finished.connect(self.close)
self.timeline.setDuration(333)
self.timeline.start()
self.resize(new_widget.size())
self.show()
def paintEvent(self, event):
painter = QPainter(self)
painter.setOpacity(self.pixmap_opacity)
painter.drawPixmap(0, 0, self.old_pixmap)
def animate(self, value):
self.pixmap_opacity = 1.0 - value
self.update()
class StackedWidget(QStackedWidget):
def setCurrentIndex(self, index):
if self.currentIndex() != index:
self.fader_widget = FaderWidget(self.currentWidget(), self.widget(index))
super().setCurrentIndex(index)
def enterEvent(self, event):
self.setCurrentIndex(1)
def leaveEvent(self, event):
self.setCurrentIndex(0)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = QWidget()
stack = StackedWidget()
cal = QCalendarWidget()
stack.addWidget(cal)
editor = QTextEdit()
editor.setPlainText("Hello world! " * 100)
stack.addWidget(editor)
layout = QGridLayout(window)
layout.addWidget(stack, 0, 0, 1, 2)
window.show()
sys.exit(app.exec_())
The application should display an image from a grid of pixels. The color of all pixels should change 30 times per second. After starting the app works for a few seconds and after that the pixels updates will stop. When window resized the pixels updating resumes. With a long-term update of the pixel network, the CPU consumption increases greatly. I tested it on Windows and there the pixel update stops almost immediately. Used the Threading library, and the PyQt5 library to display the interface. How can I make a stable pixels updates in grid?
Here is my code:
from random import choice, randint
from sys import argv
from threading import Thread
from time import sleep
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QIcon, QPalette
from PyQt5.QtWidgets import (QApplication, QFrame, QGridLayout, QMainWindow,
QMenu, QToolBar, QWidget)
class EmulatorWindow(QMainWindow):
spacing = None
app_running = True
def __init__(self, spacing=1, screen_resolution=(16, 16)):
super().__init__()
self.spacing = spacing
# Pixel Grid
self.grid = QGridLayout()
self.grid.setContentsMargins(0, 0, 0, 0)
self.grid.setSpacing(self.spacing)
for x in range(0, screen_resolution[0]):
for y in range(0, screen_resolution[1]):
pixel = QWidget()
pixel.setAutoFillBackground(True)
self.grid.addWidget(pixel, y, x)
# Application thread
self.applicationThread = Thread(target=self.applicationRunner, args=())
self.applicationThread.start()
# Window Properties
self.setGeometry(300, 300, 450, 495)
self.setWindowTitle('Pixels Grid')
widget = QWidget()
widget.setLayout(self.grid)
self.setCentralWidget(widget)
self.setMinimumSize(QSize(450, 495))
self.show()
def applicationRunner(self):
color = 0
while True:
if self.app_running == False:
break
for x in range(0, 16):
for y in range(0, 16):
self.grid.itemAtPosition(x, y).widget().setPalette(QPalette([Qt.red, Qt.blue, Qt.green][color]))
sleep(1 / 30)
color = color + 1
if color == 3:
color = 0
def switchSpacing(self):
self.grid.setSpacing(self.spacing if self.grid.spacing() == 0 else 0)
if __name__ == '__main__':
app = QApplication(argv)
ex = EmulatorWindow()
app.exec_()
ex.app_running = False
Activity Monitor Screenshot
In the screenshot is MenuBar and ToolBar, but, they do not affect the problem
Application Screenshot
The reason why the GUI is not updated with the thread is that Qt prohibits the updating of graphic elements from another thread, for more information read GUI Thread and Worker Thread. A thread should not be used if the task is not heavy, for example if we test when it consumes changing a color using the following code:
t = QtCore.QElapsedTimer()
t.start()
pal = QtGui.QPalette([QtCore.Qt.red, QtCore.Qt.blue, QtCore.Qt.green][self._color])
for x in range(self.grid.rowCount()):
for y in range(self.grid.columnCount()):
w = self.grid.itemAtPosition(x, y).widget()
if w is not None:
w.setPalette(pal)
self._color = (self._color +1) % 3
print(t.elapsed(), " milliseconds")
Obtaining the following results:
4 milliseconds
2 milliseconds
2 milliseconds
3 milliseconds
2 milliseconds
3 milliseconds
3 milliseconds
2 milliseconds
3 milliseconds
# ...
Supporting my statement that it is not a heavy task so in this case you should use a QTimer that allows you to do periodic tasks:
from PyQt5 import QtCore, QtGui, QtWidgets
class EmulatorWindow(QtWidgets.QMainWindow):
def __init__(self, spacing=1, screen_resolution=(16, 16)):
super().__init__()
self.spacing = spacing
# Pixel Grid
self.grid = QtWidgets.QGridLayout()
self.grid.setContentsMargins(0, 0, 0, 0)
self.grid.setSpacing(self.spacing)
for x in range(screen_resolution[0]):
for y in range(screen_resolution[1]):
pixel = QtWidgets.QWidget(autoFillBackground=True)
self.grid.addWidget(pixel, y, x)
# Window Properties
self.setGeometry(300, 300, 450, 495)
self.setWindowTitle('Pixels Grid')
widget = QtWidgets.QWidget()
self.setCentralWidget(widget)
widget.setLayout(self.grid)
self.setMinimumSize(QtCore.QSize(450, 495))
self._color = 0
timer = QtCore.QTimer(self, interval=1000/30, timeout=self.applicationRunner)
timer.start()
#QtCore.pyqtSlot()
def applicationRunner(self):
pal = QtGui.QPalette([QtCore.Qt.red, QtCore.Qt.blue, QtCore.Qt.green][self._color])
for x in range(self.grid.rowCount()):
for y in range(self.grid.columnCount()):
w = self.grid.itemAtPosition(x, y).widget()
if w is not None:
w.setPalette(pal)
self._color = (self._color +1) % 3
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
ex = EmulatorWindow()
ex.show()
sys.exit(app.exec_())
What am I doing wrong here? I expect that "image1.jpg" is shown over "image.jpg" ,at position where I've clicked, but it does not. Here is my code (image1.jpg is 10 times smaller then image.jpg):
import sys
from PyQt4 import QtGui, QtCore
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class DrawImage(QMainWindow):
def __init__(self, parent=None):
super(QMainWindow, self).__init__(parent)
self.setWindowTitle('Select Window')
self.local_image = QImage('image.JPG')
self.local_grview = QGraphicsView()
self.setCentralWidget( self.local_grview )
self.local_scene = QGraphicsScene()
self.image_format = self.local_image.format()
self.pixMapItem = QGraphicsPixmapItem(QPixmap(self.local_image), None, self.local_scene)
self.pixMapItem.setZValue(10.0)
self.local_grview.setScene( self.local_scene )
self.pixMapItem.mousePressEvent = self.pixelSelect
def pixelSelect( self, event ):
position = QPoint( event.pos().x(), event.pos().y())
local_image = QImage('image1.JPG')
pixMapItem = QGraphicsPixmapItem(QPixmap(local_image), self.pixMapItem, self.local_scene)
pixMapItem.setZValue(100.0)
pixMapItem.setPos(position.x(), position.y());
print position, self.pixMapItem.zValue(), pixMapItem.zValue()
def main():
app = QtGui.QApplication(sys.argv)
form = DrawImage()
form.show()
app.exec_()
if __name__ == '__main__':
main()
Edit 1 I've tried self.local_grview.setUpdatesEnabled(True) and updating scene at the end of pixelSelect method: self.local_grview.update() , nothing changed
Your code looks correct and works as expected for me ie. the second smaller image displays over the first.
Have you tried displaying just the second image? Perhaps you have an incorrect path which is causing your second image not to show.