37
votes

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
            }
        }
    }
}
4
What is wrong about the image provider again?lpapp
I cannot find any example code that explains how to give the image provider my signal with a QImage.trianta2
What do you mean by "give your signal"? What signal would you like to emit, and how would you like it to be handled?lpapp
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.trianta2

4 Answers

45
votes

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.

5
votes

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.

0
votes

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
}
0
votes
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