Qt/Qml:Send Qimage from C++ to Qml and Display the Qimage on Gui

Qt/QML : Send QImage From C++ to QML and Display The QImage On GUI

In other words, you have a class emitting a signal carrying a QImage and want to update an item in QML with that image? There are various solutions, none of which involves "converting a QImage to a QUrl" (whatever that means, surely you don't need to get a data URL carrying your image data...)

Use an image provider

This means you can use a plain Image item in your QML files.

  1. Create a QQuickImageProvider subclass; give it a QImage member (the image to provider), override requestImage to provide that image (the actual id requested does not really matter, see below), and a slot that receives a QImage and updates the member.
  2. Connect your Publisher signal to your provider's slot
  3. Install the provider into the QML engine via QQmlEngine::addImageProvider (see QQuickView::engine); again the id does not really matter, just use a sensible one
  4. In QML, just use a plain Image element with a source like this

    Image {
    id: myImage
    source: "image://providerIdPassedToAddImageProvider/foobar"
    }

    foobar will be passed to your provider, but again, it doesn't really matter.

  5. We're almost there, we now only need a way to push the image updates to the QML world (otherwise Image will never know when to update itself). See my answer here for how to do that with a Connections element and a bit of JS.

    Note that in general you don't need to make Publisher a QML type, you just need to create one instance in C++ and expose it to the QML world via QQmlContext::setContextProperty.

Use a custom Qt Quick 2 Item

QQuickPaintedItem is probably the most convenient for the job as it offers a paint method taking a QPainter. Hence the big plan is

  1. Subclass QQuickPaintedItem: the subclass stores the QImage to be painted and has a slot that sets the new QImage. Also its paint implementation simply paints the image using QPainter::drawImage.
  2. Expose the subclass to the QML world via qmlRegisterType (so that you can use it in QML)
  3. Figure out a way to connect the signal carrying the new image to the items' slot.

    This might be the tricky part.

    To perform the connection in C++ you need a way to figure out that the item has been created (and get a pointer to it); usually one does this by means of assigning the objectName property to some value, then using findChild on the root object (as returned by QQuickView::rootObject()) to get a pointer to the item itself. Then you can use connect as usual.

    Or, could instead perform the connection in QML, just like above, via a Connections element on the publisher C++ object exposed to the QML world:

    MyItem {
    id: myItem
    }

    Connections {
    target: thePublisherObjectExposedFromC++
    onNewImage: myItem.setImage(image)
    }

    This has the advantage of working no matter when you create the MyItem instance; but I'm not 100% sure it will work because I'm not sure you can handle the QImage type in QML.

Pass QImage to QML

Answering my own question
Problem solved. Here is the solution step by step:

1 - Create a class that inherits from QQuickImageProvider and QObject and inside it create a Image member (QImage) that is the image to be provided.

class provedorImagem : public QObject, public QQuickImageProvider

Implement the virtual requestImage method. This is the method that will return the image to Qml

QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize)

Create a method to load the provider’s image to return

void provedorImagem::carregaImagem(QImage imagemRecebida)
{
imagem = imagemRecebida;
}

Now set it as the engine image provider in the main.cpp file

provedorImagem *provedorImg = new provedorImagem;
engine.rootContext()->setContextProperty("ProvedorImagem", provedorImg);

2 - Create another class that inherits from QObject.

class processaImagem : public QObject

Inside this class you must implement a method that will get the image from camera provider, perform the image modifications and return the modified image.
PS: The p_caminhoImagem is a property that I created inside the processaImagem class that receives the camera preview path.

QImage processaImagem::carregaImagem()
{
QUrl caminhoImagem(p_caminhoImagem);
QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
QQmlImageProviderBase *imageProviderBase = engine->imageProvider(caminhoImagem.host());
QQuickImageProvider *imageProvider = static_cast<QQuickImageProvider*>(imageProviderBase);

QSize imageSize;
QString imageId = caminhoImagem.path().remove(0, 1);
QImage imagem = imageProvider->requestImage(imageId, &imageSize, imageSize);

if(imagem.isNull())
{
imagem = QImage();
}
else
{
//Perform the modifications
}

return imagem;
}

3 - Now is the main part. The image requestImage provider method must receive the modified image from the processaImagem class to provide it to QML. To do it the provider class pointer must be accessible to the QML file, so, in the main.cpp file just make the pointer available to QML as a property

engine.rootContext()->setContextProperty("ProvedorImagem", provedorImg);

and register the processaImagem class as a QML type

qmlRegisterType<processaImagem>("ProcessaImagemQml", 1, 0, "ProcessaImagem");

Now we link it inside the QML file

ProvedorImagem.carregaImagem(processaImagem.carregaImagem());

4 - It is done. Now just request the image from the provider:

imagemPreview.source = "image://provedor/imagemEditada_" + camera.numeroImagem.toString();

Here is the entire code:

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include <QtQml>

#include "processaimagem.h"
#include "provedorimagem.h"

int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);

qmlRegisterType<processaImagem>("ProcessaImagemQml", 1, 0, "ProcessaImagem");

QQmlApplicationEngine engine;

provedorImagem *provedorImg = new provedorImagem;

engine.rootContext()->setContextProperty("ProvedorImagem", provedorImg);

engine.addImageProvider("provedor", provedorImg);

engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

return app.exec();
}

main.qml

import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Controls 1.3
import QtMultimedia 5.4

import ProcessaImagemQml 1.0

Window {
visible: true

width: 360
height: 640

maximumHeight: 640
minimumHeight: 640

maximumWidth: 360
minimumWidth: 360

title: "Camera Preview Test"

Rectangle {
id: principal

anchors.fill: parent

ProcessaImagem {
id: processaImagem

caminhoImagem: camera.caminhoPreview
caminhoSalvar: camera.caminhoSalvar
rectRecorte: camera.rectRecorte
tamanhoImagem: camera.tamanhoImagem
anguloOrientacaoCamera: camera.orientation
posicaoCamera: camera.position

onCaminhoImagemChanged: {
rectRecorte = cameraView.mapRectToSource(Qt.rect(cameraView.x, cameraView.y, cameraView.width, cameraView.height));
tamanhoImagem = Qt.size(cameraView.sourceRect.width, cameraView.sourceRect.height);
ProvedorImagem.carregaImagem(processaImagem.carregaImagem());
}

onCaminhoSalvarChanged: {
removeImagemSalva();
}
}

Rectangle {
id: cameraRectangle

width: parent.width
height: parent.width

anchors.top: parent.top

color: "lightGrey"

visible: true

Camera {
id: camera

property string caminhoPreview: ""
property string caminhoSalvar: ""
property int numeroImagem: 0

captureMode: Camera.CaptureStillImage

imageCapture {
onImageCaptured: {
camera.caminhoPreview = preview;

camera.stop();

imagemPreview.source = "image://provedor/imagemEditada_" + camera.numeroImagem.toString();

camera.numeroImagem = camera.numeroImagem + 1;

imagemPreviewRectangle.visible = true;

cameraRectangle.visible = false;
}

onImageSaved: {
camera.caminhoSalvar = path;
}
}
}

VideoOutput {
id: cameraView

visible: true

focus: visible

anchors.fill: parent

source: camera
orientation: camera.orientation
fillMode: VideoOutput.PreserveAspectCrop
}
}

Rectangle {
id: imagemPreviewRectangle

width: parent.width
height: parent.width

anchors.top: parent.top

color: "lightGrey"

visible: false

Image {
id: imagemPreview

fillMode: Image.PreserveAspectFit

anchors.fill: parent
}
}

Rectangle {
id: controleRectangle

width: parent.width
height: parent.height - cameraRectangle.height

color: "grey"

anchors.top: cameraRectangle.bottom

Button {
id: tirarFotoButton

text: "Tirar foto"

anchors.left: parent.left
anchors.top: parent.top

onClicked: {
camera.imageCapture.capture();
}
}

Button {
id: novaFotoButton

text: "Tirar nova foto"

anchors.right: parent.right
anchors.top: parent.top

onClicked: {
camera.start();

imagemPreviewRectangle.visible = false;

cameraRectangle.visible = true;
}
}
}
}
}

processaimagem.h

#ifndef PROCESSAIMAGEM_H
#define PROCESSAIMAGEM_H

#include <QObject>
#include <QImage>
#include <QQmlEngine>
#include <QQmlContext>
#include <QQuickImageProvider>
#include <QFile>

#include "provedorimagem.h"

class processaImagem : public QObject
{
Q_OBJECT

Q_PROPERTY(QString caminhoImagem READ caminhoImagem WRITE setCaminhoImagem NOTIFY caminhoImagemChanged)
Q_PROPERTY(QString caminhoSalvar READ caminhoSalvar WRITE setCaminhoSalvar NOTIFY caminhoSalvarChanged)
Q_PROPERTY(QRect rectRecorte READ rectRecorte WRITE setRectRecorte NOTIFY rectRecorteChanged)
Q_PROPERTY(QSize tamanhoImagem READ tamanhoImagem WRITE setTamanhoImagem NOTIFY tamanhoImagemChanged)
Q_PROPERTY(int anguloOrientacaoCamera READ anguloOrientacaoCamera WRITE setAnguloOrientacaoCamera NOTIFY anguloOrientacaoCameraChanged)
Q_PROPERTY(int posicaoCamera READ posicaoCamera WRITE setPosicaoCamera NOTIFY posicaoCameraChanged)

public slots:
QImage carregaImagem();
void removeImagemSalva();

public:
processaImagem(QObject *parent = 0);

QString caminhoImagem() const;
void setCaminhoImagem(const QString valor);

QString caminhoSalvar() const;
void setCaminhoSalvar(const QString valor);

QRect rectRecorte() const;
void setRectRecorte(const QRect valor);

QSize tamanhoImagem() const;
void setTamanhoImagem(const QSize valor);

int anguloOrientacaoCamera() const;
void setAnguloOrientacaoCamera(const int valor);

int posicaoCamera() const;
void setPosicaoCamera(const int valor);

private:
QString p_caminhoImagem = "";
QString p_caminhoSalvar = "";
QRect p_rectRecorte = QRect(0, 0, 0, 0);
QSize p_tamanhoImagem = QSize(0, 0);
int p_anguloOrientacaoCamera = 0;
int p_posicaoCamera = 0;

signals:
void caminhoImagemChanged();
void caminhoSalvarChanged();
void rectRecorteChanged();
void tamanhoImagemChanged();
void anguloOrientacaoCameraChanged();
void posicaoCameraChanged();
};

#endif // PROCESSAIMAGEM_H

processaimagem.cpp

#include "processaimagem.h"

#include <QDebug>

processaImagem::processaImagem(QObject *parent)
{

}

QImage processaImagem::carregaImagem()
{
QUrl caminhoImagem(p_caminhoImagem);
QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
QQmlImageProviderBase *imageProviderBase = engine->imageProvider(caminhoImagem.host());
QQuickImageProvider *imageProvider = static_cast<QQuickImageProvider*>(imageProviderBase);

QSize imageSize;
QString imageId = caminhoImagem.path().remove(0, 1);
QImage imagem = imageProvider->requestImage(imageId, &imageSize, imageSize);

if(imagem.isNull())
{
qDebug() << "Erro ao carregar a imagem";
imagem = QImage();
}
else
{
if((p_anguloOrientacaoCamera == 90) || (p_anguloOrientacaoCamera == 270))
{
int larguraImagem = p_tamanhoImagem.width();
int alturaImagem = p_tamanhoImagem.height();

p_tamanhoImagem.setWidth(alturaImagem);
p_tamanhoImagem.setHeight(larguraImagem);

int recorteX = p_rectRecorte.x();
int recorteY = p_rectRecorte.y();
int recorteLargura = p_rectRecorte.width();
int recorteAltura = p_rectRecorte.height();

p_rectRecorte.setRect(recorteY, recorteX, recorteAltura, recorteLargura);

if(imagem.size().width() > imagem.size().height())
{
QTransform rotacao;
rotacao.rotate(360 - p_anguloOrientacaoCamera);
imagem = imagem.transformed(rotacao);

qDebug() << "Rodou";
}
}

if(imagem.width() != p_tamanhoImagem.width())
{
imagem = imagem.scaled(p_tamanhoImagem);
}

imagem = imagem.copy(p_rectRecorte);
}

return imagem;
}

void processaImagem::removeImagemSalva()
{
QFile::remove(p_caminhoSalvar);
}

QString processaImagem::caminhoImagem() const
{
return p_caminhoImagem;
}

void processaImagem::setCaminhoImagem(const QString valor)
{
if (valor != p_caminhoImagem)
{
p_caminhoImagem = valor;
emit caminhoImagemChanged();
}
}

QString processaImagem::caminhoSalvar() const
{
return p_caminhoSalvar;
}

void processaImagem::setCaminhoSalvar(const QString valor)
{
if (valor != p_caminhoSalvar)
{
p_caminhoSalvar = valor;
emit caminhoSalvarChanged();
}
}

QRect processaImagem::rectRecorte() const
{
return p_rectRecorte;
}

void processaImagem::setRectRecorte(const QRect valor)
{
bool alterou = false;

if (valor.x() != p_rectRecorte.x())
{
p_rectRecorte.setX(valor.x());
alterou = true;
}

if (valor.y() != p_rectRecorte.y())
{
p_rectRecorte.setY(valor.y());
alterou = true;
}

if (valor.width() != p_rectRecorte.width())
{
p_rectRecorte.setWidth(valor.width());
alterou = true;
}

if (valor.height() != p_rectRecorte.height())
{
p_rectRecorte.setHeight(valor.height());
alterou = true;
}

if(alterou)
{
emit rectRecorteChanged();
}
}

QSize processaImagem::tamanhoImagem() const
{
return p_tamanhoImagem;
}

void processaImagem::setTamanhoImagem(const QSize valor)
{
bool alterou = false;

if (valor.width() != p_tamanhoImagem.width())
{
p_tamanhoImagem.setWidth(valor.width());
alterou = true;
}

if (valor.height() != p_tamanhoImagem.height())
{
p_tamanhoImagem.setHeight(valor.height());
alterou = true;
}

if(alterou)
{
emit tamanhoImagemChanged();
}
}

int processaImagem::anguloOrientacaoCamera() const
{
return p_anguloOrientacaoCamera;
}

void processaImagem::setAnguloOrientacaoCamera(const int valor)
{
if (valor != p_anguloOrientacaoCamera)
{
p_anguloOrientacaoCamera = valor;
emit anguloOrientacaoCameraChanged();
}
}

int processaImagem::posicaoCamera() const
{
return p_posicaoCamera;
}

void processaImagem::setPosicaoCamera(const int valor)
{
if (valor != p_posicaoCamera)
{
p_posicaoCamera = valor;
emit posicaoCameraChanged();
}
}

provedorimagem.h

#ifndef PROVEDORIMAGEM_H
#define PROVEDORIMAGEM_H

#include <QObject>
#include <QImage>
#include <QQuickImageProvider>

class provedorImagem : public QObject, public QQuickImageProvider
{
Q_OBJECT

public:
provedorImagem();

QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);

public slots:
void carregaImagem(QImage imagemRecebida);

private:
QImage imagem;
};

#endif // PROVEDORIMAGEM_H

provedorimagem.cpp

#include "provedorimagem.h"

#include <QDebug>

provedorImagem::provedorImagem() : QQuickImageProvider(QQuickImageProvider::Image)
{

}

QImage provedorImagem::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
if(imagem.isNull())
{
qDebug() << "Erro ao prover a imagem";
}

return imagem;
}

void provedorImagem::carregaImagem(QImage imagemRecebida)
{
imagem = imagemRecebida;
}

Updating an image from c++ to QML

In general you should avoid modifying an item created in QML from C ++ directly, before that I will improve your implementation by adding the image as qproperty:

*.h

#ifndef IMAGEITEM_H
#define IMAGEITEM_H

#include <QImage>
#include <QQuickPaintedItem>

class ImageItem : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QImage image READ image WRITE setImage NOTIFY imageChanged)
public:
ImageItem(QQuickItem *parent = nullptr);
QImage image() const;
void setImage(const QImage &image);

void paint(QPainter *painter);
signals:
void imageChanged();
private:
QImage m_image;
};
#endif // IMAGEITEM_H

*.cpp

#include "imageitem.h"
#include <QDebug>
#include <QPainter>

ImageItem::ImageItem(QQuickItem *parent):QQuickPaintedItem(parent)
{
qDebug() << Q_FUNC_INFO << "initializing new item, parent is: " << parent;
setImage(QImage(":/resources/images/logo.png"));
}

QImage ImageItem::image() const
{
qDebug() << Q_FUNC_INFO << "image requested...";
return m_image;
}

void ImageItem::setImage(const QImage &image)
{
qDebug() << Q_FUNC_INFO << "setting new image...";
if(image == m_image)
return;
m_image = image;
emit imageChanged();
update();
}

void ImageItem::paint(QPainter *painter)
{
if(m_image.isNull())
return;
qDebug() << Q_FUNC_INFO << "paint requested...";

QRectF bounding_rect = boundingRect();
QImage scaled = m_image.scaledToHeight(bounding_rect.height());
QPointF center = bounding_rect.center() - scaled.rect().center();

if (center.x() < 0)
center.setX(0);
if (center.y() < 0)
center.setY(0);
painter->drawImage(center, scaled);
}

In this part I will answer your direct question, although it is not the best because if you do not know how to handle you could have problems, for example if you set the item in a StackView Page since they are created and deleted every time you change pages.

QObject *obj = engine.rootObjects().first()->findChild<QObject*>("liveImageItem");

if(obj){
QImage image = ...;
QQmlProperty::write(obj, "image", image);
}

Example:
main.cpp

#include "imageitem.h"

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlProperty>

#include <QTime>
#include <QTimer>

int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

qsrand(QTime::currentTime().msec());
qmlRegisterType<ImageItem>("com.eyllanesc.org", 1, 0, "ImageItem");
QGuiApplication app(argc, argv);

QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;

QObject *obj = engine.rootObjects().first()->findChild<QObject*>("liveImageItem");

QTimer timer;
if(obj){
QObject::connect(&timer, &QTimer::timeout, [obj](){
QImage image(100,100, QImage::Format_ARGB32);
image.fill(QColor(qrand()%255, qrand()%255, qrand()%255));
QQmlProperty::write(obj, "image", image);
});
timer.start(1000);
}

return app.exec();
}

For me a better idea is to implement a Helper and make the connection in QML:

#include "imageitem.h"

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlProperty>
#include <QQmlContext>

#include <QTime>
#include <QTimer>

class Helper: public QObject{
Q_OBJECT
Q_PROPERTY(QImage image READ image WRITE setImage NOTIFY imageChanged)
public:
QImage image() const{ return m_image; }
void setImage(const QImage &image){
if(m_image == image)
return;
m_image = image;
emit imageChanged();
}
signals:
void imageChanged();
private:
QImage m_image;
};

int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

qsrand(QTime::currentTime().msec());
qmlRegisterType<ImageItem>("com.eyllanesc.org", 1, 0, "ImageItem");
QGuiApplication app(argc, argv);
Helper helper;

QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("helper", &helper);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;

QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&helper](){
QImage image(100,100, QImage::Format_ARGB32);
image.fill(QColor(qrand()%255, qrand()%255, qrand()%255));
helper.setImage(image);
});
timer.start(1000);

return app.exec();
}

#include "main.moc"

*.qml

...
ImageItem{
id: liveImageItem
height: parent.height
width: parent.width
}
Connections{
target: helper
onImageChanged: liveImageItem.image = helper.image
}
...

How to set a QImage object to QML's Image element from C++ side multiple times?

This is a quick and dirty example (not for copy and paste!)

// in main.cpp (imports are missing)

class MyImageProvider: public QQuickImageProvider
{
QImage m_image;
public:
MyImageProvider(QImage img)
: QQuickImageProvider(QQuickImageProvider::Image)
, m_image(img)
{}

QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override {
qDebug() << m_image;
return m_image;
}
};

int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.addImageProvider("myimg", new MyImageProvider(QImage("C:/.../image.png")));
engine.load(QUrl(QStringLiteral("main.qml")));

return app.exec();
}

// in main.qml

ApplicationWindow {
id: rootWin
width: 800; height: 600; visible: true

Image {
source: "image://myimg/1"
}
}

The source is:

image://registeredNameOfImageProvider/potentialId

QImage into QML

Yes, unfortunately the Image element is missing an update() method (to forcibly reset it). Setting the very same source URL will not trigger an update.

You can use something like this as a workaround:

Image {
source: "image://yourImageProvider/something"
cache: false
function reload() {
var oldSource = source;
source = "";
source = oldSource;
}
}

(Or just switch between two URLS, with the same provider name, but different paths...)

You should also push those JPEGs you receive to the QML layer. Once you receive a new image, you should emit a signal from C++'s side from some object exposed to the QML engine, and connect that signal to that reload() function. The Connections element will help you there.

Connections {
target: myC++ObjectExposedToTheQMLEngine
onNewFrameReceived: image.reload();
}


Related Topics



Leave a reply



Submit