I need to draw pixel data that is being held by a library as uint8_t * and that is updated frequently and partially. I get a call-back from the library every time an update is done, that looks like this:
void gotFrameBufferUpdate(int x, int y, int w, int h);
I've tried creating a QImage using the pixel data pointer
QImage bufferImage(frameBuffer, width, height, QImage::Format_RGBX8888);
and let the call-back trigger update() of my widget
void gotFrameBufferUpdate(int x, int y, int w, int h)
{
update(QRect(QPoint(x, y), QSize(w, h)));
}
which simply draws the updated area of the QImage via paint():
void MyWidget::paint(QPainter *painter)
{
QRect rect = painter->clipBoundingRect().toRect();
painter->drawImage(rect, bufferImage, rect);
}
The problem with this approach is that the QImage does not seem to reflect any updates to the pixel buffer. It keeps showing its initial contents.
My current workaround is to re-create a QImage instance each time the buffer is updated:
void gotFrameBufferUpdate(int x, int y, int w, int h)
{
if (bufferImage)
delete bufferImage;
bufferImage = new QImage(frameBuffer, width, height,
QImage::Format_RGBX8888);
update(QRect(QPoint(x, y), QSize(w, h)));
}
This works but seems very inefficient to me. Is there a better way of dealing with externally updated pixel data in Qt? Can I make my QImage aware of updates to its memory buffer?
(Background: I'm writing a custom QML type with C++ backend that shall display the contents of a VNC session. I'm using LibVNC/libvncclient for this.)
The QImage is updated if one calls QImage::bits().
It does not allocate a new buffer, you can discard the result, but it magically triggers a refresh of the image.
It is required every time you want a refresh.
I don't know if this is guaranteed behaviour, nor if it saves anything over recreating it.
I would guess that some sort of caching mechanism is interfering with your expectations. QImage has a cacheKey, which changes if the QImage is altered. This can of course only happen if you change the image through QImage functions. As far as I can see, you're changing the underlying buffer directly, so QImage's cacheKey will remain the same. Qt's pixmap cache then has that key in its cache, and uses the cached pixmap for performance reasons.
Unfortunately, there doesn't seem to be a direct way to update this cacheKey or otherwise "invalidate" a QImage. You have two options:
Recreate the QImage when you need it. No need to new it, so you can save a heap allocation. Creating a back-buffered QImage seems like a "cheap" operation, so I doubt this is a bottleneck.
Do a trivial operation on the QImage (i.e. setPixel on a single pixel to black and then to the old value). This is somewhat "hackish" but probably the most efficient way to work around this API deficiency (it will trigger an update to cacheKey as far as I can tell).
AFAICT the QImage class already does work the way you think it should -- in particular, simply writing into the external frame-buffer does, in fact, update the contents of the QImage. My guess is that in your program, some other bit of code is copying the QImage data into a QPixmap internally somewhere (since a QPixmap will always store its internal buffer in the hardware's native format and thus it will be more efficient to paint onto the screen repeatedly) and it is that QPixmap that is not getting modified when the frameBuffer is updated.
As evidence that a QImage does in fact always contain the data from the frameBuffer, here is a program that writes a new color into its frame-buffer every time you click on the window, and then calls update() to force the widget to re-draw itself. I see that the widget changes color on every mouse-click:
#include <stdio.h>
#include <stdint.h>
#include <QPixmap>
#include <QWidget>
#include <QApplication>
#include <QPainter>
const int width = 500;
const int height = 500;
const int frameBufferSizeBytes = width*height*sizeof(uint32_t);
unsigned char * frameBuffer = NULL;
class MyWidget : public QWidget
{
public:
MyWidget(QImage * img) : _image(img) {/* empty */}
virtual ~MyWidget() {delete _image;}
virtual void paintEvent(QPaintEvent * e)
{
QPainter p(this);
p.drawImage(rect(), *_image);
}
virtual void mousePressEvent(QMouseEvent * e)
{
const uint32_t someColor = rand();
const size_t frameBufferSizeWords = frameBufferSizeBytes/sizeof(uint32_t);
uint32_t * fb32 = (uint32_t *) frameBuffer;
for (size_t i=0; i<frameBufferSizeWords; i++) fb32[i] = someColor;
update();
}
private:
QImage * _image;
};
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
frameBuffer = new unsigned char[frameBufferSizeBytes];
memset(frameBuffer, 0xFF, frameBufferSizeBytes);
QImage * img = new QImage(frameBuffer, width, height, QImage::Format_RGBX8888);
MyWidget * w = new MyWidget(img);
w->resize(width, height);
w->show();
return app.exec();
}
Related
How can we create the QIcon object having SVG icon contents in the memory buffer?
P.S. Initially wanted to create QSvgIconEngine, but it is hidden on the plugins layer, and I can not create it explicitly. How can I do it with loading from plugin (taking in account, that plugin is loaded)?
After digging for a while here and there, and digging in how QIcon itself uses an svg file to load an icon, here's what I learned:
QIcon, when called with an svg file (or any other image type for that matter), it calles addFile() subsequently, which only uses the extension of the file (called QFileInfo::suffix in Qt) to determine the method that converts the image file to an icon.
The method (semantically speaking) is determined by a QIconEngine instance
QIconEngine classes for every image type are not apparently simply accessible by us (the Qt developers); apparently there's a plugin system to be used and it's not available at compile-time (not simply at least)
On the other hand; How does QIcon work? When an icon is requested from QIcon, it uses the information passed to it to determine what engine to use, and creates an instance of the engine. Then, every time the icon needs to draw something, it asks the engine to draw an icon with some size given to it. The size is used on the function QIconEngine::pixmap(), which creates a pixmap with the required size, then the method QIconEngine::paint() is used to paint on that pixmap.
So, given this information, the solution is simply to write an Icon Engine that QIcon will use in order to generate the icon depending on the size passed to it. Here's how to do that:
So here's the header file SvgIconEngine.h
#ifndef SVGICONENGINE_H
#define SVGICONENGINE_H
#include <QIconEngine>
#include <QSvgRenderer>
class SVGIconEngine : public QIconEngine {
QByteArray data;
public:
explicit SVGIconEngine(const std::string &iconBuffer);
void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode,
QIcon::State state) override;
QIconEngine *clone() const override;
QPixmap pixmap(const QSize &size, QIcon::Mode mode,
QIcon::State state) override;
signals:
public slots:
};
#endif // SVGICONENGINE_H
And here's the implementation, SvgIconEngine.cpp
#include "SvgIconEngine.h"
#include <QPainter>
SVGIconEngine::SVGIconEngine(const std::string &iconBuffer) {
data = QByteArray::fromStdString(iconBuffer);
}
void SVGIconEngine::paint(QPainter *painter, const QRect &rect,
QIcon::Mode mode, QIcon::State state) {
QSvgRenderer renderer(data);
renderer.render(painter, rect);
}
QIconEngine *SVGIconEngine::clone() const { return new SVGIconEngine(*this); }
QPixmap SVGIconEngine::pixmap(const QSize &size, QIcon::Mode mode,
QIcon::State state) {
// This function is necessary to create an EMPTY pixmap. It's called always
// before paint()
QImage img(size, QImage::Format_ARGB32);
img.fill(qRgba(0, 0, 0, 0));
QPixmap pix = QPixmap::fromImage(img, Qt::NoFormatConversion);
{
QPainter painter(&pix);
QRect r(QPoint(0.0, 0.0), size);
this->paint(&painter, r, mode, state);
}
return pix;
}
Notice: You must override clone(), because it's an abstract method, and you must override pixmap(), because without that, you'll not have an empty pixmap to paint the svg on.
To use this, simply do this:
std::string iconSvgData = GetTheSvgPlainData();
QIcon theIcon(new SVGIconEngine(iconSvgData));
//Use the icon!
Notice that QIcon takes ownership of the engine object. It'll destroy it when it's destroyed.
Have fun!
I don't have c++ at hand, but it should be easy to convert:
QtGui.QIcon(
QtGui.QPixmap.fromImage(
QtGui.QImage.fromData(
b'<svg version="1.1" viewBox="0 0 32 32"'
b' xmlns="http://www.w3.org/2000/svg">'
b'<circle cx="16" cy="16" r="4.54237"/></svg>')))
This code will create transparent 32 by 32 pixels icon with a black circle.
How can we create the QIcon object having SVG icon contents in the
memory buffer?
For that all the required functionality is delivered via external interface of QSvgRenderer class. To construct that type of renderer we need to use either of:
QSvgRenderer(const QByteArray &contents, QObject *parent = Q_NULLPTR);
QSvgRenderer(QXmlStreamReader *contents, QObject *parent = Q_NULLPTR);
or we can just load the content with:
bool QSvgRenderer::load(const QByteArray &contents)
bool QSvgRenderer::load(QXmlStreamReader *contents)
And to create the actual icon:
QIcon svg2Icon(const QByteArray& svgContent, QPainter::CompositionMode mode =
QPainter::CompositionMode_SourceOver)
{
return QIcon(util::svg2Pixmap(svgContent, QSize(128, 128), mode));
}
QPixmap svg2Pixmap(const QByteArray& svgContent,
const QSize& size,
QPainter::CompositionMode mode)
{
QSvgRenderer rr(svgContent);
QImage image(size.width(), size.height(), QImage::Format_ARGB32);
QPainter painter(&image);
painter.setCompositionMode(mode);
image.fill(Qt::transparent);
rr.render(&painter);
return QPixmap::fromImage(image);
}
We can also use other composition modes as well, say,
QPainter::RasterOp_NotSourceOrDestination to invert the icon color.
I am streaming from a webcam using OpenCV. Each captured frame (type is cv::Mat) is transported via custom signal to slots in a node-based QGraphicsScene. Since I have a single type of node that actually requires to display the cv::Mat while the rest is just for processing the data I have created a viewer widget that converts the cv::Mat data to QImage data and then using paintEvent() draws it on its surface:
Here is the code for the widget itself:
qcvn_node_viewer.hpp
#ifndef QCVN_NODE_VIEWER_HPP
#define QCVN_NODE_VIEWER_HPP
#include <QWidget>
#include <QImage>
#include <QSize>
#include <QPaintEvent>
#include <opencv2/core.hpp>
class QCVN_Node_Viewer : public QWidget
{
Q_OBJECT
QImage output;
public:
explicit QCVN_Node_Viewer(QWidget *parent = 0);
~QCVN_Node_Viewer();
QSize sizeHint() const;
QSize minimumSizeHint() const;
// Source: http://stackoverflow.com/a/17137998/1559401
static QImage Mat2QImage(cv::Mat const& frame);
static cv::Mat QImage2Mat(QImage const& src);
protected:
void paintEvent(QPaintEvent *event);
signals:
// Nodes connect to sendFrame(cv::Mat) signal
void sendFrame(cv::Mat);
public slots:
void receiveFrame(cv::Mat frame);
};
#endif // QCVN_NODE_VIEWER_HPP
qcvn_node_viewer.cpp
#include "qcvn_node_viewer.hpp"
#include <opencv2/imgproc.hpp>
#include <QPainter>
QCVN_Node_Viewer::QCVN_Node_Viewer(QWidget *parent) :
QWidget(parent)
{
qRegisterMetaType<cv::Mat>("cv::Mat");
}
QCVN_Node_Viewer::~QCVN_Node_Viewer()
{
}
QSize QCVN_Node_Viewer::sizeHint() const
{
return output.size();
}
QSize QCVN_Node_Viewer::minimumSizeHint() const
{
return output.size();
}
QImage QCVN_Node_Viewer::Mat2QImage(cv::Mat const& frame)
{
cv::Mat temp;
cvtColor(frame, temp,CV_BGR2RGB);
QImage dest((const uchar *) temp.data, temp.cols, temp.rows, temp.step, QImage::Format_RGB888);
dest.bits(); // enforce deep copy
return dest;
}
cv::Mat QCVN_Node_Viewer::QImage2Mat(QImage const& frame)
{
cv::Mat tmp(frame.height(),frame.width(),CV_8UC3,(uchar*)frame.bits(),frame.bytesPerLine());
cv::Mat result; // deep copy just in case (my lack of knowledge with open cv)
cvtColor(tmp, result,CV_BGR2RGB);
return result;
}
void QCVN_Node_Viewer::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawImage(QPoint(0,0), output);
painter.end();
}
void QCVN_Node_Viewer::receiveFrame(cv::Mat frame)
{
if(frame.empty()) return;
// Pass along the received frame
emit sendFrame(frame);
// Generate
// http://stackoverflow.com/questions/17127762/cvmat-to-qimage-and-back
// Applications crashes sometimes! Conversion seems to be incorrect
output = Mat2QImage(frame);
output.bits();
setFixedSize(output.width(), output.height());
repaint();
}
As you can see from the screenshot above the QWidget is embedded in a QGraphicsScene using QGraphicsProxyWidget along with a QGraphicsRectItem that I use for selecting/moving the node around in the scene.
Using QPainter is considered to be more efficient than using QLabel and settings its QPixmap to the captured and converted frame. Using an OpenGL viewport inside a QGraphicsScene as far as I know is not an option. It seems that processing the paint events in the scene also handles the local ones inside the QGraphicsItems and QGraphicsProxyWidgets. The frame that the viewer paints on its surface changes only when the scene's paint event handler is called (hovering some of my nodes, moving them around etc.).
Is there a way to fix this? I'm about to go for the QLabel+QPixmap option (even though I'm not sure if it won't suffer from the same or different problem) but first I'd like to make sure there is nothing I can do to keep my current way of doing things.
EDIT: Okay, so I have changed my viewer to use QLabel instead of the widget's paint event and it works like a charm. The question is still opened though since I'd like to know how to use local paint events.
I've looked through the documentation and also whatever I could find on the internet, but it doesn't seem like it is possible to access a QML Image from C++.
Is there a way to work around that?
It was possible to do in QtQuick1 but that functionality was removed in QtQuick2.
The solution I've come up with allows to have the same image in QML and C++ by implementing a QQuickImageProvider that basically works with QPixmap * which is converted to string and then back to a pointer type(it does sound a little unsafe but has proven to work quite well).
class Pixmap : public QObject {
Q_OBJECT
Q_PROPERTY(QString data READ data NOTIFY dataChanged)
public:
Pixmap(QObject * p = 0) : QObject(p), pix(0) {}
~Pixmap() { if (pix) delete pix; }
QString data() {
if (pix) return "image://pixmap/" + QString::number((qulonglong)pix);
else return QString();
}
public slots:
void load(QString url) {
QPixmap * old = 0;
if (pix) old = pix;
pix = new QPixmap(url);
emit dataChanged();
if (old) delete old;
}
void clear() {
if (pix) delete pix;
pix = 0;
emit dataChanged();
}
signals:
void dataChanged();
private:
QPixmap * pix;
};
The implementation of the Pixmap element is pretty straightforward, although the initial was a bit limited, since the new pixmap happened to be allocated at the exactly same memory address the data string was the same for different images, causing the QML Image component to not update, but the solution was as simple as deleting the old pixmap only after the new one has been allocated. Here is the actual image provider:
class PixmapProvider : public QQuickImageProvider {
public:
PixmapProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap) {}
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) {
qulonglong d = id.toULongLong();
if (d) {
QPixmap * p = reinterpret_cast<QPixmap *>(d);
return *p;
} else {
return QPixmap();
}
}
};
Registration:
//in main()
engine.addImageProvider("pixmap", new PixmapProvider);
qmlRegisterType<Pixmap>("Test", 1, 0, "Pixmap");
And this is how you use it in QML:
Pixmap {
id: pix
}
Image {
source: pix.data
}
// and then pix.load(path)
ALSO Note that in my case there was no actual modification of the pixmap that needed to be updated in QML. This solution will not auto-update the image in QML if it is changed in C++, because the address in memory will stay the same. But the solution for this is just as straightforward - implement an update() method that allocates a new QPixmap(oldPixmap) - it will use the same internal data but give you a new accessor to it with a new memory address, which will trigger the QML image to update on changes. This means the proffered method to access the pixmap will be through the Pixmap class, not directly from the QPixmap * since you will need the Pixmap class to trigger the data change, so just add an accessor for pix, and just in case you do complex or threaded stuff, you might want to use QImage instead and add a mutex so that the underlying data is not changed in QML while being changed in C++ or the other way around.
I have class Paintable that is able to paint itself with QPainter provided as an argument:
class Paintable
{
public:
virtual void paint(QPainter*) = 0;
};
Instances of this class are being painted on a single QImage:
QImage paint(const std::vector<Paintable*>& paintables) {
QImage result;
QPainter p(&result);
for(int i = 0; i < paintables.size(); ++i) {
paintables[i]->paint(&p);
}
return result;
}
What I want to achieve is that function paint could also form a matrix of size equal to result image size in which each cell contains a pointer to Paintable which has drawn corresponding pixel in result image (something like z-buffer).
It could be achieved easily if draw methods of QPainter somehow let me know of which pixels of QPaintDevice were changed during last draw operation. Any ideas of how to do it? Should I create class derived from QPaintDevice or QPaintEngine?
I am using Qt 4.6.4.
Thanks.
Perhaps instead of having all your Paintables paint onto the same QImage, have each one paint onto a temporary blank QImage -- i.e. a QImage with all pixels set to RGBA=(0,0,0,0). That way, after a given Paintable's paint() method returns, you know that any pixels in the QImage that are now non-transparent must have been painted by that Paintable object. You could then update your own z-buffer like data-structure based on that information, and then drawImage() the QImage over to a separate "accumulation QImage" (assuming you also want the composited result), clear the temporary QImage again, and repeat as necessary.
QGraphicsPixmapItem, like QGraphicsItem, has a method update(x0, y0, width, height), in order to redraw a pixmap only partly on a QGraphicsScene. Calling this will schedule a paint() (in Qt's event loop) on the QGraphicsItem, and after this paint() is executed the boundingbox (x,y,width,height) will be redrawn to the QGraphcisScene.
The unfortunate part is that there is no way to schedule the paint-event with a boundingbox, meaning that QGraphicsPixmapItem::paint() is forced to repaint the whole QPixmap, therefore reimplementing this paint()-method in a subclass gives no way to only partly update the QPixmap, therefore making a small (local) update to the QPixmap unacceptably slow.
Such a subclass would look something like this:
class LocallyUdatablePixmapItem : public QGraphicsPixmapItem {
private:
QImage ℑ
public:
LocallyUdatablePixmapItem(QImage &img) : QGraphicsPixmapItem(), image(img) {}
paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QStyle *widget) {
//locall update, unfortunately without a boundig box :( therefore -> slow
}
};
Another option would be to keep the 'internal QPixmap' of the QGraphicsPixmapItem, and draw the QImage to it partly, like this:
//some initialization of variables
QGraphicsScene scene = ...;
QImage img = ...; //some image data that I wish to manipulate from time to time
QPixmap pixmap = QPixmap::fromImage(this->shown);
QPainter painter = new QPainter(&this->pixmap);
QGraphicsPixmapItem item = this->addPixmap(this->pixmap);
item->setPixmap(this->pixmap);
//this should not matter, but actually it does, as I will explain shortly
//delete painter;
//painter = new QPainter(item->pixmap());
//For some reason I decide to update (manimulate) img within a small boundigbox
int x0, y0, width, height; //bounding box, assume they are set to whatever is appropriate for the previous update
painter->drawImage (x0, y0, img, x0, y0, width, height);
//now the pixmap is updated, unfortunately the item is not. This does not affect it:
item->update(x0, y0, width, height);
//nor does this:
item->update();
//but this, which makes the whole thing slow, does:
item.setPixmap(&pixmap);
Given I that I needed to set the pixmap to fix it, I assumed it was somehow not set in the initialization, therefore uncommenting the mentioned lines before seemed like a nice idea. Unfortunately, the drawImage() call then segfaults into:
QPaintDevice: Cannot destroy paint device that is being painted
I would like to have an alternative to the "item.setPixmap(&pixmap);", which does not redraw the whole thing, but does work nicely. Any input is very well appreciated :)
Before I propose a solution, a few thoughts:
First, the Graphics View framework is intended to be a solution for displaying many graphic objects, so one large image isn't really that fitting. Of course, I realize your example is probably just a contrived one, so this point might not really apply. Second, since the framework is very transform-centric, it might not make sense to only redraw parts of a QGraphicsItem unless all the transforms are identity, there is no scrolling, etc.
Anyways, if you only want to draw part of a QGraphicsItem, you could simply store the rect that needs to be updated, and access it from inside your paint() method. For example:
CustomItem::setPaintRect(const QRectF &rect)
{
paintRect = rect;
update();
}
CustomItem::paint(QPainter *painter /* etc. */)
{
painter->fillRect(paintRect, brush);
}