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

4

44

I created a class Publisher which periodically emits a QImage object.

However I'm having a tough time drawing the QImage to a QML element. It appears that the Image and Canvas QML components require a QUrl instead of a QImage, but I'm not sure how to convert my QImage to a QUrl. Edit4: When I say QUrl, I don't mean I'm trying to convert an image to a URL. That's nonsense. I mean I want to generate a reference to this image, which is not on disk, and the data type that QML components are asking for is a URL.

I've done some research and found that QQuickImageProvider provides a solution, but I haven't found any documentation explaining how to convert my QImage signal to a QUrl that I can use for drawing. Any example code or reference documentation would be appreciated.

Thanks for your help!

Edit1:

I've taken a look here: http://qt-project.org/doc/qt-5.0/qtquick/qquickimageprovider.html and I do not see how I pass a QImage to the quick image provider and from it create a QUrl.

Edit2. Here is the header. The implementation should not be important.

class Publisher
{
    Q_OBJECT

public:
    Publisher(QObject* parent = 0);

    virtual ~Publisher(void);

Q_SIGNALS:

    void newImage(const QImage& newImage);
};

Edit 3. Here is my QML code, but I don't know how to draw my QImage, so this code is kind of meaningless.

my main.cpp file:

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

    qmlRegisterType<Publisher>("Components", 1, 0, "Publisher");

    QtQuick2ApplicationViewer viewer;
    viewer.setMainQmlFile(QStringLiteral("qml/QQuickViewExample/main.qml"));
    viewer.showExpanded();

    return app.exec();
}

my main.qml file:

import QtQuick 2.0
import Components 1.0

Rectangle {
    id : testRect
    width: 360
    height: 360

    Image{
        anchors.fill: parent
        id: myImage

        Publisher {
            id: myPub

            onNewImage: {
                myImage.source = newImage;  #I know this doesnt work, it needs a QUrl and not a QImage
            }
        }
    }
}
Weevily answered 19/12, 2013 at 20:50 Comment(18)
What is wrong about the image provider again?Charismatic
I cannot find any example code that explains how to give the image provider my signal with a QImage.Weevily
What do you mean by "give your signal"? What signal would you like to emit, and how would you like it to be handled?Charismatic
My custom class has been exported to QML and it emits a QImage signal periodically. I want the QImage signal to update the source of an Image component and thus have it drawn on the GUI.Weevily
What does it mean "it emits a QImage periodically"?Charismatic
By periodically, I mean that every x seconds, my custom class reads an image from disk, stores the image data in a QImage, and calls "emit newImage(myImage)", where newImage is the signal name, and myImage is a QImage object.Weevily
What is the Publisher element supposed to do? You need to paste more code and context. This conversation feels like the "Twenty Questions".Charismatic
I don't understand why more information about Publisher is necessary. All there is to know is that there is an exported C++ class that will emit a signal of type QImage. I don't understand how to now draw this QImage to a GUI.Weevily
Look, it is very hard for me to understand what you are trying to achieve. If you do not wish to clarify, it will be more difficult to find a good solution for your problem. Let us hope it is just me...Charismatic
let us continue this discussion in chatWeevily
Now, you would need to paste the QML experiment as well so that we can see what you have tried to achieve, or at least, you could post some pseudo-code that you would like to reach.Charismatic
Please view my edits.Weevily
Thanks; now it is much clearer. Since you load the QImage based on your comment, why not emit with the path itself as opposed to the QImage? "newImageUrl/Path"? Why not add a new signal? You will need that because QImage has no url information for you. The responsibility of the class is only storing the image data, not the source where it came from, etc.Charismatic
This is only for test purposes. I did not want to explain the complexity behind this test, but in reality, Publisher is connected to a websocket and it is receiving a stream of image data, formatting the image data in a QImage object, and then emitting the QImage object to the GUI to (hopefully) be drawn. There is no file I/O and I want to avoid file I/O.Weevily
Right, the work around is always there to save it into a path, although that could be overly slow with big QImages. I do not know the answer for this question, I am afraid, but at least the problem is now clear. :-)Charismatic
As per IRC discussion with @peppe, you could probably make a dirty workaround by reimplementing requestImage, and the url from qml would be arbitrary since it would not be used in the C++ code.Charismatic
Image provider should work, though achieving animation needed some trickery in Qt4 to force QML to update image with new contents. Don't remember details and can't check right now, just writing this to say it does work and google does find something relevant.Moersch
check huber.xyz/?p=477Sheree
N
50

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.

Numismatology answered 19/12, 2013 at 22:44 Comment(2)
For the record, I've just used the second technique suggested above in Qt5.4 successfully: to wire one C++ QtQuick item's QImage property to another C++ class which is QQuickPainter subclass and exists for the sole purpose of displaying QImage's. I don't even need the connections, in the QML MyItem {image: otherItem.image;} binding works fine when otherItem emits an imageChanged.Selfheal
Method 1 doesn't seem to work, because QQuickImageProvider is not a QObject, and therefore cannot have any slots. Using multiple inheritance to give it slots anyway seems to be a bad idea as well; MOC doesn't like it.Worn
S
5

When I've had image-producing C++ classes I've wanted to embed in QML, I've always done it by making the C++ class a subclass of QDeclarativeItem (there'll be a new QtQuick 2.0 equivalent of course), overriding the paint method with the appropriate drawing code, which maybe as simple as

void MyItem::paint(QPainter* painter,const QStyleOptionGraphicsItem*,QWidget*) {
  painter->drawImage(QPointF(0.0f,0.0f),_image);
}

if you have a QImage of the right size already... and Job Done. For animation, just ping update() when there's something new to draw.

Selfheal answered 19/12, 2013 at 21:51 Comment(2)
Note that OP seems to be asking about Qt Quick 2, your solution is QtQuick 1 based. The closest fit is QQuickPaintedItem.Numismatology
QQuickPaintedItem is related to old painting method. For new technology and most performance, you need to use QQuickItem and use texture.Graticule
B
3
QString getImage()
{
QByteArray byteArray;
QBuffer buffer(&byteArray);
buffer.open(QIODevice::WriteOnly);
img.save(&buffer,"JPEG");
 //save image data in string
QString image("data:image/jpg;base64,");
image.append(QString::fromLatin1(byteArray.toBase64().data()));
return image;
}

send image string directly to qml source

Baron answered 12/12, 2020 at 16:1 Comment(2)
This will probably work, but is knowlngly wasting resources. Therefor I must downvote; the planet has too much CO2 already.Intercolumniation
I, independently came up with the same solution, but, I used PNG instead of JPEG because it was lossless.Afrika
T
0

Use QQuickItem. There is a Qt example that does this.

C:\Qt\Examples\Qt-5.14.1\quick\scenegraph\threadedanimation

You make a class derived from QQuickItem and register it with qmlRegisterType. Provide the override function 'updatePaintNode' in your class. In the example, the class is 'Spinner'

In updatePaintNode:

Create a node class derived from QObject and QSGTransformNode

In the node class constructor:

Convert your Qimage to an QSGTexture with createTextureFromImage.

Create QSGSimpleTextureNode, set QSGTexture using setTexture

appendChildNode with QSGSimpleTextureNode

In QML add

import Spinner 1.0

and

Spinner {
    anchors.centerIn: parent
    anchors.horizontalCenterOffset: 80
    spinning: true
}
Teyde answered 26/2, 2020 at 17:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.