Qt/QML:如何从C++发送QImage到QML并在GUI上显示QImage

44

我创建了一个名为Publisher的类,周期性地发射一个QImage对象。

然而,我很难将QImage绘制到QML元素上。似乎ImageCanvas QML组件需要使用QUrl而不是QImage,但我不知道如何将我的QImage转换为QUrl。编辑4:当我说QUrl时,我并不是说我要将图像转换为URL。那是无意义的。我的意思是我想生成对这个图像的引用,该图像不在磁盘上,并且QML组件正在请求的数据类型是URL。

我做了一些研究,发现QQuickImageProvider提供了解决方案,但我没有找到任何文档说明如何将我的QImage信号转换为可用于绘制的QUrl。如果有示例代码或参考文档,将不胜感激。

谢谢您的帮助!

编辑1:

我看过这里:http://qt-project.org/doc/qt-5.0/qtquick/qquickimageprovider.html,但我不知道如何将一个QImage传递给快速图像提供程序,并从中创建一个QUrl

编辑2。这是头文件。实现应该无关紧要。

class Publisher
{
    Q_OBJECT

public:
    Publisher(QObject* parent = 0);

    virtual ~Publisher(void);

Q_SIGNALS:

    void newImage(const QImage& newImage);
};

编辑 3. 这是我的 QML 代码,但我不知道如何绘制我的 QImage,所以这段代码有点没有意义。

我的 main.cpp 文件:

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();
}

我的 main.qml 文件:

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

1
我找不到任何示例代码来解释如何使用QImage向图像提供程序提供信号。 - trianta2
“给出你的信号”是什么意思?您想发出什么信号,以及您希望如何处理它? - László Papp
我所说的定期是指每隔x秒,我的自定义类从磁盘读取一张图像,将图像数据存储在QImage中,并调用“emit newImage(myImage)”,其中newImage是信号名称,myImage是一个QImage对象。 - trianta2
1
这仅用于测试目的。我不想解释这个测试背后的复杂性,但实际上,Publisher连接到一个websocket并接收图像数据流,将图像数据格式化为QImage对象,然后发射QImage对象到GUI以(希望)被绘制。没有文件I/O,我想避免文件I/O。 - trianta2
1
请返回已翻译的文本。 - Mohammad Reza Rastegari
显示剩余13条评论
4个回答

50
换句话说,您有一个发出携带QImage的信号的类,并希望使用该图像更新QML中的项目?有多种解决方案,其中没有一种涉及“将QImage转换为QUrl”(不管那是什么意思,您肯定不需要获取携带图像数据的data URL...)。
使用图像提供程序
这意味着您可以在QML文件中使用普通的Image项。
1. 创建一个 QQuickImageProvider 子类;给它一个 QImage 成员(要提供的图像),重写 requestImage 以提供该图像(实际请求的id并不重要,请参见下文),以及一个接收 QImage 并更新成员的槽。 2. 将您的 Publisher信号连接到提供程序的槽。 3. 通过 QQmlEngine::addImageProvider (请参阅QQuickView::engine),将提供程序安装到 QML 引擎中;再次强调,id 并不真正重要,只需使用明智的一个。 4. 在 QML 中,只需使用一个普通的Image元素,并设置如下源。
Image {
    id: myImage
    source: "image://providerIdPassedToAddImageProvider/foobar"
}

foobar将被传递给您的提供程序,但再次强调,这并不重要。

  • 我们接近成功了,现在我们只需要一种方法来推送图像更新到QML世界中(否则Image将永远不知道何时更新自己)。请参阅我的答案,了解如何使用Connections元素和一些JS来实现。

    请注意,通常情况下,您无需将Publisher作为QML类型,您只需要在C++中创建一个实例,并通过QQmlContext::setContextProperty将其公开到QML世界中即可。

  • 使用自定义的Qt Quick 2项

    QQuickPaintedItem可能是最方便的工具,因为它提供了一个采用QPainterpaint方法。因此,大计划是

    1. 子类化QQuickPaintedItem:该子类存储要绘制的QImage,并具有设置新QImage的插槽。此外,它的paint实现只是使用QPainter::drawImage绘制图像。
    2. 通过qmlRegisterType将该子类公开到QML世界中(以便您可以在QML中使用它)
    3. 找到一种方法将携带新图像的信号连接到项的插槽中。

      这可能是棘手的部分。

      要在C++中执行连接,您需要一种方法来确定是否已创建了该项(并获取指向该项的指针)。通常情况下,人们通过将objectName属性分配给某个值,然后在根对象上(由QQuickView::rootObject()返回)使用findChild来获取指向该项本身的指针。然后您可以像通常一样使用connect

      或者,可以像上面那样通过在暴露给QML世界的发布者C++对象上使用Connections元素在QML中执行连接:

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

      这种方法的优点是无论何时创建实例都可以工作;但我不能100%确定它能够工作,因为我不确定你是否能够在QML中处理QImage类型。


    1
    仅供记录,我刚刚在Qt5.4中成功地使用了上述第二种技术:将一个C++ QtQuick项目的QImage属性连接到另一个C++类,该类是QQuickPainter子类,并且存在于仅用于显示QImage的目的。 我甚至不需要连接,在QML MyItem {image: otherItem.image;}绑定中,当otherItem发出imageChanged时正常工作。 - timday
    2
    方法1似乎不起作用,因为 QQuickImageProvider 不是一个 QObject,因此不能拥有任何插槽。使用多重继承来赋予它插槽也似乎不是一个好主意,因为MOC不喜欢这样做。 - Daniel Saner

    5

    当我需要在QML中嵌入生成图像的C++类时,我通常会将该C++类作为QDeclarativeItem的子类(当然,在QtQuick 2.0中会有一个新的等效类),并重写paint方法,使用适当的绘制代码进行绘制,可能非常简单,例如:

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

    如果你已经有了一个正确大小的QImage......任务完成。对于动画,当有新的绘制内容时,请调用update()函数。

    请注意,OP似乎在询问关于Qt Quick 2的内容,而您的解决方案是基于QtQuick 1的。最接近的匹配是QQuickPaintedItem - peppe
    2
    QQuickPaintedItem 是与旧绘画方法相关的。为了使用新技术并获得最佳性能,您需要使用 QQuickItem 并使用纹理。 - S.M.Mousavi

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

    将图像字符串直接发送到 QML 源


    1
    这可能有效,但明知浪费资源。因此我必须投反对票;地球上的二氧化碳已经太多了。 - xtofl
    我独立想出了相同的解决方案,但是我使用了PNG而不是JPEG,因为它是无损的。 - Stephen Quan

    0
    使用QQuickItem。有一个Qt示例可以做到这一点。

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

    你需要创建一个继承自QQuickItem的类,并使用qmlRegisterType将其注册。

    在你的类中提供重载函数'updatePaintNode'。在这个例子中,这个类叫做'Spinner'。

    在updatePaintNode函数中:

    创建一个继承自QObject和QSGTransformNode的节点类。

    在节点类的构造函数中:

    使用createTextureFromImage将你的Qimage转换为QSGTexture。

    创建QSGSimpleTextureNode,使用setTexture设置QSGTexture。

    使用appendChildNode添加QSGSimpleTextureNode。

    在QML中添加相应内容。

    import Spinner 1.0
    

    并且

    Spinner {
        anchors.centerIn: parent
        anchors.horizontalCenterOffset: 80
        spinning: true
    }
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接