简体   繁体   中英

Updating an image from c++ to QML

I am trying to update an QImage from c++ to QML,

I am using the approach from: https://www.huber.xyz/?p=477

I implemented the QQuickPaintedItem - the initial QImage is shown, but I don't find a way to update the QImage from c++ if I receive a signal there (FileWatcher).

My implementation looks like:

QML:

ImageItem {
 id: liveImageItem
 height: parent.height
 width: parent.width            
 objectName: "liveImageItem"
}

I register the image with:

qmlRegisterType<QUIQImageItem>("imageItem", 1, 0, "ImageItem");

The implementation of the Image:

ImageItem::ImageItem(QQuickItem *parent) : QQuickPaintedItem(parent) {
  qDebug() << Q_FUNC_INFO << "initializing new item, parent is: " << parent;

  this->current_image = QImage(":/resources/images/logo.png");
}

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

  QRectF bounding_rect = boundingRect();
  QImage scaled = this->current_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);
}

QImage ImageItem::image() const {
  qDebug() << Q_FUNC_INFO << "image requested...";
  return this->current_image;
}

void ImageItem::setImage(const QImage &image) {
  qDebug() << Q_FUNC_INFO << "setting new image...";

  this->current_image = image;
  emit imageChanged();
  update();
}

How could I get the reference of the ImageItem on c++ side to manage an update of the Image via setImage?

Is this way possible or should I try another solution?

I tried to get the item by

QList<ImageItem*> res = engine->findChildren<ImageItem*>();

and also:

QList<ImageItem*> res = engine->findChildren<ImageItem*>("liveImageItem");

the list of ImageItems (res) is always empty.

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
}
...

To slightly improve upon @eyllanesc's solution, the Helper class should probably hold the state while the ImageItem should just be a dumb representation of the image.

Also, you don't need the separate Connection element.

My setup is then as follows:

LiveImage.h

#ifndef LIVEIMAGE_H
#define LIVEIMAGE_H

#include <QImage>
#include <QQuickPaintedItem>
#include <QPainter>

class LiveImage : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(QImage image MEMBER m_image WRITE setImage)

    // Just storage for the image
    QImage m_image;

public:
    explicit LiveImage(QQuickItem *parent = nullptr);
    void setImage(const QImage &image);
    void paint(QPainter *painter) override;
};

#endif // LIVEIMAGE_H

LiveImage.cpp

#include "LiveImage.h"

LiveImage::LiveImage(QQuickItem *parent) : QQuickPaintedItem(parent), m_image{}
{}

void LiveImage::paint(QPainter *painter)
{
    painter->drawImage(0, 0, m_image);
}

void LiveImage::setImage(const QImage &image)
{
    // Update the image
    m_image = image;

    // Redraw the image
    update();
}

ImageProvider.h

#ifndef IMAGEPROVIDER_H
#define IMAGEPROVIDER_H

#include <QObject>
#include <QImage>

class ImageProvider : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QImage image MEMBER m_image READ image WRITE setImage NOTIFY imageChanged)

    QImage m_image;

public:
    explicit ImageProvider(QObject *parent = nullptr);
    void setImage(QImage const &image);
    QImage image() const;

signals:
    void imageChanged();
};

#endif // IMAGEPROVIDER_H

ImageProvider.cpp

#include "ImageProvider.h"

ImageProvider::ImageProvider(QObject *parent)
    : QObject(parent)
{}

void ImageProvider::setImage(QImage const &image)
{
    m_image = image;
    emit imageChanged();
}

QImage ImageProvider::image() const
{
    return m_image;
}

And then in you main function, register the LiveImage as an instantiable QML type, and make instances of ImageProvider available from QML as well:

qmlRegisterType<LiveImage>("MyApp.Images", 1, 0, "LiveImage");
ImageProvider provider{};
engine.rootContext()->setContextProperty("LiveImageProvider", &provider);

QTimer::singleShot(1000, [&provider](){
    QImage image{480, 480, QImage::Format_ARGB32};
    image.fill(Qt::yellow);
    provider.setImage(std::move(image));
});

Finally, your QML would look like this:

import MyApp.Images

...

LiveImage {
    width: 480
    height: 480
    x: 0
    y: 0
    image: LiveImageProvider.image
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM