6

I am trying to display network image using qml and then save this image using c++ code,

Here is the qml code,

import QtQuick 2.3
import QtQuick.Window 2.2
import com.login 1.0
Window {
    visible: true
    width : 500
    height: 500

     Login{id: login}

    MouseArea {
        anchors.fill: parent
        onClicked: {
          //  Qt.quit();
            login.save(image);
        }
    }



    Image {
        id: image
        source: "http://www.test.com/webp/gallery/4.jpg"
    }


}

And inside my login class saving image like,

void Login::save( QQuickItem *item)
{
    qDebug()<<"width: "<<item->width();
    qDebug()<<"height: "<<item->height();

    QQuickWindow *window = item->window();
    QImage image = window->grabWindow();

    QPixmap pix = QPixmap::fromImage(image);
    pix.save("C:/Users/haris/Desktop/output.png");
}

I am getting the correct width and height of the image inside c++ class, but the problem is I cannot find a way to save the image item from QQuickItem.

Right now I am saving the image by grabing the window, which actually not giving the actual image size on output file, instead giving output file with current qml window size.

Basically I am following the code here saving QML image but it seems QDeclarativeItem is deprecated in Qt5, so I choose QQuickItem where as there is no paint option in QQuickItem.

Community
  • 1
  • 1
Haris
  • 13,645
  • 12
  • 90
  • 121

2 Answers2

12

Fortunately QQuickItem has a convenient grabToImage function which does that.

void Login::save( QQuickItem *item)
{
    QSharedPointer<const QQuickItemGrabResult> grabResult = item->grabToImage();

    connect(grabResult.data(), &QQuickItemGrabResult::ready, [=]() {
        grabResult->saveToFile("C:/Users/haris/Desktop/output.png");
        //grabResult->image() gives the QImage associated if you want to use it directly in the program
    });
}

Alternate solution without using lambdas:

void Login::save( QQuickItem *item)
{
    QSharedPointer<const QQuickItemGrabResult> grabResult = item->grabToImage();
    
    /* Need to store grabResult somewhere persistent to avoid the SharedPointer mechanism from deleting it */
    ...

    connect(grabResult.data(), SIGNAL(ready()), this, SLOT(onAsynchroneousImageLoaded()));
}

void Login::onAsynchroneousImageLoaded() {
    auto grabResult = qobject_cast<const QQuickItemGrabResult*>(sender());
    if (grabResult) {
        grabResult->saveToFile("C:/Users/haris/Desktop/output.png");
    } else {
        //something went wrong
    }
});
coyotte508
  • 9,175
  • 6
  • 44
  • 63
  • Hi Thanks for the answer, I have tried both `grabResult->saveToFile()` and `grabResult->image()` but both fails, in first case it always return `false` and in second case it return null image, what could be the issue? – Haris May 25 '16 at 14:31
  • I think the problem is as docs says `The grab happens asynchronously and the signal QQuickItemGrabResult::ready() is emitted when the grab has been completed`, I may need to use ready signal. – Haris May 25 '16 at 14:35
  • 2
    Thanks, I edited the answer with code that handles that using a lambda function to react to the signal. – coyotte508 May 25 '16 at 15:06
  • I think there is a syntax error in the lambda function, can you please edit it. Actually I am not well familiar with lambda function. – Haris May 25 '16 at 15:14
  • 2
    Done. Don't forget to make sure your project has c++ enabled (CONFIG+=c++11) – coyotte508 May 25 '16 at 15:27
  • Yes it already enabled in pro file, but still giving the error like `E:\Qt_Project\PDF\login.cpp:45: error: C2664: 'QMetaObject::Connection QObject::connect(const QObject *,const char *,const char *,Qt::ConnectionType) const' : cannot convert argument 3 from 'Login::save::' to 'const char *' No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called` – Haris May 25 '16 at 15:31
  • @Haris it seems to be a problem with your Qt version then. Instead you can just connect the signal `ready()` to any of your slots and use `qobject_cast(sender())->saveToFile(...)` in it. I updated the answer to add that code as well. – coyotte508 May 25 '16 at 16:05
  • I tried that too, but seems the slot not getting called, might be some issue with qt version, I am using 5.5.1 on Windows8. – Haris May 25 '16 at 16:16
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/112945/discussion-between-haris-and-coyotte508). – Haris May 25 '16 at 16:31
  • 1
    Thank you a lot, this answer is awesome. I fixed my problem but I want to contribute to how the way of implementing Qt 5.15 because of the method `saveToFile()` without const was deprecated. You can not use the auto declared instead of the snippet below `QSharedPointer result = item->grabToImage(); connect(result.data(), &QQuickItemGrabResult::ready, [=](){ result->saveToFile("C:/Users/MarcusPC/Desktop/something.png"); });` – Chinh Nguyen Huu Nov 19 '20 at 03:40
  • @ChinhNguyenHuu thanks, I edited the answer (you can also suggest edits on SO!) – coyotte508 Nov 20 '20 at 11:14
0

In a QObject-derived class (ImageSaver) register it as you would. It needs one member:

bool ImageSaver::saveImage(const QUrl &imageProviderUrl, const QString &filename){
    qDebug() << Q_FUNC_INFO <<imageProviderUrl << filename;
    QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
    QQmlImageProviderBase *imageProviderBase = engine->imageProvider(imageProviderUrl.host());
    QQuickImageProvider *imageProvider = static_cast<QQuickImageProvider*>(imageProviderBase);
    QSize imageActualSize;
    QSize imageRequestedSize;
QString imageId = imageProviderUrl.path().remove(0,1);

    QImage image = imageProvider->requestImage(imageId, &imageActualSize, imageRequestedSize);
    qDebug() << Q_FUNC_INFO << imageId << imageActualSize;
    return image.save(filename);
}

then in QML:

ImageSaver { id: imageSaver} 
...
imageSaver.saveImage(image.source, "my.png");
...

Whereas grabToImage will grab the item using the items's size, this can preserve the actual size of the image.

user9170
  • 950
  • 9
  • 18