How to make QImage aware of updated memory buffer?
-
I manually twiddle the memory returned by
bits()
, and i want thecacheKey
to be invaliated, so it knows the image cached on the video card is stale. I want to mix QPainter calls with my manual bit-twiddles. There doesn't seem to be a way to do this.I have way too much legacy code that does manual bit twiddling and buffer iteration for graphics ops, so it's not an option to just rewrite everything to "use QPainter calls only" on my buffers.
I saw this post:
https://stackoverflow.com/questions/55869926/qt-how-to-make-qimage-aware-of-updated-memory-bufferbut the solution offered seems like a hack, and could be a performance problem too since i think it will copy the bits from the mem buffer back to the video card every time?
Is there or will there be a sanctioned method for this?
-
@davecotter QImage is an in-memory representation of an image. The underlying buffer is not directly connected to any video memory. The software (yours and the display manager's) cannot avoid copying data from the QImage in the process of displaying it: scaling, cropping, colour depth conversion, bit ordering, composition, all sorts... Exactly what is happening depends on how it gets to a screen. Since you do not divulge how this QImage gets onto a screen there's no specifics we can help with.
The offered solution creates a new QImage object without copying the buffer, e.g.:
QImage image(m_buffer.data(), _width, _height, QImage::Format_Mono);
This buffer copy appears to be what you are concerned about, but needn't be.
-
Okay, the QImage is sent to the screen via QGraphicsPixmapItem to a QGraphicsScene.
When i call pix(), the bits change, as if they get copied back from the graphics card? -
i tried calling bits() after every API call, but that slowed things down considerably, to like 1fps. without calling bits i get > 30fps.
I just wish there was a call to invalidate the cache WITHOUT changing my bits, my bits should stay the same, but they CHANGE, why is that? i want to twiddle my bits, and invalidate the cache, and have the bits that I PUT THERE get displayed, then i want to chnage my bits again, and have them display again. but if i call bits(), the buffer i get back is now a DIFFERENT buffer, which confuses me. -
@davecotter As documented, calling the non-const QImage::bits() performs a deep copy i.e. a different buffer. Image uses implicit sharing so:
QImage image1(256, 256, QImage::Format_RGB32); QImage image2 = image1;
results in the two QImage objects sharing the same buffer. If you modify either object using QImage methods then a deep-copy will occur and each copy will become independent. QImage::bits() gives you something you can modify so, if the QImage is sharing a buffer a deep-copy will happen.
Something like this, where there is only one persistent QImage should avoid the issue:
// member vars QVector<uchar> m_buffer; // you can bit twiddle in here behind QImage's back QImage m_image; // Constructor m_buffer.resize(NUMBER_OF_BYTES_IN_IMAGE); m_image = QImage(m_buffer.data(), _width, _height_, QImage::Format_RGB32); // Update time // twiddle bits directly in m_buffer.data(). Do not call QImage::bits() QPainter p(&m_image); // paint in m_image // update the QGraphicsPixmapItem // Create a QPixmap from a const reference to the image. item->setPixmap(QPixmap::fromImage(m_image));
-
A more complete example:
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QLabel> #include <QImage> class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private slots: void updateDisplay(); private: QLabel *m_label; QImage m_image; QVector<uchar> m_buffer; }; #endif // WIDGET_H
#include "widget.h" #include <QVBoxLayout> #include <QLabel> #include <QImage> #include <QDebug> #include <QTimer> #include <QRandomGenerator> #include <QPainter> namespace { // 640x480 mono image: 80 bytes per scan line, 480 scan lines int _width(640); int _height(480); int _bufferSize(_width * _height / 8); } Widget::Widget(QWidget *parent) : QWidget(parent) { // set up the initial empty image m_buffer.resize(_bufferSize); m_image = QImage(m_buffer.data(), _width, _height, QImage::Format_Mono); m_image.fill(Qt::color0); qDebug() << Q_FUNC_INFO << m_image << m_image.cacheKey(); // somewhere to display it QVBoxLayout *layout = new QVBoxLayout(this); m_label = new QLabel(this); layout->addWidget(m_label); m_label->setPixmap(QPixmap::fromImage(m_image)); // generate some demo output QTimer *timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &Widget::updateDisplay); timer->start(500); } Widget::~Widget() { } void Widget::updateDisplay() { // modify some bytes in the buffer directly for (qsizetype i = 0; i < 10; ++i) { int index = QRandomGenerator::global()->bounded(m_buffer.size()); m_buffer[index] ^= 0xff; } // Paint on the same image QPainter p; if (p.begin(&m_image)) { p.setPen(Qt::color1); QPoint a(QRandomGenerator::global()->bounded(_width), QRandomGenerator::global()->bounded(_height)); QPoint b(QRandomGenerator::global()->bounded(_width), QRandomGenerator::global()->bounded(_height)); p.drawLine(a, b); p.end(); } qDebug() << Q_FUNC_INFO << "Out" << m_image << m_image.cacheKey(); m_label->setPixmap(QPixmap::fromImage(m_image)); }
-