qtqmlqtquick2qt-quickqtquickcontrols

How can I save an image with OpacityMask applied?


I am working on a project where I want to save a thumbnail image of a rendered screen exactly as it's displayed, including with the application of an OpacityMask. The problem is that no matter what I do, it always creates the thumbnail image without it applied.

So even though the screen looks like this:

screen shot of red rectangle with round mask

The created image looks like this:

red rectangle without mask

import QtQuick 2.15
import QtGraphicalEffects 1.12
import QtQuick.Layouts 1.2
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.1

ApplicationWindow {
    id: root
    width: 640
    property bool round: true

    toolBar: ToolBar {
        RowLayout {
            anchors.fill: parent
            anchors.margins: spacing
            CheckBox {
                id: roundCheckBox
                text: "Round"
                Component.onCompleted: checked = round
                onCheckedChanged: round = checked; 
            }
            Item { Layout.fillWidth: true }
        }
    }

    Rectangle {
        height: 640
        width: 640

        Rectangle {
            id: frame
            anchors.fill: parent
            focus: true
            color: "black"
            Image {
                id: background
                source: "background.jpg"
                anchors.fill: parent
            }
            Rectangle {
                anchors.centerIn: parent
                width: parent.width/2
                height: parent.height/2
                color: "red"
            }

            layer.enabled: round
            layer.effect:  OpacityMask {
                anchors.fill: parent
                source: frame
                maskSource: Rectangle {
                    width: frame.width
                    height: frame.height
                    radius: frame.width/2
                }
            }

            Keys.onReturnPressed: frame.grabToImage(
            function(result) {  
                result.saveToFile("example.jpg") 
            }, Qt.size(320, 320) )

        }
    }
}


Solution

  • This seems to be a scoping problem, where you are grabbing the image at a level where the mask is not applied. Use the grabToImage() function of your frame Rectangle's parent. For example, I have added an id to that parent, and instead call it, and get the proper output:

    import QtQuick 2.15
    import QtGraphicalEffects 1.12
    import QtQuick.Layouts 1.2
    import QtQuick.Controls 1.4
    import QtQuick.Dialogs 1.1
    
    ApplicationWindow {
        id: root
        width: 640
        property bool round: true
    
        toolBar: ToolBar {
            RowLayout {
                anchors.fill: parent
                anchors.margins: spacing
                CheckBox {
                    id: roundCheckBox
                    text: "Round"
                    Component.onCompleted: checked = round
                    onCheckedChanged: round = checked;
                }
                Item { Layout.fillWidth: true }
            }
        }
    
        Rectangle {
            id: frameParent // set parent ID here
            height: 640
            width: 640
    
            Rectangle {
                id: frame
                anchors.fill: parent
                focus: true
                color: "black"
                Image {
                    id: background
                    source: "background.jpg"
                    anchors.fill: parent
                }
                Rectangle {
                    anchors.centerIn: parent
                    width: parent.width/2
                    height: parent.height/2
                    color: "red"
                }
    
                layer.enabled: round
                layer.effect:  OpacityMask {
                    anchors.fill: parent
                    source: frame
                    maskSource: Rectangle {
                        width: frame.width
                        height: frame.height
                        radius: frame.width/2
                    }
                }
    
                Keys.onReturnPressed: frameParent.grabToImage( // use parent ID here
                function(result) {
                    result.saveToFile("example.jpg")
                }, Qt.size(320, 320) )
    
            }
        }
    }
    

    Results in the following output example.jpg (using my own random background image): enter image description here

    I also was able to semi-successfully get this to work by re-organizing your layer.effect and instead calling the grabToImage() function on the OpacityMask instead. The solution above was much simpler though.