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.
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 want to show a image on qt label. I am getting image data in the form of QByteArray and I am loading it into label.
Below is the code :
defined in constructor
QPixmap *pixmapTest;
pixmapTest = NULL;
the following code is in a function which is getting called multiple times :
RequestCompleted(QNetworkReply *reply)
{
if(pixmapTest){
qDebug()<<"delete showImage Pixmap Object";
delete pixmapTest;
pixmapTest = NULL;
}
pixmapTest = new QPixmap();
QByteArray jpegData = reply->readAll();
pixmapTest->loadFromData(jpegData);
ui.qtLabel->setPixmap(*pixmapTest);
}
After calling this function for around 500 times I am getting this error
QImage: out of memory, returning null image.
I am not getting what is the error in the above code. Can someone please tell me how to solve this?
First off allocating and de-allocating memory for a variable (pixmapTest) in a function that gets called many times doesn't make sense enough. You should allocate the memory first and once all is done de-allocate it. For example:
pixmapTest = new QPixmap();
for(size_t i = 0; i < 1000; i++){
// Call that function
}
delete pixmapTest;
Apart from this, it sounds like your app is leaking memory. Note that by deleting the pixmapTest, you're not deleteing the memory jpegData points to. Therefore, in each function call you should take care of the memory to which jpegData points.
Most likely, you're not freeing the reply itself.
There's also no need to store the pixmap, nor to manage it through a pointer. Keep it by value, and assign new value each time you receive a reply.
E.g.:
class MyClass : public QWidget {
Q_OBJECT
Ui::MyClass ui;
explicit MyClass(QWidget *parent = nullptr) : QWidget(parent) {
ui.setupUi(this);
}
Q_SLOT void requestCompleted(QNetworkReply *reply) {
QPixmap pix;
pix.loadFromData(reply->readAll());
ui.qtLabel->setPixmap(pix);
reply->deleteLater();
}
};
I'm having a question about a weird (At least it was unexpected for me) behavior (It crashes) of qt when initializing pointers on a member class different than the constructor. I am attaching part of my code:
In mainwindow.h:
class MainWindow : public QMainWindow
{
...
private:
QPixmap *qpm_s1_yaw;
QPainter *s1_yaw_painter;
...
}
In mainwindow.cpp:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
...
initGraph(qpm_s1_yaw, s1_yaw_painter, ui->YAW1);
...
}
void MainWindow::initGraph(QPixmap *map, QPainter *painter, QLabel *label)
{
map = new QPixmap(label->size());
map->fill(Qt::white);
painter = new QPainter(map);
... doing some stuff ...
label->setPixmap(*map); // ++(Remember this LINE)++
}
That actually works, but when I comment the line:
label->setPixmap(*map)
and instead set the Pixmap in the constructor (MainWindow::MainWindow) by writing:
ui->YAW1->setPixmap(*qpm_s1_yaw)
I got a segmentation Fault.
Could someone explain what is wrong with it? To make it work I had to initialize all the pointers in the constructor (and commenting those line in the classs member initGraph), like this:
qpm_s1_yaw = new QPixmap(ui->YAW1->size());
s1_yaw_painter = new QPainter(qpm_s1_yaw);
initGraph(qpm_s1_yaw, s1_yaw_painter, ui->YAW1);
ui->YAW1->setPixmap(*qpm_s1_yaw);
Thanks
This is a trivial misunderstanding of how C++ works, nothing to do with Qt.
Your code lies to you: you can equally well write: initGraph(0, 0, ui->YAW1). You're initializing local variables instead of class members. The values you pass as the first two arguments are not used for anything.
It's also completely unnecessary to hold the pixmap and the painter by pointer. Hold the pixmap by value, and only instantiate a painter for it when you do the painting.
Holding a painter to a pixmap when you're not painting on it can cause unnecessary copies of the pixmap to be made when the pixmap is consumed (read from): a pixmap with an active painter is considered "dirty".
What you should do then is to hold pixmaps by value and you can return the new value from initGraph - this decouples initGraph from the detail of the surrounding class where the pixmaps are stored. The user of initGraph has an option of not storing the pixmap, and e.g. querying the label itself for it.
class MainWindow : public QMainWindow
{
Ui::MainWindow ui; // hold by value
...
QPixmap qpm_s1_yaw; // hold by value
QPixmap initGraph(QLabel *label) {
QPixmap pixmap{label->size()};
pixmap.fill(Qt::white);
QPainter painter{&pixmap};
//... doing some stuff ...
label->setPixmap(pixmap);
return pixmap;
}
public:
explicit MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) {
ui.setupUi(this);
gpm_s1_yaw = initGraph(ui.YAW1);
}
};
I am developing an application using QML and C++, the goal is to download an image named "test.jpg" from a HTTP server and show it in a QML Image component without saving it in hard disk drive.
The class "AppController" downloads the image "test.jpg" from server and store in a global QPixmap variable named "imageReadFromServer".
#ifndef APPCONTROLLER_H
#define APPCONTROLLER_H
#include <QObject>
class AppController : public QObject
{
Q_OBJECT
public:
AppController(QObject *parent = 0);
Q_INVOKABLE void sendImage(QString _imagePath, QString serverLocation);
Q_INVOKABLE void readImage(QString serverLocation);
private:
QByteArray serverResponse;
QPixmap imageReadFromServer;
};
#endif // APPCONTROLLER_H
To display images on QML Image component I know that it is necessary to use "QQuickImageProvider" class, I have done one class named "ImageProvider" and reimplemented its virtual method QPixmap requestPixmap() to handle when QML requests image showing. To test if everything was working well until now, I used the next example code provided by Qt documentation that returns a QPixmap filled with blue color:
QPixmap ImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
qDebug() << "pixmap requested";
int width = 160;
int height = 100;
if(size)
*size = QSize(width,height);
QPixmap pixmap(requestedSize.width() > 0 ? requestedSize.width() : width,
requestedSize.height() > 0 ? requestedSize.height() : height);
pixmap.fill(QColor("blue").rgba());
//return pixmap;
return pixmap;
}
In main.cpp I added its respective image provider.
engine.addImageProvider(QLatin1String("imagefromserver"), new ImageProvider);
When download button is clicked on QML, it calls ImageProvider class method successfully:
onClicked: {
imageVisualizer.visible = true
imageVisualizer.source = "image://imagefromserver/"
}
What I need is:
When requestPixmap() method from ImageProvider class is called, it should return the global variable imageReadFromServer already stored in AppController class.
I have tried to make AppController class inherits directly from QQuickImageProvider but it didn't work because imageReaderFromServer value was weirdly cleared just before call requestPixmap().
I thought that I could use SIGNALS and SLOTS to pass QPixmap between classes but QQuickImageProvider doesn't inherits from QObject, so I cannot use them.
I appreciate any suggestion.
Thanks.
EDIT
I have again tried to implement QQuickImageProvider inheritance in AppController class. It is already working, the mistake was in main.cpp file, I was declaring image provider as shown below:
AppController applicationController;
QQmlApplicationEngine engine;
engine.addImageProvider(QLatin1String("imagefromserver"), new AppController);
This created a new instance of AppController class, and when I accessed to requestPixmap() function it couldn't get the global QPixmap variable imageReadFromServer because it was created in the instance named applicationController. The solution was to assign the same instance to imageProvider as shown below:
engine.addImageProvider(QLatin1String("imagefromserver"), &applicationController);
I'm making a custom widget in Qt, and drawing an image as its background. The background image is to be exactly the same for all instances of widgets. I'd like to know if I'm doing it in an appropriate way.
Here's how I'm doing it now:
// QMyWidget.h
class QMyWidget : public QWidget
{
/* some stuff.. and then: */
protected:
static QImage *imgBackground;
}
// QMyWidget.cpp
QImage *QMyWidget::imgDial = NULL;
QMyWidget::QMyWidget(QWidget *parent) : QWidget(parent)
{
if(imgBackground== NULL)
{
imgBackground= new QImage();
imgBackground->load(":/Images/background.png");
}
}
void QMyWidget::paintEvent(QPaintEvent *e)
{
QPainter painter(this);
painter.drawImage(QPoint(), *imgBackground);
}
The code works just fine, but is this considered a good way to do it?
That's one way of doing it, and it's a reasonably good way if you're only dealing with a single image, but if you ever decide to scale and use a couple of custom resources then the Qt Resource System is the better way to do it. It'll save you time in code (don't need to repeatedly do QImage), and has a few other nice features like resource compression.