486 lines
19 KiB
QML
486 lines
19 KiB
QML
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2017 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the Qt Graphical Effects module.
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU Lesser General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU Lesser General Public License version 3 requirements
|
|
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 2.0 or (at your option) the GNU General
|
|
** Public license version 3 or any later version approved by the KDE Free
|
|
** Qt Foundation. The licenses are as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
|
** https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
import QtQuick 2.12
|
|
import QtGraphicalEffects.private 1.12
|
|
|
|
/*!
|
|
\qmltype Blend
|
|
\inqmlmodule QtGraphicalEffects
|
|
\since QtGraphicalEffects 1.0
|
|
\inherits QtQuick2::Item
|
|
\ingroup qtgraphicaleffects-blend
|
|
\brief Merges two source items by using a blend mode.
|
|
|
|
Blend mode can be selected with the \l{Blend::mode}{mode} property.
|
|
|
|
\table
|
|
\header
|
|
\li source
|
|
\li foregroundSource
|
|
\li Effect applied
|
|
\row
|
|
\li \image Original_bug.png
|
|
\li \image Original_butterfly.png
|
|
\li \image Blend_bug_and_butterfly.png
|
|
\endtable
|
|
|
|
\note This effect is available when running with OpenGL.
|
|
|
|
\section1 Example
|
|
|
|
The following example shows how to apply the effect.
|
|
\snippet Blend-example.qml example
|
|
|
|
*/
|
|
|
|
Item {
|
|
id: rootItem
|
|
|
|
/*!
|
|
This property defines the source item that is going to be the base when
|
|
\l{Blend::foregroundSource}{foregroundSource} is blended over it.
|
|
|
|
\note It is not supported to let the effect include itself, for
|
|
instance by setting source to the effect's parent.
|
|
*/
|
|
property variant source
|
|
|
|
/*!
|
|
This property defines the item that is going to be blended over the
|
|
\l{Blend::source}{source}.
|
|
|
|
\note It is not supported to let the effect include itself, for
|
|
instance by setting foregroundSource to the effect's parent.
|
|
*/
|
|
property variant foregroundSource
|
|
|
|
/*!
|
|
This property defines the mode which is used when foregroundSource is
|
|
blended over source. Values are case insensitive.
|
|
|
|
\table
|
|
\header
|
|
\li mode
|
|
\li description
|
|
\row
|
|
\li normal
|
|
\li The pixel component values from foregroundSource are written
|
|
over source by using alpha blending.
|
|
\row
|
|
\li addition
|
|
\li The pixel component values from source and foregroundSource are
|
|
added together and written.
|
|
\row
|
|
\li average
|
|
\li The pixel component values from source and foregroundSource are
|
|
averaged and written.
|
|
\row
|
|
\li color
|
|
\li The lightness value from source is combined with hue and
|
|
saturation from foregroundSource and written.
|
|
\row
|
|
\li colorBurn
|
|
\li The darker pixels from source are darkened more, if both source
|
|
and foregroundSource pixels are light the result is light.
|
|
\row
|
|
\li colorDodge
|
|
\li The lighter pixels from source are lightened more, if both
|
|
source and foregroundSource pixels are dark the result is dark.
|
|
\row
|
|
\li darken
|
|
\li The darker pixel component value from source and
|
|
foregroundSource is written.
|
|
\row
|
|
\li darkerColor
|
|
\li The lower luminance pixel rgb-value from source and
|
|
foregroundSource is written.
|
|
\row
|
|
\li difference
|
|
\li The absolute pixel component value difference between source and
|
|
foregroundSource is written.
|
|
\row
|
|
\li divide
|
|
\li The pixel component values from source is divided by the value
|
|
from foregroundSource and written.
|
|
\row
|
|
\li exclusion
|
|
\li The pixel component value difference with reduced contrast
|
|
between source and foregroundSource is written.
|
|
\row
|
|
\li hardLight
|
|
\li The pixel component values from source are lightened or darkened
|
|
according to foregroundSource values and written.
|
|
\row
|
|
\li hue
|
|
\li The hue value from foregroundSource is combined with saturation
|
|
and lightness from source and written.
|
|
\row
|
|
\li lighten
|
|
\li The lightest pixel component value from source and
|
|
foregroundSource is written.
|
|
\row
|
|
\li lighterColor
|
|
\li The higher luminance pixel rgb-value from source and
|
|
foregroundSource is written.
|
|
\row
|
|
\li lightness
|
|
\li The lightness value from foregroundSource is combined with hue
|
|
and saturation from source and written.
|
|
\row
|
|
\li multiply
|
|
\li The pixel component values from source and foregroundSource are
|
|
multiplied together and written.
|
|
\row
|
|
\li negation
|
|
\li The inverted absolute pixel component value difference between
|
|
source and foregroundSource is written.
|
|
\row
|
|
\li saturation
|
|
\li The saturation value from foregroundSource is combined with hue
|
|
and lightness from source and written.
|
|
\row
|
|
\li screen
|
|
\li The pixel values from source and foregroundSource are negated,
|
|
then multiplied, negated again, and written.
|
|
\row
|
|
\li subtract
|
|
\li Pixel value from foregroundSource is subracted from source and
|
|
written.
|
|
\row
|
|
\li softLight
|
|
\li The pixel component values from source are lightened or darkened
|
|
slightly according to foregroundSource values and written.
|
|
|
|
\endtable
|
|
|
|
\table
|
|
\header
|
|
\li Example source
|
|
\li Example foregroundSource
|
|
\row
|
|
\li \image Original_bug.png
|
|
\li \image Original_butterfly.png
|
|
\endtable
|
|
|
|
\table
|
|
\header
|
|
\li Output examples with different mode values
|
|
\li
|
|
\li
|
|
\row
|
|
\li \image Blend_mode1.png
|
|
\li \image Blend_mode2.png
|
|
\li \image Blend_mode3.png
|
|
\row
|
|
\li \b { mode: normal }
|
|
\li \b { mode: addition }
|
|
\li \b { mode: average }
|
|
\row
|
|
\li \image Blend_mode4.png
|
|
\li \image Blend_mode5.png
|
|
\li \image Blend_mode6.png
|
|
\row
|
|
\li \b { mode: color }
|
|
\li \b { mode: colorBurn }
|
|
\li \b { mode: colorDodge }
|
|
\row
|
|
\li \image Blend_mode7.png
|
|
\li \image Blend_mode8.png
|
|
\li \image Blend_mode9.png
|
|
\row
|
|
\li \b { mode: darken }
|
|
\li \b { mode: darkerColor }
|
|
\li \b { mode: difference }
|
|
\row
|
|
\li \image Blend_mode10.png
|
|
\li \image Blend_mode11.png
|
|
\li \image Blend_mode12.png
|
|
\row
|
|
\li \b { mode: divide }
|
|
\li \b { mode: exclusion }
|
|
\li \b { mode: hardlight }
|
|
\row
|
|
\li \image Blend_mode13.png
|
|
\li \image Blend_mode14.png
|
|
\li \image Blend_mode15.png
|
|
\row
|
|
\li \b { mode: hue }
|
|
\li \b { mode: lighten }
|
|
\li \b { mode: lighterColor }
|
|
\row
|
|
\li \image Blend_mode16.png
|
|
\li \image Blend_mode17.png
|
|
\li \image Blend_mode18.png
|
|
\row
|
|
\li \b { mode: lightness }
|
|
\li \b { mode: negation }
|
|
\li \b { mode: multiply }
|
|
\row
|
|
\li \image Blend_mode19.png
|
|
\li \image Blend_mode20.png
|
|
\li \image Blend_mode21.png
|
|
\row
|
|
\li \b { mode: saturation }
|
|
\li \b { mode: screen }
|
|
\li \b { mode: subtract }
|
|
\row
|
|
\li \image Blend_mode22.png
|
|
\row
|
|
\li \b { mode: softLight }
|
|
\endtable
|
|
*/
|
|
property string mode: "normal"
|
|
|
|
/*!
|
|
This property allows the effect output pixels to be cached in order to
|
|
improve the rendering performance.
|
|
|
|
Every time the source or effect properties are changed, the pixels in the
|
|
cache must be updated. Memory consumption is increased, because an extra
|
|
buffer of memory is required for storing the effect output.
|
|
|
|
It is recommended to disable the cache when the source or the effect
|
|
properties are animated.
|
|
|
|
By default, the property is set to false.
|
|
|
|
*/
|
|
property bool cached: false
|
|
|
|
SourceProxy {
|
|
id: backgroundSourceProxy
|
|
input: rootItem.source
|
|
}
|
|
|
|
SourceProxy {
|
|
id: foregroundSourceProxy
|
|
input: rootItem.foregroundSource
|
|
}
|
|
|
|
ShaderEffectSource {
|
|
id: cacheItem
|
|
anchors.fill: parent
|
|
visible: rootItem.cached
|
|
smooth: true
|
|
sourceItem: shaderItem
|
|
live: true
|
|
hideSource: visible
|
|
}
|
|
|
|
ShaderEffect {
|
|
id: shaderItem
|
|
property variant backgroundSource: backgroundSourceProxy.output
|
|
property variant foregroundSource: foregroundSourceProxy.output
|
|
property string mode: rootItem.mode
|
|
anchors.fill: parent
|
|
|
|
fragmentShader: fragmentShaderBegin + blendModeNormal + fragmentShaderEnd
|
|
|
|
function buildFragmentShader() {
|
|
var shader = fragmentShaderBegin
|
|
|
|
switch (mode.toLowerCase()) {
|
|
case "addition" : shader += blendModeAddition; break;
|
|
case "average" : shader += blendModeAverage; break;
|
|
case "color" : shader += blendModeColor; break;
|
|
case "colorburn" : shader += blendModeColorBurn; break;
|
|
case "colordodge" : shader += blendModeColorDodge; break;
|
|
case "darken" : shader += blendModeDarken; break;
|
|
case "darkercolor" : shader += blendModeDarkerColor; break;
|
|
case "difference" : shader += blendModeDifference; break;
|
|
case "divide" : shader += blendModeDivide; break;
|
|
case "exclusion" : shader += blendModeExclusion; break;
|
|
case "hardlight" : shader += blendModeHardLight; break;
|
|
case "hue" : shader += blendModeHue; break;
|
|
case "lighten" : shader += blendModeLighten; break;
|
|
case "lightercolor" : shader += blendModeLighterColor; break;
|
|
case "lightness" : shader += blendModeLightness; break;
|
|
case "negation" : shader += blendModeNegation; break;
|
|
case "normal" : shader += blendModeNormal; break;
|
|
case "multiply" : shader += blendModeMultiply; break;
|
|
case "saturation" : shader += blendModeSaturation; break;
|
|
case "screen" : shader += blendModeScreen; break;
|
|
case "subtract" : shader += blendModeSubtract; break;
|
|
case "softlight" : shader += blendModeSoftLight; break;
|
|
default: shader += "gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);"; break;
|
|
}
|
|
|
|
shader += fragmentShaderEnd
|
|
fragmentShader = shader
|
|
|
|
// Workaraound for a bug just to make sure display gets updated when the mode changes.
|
|
backgroundSourceChanged()
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
buildFragmentShader()
|
|
}
|
|
|
|
onModeChanged: {
|
|
buildFragmentShader()
|
|
}
|
|
|
|
property string blendModeAddition: "result.rgb = min(rgb1 + rgb2, 1.0);"
|
|
property string blendModeAverage: "result.rgb = 0.5 * (rgb1 + rgb2);"
|
|
property string blendModeColor: "result.rgb = HSLtoRGB(vec3(RGBtoHSL(rgb2).xy, RGBtoL(rgb1)));"
|
|
property string blendModeColorBurn: "result.rgb = clamp(1.0 - ((1.0 - rgb1) / max(vec3(1.0 / 256.0), rgb2)), vec3(0.0), vec3(1.0));"
|
|
property string blendModeColorDodge: "result.rgb = clamp(rgb1 / max(vec3(1.0 / 256.0), (1.0 - rgb2)), vec3(0.0), vec3(1.0));"
|
|
property string blendModeDarken: "result.rgb = min(rgb1, rgb2);"
|
|
property string blendModeDarkerColor: "result.rgb = 0.3 * rgb1.r + 0.59 * rgb1.g + 0.11 * rgb1.b > 0.3 * rgb2.r + 0.59 * rgb2.g + 0.11 * rgb2.b ? rgb2 : rgb1;"
|
|
property string blendModeDifference: "result.rgb = abs(rgb1 - rgb2);"
|
|
property string blendModeDivide: "result.rgb = clamp(rgb1 / rgb2, 0.0, 1.0);"
|
|
property string blendModeExclusion: "result.rgb = rgb1 + rgb2 - 2.0 * rgb1 * rgb2;"
|
|
property string blendModeHardLight: "result.rgb = vec3(channelBlendHardLight(rgb1.r, rgb2.r), channelBlendHardLight(rgb1.g, rgb2.g), channelBlendHardLight(rgb1.b, rgb2.b));"
|
|
property string blendModeHue: "result.rgb = HSLtoRGB(vec3(RGBtoHSL(rgb2).x, RGBtoHSL(rgb1).yz));"
|
|
property string blendModeLighten: "result.rgb = max(rgb1, rgb2);"
|
|
property string blendModeLighterColor: "result.rgb = 0.3 * rgb1.r + 0.59 * rgb1.g + 0.11 * rgb1.b > 0.3 * rgb2.r + 0.59 * rgb2.g + 0.11 * rgb2.b ? rgb1 : rgb2;"
|
|
property string blendModeLightness: "result.rgb = HSLtoRGB(vec3(RGBtoHSL(rgb1).xy, RGBtoL(rgb2)));"
|
|
property string blendModeMultiply: "result.rgb = rgb1 * rgb2;"
|
|
property string blendModeNegation: "result.rgb = 1.0 - abs(1.0 - rgb1 - rgb2);"
|
|
property string blendModeNormal: "result.rgb = rgb2; a = max(color1.a, color2.a);"
|
|
property string blendModeSaturation: "lowp vec3 hsl1 = RGBtoHSL(rgb1); result.rgb = HSLtoRGB(vec3(hsl1.x, RGBtoHSL(rgb2).y, hsl1.z));"
|
|
property string blendModeScreen: "result.rgb = 1.0 - (vec3(1.0) - rgb1) * (vec3(1.0) - rgb2);"
|
|
property string blendModeSubtract: "result.rgb = max(rgb1 - rgb2, vec3(0.0));"
|
|
property string blendModeSoftLight: "result.rgb = rgb1 * ((1.0 - rgb1) * rgb2 + (1.0 - (1.0 - rgb1) * (1.0 - rgb2)));"
|
|
|
|
property string fragmentCoreShaderWorkaround: (GraphicsInfo.profile === GraphicsInfo.OpenGLCoreProfile ? "#version 150 core
|
|
#define varying in
|
|
#define texture2D texture
|
|
out vec4 fragColor;
|
|
#define gl_FragColor fragColor
|
|
" : "")
|
|
|
|
property string fragmentShaderBegin: fragmentCoreShaderWorkaround + "
|
|
varying mediump vec2 qt_TexCoord0;
|
|
uniform highp float qt_Opacity;
|
|
uniform lowp sampler2D backgroundSource;
|
|
uniform lowp sampler2D foregroundSource;
|
|
|
|
highp float RGBtoL(highp vec3 color) {
|
|
highp float cmin = min(color.r, min(color.g, color.b));
|
|
highp float cmax = max(color.r, max(color.g, color.b));
|
|
highp float l = (cmin + cmax) / 2.0;
|
|
return l;
|
|
}
|
|
|
|
highp vec3 RGBtoHSL(highp vec3 color) {
|
|
highp float cmin = min(color.r, min(color.g, color.b));
|
|
highp float cmax = max(color.r, max(color.g, color.b));
|
|
highp float h = 0.0;
|
|
highp float s = 0.0;
|
|
highp float l = (cmin + cmax) / 2.0;
|
|
highp float diff = cmax - cmin;
|
|
|
|
if (diff > 1.0 / 256.0) {
|
|
if (l < 0.5)
|
|
s = diff / (cmin + cmax);
|
|
else
|
|
s = diff / (2.0 - (cmin + cmax));
|
|
|
|
if (color.r == cmax)
|
|
h = (color.g - color.b) / diff;
|
|
else if (color.g == cmax)
|
|
h = 2.0 + (color.b - color.r) / diff;
|
|
else
|
|
h = 4.0 + (color.r - color.g) / diff;
|
|
|
|
h /= 6.0;
|
|
}
|
|
return vec3(h, s, l);
|
|
}
|
|
|
|
highp float hueToIntensity(highp float v1, highp float v2, highp float h) {
|
|
h = fract(h);
|
|
if (h < 1.0 / 6.0)
|
|
return v1 + (v2 - v1) * 6.0 * h;
|
|
else if (h < 1.0 / 2.0)
|
|
return v2;
|
|
else if (h < 2.0 / 3.0)
|
|
return v1 + (v2 - v1) * 6.0 * (2.0 / 3.0 - h);
|
|
|
|
return v1;
|
|
}
|
|
|
|
highp vec3 HSLtoRGB(highp vec3 color) {
|
|
highp float h = color.x;
|
|
highp float l = color.z;
|
|
highp float s = color.y;
|
|
|
|
if (s < 1.0 / 256.0)
|
|
return vec3(l, l, l);
|
|
|
|
highp float v1;
|
|
highp float v2;
|
|
if (l < 0.5)
|
|
v2 = l * (1.0 + s);
|
|
else
|
|
v2 = (l + s) - (s * l);
|
|
|
|
v1 = 2.0 * l - v2;
|
|
|
|
highp float d = 1.0 / 3.0;
|
|
highp float r = hueToIntensity(v1, v2, h + d);
|
|
highp float g = hueToIntensity(v1, v2, h);
|
|
highp float b = hueToIntensity(v1, v2, h - d);
|
|
return vec3(r, g, b);
|
|
}
|
|
|
|
lowp float channelBlendHardLight(lowp float c1, lowp float c2) {
|
|
return c2 > 0.5 ? (1.0 - (1.0 - 2.0 * (c2 - 0.5)) * (1.0 - c1)) : (2.0 * c1 * c2);
|
|
}
|
|
|
|
void main() {
|
|
lowp vec4 result = vec4(0.0);
|
|
lowp vec4 color1 = texture2D(backgroundSource, qt_TexCoord0);
|
|
lowp vec4 color2 = texture2D(foregroundSource, qt_TexCoord0);
|
|
lowp vec3 rgb1 = color1.rgb / max(1.0/256.0, color1.a);
|
|
lowp vec3 rgb2 = color2.rgb / max(1.0/256.0, color2.a);
|
|
highp float a = max(color1.a, color1.a * color2.a);
|
|
"
|
|
|
|
property string fragmentShaderEnd: "
|
|
gl_FragColor.rgb = mix(rgb1, result.rgb, color2.a);
|
|
gl_FragColor.rbg *= a;
|
|
gl_FragColor.a = a;
|
|
gl_FragColor *= qt_Opacity;
|
|
}
|
|
"
|
|
}
|
|
}
|