Reputation: 50
I have been working on a project where I need to apply image masking that applies an effect like this:
Pic1: https://i.sstatic.net/6zI2x.jpg
Pic2: https://i.sstatic.net/z7IVX.jpg
Mask frame: https://i.sstatic.net/3syEm.jpg
Desired effect: https://i.sstatic.net/t2kO5.jpg
I got it to work by using OpacityMask however to do that I had to use some photoshop and edit my mask frame image. I need to apply this affect to multiple mask frames with different shapes therefore using photoshop to edit all of them seem troublesome. Moreover, the inside of the mask frame images arent all transparent either.
Is there any ideas you can give me to solve this issue without using any pre photoshoping each mask frame images. I tried to look into ShaderEffect but I could not really understand how I should use it for my purpose. Moreover I searched for a OpacityMask like effect but working only on part of the mask image which has a specific color/specific shaped area. However, I could not find any.
Upvotes: 1
Views: 1625
Reputation: 984
ShaderEffect
appears to be the only option, considering what you said in the comments that the frame shape could be anything.
The code examples below show how to solve your issue with ShaderEffect
.
The only property on the QML side is the rect
, which defines the x
, y
, width
, and height
of the frame, which are scaled down to between 0
and 1
.
Image { id: img; visible: false; source: "2.jpg" }
Image { id: frme; visible: false; source: "3.jpg" }
Image { id: back; visible: false; source: "1.jpg" }
ShaderEffect {
width: img.sourceSize.width / 3.5
height: img.sourceSize.height / 3.5
property var back: back
property var image: img
property var frame: frme
property vector4d rect: Qt.vector4d(width/2-50, height/2-60, 100, 120);
readonly property vector4d frect: Qt.vector4d(rect.x/width,rect.y/height,
rect.z/width,rect.w/height);
fragmentShader: "qrc:/shader.glsl"
}
shader.glsl
I discovered that the saturation inside the image is very different from other areas after using a color picker in different points of the frame image.
So, in order to decide where to mask in the image, I used saturation.
uniform highp sampler2D back;
uniform highp sampler2D image;
uniform highp sampler2D frame;
varying highp vec2 qt_TexCoord0;
uniform highp vec4 frect;
uniform highp float qt_Opacity;
// From https://gist.github.com/983/e170a24ae8eba2cd174f
vec3 rgb2hsv(vec3 c) {
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
void main() {
vec2 u = qt_TexCoord0;
vec2 frameCoord = (u - frect.xy) / frect.zw ;
gl_FragColor = texture2D(back, u);
if(frameCoord.x>0. && frameCoord.y>0. && frameCoord.x<1. && frameCoord.y<1.) {
vec4 mask = texture2D(frame, frameCoord);
vec3 hsv = rgb2hsv(mask.xyz);
gl_FragColor = mask;
// Check that the saturation is between 0 and 0.2.
if(abs(hsv.y - 0.1) < 0.1) {
gl_FragColor = texture2D(image, u);
}
}
}
You can also change the last line of code if you want the frame's shadow to cover your image.
gl_FragColor = mix(texture2D(image, u), mask, 1. - hsv.z);
Upvotes: 1
Reputation: 25871
If you know that the geometry of your picture frame is just a rectangle, we can just create a rectangular mask aligned within your picture frame. I worked out that your picture frame was 410x500 pixels and decided to shrink it to 50% i.e. 205x250 pixels. At that scale, I worked out that your picture frame had a border size of about 18 pixels. So I created an inner rectangle based on those dimensions and used that rectangle for the OpacityMask
maskSource
:
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtGraphicalEffects 1.0
Page {
Image {
id: pic1
anchors.fill: parent
source: "https://i.sstatic.net/6zI2x.jpg"
}
Image {
id: pic2
anchors.fill: parent
visible: false
source: "https://i.sstatic.net/z7IVX.jpg"
}
Image {
id: rawMask
anchors.centerIn: parent
width: 205
height: 250
source: "https://i.sstatic.net/3syEm.jpg"
}
Item {
id: mask
anchors.fill: parent
Rectangle {
id: borderMask
x: rawMask.x + 18
y: rawMask.y + 18
width: rawMask.width - 36
height: rawMask.height - 36
color: "white"
}
}
OpacityMask {
anchors.fill: parent
source: pic2
maskSource: mask
}
}
Upvotes: 1