2

I know how to take a screenshot of the whole window in QML.

I have a Video element in the QML window. That video is shown in a Rectangle.

What would be the way to take a screenshot of that Rectangle rather than the whole window?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Aquarius_Girl
  • 21,790
  • 65
  • 230
  • 411

3 Answers3

5

It is very interesting question. As a quick and working solution I can propose you to use grabToImage method of Item. It takes first argument as callback function, and the second as path where you want to save.

I wrote small function for grabbing any Item:

// what -- name of item needed to be grabbed
// where -- string
function render(what, where) {
    // Find existent item with given name `what`
    var i = 0
    var found = false
    for (i = 0; i < window.contentItem.children.length; i++) {
        if (window.contentItem.children[i].objectName === what) {
            // We found respective item
            found = true
            break
        }
    }
    if (found) {
        console.log("We found item " + what + ". Grabbing it to " + where)
        var item = window.contentItem.children[i]
        // Grab image and save it (via callback f-ion)
        item.grabToImage( function(result) { result.saveToFile(where) })
    } else {
        console.warn("No item called " + what)
    }
}

So you can use it as on QML/QtQuick side, as on Qt (using QMetaObject::invokeMethod).


There is also QQuickItem::grabToImage method, but I'll be glad to see any adequate example of its usage.


Another way of doing grab is to use ShaderEffectSource and use as you wish.


All I wrote above was prepared as a project and located on github. Code is commented, so hopefully all will be clear. You can take it and do some hacking. Pull-requests are welcome as well.

NG_
  • 6,895
  • 7
  • 45
  • 67
  • _"There is also QQuickItem::grabToImage method, but I'll be glad to see any adequate example of its usage."_ You use it like this **void Screenshot::takeScreenShot(QQuickItem* callingObject) { auto itemScreenshot = callingObject->grabToImage(QSize(200, 200)); QObject::connect(itemScreenshot.get(), &QQuickItemGrabResult::ready, [itemScreenshot](){ itemScreenshot->saveToFile("screenshot.png"); }); }** – mattsson Nov 27 '19 at 11:51
  • @mattsson Could you please make your own answer, and include how you would use this function from QML? – Rob Jul 22 '20 at 17:01
  • @Rob Sure. I can do that. Is it yourself that wants to know? How much experience do you have of writing C++ classes and interacting with them from QML? Cause it's going to be quite an extensive answer if I have to include everything you need to do to call C++ methods from QML. In my previous comment I did above I just assumed that most QT/QML programmers already have that knowledge. (I know, I know. It's a bad thing to assume such things when writing explanations.) – mattsson Jul 23 '20 at 07:58
  • @mattsson If you want to use lots of links and be brief about the connections that would be fine. There are many ways to connect QML to C++ from what I understand, no? – Rob Jul 27 '20 at 18:15
3

Take a look at Item's grabToImage method.

bool grabToImage(callback, targetSize)

Grabs the item into an in-memory image.

The grab happens asynchronously and the JavaScript function callback is invoked when the grab is completed.

Use targetSize to specify the size of the target image. By default, the result will have the same size as the item.

If the grab could not be initiated, the function returns false.

The following snippet shows how to grab an item and store the results to a file.

Rectangle {
    id: source
    width: 100
    height: 100
    gradient: Gradient {
        GradientStop { position: 0; color: "steelblue" }
        GradientStop { position: 1; color: "black" }
    }
}
    // ...
    source.grabToImage(function(result) {
                           result.saveToFile("something.png");
                       });

The following snippet shows how to grab an item and use the results in another image element.

Image {
    id: image
}
    // ...
    source.grabToImage(function(result) {
                           image.source = result.url;
                       },
                       Qt.size(50, 50));

Note: This function will render the item to an offscreen surface and copy that surface from the GPU's memory into the CPU's memory, which can be quite costly. For "live" preview, use layers or ShaderEffectSource.

This works with QtQuick 2.0 too.

Aquarius_Girl
  • 21,790
  • 65
  • 230
  • 411
Luigi
  • 70
  • 1
  • 3
  • **Is `ShaderEffectSource` really faster? Doesn't look so from Qt documentation** Reading though official documentation of [ShaderEffectSource](http://doc.qt.io/qt-5/qml-qtquick-shadereffectsource.html) clearly states with a *Warning* tag that in most cases it will decrease performance & increase video memory usage. Can you please clarify? I'm looking for the fastest way to save an image. I also see Qt official doc stating that `ShaderEffectSource` is dependent on underlying hardware & might not work the same on all devices specifically if your QtApp is running on many kinds of android devices. – TheWaterProgrammer Jul 19 '17 at 19:56
  • Is it possible to get the snapshot of the QQuickItem only without its children? – TheWaterProgrammer Jul 04 '18 at 21:57
1

You can use the grabWindow() method and then crop the resulting image. You will need to find the absolute position of the QML element on the window, just get its position and add it to the positions of every parent element until you hit the root element, and use QImage::copy(x, y, w, h) to crop the window image to the element position and size.

There are few disadvantages to this - it is slower, since it has overhead of grabbing the entire window and cropping, and if your element is not rectangular and opaque, it will also grab stuff visible under the element. But if it is an opaque rectangle, and performance isn't an issue, this is the easy way to it.

The hard, but faster way: you can use create a ShaderEffectSource in QML and set its sourceItem to the element you want. This will effectively render that element into a texture so you can use shader effects on it. Then on the C++ side, from QQuickShaderEffectSource you can use its QSGTextureProvider *textureProvider() method to get the texture provider, then from it you use QSGTexture * QSGTextureProvider::texture() to get the texture, and from the texture you can find the texture id with int QSGTexture::textureId(). Then finally, you can get an image from the texture id and use the raw data to construct a QImage.

Community
  • 1
  • 1
dtech
  • 47,916
  • 17
  • 112
  • 190