Dinendal
Dinendal

Reputation: 279

Display and refresh an image in QML

I don't understand how to display an image in QML with QQuickPaintedItem class (or maybe another method ?).

My program is currently displaying it using QImage and an ImageProvider, it works but randomly crashed so I would like to try another method.

Moreover, the image should be refreshed each time a new frame is transmitted.

I get the information from an uchar raw data but I don't see how to paint from raw data.

Moreover a new raw data is coming at approximately 60 fps so the QML need to refresh the image at this rate.

EDIT :

Other parts of the code :

My class is :

class StreamPainter : public QQuickPaintedItem
{
    Q_OBJECT

public:
    StreamPainter(QQuickItem *p = 0):QQuickPaintedItem(p){}
    ~StreamPainter();

    void paint(QPainter *painter){painter->drawImage(QPoint(0, 0), _streamImage);}


public slots:
    void onImageAvailable(QImage image) {
        _streamImage = image;
        update();
private:
    QImage _streamImage;

};

In main :

for the invalid property name, it was due to the import StreamPainter 1.0, seems it didn't like that name since it was consfused, I've changed it to myModule and the error disappeared.

qmlRegisterType<StreamPainter>("myModule", 1, 0, "StreamPainter");

Creation of the object and of the thread :

streamImage = new StreamPainter();
myPipeThread = new PipeThread(streamImage);

Thread constructor :

PipeThread::PipeThread(StreamPainter* spvideostr)
:QThread(),
 _namedPipe(INVALID_HANDLE_VALUE),
 _spvideostr(spvideostr),
 _abort(false),
 _rawData(NULL)
{
    ...
}

Thread class, I've added :

signals:
    void imageAvailable(QImage image);

Then in my thread, I do the connect and in the while I create the QImage and emit the signal :

connect(this, SIGNAL(imageAvailable(QImage)), _spvideostr, SLOT(onImageAvailable(QImage)));

while(...)
    QImage clImage(_rawData, IMG_WIDTH, IMG_HEIGHT,     QImage::Format_Indexed8);
    emit imageAvailable(clImage);

QML :

import QtQuick 2.0

import MyModule 1.0

StreamPainter {

    width : 100 
    height : 100 

Upvotes: 3

Views: 5859

Answers (3)

BuvinJ
BuvinJ

Reputation: 11076

I resolved refreshing an image in QML by binding the source property of an Image to a url to which I tacked on an arbitrary query string and simply changed the value in that appended part of the image location using an auto incrementing counter each time the value is retrieved.

Roughly, something like this (from the C Side):

QString imageURL()
{
    static const QString QUERY_STRING_KEY( "?refresh=" );
    static ushort imageRefreshCounter( 0 );
    return imageUrl_ +
        QUERY_STRING_KEY + QString::number( ++imageRefreshCounter );
}

This function would be fired off implicitly via a Q_PROPERTY when some signal is emitted (e.g. emit imageChanged();) which triggers a QML binding refresh.

Doing this, there is no need to change the name of the actual image, or play a game with clearing and resetting the source property on the QML side.

Note: depending on how you want write this in your own project, you might opt to further abstract this, use a literal QUrl, and call upon its setQuery method. I'm showing this here with raw strings just to keep it a bit more simple/straightforward.

Upvotes: 2

Luis Manrique
Luis Manrique

Reputation: 306

In my case I refresh an Image in QML with a function inside the component like this:

Image {
                id: imageLogo
                cache: false
                anchors.fill: parent
                //anchors.leftMargin: 20
                Layout.preferredWidth: Math.round(parent.width / 1.5)
                Layout.preferredHeight: Math.round(parent.height * 2)

                function reloadImage() {
                    var oldSource = source
                    source = ""
                    source = oldSource
                }//function to refresh the source

            }

And then I only call this function wherever you want to reload the image

imageLogo.reloadImage()

Upvotes: 3

dtech
dtech

Reputation: 49319

It is very simple, all you have to do is draw the image in the paint() method:

void paint(QPainter *painter) {
    painter->drawImage(QPoint(0, 0), yourImage);
}

Then call update() every time yourImage changes.

Don't draw raw data, create a QImage from it with the proper format and draw that. You can use the static method QImage fromData(const uchar *data, int size, const char *format = Q_NULLPTR). Since QImage is an implicitly shared object, you can efficiently yourImage = QImage::fromData(...) and then update() every time you get new data.

EDIT:

You should be creating the object in QML. Also, seeing how you use threads, keep in mind UI elements can only be accessed from the main thread. This means that you can't do this:

_spvideostr->setStreamImage(clImage);
_spvideostr->update();

as you are accessing a UI object from another thread. Instead you should use a signal and slot with a queued connection, pass the image through that connection, and set the image and call update() from the slot of StreamPainter.

You don't need that:

Q_PROPERTY(QImage streamImage READ streamImage WRITE setStreamImage NOTIFY streamImageChanged)

This image will only be accessed from that class. So you don't need those either:

QImage streamImage();
void setStreamImage(QImage clImage);

signals:
    void streamImageChanged(QImage Image);

What you need there is a:

public slots:
    void onImageAvaiable(QImage image) { ...set image and call update }

In PipeThread you need a signal void imageAvaiable(QImage), connect imageAvaiable to onImageAvaiable and emit the image:

QImage clImage(_rawData, IMG_WIDTH, IMG_HEIGHT, QImage::Format_Indexed8);
emit imageAvaiable(clImage);

Upvotes: 2

Related Questions