I'm trying to get the boundingRect() of a subclass of a QGraphicsRectItem while it is being moved by the user. However, as the rectangle is moved in the scene, the rect returned by boundingRect() always returns the original value for the bounding rectangle. The example below demonstrates the issue. The value of boundinRect() is printed for every mouse move while the RectItem is moved around the scene. Why isn't boundingRect() returning the new bounding rectangle? What am I doing wrong?
from sys import argv
from PyQt4.Qt import QApplication
from PyQt4.QtCore import QPointF, QRectF, Qt
from PyQt4.QtGui import (
QGraphicsItem,
QGraphicsRectItem,
QGraphicsScene,
QGraphicsView,
)
class RectItem(QGraphicsRectItem):
def __init__(self, parent):
super(RectItem, self).__init__(parent)
self.setFlag(QGraphicsItem.ItemIsMovable, enabled=True)
self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=True)
def mouseMoveEvent(self, event):
super(RectItem, self).mouseMoveEvent(event)
print self.boundingRect()
class ViewItem(QGraphicsRectItem):
def __init__(self, x, y, width, height, parent=None, scene=None):
super(ViewItem, self).__init__(parent, scene)
self.setRect(QRectF(x, y, width, height))
self.create_child_item(100, 100)
def create_child_item(self, x, y):
self.child_item = RectItem(self)
self.child_item.setRect(x, y, 200, 100)
self.child_item.setSelected(True)
class View(QGraphicsView):
def __init__(self, parent=None):
super(View, self).__init__(parent)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setInteractive(True)
self.graphics_scene = QGraphicsScene(self)
self.setScene(self.graphics_scene)
self.setAlignment(Qt.AlignTop)
self.add_view_item()
def add_view_item(self):
self.view_item = ViewItem(0.0, 0.0, 1024.0, 768.0)
self.graphics_scene.addItem(self.view_item)
if __name__ == '__main__':
app = QApplication(argv)
view = View()
view.setGeometry(100, 100, 1024, 768)
view.setWindowTitle('View Test')
view.show()
app.exec_()
QGraphicsItem's boundingRect() is always in its own coordinate. Moving a item doesn't change its bounding rect relative to itself.
If you need the bounding box of the item relative to the scene, use sceneBoundingRect()
Related
Typically, when we increase DPI without changing anything else, the image/text should decrease in size. This is because more dots/pixels are drawn per inch, hence, decreasing the size. However, I have found the opposite behaviour here where increasing the DPI will also increase the size of my drawn text. This is not what I expected, would anybody know why?
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QFont, QImage, QPainter
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget
DEFAULT_WIDTH = DEFAULT_HEIGHT = 250
class QPainterWidget(QWidget):
def __init__(self):
super().__init__()
self.image = QImage(DEFAULT_WIDTH, DEFAULT_HEIGHT, QImage.Format.Format_RGB32)
self.image.fill(Qt.GlobalColor.green)
self.image.setDevicePixelRatio(1)
print(self.image.dotsPerMeterX())
self.image.setDotsPerMeterX(25000) # 5000, 10000, 15000, 20000
self.image.setDotsPerMeterY(25000)
painter = QPainter(self.image)
point_size_font = QFont('arial')
point_size_font.setPointSizeF(1)
painter.setFont(point_size_font)
painter.drawText(0, 0, DEFAULT_WIDTH//2, DEFAULT_HEIGHT//2, Qt.AlignmentFlag.AlignCenter, "point font text")
painter.end()
def paintEvent(self, event):
painter = QPainter(self)
painter.drawImage(0, 0, self.image)
painter.end()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.resize(DEFAULT_WIDTH, DEFAULT_HEIGHT)
self.setCentralWidget(QPainterWidget())
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
I'm not really sure where the confusion lies. You have...
self.image.setDotsPerMeterX(25000)
self.image.setDotsPerMeterY(25000)
which tells Qt that the QImage is to be treated as having a resolution of 625 dpi. You then select a font with size 1pt using...
point_size_font = QFont('arial')
point_size_font.setPointSizeF(1)
painter.setFont(point_size_font)
1pt is 1/72 inches which combined with the 625 dpi resolution evaluates to approximately 9 'pixels'. Hence the underlying paint device will draw the text with a 9 pixel high font.
I have an RGB888 format qImage defined as follows:
int sizeX = 300; int sizeY = 300;
QImage img = QImage(sizeX, sizeY, QImage::Format_RGB888);
I wish to print current pixel of img one by one. So, I followed the example here:`
for(int i=0; i<sizeX; i++){
for(int j=0; j<sizeY; j++){
img.setPixel(i, j, qRgb(rand()%256, rand()%256, rand()%256));
}
}
QGraphicsScene *graphic = new QGraphicsScene(this);
graphic->addPixmap(QPixmap::fromImage(img));
ui->graphicsView->setScene(graphic);
But it prints the whole QImage. I want to print one by one.
Thanks. Best regards.
As JarMan commented, you are looking for "animation".
Drawing pixel by pixel, and updating the UI for every pixel, may use animation solution as described in the following post.
In Qt5 we have to execute QtWidgets.QApplication.processEvents() for forcing Qt to redraw.
Note:
I implemented the code in Python (with PyQt5), the syntax is different from C++, but the concept is the same.
Initialization stage:
The main class is inherited from QGraphicsView class.
Create QGraphicsScene object:
graphic = QGraphicsScene(0, 0, sizeX, sizeY)
Create QPixmap object from image:
pixmap = QPixmap.fromImage(img)
add addPixmap to the graphic object, and keep the returned reference:
pixmap_item = graphic.addPixmap(pixmap)
Execute setScene(graphic)
Animation:
In the animation loop, we have to execute setPixmap on every iteration for updating the entire image.
(The is a way for drawing pixel by pixel, but it's a deviation from your question).
Simplified code for updating the image (executed each iteration):
img.setPixel(i, j, qRgb(randrange(256), randrange(256), randrange(256)));
pixmap = QPixmap.fromImage(img)
pixmap_item.setPixmap(pixmap)
QtWidgets.QApplication.processEvents()
Complete Python code sample:
import sys
from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView, QApplication
from PyQt5 import QtWidgets
from PyQt5.QtGui import QImage, QPixmap, QPainter, QColor, qRgb
from random import randrange
import time
class Example(QGraphicsView):
def __init__(self):
super().__init__()
self.mPixmap = QPixmap()
self.sizeX = 300
self.sizeY = 300
self.img = QImage(self.sizeX, self.sizeY, QImage.Format_RGB888)
self.img.fill(QColor(60, 60, 60)) # Fill image with gray color
self.initUI()
def initUI(self):
self.graphic = QGraphicsScene(0, 0, self.sizeX, self.sizeY)
self.pixmap = QPixmap.fromImage(self.img)
self.pixmap_item = self.graphic.addPixmap(self.pixmap)
self.setScene(self.graphic)
self.show()
def doAnimation(self):
for i in range(self.sizeX):
for j in range(self.sizeY):
self.img.setPixel(i, j, qRgb(randrange(256), randrange(256), randrange(256)));
pixmap = QPixmap.fromImage(self.img)
self.pixmap_item.setPixmap(pixmap)
QtWidgets.QApplication.processEvents()
time.sleep(0.001) # Sleep 1msec (In Python it's slow enough without the sleep...)
if __name__ == '__main__':
app = QApplication(sys.argv)
example = Example()
example.doAnimation()
app.exec_()
I hope it's not too much Python for you...
Sample output:
I am trying to write a simple application in PyQt5 with one QWidget that has the following two behaviors:
Overrides the mouse cursor
Transparent for input; meaning that it can ignore mouse inputs and send it to the background widget or OS UI (i.e. like an overlaying behavior)
I can achieve each behavior but individually i.e. When I combine them together the cursor returns to it's default state (i.e. I lose the ability to override it)!
I wonder if this is achievable in QT?
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QWidget
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
# Goal no. 1: override mouse cursor
QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
w = QWidget()
w.setWindowOpacity(0.1)
# Goal no.2: Make widget transparent for input
w.setWindowFlags(w.windowFlags()
| QtCore.Qt.WindowTransparentForInput | QtCore.Qt.WindowStaysOnTopHint)
w.resize(900, 900)
w.show()
sys.exit(app.exec_())
Based on the great advice of #ekhumoro:
I found a solution to use a transparent widget and draw an animated shape on the screen that will follow the mouse cursor
The steps are:
Create a fully transparent widget (i.e. completely hidden)
This widget shall cover the whole screen; Similar to screenshot applications
This widget is then configured to ignore all mouse inputs ⇒ It should allow mouse cursor to interact with all UI behind it exactly as if this widget does not exist as all (e.g. user can click through this widget)
Then shapes/texts/animations can be drawn and moved on that transparent widget
The coordinates of these shapes can be determined based on the mouse cursor coordinates
The only limitation of this approach is that we not override the system cursor but we will be shown animations next to it which I believe satisfies the original use case
from PySide6 import QtCore, QtWidgets, QtGui
from PySide6.QtCore import QTimer
from PySide6.QtGui import QPainter, QFont
from PySide6.QtWidgets import QApplication
class TransparentWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(TransparentWidget, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_NoSystemBackground)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
# https://stackoverflow.com/questions/52827296/pyside2-pass-mouse-events-to-system
self.setWindowFlags(self.windowFlags()
# | QtCore.Qt.WindowTransparentForInput
| QtCore.Qt.X11BypassWindowManagerHint
| QtCore.Qt.WindowStaysOnTopHint
| QtCore.Qt.WA_MouseNoMask
| QtCore.Qt.WA_TranslucentBackground
| QtCore.Qt.FramelessWindowHint)
self.x = 0
self.y = 0
self.number = 4
def paintEvent(self, event):
print(f"paintEvent {self.x} {self.y}")
if not self.number:
return
painter = QPainter()
painter.begin(self)
font = QFont()
font.setBold(True)
font.setPixelSize(15)
painter.setFont(font)
pen = QtGui.QPen()
pen.setWidth(3)
pen.setColor(QtCore.Qt.red)
painter.setPen(pen)
painter.setBrush(QtCore.Qt.white)
painter.drawEllipse(self.x + 15, self.y + 15, 30, 30)
pen = QtGui.QPen()
pen.setColor(QtCore.Qt.black)
painter.setPen(pen)
painter.drawText(self.x + 26, self.y + 35, str(self.number))
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = TransparentWidget()
w.showFullScreen()
def func():
w.x += 1
w.y += 1
w.update()
timer = QTimer()
timer.timeout.connect(func)
timer.start(10)
sys.exit(app.exec())
I have this simple scene showing a QGraphicsPixmapItem. I update its content periodically from a QThread. The first time it is updated, it displays correctly. However the second time around, the image disappears. Any idea why?
import numpy as np
from PyQt5.QtCore import QThread
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QGraphicsPixmapItem, QGraphicsScene, QGraphicsView, QMainWindow
import sys
app = QApplication([])
window = QMainWindow()
window.setGeometry(100, 100, 400, 400)
view = QGraphicsView()
scene = QGraphicsScene()
gpix = QGraphicsPixmapItem()
scene.addItem(gpix)
view.setScene(scene)
window.setCentralWidget(view)
window.show()
class Thread(QThread):
def run(self):
while True:
im = np.random.randint(255, size=(256, 256), dtype=np.uint8)
qim = QImage(im, im.shape[1], im.shape[0], QImage.Format_Grayscale8)
pix = QPixmap.fromImage(qim)
gpix.setPixmap(pix)
self.sleep(1)
thread = Thread()
thread.finished.connect(app.exit)
thread.start()
sys.exit(app.exec_())
There are two basic issues. Firstly you are potentially accessing the same variable gpix simultaneously from multiple threads. Secondly you are trying to use QPixmap from a secondary thread (a thread other than that on which main is running).
To fix the first problem you can use a signal/slot to notify the main thread that a new image is available.
For the second problem you can defer the creation of the QPixmap from the QImage until the point at which the signal is handled.
import numpy as np
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QGraphicsPixmapItem, QGraphicsScene, QGraphicsView, QMainWindow
import sys
app = QApplication([])
window = QMainWindow()
window.setGeometry(100, 100, 400, 400)
view = QGraphicsView()
scene = QGraphicsScene()
gpix = QGraphicsPixmapItem()
scene.addItem(gpix)
view.setScene(scene)
window.setCentralWidget(view)
window.show()
class Thread(QThread):
# Add a signal that accepts a QImage.
sig_new_image = pyqtSignal(QImage)
def run(self):
while True:
im = np.random.randint(255, size=(256, 256), dtype=np.uint8)
qim = QImage(im, im.shape[1], im.shape[0], QImage.Format_Grayscale8)
pix = QPixmap.fromImage(qim)
# Emit the signal with the new QImage.
self.sig_new_image.emit(qim)
self.sleep(1)
thread = Thread()
thread.finished.connect(app.exit)
# Connect the Thread.sig_new_image signal to a lambda that will update
# the pixmap accordingly.
thread.sig_new_image.connect(lambda i: gpix.setPixmap(QPixmap.fromImage(i)))
thread.start()
sys.exit(app.exec_())
I want to do the following:
create a QGraphicsView
connect it to QGraphicsScene
Get the size of the QGraphicsScene
create a QGraphicsRectItem with this size
My problem is how to get the size of the scene
In all the examples I saw that they put constants in setSceneRect()
The following is my code
class CharIdentifierInput(QDialog, Ui_CharIdentifierInput):
"""description of class"""
def __init__(self, parent):
QDialog.__init__(self, parent)
self.setupUi(self)
self.setFixedSize(self.width(), self.height())
self.leftMouseButtonPressed = False
self.createGui()
def createGui(self):
self.graphicsScene = QGraphicsScene()
self.graphicsView.setScene(self.graphicsScene)
# I want to replace this line with a line that sets to the actual scene size
self.graphicsScene.setSceneRect(0,0,368,235)
mainItem = MyQtCharIdentifierMain(self.graphicsScene.sceneRect())
self.graphicsScene.addItem(mainItem)
mainItem.setPos(0,0)
Getters of the dimensions of the sceneRect are
QGraphicsScene.sceneRect(), QGraphicsScene.width() and QGraphicsScene.height()
if the sceneRect changes, QGraphicsScene.sceneRectChanged -signal is emitted, sendig the new sceneRect as parameter.
If QGraphicsView.setSceneRect() is not set, QGraphicsView.sceneRect() will
return the same value as QGraphicsScene.sceneRect() and it changes with QGraphicsScene.sceneRect(), see documentation QGraphicsView and documentation QGraphicsScene