Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to apply opacity mask to a QML item?

The look and feel I'm trying to go for is to have a solid color button, and text on it like "Hello World" where the text is completely transparent, and the background shows through the button.

In other words, having text as a transparency mask on a button element.

like image 881
James Avatar asked Oct 06 '16 18:10

James


2 Answers

Here is one way to do it:

// TB.qml
MouseArea {
  width: txt.contentWidth + 20
  height: txt.contentHeight + 10
  property alias text: txt.text
  property alias color: sh.color
  ShaderEffect {
    id: sh
    anchors.fill: parent
    property color color: "red" 
    property var source : ShaderEffectSource {
      sourceRect: Qt.rect(0, 0, sh.width, sh.height)
      sourceItem: Item {
        width: sh.width
        height: sh.height
        Text {
          id: txt
          anchors.centerIn: parent
          font.bold: true
          font.pointSize: 30
          text: "test"
        }
      }
    }
    fragmentShader:
        "varying highp vec2 qt_TexCoord0;
           uniform highp vec4 color;
           uniform sampler2D source;
           void main() {
               gl_FragColor = color * (1.0 - texture2D(source, qt_TexCoord0).w);
           }"
  }
}

Using it:

  TB {
    text: "HELLO WORLD!!!"
    color: "red"
    onClicked: console.log("hi world")
  }

Result:

enter image description here

The button is red, the text is grey from the grey background, and it will accurately show anything that's beneath the button.

Obviously, the button is rudimentary, but the example outta be enough to get you going and implement something according to your needs.

The key element here is the custom shader, which is a very basic one - it colorizes every fragment and applies the mask as alpha. Obviously, you can use ShaderEffectSource to turn any QML Item to a texture, and replace the ShaderEffectSource with another sampler 2D and mix the two textures in any way you want, cut using the alpha channel, or any of the RGB if you are using a grayscale mask. And unlike the rather limited OpacityMask element, this will actually cut through and show anything that is underneath as it is supposed to.

like image 155
dtech Avatar answered Oct 24 '22 09:10

dtech


You can achieve that using layer attached property as follow without using OpacityMask.
Also you does not any limitation and you can use any qml item, use any QtQuick.Controls and style it as usual :)

result

Image {
    id: bk
    source: "http://l7.alamy.com/zooms/7b6f221aadd44ffab6a87c234065b266/sheikh-lotfollah-mosque-at-naqhsh-e-jahan-square-in-isfahan-iran-interior-g07fw2.jpg"
}

Button {
    id: button
    anchors.centerIn: bk
    width: 210; height: 72
    visible: true
    opacity: 0.0
    layer.enabled: true
    layer.smooth: true
    onClicked: console.log("Clicked")
}

Rectangle {
    id: _mask
    anchors.fill: button
    color: "transparent"
    visible: true
    Text {
        font { pointSize: 20; bold: true }
        anchors.centerIn: parent
        text: "Hello World!"
    }

    layer.enabled: true
    layer.samplerName: "maskSource"
    layer.effect: ShaderEffect {
        property variant source: button
        fragmentShader: "
                varying highp vec2 qt_TexCoord0;
                uniform highp float qt_Opacity;
                uniform lowp sampler2D source;
                uniform lowp sampler2D maskSource;
                void main(void) {
                    gl_FragColor = texture2D(source, qt_TexCoord0.st) * (1.0-texture2D(maskSource, qt_TexCoord0.st).a) * qt_Opacity;
                }
            "
    }
}
like image 6
S.M.Mousavi Avatar answered Oct 24 '22 11:10

S.M.Mousavi