How to print current pixel of an QImage? - qt

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:

Related

Why is increasing the DPI of my QImage also increasing the size of my text?

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.

PyQt5 - Overriding mouse cursor and setting Qwidget transparent for input together

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

When I update the QPixmap of a QGraphicsPixmapItem, the image disappears from the scene

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

get the size of QGraphicsScene

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

How to show QProgressBar smoothly?

I'm learning Pyside QProgressBar on MacOSX. When I use QProgressBar like following, it only indicate 0% or 100%. How to make a QProgressBar smoothly? Is there any way to do this?
from PySide.QtGui import QApplication, QProgressBar, QWidget
from PySide.QtCore import QTimer
import time
app = QApplication([])
pbar = QProgressBar()
pbar.setMinimum(0)
pbar.setMaximum(100)
pbar.show()
def drawBar():
global pbar
pbar.update()
t = QTimer()
t.timeout.connect(drawBar)
t.start(100)
for i in range(1,101):
time.sleep(0.1)
pbar.setValue(i)
app.exec_()
Get rid of this code:
for i in range(1,101): # this won't work, because
time.sleep(0.1) # Qt's event loop can't run while
pbar.setValue(i) # you are forcing the thread to sleep
and instead, add a global variable p:
p = 0
and increment it in your drawBar() function:
def drawBar():
global pbar
global p
p = p + 1
pbar.setValue(p)
pbar.update()
QPropertyAnimation is easy to use and it does the smooth change for you.
animation = QtCore.QPropertyAnimation(pbar, "value")
animation.setDuration(???)
animation.setStartValue(0)
animation.setEndValue(100)
animation.start()
Edited post:
Just replace everything between pbar.show() and app.exec() by the code I suggested
Here is the complete code:
from PyQt5.QtWidgets import QWidget, QProgressBar, QApplication
from PyQt5.QtCore import QTimer, QPropertyAnimation
app = QApplication([])
pbar = QProgressBar()
pbar.setMinimum(0)
pbar.setMaximum(100)
pbar.show()
animation = QPropertyAnimation(pbar, "value")
animation.setDuration(2000)
animation.setStartValue(0)
animation.setEndValue(100)
animation.start()
app.exec_()

Resources