I was wondering if it is possible via Qt Quick or C++ to use a shader effect on a image and then save it with the effect applied.
The reason for doing this is to achieve opengl assisted image effect manipulation (ie. emboss, pixelize, etc)
I tried this:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Dialogs 1.2
ApplicationWindow {
id: root
title: qsTr("Hello World")
width: 640
height: 480
visible: true
Image {
id: effectImage
width: 200
height: 200
sourceSize.width: width
sourceSize.height: height
anchors.centerIn: parent
source: "qrc:/myimage.png"
layer.enabled: true
layer.effect: ShaderEffect {
fragmentShader: "
uniform lowp sampler2D source; // this item
uniform lowp float qt_Opacity; // inherited opacity of this item
varying highp vec2 qt_TexCoord0;
void main() {
lowp vec4 p = texture2D(source, qt_TexCoord0);
lowp float g = dot(p.xyz, vec3(0.344, 0.5, 0.156));
gl_FragColor = vec4(g, g, g, p.a) * qt_Opacity;
}"
}
}
Button {
id: grabIt
anchors.top: effectImage.bottom
text: qsTr("Grab")
onClicked: {
effectImage.grabToImage(function(result) {
console.debug("hallo");
result.saveToFile("test.jpg","jpg");
});
}
}
}
Applying a simple grayscale effect on the image. It works on-screen, but grabToImage() does not apply the Effect.
Is there any way to do this?
OK that was easy, I solved the problem by wrapping the image with an Item element and grabbing the image from that instead:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Dialogs 1.2
ApplicationWindow {
id: root
title: qsTr("Hello World")
width: 640
height: 480
visible: true
Item {
id: effectImage
width: 200
height: 200
anchors.centerIn: parent
Image {
anchors.fill: parent
sourceSize.width: width
sourceSize.height: height
source: "qrc:/myimage.png"
layer.enabled: true
layer.effect: ShaderEffect {
fragmentShader: "
uniform lowp sampler2D source; // this item
uniform lowp float qt_Opacity; // inherited opacity of this item
varying highp vec2 qt_TexCoord0;
void main() {
lowp vec4 p = texture2D(source, qt_TexCoord0);
lowp float g = dot(p.xyz, vec3(0.344, 0.5, 0.156));
gl_FragColor = vec4(g, g, g, p.a) * qt_Opacity;
}"
}
}
}
Button {
id: grabIt
anchors.top: effectImage.bottom
text: qsTr("Grab")
onClicked: {
effectImage.grabToImage(function(result) {
console.debug("hallo");
result.saveToFile("test.jpg","jpg");
});
}
}
}
Hope this helps someone else :-)
Related
I was able to write 'Color Wheel' in QML with Rectangle elements:
And now I'm wondering how to get color of the handler's position.
I have seen that it's should be possible to achieve with canvas, but may be there is a better solution?
Here the code:
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtGraphicalEffects 1.12
Item
{
id: colorWheel
Rectangle
{
id: ring
color: "transparent"
implicitWidth: 80
implicitHeight: implicitWidth
width: parent.width < parent.height ? parent.width : parent.height
height: width
radius: width / 2
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
border.width: width / 15
property double wheelRadius: ring.width / 2 - border.width /2
ConicalGradient
{
source: parent
anchors.fill: parent
gradient: Gradient
{
GradientStop { position: 0; color: "#7FFF00" }
GradientStop { position: 1; color: "#7FFF00" }
}
}
MouseArea
{
id: dragArea
anchors.fill: parent
drag.target: parent
onPositionChanged: {
[handler.x, handler.y] = calculatePos(mouseX, mouseY)
}
function calculatePos(x, y)
{
var vX = x - ring.wheelRadius
var vY = y - ring.wheelRadius
var magV = Math.sqrt((Math.pow(vX, 2) + Math.pow(vY, 2)))
var aX = ring.wheelRadius + vX / magV * ring.wheelRadius
var aY = ring.wheelRadius + vY / magV * ring.wheelRadius
return [aX, aY]
}
}
Rectangle
{
id: handler
width: ring.width / 15
height: width
radius: ring.width / 30
border.width: 1
color: "transparent"
}
}
}
I was asked to add some more details to be able to post the code:
As you can see I change handler position according mouse position. I want to grab ring's color under handler's position.
Finally I have used canvas and it seems that it works well, here the shorten version of the code(didn't include handler logic):
Canvas
{
id: ring
property double wheelFullRadius: width / 2
property double borderWidth: wheelFullRadius / 6
property double wheelInnerRadius: wheelFullRadius - borderWidth / 2
function getColor()
{
return context.getImageData(handler.x, handler.y, 1, 1)
}
onPaint:
{
var context = getContext('2d')
context.reset()
context.arc(ring.wheelFullRadius, ring.wheelFullRadius, ring.wheelInnerRadius,
0, 2*Math.PI)
var grd = context.createConicalGradient(
ring.wheelFullRadius, ring.wheelFullRadius, 0
)
grd.addColorStop(0, "#7FFF00" )
grd.addColorStop(1/12, "#FFFF00" )
grd.addColorStop(2/12, "#FF7F00" )
grd.addColorStop(3/12, "#FF0000" )
grd.addColorStop(4/12, "#FF007F" )
grd.addColorStop(5/12, "#FF00FF" )
grd.addColorStop(6/12, "#7F00FF" )
grd.addColorStop(7/12, "#0000FF" )
grd.addColorStop(8/12, "#007FFF" )
grd.addColorStop(9/12, "#00FFFF" )
grd.addColorStop(10/12, "#00FF7F" )
grd.addColorStop(11/12, "#00FF00" )
grd.addColorStop(1, "#7FFF00" )
context.strokeStyle = grd
context.lineWidth = ring.borderWidth
context.stroke()
handler.update()
}
....
}
I still have concerns about performance on mobile devices, but for now it works.
You can also use Math.arc2 to get mouse angle based on ring center, then use Qt.hsva or Qt.hsla methods to get the color at the mouse position.
property color color: Qt.hsva(mousearea.angle, 1.0, 1.0, 1.0);
MouseArea {
id: mousearea
anchors.fill: parent
// angle value is between 0 and 1
property real angle: Math.atan2(width/2 - mouseX, mouseY - height/2) / 6.2831 + 0.5
}
Preview
Here is a circular color picker which I wrote.
I used shader effect to create a perfect color wheel
ColorPicker.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
Control {
id: control
property real ringWidth: 25
property real hsvValue: 1.0
property real hsvSaturation: 1.0
readonly property color color: Qt.hsva(mousearea.angle, 1.0, 1.0, 1.0)
contentItem: Item {
implicitWidth: 175
implicitHeight: width
ShaderEffect {
id: shadereffect
width: parent.width; height: parent.height
readonly property real ringWidth: control.ringWidth / width / 2
readonly property real s: control.hsvSaturation
readonly property real v: control.hsvValue
fragmentShader: "
#version 330
varying highp vec2 qt_TexCoord0;
uniform highp float qt_Opacity;
uniform highp float ringWidth;
uniform highp float s;
uniform highp float v;
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
highp vec2 coord = qt_TexCoord0 - vec2(0.5);
highp float ring = smoothstep(0, 0.01, -abs(length(coord) - 0.5 + ringWidth) + ringWidth);
gl_FragColor = vec4(hsv2rgb(vec3(-atan(coord.x, coord.y) / 6.2831 + 0.5, s, v)),1);
gl_FragColor *= ring;
}"
}
Rectangle {
id: indicator
x: (parent.width - width)/2
y: control.ringWidth * 0.1
width: control.ringWidth * 0.8; height: width
radius: width/2
color: 'white'
border {
width: mousearea.containsPress ? 3 : 1
color: Qt.lighter(control.color)
Behavior on width { NumberAnimation { duration: 50 } }
}
transform: Rotation {
angle: mousearea.angle * 360
origin.x: indicator.width/2
origin.y: control.availableHeight/2 - indicator.y
}
}
Rectangle {
anchors.centerIn: parent
width: control.availableWidth * 0.3
height: width
radius: width
color: control.color
border {
width: 5
color: Qt.lighter(control.color, 1.8)
}
}
MouseArea {
id: mousearea
anchors.fill: parent
property real angle: Math.atan2(width/2 - mouseX, mouseY - height/2) / 6.2831 + 0.5
}
}
}
I have a Background groove image
for which I have to produce progress bar effect using Progress filling Image
How to clip the Progress filling Image along the Path of the groove of the Progress Bar (Background groove image).
Currently I am trying to do clipping widthwise but it is not what I want. The clipping should happen perpendicular to the path as mentioned in path interpolator of my code.
In the code "RPM_BG.png" is background groove image which is of similar shape as "RPM_Fill.png"(Progress filling Image).
import QtQuick 2.5
import QtQuick.Window 2.2
import QtGraphicalEffects 1.0
import QtQuick.Controls 1.4
import QtMultimedia 5.0
import QtQuick.Controls.Styles 1.4
Window {
visible: true
color:"black"
width: 357
height: 221+200
Image
{
id:groove1
source:"qrc:/RPM_BG.png"
anchors.top:parent.top
anchors.left:parent.left
Item{
id: displayWindow1
height: parent.height
width: (225*(slider.value)/8000)+32
clip: true
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right:needle.right
anchors.rightMargin:/*8*/{switch(true)
{
case slider.value>=0 && slider.value < 111:return 10;
case slider.value>=111 && slider.value < 124:return 9.7;
case slider.value>=124 && slider.value < 132:return 8.4;
case slider.value>=132 && slider.value < 135:return 8;
case slider.value>=135 && slider.value <= 165:return 7.15;
case slider.value>=165 && slider.value <= 240:return 6;
}
}
Image
{
id:speedarcfill
anchors.top:parent.top
anchors.left:parent.left
source:"qrc:/RPM_Fill.png"
z: 1
}
}
PathInterpolator {
id: motionPath
property int value
path: Path {
startX: 27; startY: 189
PathLine { x: 98; y: 54 }
PathArc { x: 176; y: 12; radiusX: 90; radiusY: 90 }
PathLine { x: 245; y: 11 }
}
progress:slider.value/8000
}
}
Slider {
id: slider
anchors.top:groove1.bottom
anchors.topMargin:100
anchors.left:parent.left
anchors.leftMargin: 5
width: parent.width-10
height: 100
style: SliderStyle {
handle:
Rectangle {
anchors.centerIn: parent
color: control.pressed ? "white" : "lightgray"
border.color: "gray"
implicitWidth: 10
implicitHeight: 40
}
groove: Rectangle {
width: slider.width
height: 10
color:"black"
LinearGradient {
anchors.verticalCenter: parent.verticalCenter
start: Qt.point(0, 0)
end: Qt.point(parent.width, 0)
width: styleData.handlePosition
height: 10
gradient: Gradient {
GradientStop {position: 0.0; color: "#008BFF" }
GradientStop {position: 0.5; color: "#3FFFD0" }
GradientStop { position: 1.0; color: "#3FFF41" }
}
}
}
}
maximumValue: 8000
}
}
Please suggest a way for me so that I can clip the Progress filling Image perpendicular to the path of progressing.
You can use a basic fragment shader for this. Something like:
ShaderEffect {
id: displayWindow2
height: groove1.height
width: groove1.width
anchors.top: parent.top
anchors.right: parent.right
property var base: groove1
property var overlay: speedarcfill
property real pointX: motionPath.x/width
property real pointY: motionPath.y/height
property real pointAngle: (motionPath.angle + 90)%360
fragmentShader: "
uniform sampler2D base;
uniform sampler2D overlay;
varying highp vec2 qt_TexCoord0;
uniform lowp float qt_Opacity;
uniform highp float pointAngle;
uniform highp float pointX;
uniform highp float pointY;
void main() {
lowp vec4 baseTex = texture2D(base, qt_TexCoord0.st);
lowp vec4 overlayTex = texture2D(overlay, qt_TexCoord0.st);
//line equation => (y - y1)/(x - x1) = slope ; slope != infinity
highp float angle = radians(pointAngle);
highp float slope = tan(angle);
highp float deltay = qt_TexCoord0.y - pointY;
highp float deltax = qt_TexCoord0.x - pointX;
//If overlay is transparent, get the texture from base
if(overlayTex.a > 0.0)
{
//check where the current point lies, wrt the normal.
if( ( slope >= 0.0 && deltay - deltax*slope > 0.0 ) || (slope < 0.0 && deltax < 0.0))
gl_FragColor = overlayTex * qt_Opacity;
else gl_FragColor = baseTex*qt_Opacity;
}
else gl_FragColor = baseTex*qt_Opacity;
}"
}
Here's the full file,I've played around with to write this: https://bpaste.net/show/2b0c0fd1cc69
It masks the source item with another item, so the source is visible only where the mask is opaque. How to make source visible only where the mask is transparent?
Currently it's done with a shader, but I want to replace that.
Can't invert the mask, because it consists of multiple images: the sum of individually inverted images is not the same as the inversion of the sum.
There is no component made by Qt that can do what you need. OpacityMask is closest to what you need. You can view its code here on the official repository or on your machine in this path: Qt_folder/Qt_version/Qt_kit/qml/QtGraphicalEffects/OpacityMask.qml. This way you can easily browse the source of all QtGraphicalEffects components.
Using ShaderEffect is a good choice for the task.
As GrecKo pointed out there already is an invert property in OpacityMask object. It will be available with Qt 5.7 but the code is already available in the link above. You can either wait for the update or download the component and use it in your project.
If you like to use QML Items (Rectangle) you can use following codes:
import QtQuick 2.6
Item {
anchors.fill: parent
Image {
anchors.fill: parent
source: "http://i.imgur.com/R3yMj0y.jpg"
fillMode: Image.PreserveAspectCrop
focus: true
Keys.onRightPressed: _mask.maskX += 100
Keys.onLeftPressed: _mask.maskX -= 100
Keys.onUpPressed: _mask.maskY -= 100
Keys.onDownPressed: _mask.maskY += 100
MouseArea {
anchors.fill: parent
hoverEnabled: true
onPositionChanged: {
_mask.maskX = mouseX;
_mask.maskY = mouseY;
}
}
}
Rectangle {
id: _bk
anchors.fill: parent
color: "#33000000"
visible: false
layer.enabled: true
layer.smooth: true
}
Rectangle {
id: _mask
anchors.fill: parent
color: "transparent"
visible: true
property int maskX: 0
property int maskY: 0
Rectangle {
id: circle
width: 100; height: 100
x: _mask.maskX-50; y: _mask.maskY-50
radius: 50
color: "#000"
Behavior on x { NumberAnimation { duration: 400; easing.type: Easing.OutBack; easing.overshoot: 1.4 } }
Behavior on y { NumberAnimation { duration: 400; easing.type: Easing.OutBack; easing.overshoot: 1.4 } }
}
layer.enabled: true
layer.samplerName: "maskSource"
layer.effect: ShaderEffect {
property variant source: _bk
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;
}
"
}
}
Rectangle {
id: _mask2
anchors.fill: parent
color: "transparent"
visible: true
Rectangle {
id: circle2
width: 150; height: 150
x: _mask.maskX-75; y: _mask.maskY-75
radius: 75
color: "#000"
Behavior on x { NumberAnimation { duration: 550; easing.type: Easing.OutBack; easing.overshoot: 2.4 } }
Behavior on y { NumberAnimation { duration: 550; easing.type: Easing.OutBack; easing.overshoot: 2.4 } }
}
layer.enabled: true
layer.samplerName: "maskSource"
layer.effect: ShaderEffect {
property variant source: _bk
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;
}
"
}
}
Rectangle {
id: _mask3
anchors.fill: parent
color: "transparent"
visible: true
Rectangle {
id: circle3
width: 220; height: 220
x: _mask.maskX-110; y: _mask.maskY-110
radius: 110
color: "#000"
Behavior on x { NumberAnimation { duration: 650; easing.type: Easing.OutBack; easing.overshoot: 3.0 } }
Behavior on y { NumberAnimation { duration: 650; easing.type: Easing.OutBack; easing.overshoot: 3.0 } }
}
layer.enabled: true
layer.samplerName: "maskSource"
layer.effect: ShaderEffect {
property variant source: _bk
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;
}
"
}
}
}
I'm using ShaderEffect in QML to have a scaled visual copy of some Item.
This copy should be moveable and dynamic (live property of ShaderEffectSource set to true).
My problem is that I want it to be inside of a round Rectangle but it won't be clipped by it. ShaderEffect just overlaps its parent and is quadratic.
I've coded a quick QML example that shows the problem:
import QtQuick 2.4
import QtQuick.Controls 1.3
ApplicationWindow {
title: qsTr("Hello Shaders")
width: 640
height: 480
visible: true
color: "green"
Rectangle {
id: exampleRect
property bool redState: false
anchors.centerIn: parent
width: parent.width / 3
height: parent.height / 3
color: redState ? "red" : "cyan"
Rectangle {
anchors.centerIn: parent
width: parent.width / 2; height: width;
color: "white"
Rectangle {
anchors.centerIn: parent
width: parent.width / 2; height: width;
color: "green"
Rectangle {
anchors.centerIn: parent
width: parent.width / 2; height: width;
color: "yellow"
}
}
}
Timer {
interval: 2000
repeat: true
running: true
onTriggered: {
exampleRect.redState = !exampleRect.redState
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onPositionChanged: {
shaderEffectContainer.x = mouse.x
shaderEffectContainer.y = mouse.y
}
}
Rectangle {
id: shaderEffectContainer
width: 100; height: width;
radius: width / 2;
border.width: 2
ShaderEffectSource {
id: source
sourceItem: exampleRect
visible: false
}
ShaderEffect {
anchors.fill: parent
property variant source: source
vertexShader: "
uniform highp mat4 qt_Matrix;
attribute highp vec4 qt_Vertex;
attribute highp vec2 qt_MultiTexCoord0;
varying highp vec2 qt_TexCoord0;
void main() {
qt_TexCoord0 = qt_MultiTexCoord0 * 1.5 * vec2(0.5, 0.5);
gl_Position = qt_Matrix * qt_Vertex;
}"
fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform sampler2D source;
uniform lowp float qt_Opacity;
void main() {
gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity;
}"
}
}
}
As you can see, exampleRect is declared to be round, but it's child is overlapping it.
I have already tried all possible combinations of clip property and have spent all day trying to do this with fragment/vertex shaders. With no luck, as you might guess. :)
I've solved the problem using layers as shown in this answer.
Thanks #DenimPowell for the hint.
Below is an updated example code with circular ShaderEffect.
import QtQuick 2.4
import QtQuick.Controls 1.3
ApplicationWindow {
title: qsTr("Hello Shaders")
width: 640
height: 480
visible: true
color: "green"
Rectangle {
id: exampleRect
property bool redState: false
anchors.centerIn: parent
width: parent.width / 3
height: parent.height / 3
color: redState ? "red" : "cyan"
Rectangle {
anchors.centerIn: parent
width: parent.width / 2; height: width;
color: "white"
Rectangle {
anchors.centerIn: parent
width: parent.width / 2; height: width;
color: "green"
Rectangle {
anchors.centerIn: parent
width: parent.width / 2; height: width;
color: "yellow"
}
}
}
Timer {
interval: 2000
repeat: true
running: true
onTriggered: {
exampleRect.redState = !exampleRect.redState
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onPositionChanged: {
shaderEffectContainer.x = mouse.x
shaderEffectContainer.y = mouse.y
}
}
Rectangle {
id: shaderEffectContainer
width: 100; height: width;
color: "transparent"
Rectangle {
id: rectangleSource
anchors.fill: parent
ShaderEffectSource {
id: source
sourceItem: exampleRect
visible: false
}
ShaderEffect {
anchors.fill: parent
property variant source: source
vertexShader: "
uniform highp mat4 qt_Matrix;
attribute highp vec4 qt_Vertex;
attribute highp vec2 qt_MultiTexCoord0;
varying highp vec2 qt_TexCoord0;
void main() {
qt_TexCoord0 = qt_MultiTexCoord0 * 1.5 * vec2(0.5, 0.5);
gl_Position = qt_Matrix * qt_Vertex;
}"
fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform sampler2D source;
uniform lowp float qt_Opacity;
void main() {
gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity;
}"
}
visible: false
layer.enabled: true
}
Rectangle {
id: maskLayer
anchors.fill: parent
radius: parent.width / 2
color: "red"
border.color: "black"
layer.enabled: true
layer.samplerName: "maskSource"
layer.effect: ShaderEffect {
property var colorSource: rectangleSource
fragmentShader: "
uniform lowp sampler2D colorSource;
uniform lowp sampler2D maskSource;
uniform lowp float qt_Opacity;
varying highp vec2 qt_TexCoord0;
void main() {
gl_FragColor =
texture2D(colorSource, qt_TexCoord0)
* texture2D(maskSource, qt_TexCoord0).a
* qt_Opacity;
}
"
}
}
// only draw border line
Rectangle {
anchors.fill: parent
radius: parent.width / 2
border.color: "black"
border.width: 1
color: "transparent"
}
}
}
I'm trying to apply a simple alpha mask on a QML item using ShaderEffectItem.
Here is a minimal (non-)working example: I have a red-to-white gradient as the background and want to draw a green 200x200 square on top of it. The alpha mask for this square should be 0.0 at the left and 1.0 at the right border, so it should be transparent at the left border.
import QtQuick 1.1
import Qt.labs.shaders 1.0
Rectangle {
width: 300
height: 300
id: wrapper
gradient: Gradient {
GradientStop { position: 0.0; color: "red" }
GradientStop { position: 1.0; color: "white" }
}
Rectangle {
id: square
anchors.centerIn: parent
width: 200
height: 200
color: "green"
}
ShaderEffectItem {
anchors.centerIn: parent
width: 200
height: 200
property variant source: ShaderEffectSource {
sourceItem: square
hideSource: true
}
fragmentShader: "
varying vec2 qt_TexCoord0;
uniform sampler2D source;
void main(void)
{
vec4 sourceColor = texture2D(source, qt_TexCoord0);
float alpha = qt_TexCoord0.x; // = 0.0 at left, 1.0 at right border
sourceColor.a *= alpha; // apply alpha mask
gl_FragColor = sourceColor;
}
"
}
}
I expected the following output (drawn with GIMP):
But this is actually shown:
What am I doing wrong?
I'm using qmlviewer (Qt 4.8.2) to display the QML file with the -opengl option in order to enable the shader effect.
Maybe this is related to this strange behavior with alpha blending on QGLFramebufferObjects I found some weeks ago...
Try modifying the main function of the fragment shader to :
void main(void)
{
gl_FragColor = texture2D(source, qt_TexCoord0).rgba*qt_TexCoord0.x;
}