Pass QImage to QML
Asked Answered
W

2

8

I am trying to pass a QImage to QML. Could someone help me? The code is below.

The problem is that all my attempts to make the image available for the image provider fail. I have tried using a Q_PROPERTY and a QImage member inside the class, but my provider always returns a null image when I try to access it to return.

How can I make the QImage available for the provider?

QML

Camera {
    id: camera

    captureMode: Camera.CaptureStillImage

    imageCapture {
        onImageCaptured: {
            manipulaImagem.imagem = preview;

            previewImage.source = manipulaImagem.recortarFotoPerfil(preview, viewfinder.mapRectToSource(Qt.rect(viewfinder.x, viewfinder.y, viewfinder.width, viewfinder.height)));
        }
    }
}
Image {
    id: previewImage

    fillMode: Image.PreserveAspectFit
    anchors.top: parent.top

    width: parent.width
    height: parent.width
}

CPP

QImage manipulaImagem::recortarFotoPerfil(const QString &imagem, QRect rectRecorte)
{
    QUrl caminhoImagem(imagem);
    QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
    QQmlImageProviderBase *imageProviderBase = engine->imageProvider(caminhoImagem.host());
    QQuickImageProvider *imageProvider = static_cast<QQuickImageProvider*>(imageProviderBase);

    QSize imageSize;
    QString imageId = caminhoImagem.path().remove(0, 1);
    QImage imagem1 = imageProvider->requestImage(imageId, &imageSize, imageSize);
    imagem1 = imageProvider->requestImage(imageId, &imageSize, imageSize);
    return imagem1;
}

When I use this I receive the following message: Error: Cannot assign QImage to QUrl

I did not find anything to help me to solve this. How can I do that?


I have tried a different approach using the image provider as suggested by the links, but it still not working

Here is the code

.h

#ifndef MANIPULAIMAGEM_H
#define MANIPULAIMAGEM_H

#include <QObject>
#include <QImage>
#include <QQuickImageProvider>
#include <QQmlEngine>
#include <QQmlContext>

class manipulaImagem : public QObject, public QQuickImageProvider
{
    Q_OBJECT

public slots:
    QString recortarFotoPerfil(const QString &imagem, QRect rectRecorte);

public:
    manipulaImagem(QObject *parent = 0);

    QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);

private:
    void alocaImagem(const QString &imagem, QRect rectRecorte);

    QImage imagemEditada;
};

#endif // MANIPULAIMAGEM_H

.cpp

#include "manipulaimagem.h"

#include <QDebug>

manipulaImagem::manipulaImagem(QObject *parent) : QQuickImageProvider(QQmlImageProviderBase::Image)
{

}

QImage manipulaImagem::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
    if(imagemEditada.isNull())
    {
        qDebug() << "Request image: (image is null)";
    }
    else
    {
        qDebug() << "Request image: image is OK";
    }

    return imagemEditada;
}

void manipulaImagem::alocaImagem(const QString &imagem, QRect rectRecorte)
{
    QUrl caminhoImagem(imagem);
    QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
    QQmlImageProviderBase *imageProviderBase = engine->imageProvider(caminhoImagem.host());
    QQuickImageProvider *imageProvider = static_cast<QQuickImageProvider*>(imageProviderBase);

    QSize imageSize;
    QString imageId = caminhoImagem.path().remove(0, 1);
imagemEditada = imageProvider->requestImage(imageId, &imageSize, imageSize);

    if(imagemEditada.isNull())
    {
        qDebug() << "Loading image failed";
    }
    else
    {
        qDebug() << "Loading image OK";
    }
}

QString manipulaImagem::recortarFotoPerfil(const QString &imagem, QRect rectRecorte)
{
    this->alocaImagem(imagem, rectRecorte);

    QString a = "image://ProvedorImagens/imagemEditada";

    if(imagemEditada.isNull())
    {
        qDebug() << "Imagem is null";
    }
    else
    {
        qDebug() << "Imagem is loaded";
    }

    return a;
}

.qml

ManipulaImagem {
    id: manipulaImagem
}

Camera {
            id: camera

            captureMode: Camera.CaptureStillImage

            imageCapture {
                onImageCaptured: {
                    previewImage.source = manipulaImagem.recortarFotoPerfil(preview, viewfinder.mapRectToSource(Qt.rect(viewfinder.x, viewfinder.y, viewfinder.width, viewfinder.height)));
                }
            }
        }

Rectangle {
    id: previewRectangle

    visible: false

    anchors.fill: parent

    Image {
        id: previewImage

        fillMode: Image.PreserveAspectFit

        anchors.top: parent.top

        width: parent.width
        height: parent.width
    }

The output of this code is:

  • Loading image OK

  • Imagem is loaded

  • Request image: (image is null)

QML Image: Failed to get image from provider: image://provedorimagens/imagemEditada

What happens is that when I call the functions the image is not null, but when I try to return the QImage using the provider it cant return the image. I dont know why but for the image provider the image is null.

How could I solve that?

Weissmann answered 29/10, 2015 at 18:44 Comment(3)
You have to implement your own image provider in order to use QImage in QML. I have an example set up in this answer: #27429871 just modify it for QImage instead of QPixmapAgape
Possible duplicate of Qt/QML : Send QImage From C++ to QML and Display The QImage On GUIReason
The problem is that all my attempts to make the image available for the image provider fail. I have tried using a Q_PROPERTY and a QImage member inside the class, but my provider always returns a null image when I try to access it to return.Weissmann
W
10

Answering my own question Problem solved. Here is the solution step by step:

1 - Create a class that inherits from QQuickImageProvider and QObject and inside it create a Image member (QImage) that is the image to be provided.

class provedorImagem : public QObject, public QQuickImageProvider

Implement the virtual requestImage method. This is the method that will return the image to Qml

QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize)

Create a method to load the provider’s image to return

void provedorImagem::carregaImagem(QImage imagemRecebida)
{
    imagem = imagemRecebida;
}

Now set it as the engine image provider in the main.cpp file

provedorImagem *provedorImg = new provedorImagem;
engine.rootContext()->setContextProperty("ProvedorImagem", provedorImg);

2 - Create another class that inherits from QObject.

class processaImagem : public QObject

Inside this class you must implement a method that will get the image from camera provider, perform the image modifications and return the modified image. PS: The p_caminhoImagem is a property that I created inside the processaImagem class that receives the camera preview path.

QImage processaImagem::carregaImagem()
{
    QUrl caminhoImagem(p_caminhoImagem);
    QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
    QQmlImageProviderBase *imageProviderBase = engine->imageProvider(caminhoImagem.host());
    QQuickImageProvider *imageProvider = static_cast<QQuickImageProvider*>(imageProviderBase);


    QSize imageSize;
    QString imageId = caminhoImagem.path().remove(0, 1);
    QImage imagem = imageProvider->requestImage(imageId, &imageSize, imageSize);

    if(imagem.isNull())
    {
        imagem = QImage();
    }
    else
    {
        //Perform the modifications
    }

    return imagem;
}

3 - Now is the main part. The image requestImage provider method must receive the modified image from the processaImagem class to provide it to QML. To do it the provider class pointer must be accessible to the QML file, so, in the main.cpp file just make the pointer available to QML as a property

engine.rootContext()->setContextProperty("ProvedorImagem", provedorImg);

and register the processaImagem class as a QML type

qmlRegisterType<processaImagem>("ProcessaImagemQml", 1, 0, "ProcessaImagem");

Now we link it inside the QML file

ProvedorImagem.carregaImagem(processaImagem.carregaImagem());

4 - It is done. Now just request the image from the provider:

imagemPreview.source = "image://provedor/imagemEditada_" + camera.numeroImagem.toString();

Here is the entire code:

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include <QtQml>

#include "processaimagem.h"
#include "provedorimagem.h"

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

    qmlRegisterType<processaImagem>("ProcessaImagemQml", 1, 0, "ProcessaImagem");

    QQmlApplicationEngine engine;

    provedorImagem *provedorImg = new provedorImagem;

    engine.rootContext()->setContextProperty("ProvedorImagem", provedorImg);

    engine.addImageProvider("provedor", provedorImg);

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    return app.exec();
}

main.qml

import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Controls 1.3
import QtMultimedia 5.4

import ProcessaImagemQml 1.0

Window {
    visible: true

    width: 360
    height: 640

    maximumHeight: 640
    minimumHeight: 640

    maximumWidth: 360
    minimumWidth: 360

    title: "Camera Preview Test"

    Rectangle {
        id: principal

        anchors.fill: parent

        ProcessaImagem {
            id: processaImagem

            caminhoImagem: camera.caminhoPreview
            caminhoSalvar: camera.caminhoSalvar
            rectRecorte: camera.rectRecorte
            tamanhoImagem: camera.tamanhoImagem
            anguloOrientacaoCamera: camera.orientation
            posicaoCamera: camera.position

            onCaminhoImagemChanged: {
                rectRecorte = cameraView.mapRectToSource(Qt.rect(cameraView.x, cameraView.y, cameraView.width, cameraView.height));
                tamanhoImagem = Qt.size(cameraView.sourceRect.width, cameraView.sourceRect.height);
                ProvedorImagem.carregaImagem(processaImagem.carregaImagem());
            }

            onCaminhoSalvarChanged: {
                removeImagemSalva();
            }
        }

        Rectangle {
            id: cameraRectangle

            width: parent.width
            height: parent.width

            anchors.top: parent.top

            color: "lightGrey"

            visible: true

            Camera {
                id: camera

                property string caminhoPreview: ""
                property string caminhoSalvar: ""
                property int numeroImagem: 0

                captureMode: Camera.CaptureStillImage

                imageCapture {
                    onImageCaptured: {
                        camera.caminhoPreview = preview;

                        camera.stop();

                        imagemPreview.source = "image://provedor/imagemEditada_" + camera.numeroImagem.toString();

                        camera.numeroImagem = camera.numeroImagem + 1;

                        imagemPreviewRectangle.visible = true;

                        cameraRectangle.visible = false;
                    }

                    onImageSaved: {
                        camera.caminhoSalvar = path;
                    }
                }
            }

            VideoOutput {
                id: cameraView

                visible: true

                focus: visible

                anchors.fill: parent

                source: camera
                orientation: camera.orientation
                fillMode: VideoOutput.PreserveAspectCrop
            }
        }

        Rectangle {
            id: imagemPreviewRectangle

            width: parent.width
            height: parent.width

            anchors.top: parent.top

            color: "lightGrey"

            visible: false

            Image {
                id: imagemPreview

                fillMode: Image.PreserveAspectFit

                anchors.fill: parent
            }
        }

        Rectangle {
            id: controleRectangle

            width: parent.width
            height: parent.height - cameraRectangle.height

            color: "grey"

            anchors.top: cameraRectangle.bottom

            Button {
                id: tirarFotoButton

                text: "Tirar foto"

                anchors.left: parent.left
                anchors.top: parent.top

                onClicked: {
                    camera.imageCapture.capture();
                }
            }

            Button {
                id: novaFotoButton

                text: "Tirar nova foto"

                anchors.right: parent.right
                anchors.top: parent.top

                onClicked: {
                    camera.start();

                    imagemPreviewRectangle.visible = false;

                    cameraRectangle.visible = true;
                }
            }
        }
    }
}

processaimagem.h

#ifndef PROCESSAIMAGEM_H
#define PROCESSAIMAGEM_H

#include <QObject>
#include <QImage>
#include <QQmlEngine>
#include <QQmlContext>
#include <QQuickImageProvider>
#include <QFile>

#include "provedorimagem.h"

class processaImagem : public QObject
{
    Q_OBJECT

    Q_PROPERTY(QString caminhoImagem READ caminhoImagem WRITE setCaminhoImagem NOTIFY caminhoImagemChanged)
    Q_PROPERTY(QString caminhoSalvar READ caminhoSalvar WRITE setCaminhoSalvar NOTIFY caminhoSalvarChanged)
    Q_PROPERTY(QRect rectRecorte READ rectRecorte WRITE setRectRecorte NOTIFY rectRecorteChanged)
    Q_PROPERTY(QSize tamanhoImagem READ tamanhoImagem WRITE setTamanhoImagem NOTIFY tamanhoImagemChanged)
    Q_PROPERTY(int anguloOrientacaoCamera READ anguloOrientacaoCamera WRITE setAnguloOrientacaoCamera NOTIFY anguloOrientacaoCameraChanged)
    Q_PROPERTY(int posicaoCamera READ posicaoCamera WRITE setPosicaoCamera NOTIFY posicaoCameraChanged)

public slots:
    QImage carregaImagem();
    void removeImagemSalva();

public:
    processaImagem(QObject *parent = 0);

    QString caminhoImagem() const;
    void setCaminhoImagem(const QString valor);

    QString caminhoSalvar() const;
    void setCaminhoSalvar(const QString valor);

    QRect rectRecorte() const;
    void setRectRecorte(const QRect valor);

    QSize tamanhoImagem() const;
    void setTamanhoImagem(const QSize valor);

    int anguloOrientacaoCamera() const;
    void setAnguloOrientacaoCamera(const int valor);

    int posicaoCamera() const;
    void setPosicaoCamera(const int valor);

private:
    QString p_caminhoImagem = "";
    QString p_caminhoSalvar = "";
    QRect p_rectRecorte = QRect(0, 0, 0, 0);
    QSize p_tamanhoImagem = QSize(0, 0);
    int p_anguloOrientacaoCamera = 0;
    int p_posicaoCamera = 0;

signals:
    void caminhoImagemChanged();
    void caminhoSalvarChanged();
    void rectRecorteChanged();
    void tamanhoImagemChanged();
    void anguloOrientacaoCameraChanged();
    void posicaoCameraChanged();
};

#endif // PROCESSAIMAGEM_H

processaimagem.cpp

#include "processaimagem.h"

#include <QDebug>

processaImagem::processaImagem(QObject *parent)
{

}

QImage processaImagem::carregaImagem()
{
    QUrl caminhoImagem(p_caminhoImagem);
    QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
    QQmlImageProviderBase *imageProviderBase = engine->imageProvider(caminhoImagem.host());
    QQuickImageProvider *imageProvider = static_cast<QQuickImageProvider*>(imageProviderBase);


    QSize imageSize;
    QString imageId = caminhoImagem.path().remove(0, 1);
    QImage imagem = imageProvider->requestImage(imageId, &imageSize, imageSize);

    if(imagem.isNull())
    {
        qDebug() << "Erro ao carregar a imagem";
        imagem = QImage();
    }
    else
    {
        if((p_anguloOrientacaoCamera == 90) || (p_anguloOrientacaoCamera == 270))
        {
            int larguraImagem = p_tamanhoImagem.width();
            int alturaImagem = p_tamanhoImagem.height();

            p_tamanhoImagem.setWidth(alturaImagem);
            p_tamanhoImagem.setHeight(larguraImagem);

            int recorteX = p_rectRecorte.x();
            int recorteY = p_rectRecorte.y();
            int recorteLargura = p_rectRecorte.width();
            int recorteAltura = p_rectRecorte.height();

            p_rectRecorte.setRect(recorteY, recorteX, recorteAltura, recorteLargura);

            if(imagem.size().width() > imagem.size().height())
            {
                QTransform rotacao;
                rotacao.rotate(360 - p_anguloOrientacaoCamera);
                imagem = imagem.transformed(rotacao);

                qDebug() << "Rodou";
            }
        }

        if(imagem.width() != p_tamanhoImagem.width())
        {
            imagem = imagem.scaled(p_tamanhoImagem);
        }

        imagem = imagem.copy(p_rectRecorte);
    }

    return imagem;
}

void processaImagem::removeImagemSalva()
{
    QFile::remove(p_caminhoSalvar);
}

QString processaImagem::caminhoImagem() const
{
    return p_caminhoImagem;
}

void processaImagem::setCaminhoImagem(const QString valor)
{
    if (valor != p_caminhoImagem)
    {
        p_caminhoImagem = valor;
        emit caminhoImagemChanged();
    }
}

QString processaImagem::caminhoSalvar() const
{
    return p_caminhoSalvar;
}

void processaImagem::setCaminhoSalvar(const QString valor)
{
    if (valor != p_caminhoSalvar)
    {
        p_caminhoSalvar = valor;
        emit caminhoSalvarChanged();
    }
}

QRect processaImagem::rectRecorte() const
{
    return p_rectRecorte;
}

void processaImagem::setRectRecorte(const QRect valor)
{
    bool alterou = false;

    if (valor.x() != p_rectRecorte.x())
    {
        p_rectRecorte.setX(valor.x());
        alterou = true;
    }

    if (valor.y() != p_rectRecorte.y())
    {
        p_rectRecorte.setY(valor.y());
        alterou = true;
    }

    if (valor.width() != p_rectRecorte.width())
    {
        p_rectRecorte.setWidth(valor.width());
        alterou = true;
    }

    if (valor.height() != p_rectRecorte.height())
    {
        p_rectRecorte.setHeight(valor.height());
        alterou = true;
    }

    if(alterou)
    {
        emit rectRecorteChanged();
    }
}

QSize processaImagem::tamanhoImagem() const
{
    return p_tamanhoImagem;
}

void processaImagem::setTamanhoImagem(const QSize valor)
{
    bool alterou = false;

    if (valor.width() != p_tamanhoImagem.width())
    {
        p_tamanhoImagem.setWidth(valor.width());
        alterou = true;
    }

    if (valor.height() != p_tamanhoImagem.height())
    {
        p_tamanhoImagem.setHeight(valor.height());
        alterou = true;
    }

    if(alterou)
    {
        emit tamanhoImagemChanged();
    }
}

int processaImagem::anguloOrientacaoCamera() const
{
    return p_anguloOrientacaoCamera;
}

void processaImagem::setAnguloOrientacaoCamera(const int valor)
{
    if (valor != p_anguloOrientacaoCamera)
    {
        p_anguloOrientacaoCamera = valor;
        emit anguloOrientacaoCameraChanged();
    }
}

int processaImagem::posicaoCamera() const
{
    return p_posicaoCamera;
}

void processaImagem::setPosicaoCamera(const int valor)
{
    if (valor != p_posicaoCamera)
    {
        p_posicaoCamera = valor;
        emit posicaoCameraChanged();
    }
}

provedorimagem.h

#ifndef PROVEDORIMAGEM_H
#define PROVEDORIMAGEM_H

#include <QObject>
#include <QImage>
#include <QQuickImageProvider>

class provedorImagem : public QObject, public QQuickImageProvider
{
    Q_OBJECT

public:
    provedorImagem();

    QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);

public slots:
    void carregaImagem(QImage imagemRecebida);

private:
    QImage imagem;
};

#endif // PROVEDORIMAGEM_H

provedorimagem.cpp

#include "provedorimagem.h"

#include <QDebug>

provedorImagem::provedorImagem() : QQuickImageProvider(QQuickImageProvider::Image)
{

}

QImage provedorImagem::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
    if(imagem.isNull())
    {
        qDebug() << "Erro ao prover a imagem";
    }

    return imagem;
}

void provedorImagem::carregaImagem(QImage imagemRecebida)
{
    imagem = imagemRecebida;
}
Weissmann answered 12/11, 2015 at 17:3 Comment(4)
Thank you for posting this. The example would, however, benefit from being written with English class and member names. Not everyone in the world reads Portugese...Brookes
I didn't write it in English because it is part of an app of mine and this app is written in Portuguese.Weissmann
@KlaasvanGend I didn't understand where is this variable coming from: imagemEditada_. GuiDupas please help.Infracostal
It comes from the provedorImagem that uses the processaImagem to get and modify the image taken.Weissmann
T
2

The proper way to do this is via QQuickImageProvider because it performs and has the most options for customization. However, it is not usually the first choice, because it's non-trivial code.

If you are looking for a shortcut solution, you can convert the QImage to a data-URI QUrl. Once it's a data-URI QUrl it can be given to QML.

QUrl imageToUrl(const QImage& image)
{
    QByteArray byteArray;
    QBuffer buffer(&byteArray);
    buffer.open(QIODevice::WriteOnly);
    int quality = -1; // (default) compressed data, slower
    // int quality = 100; // large uncompressed data, faster
    image.save(&buffer, "png", quality);
    QString base64 = QString::fromUtf8(byteArray.toBase64());
    return QString("data:image/png;base64,") + base64;
}

Whilst the code is short to implement, its use case is limited to low-frequency applications. If you attempt to update a QML image with data-URI arriving at a high frequency, QML may show an image that's constantly flickering or, even, an image that never renders before the next data-URI arrives. If you're suffering from such a scenario you can consider setting quality = 100 to increase the speed at which the data-URI is generated and, subsequently, rendered.

Tipsy answered 28/7, 2021 at 5:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.