1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Graphical Effects module.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40import QtQuick 2.12
41import QtGraphicalEffects.private 1.12
42
43/*!
44    \qmltype Blend
45    \inqmlmodule QtGraphicalEffects
46    \since QtGraphicalEffects 1.0
47    \inherits QtQuick2::Item
48    \ingroup qtgraphicaleffects-blend
49    \brief Merges two source items by using a blend mode.
50
51    Blend mode can be selected with the \l{Blend::mode}{mode} property.
52
53    \table
54    \header
55        \li source
56        \li foregroundSource
57        \li Effect applied
58    \row
59        \li \image Original_bug.png
60        \li \image Original_butterfly.png
61        \li \image Blend_bug_and_butterfly.png
62    \endtable
63
64    \note This effect is available when running with OpenGL.
65
66    \section1 Example
67
68    The following example shows how to apply the effect.
69    \snippet Blend-example.qml example
70
71*/
72
73Item {
74    id: rootItem
75
76    /*!
77        This property defines the source item that is going to be the base when
78        \l{Blend::foregroundSource}{foregroundSource} is blended over it.
79
80        \note It is not supported to let the effect include itself, for
81        instance by setting source to the effect's parent.
82    */
83    property variant source
84
85    /*!
86        This property defines the item that is going to be blended over the
87        \l{Blend::source}{source}.
88
89        \note It is not supported to let the effect include itself, for
90        instance by setting foregroundSource to the effect's parent.
91    */
92    property variant foregroundSource
93
94    /*!
95        This property defines the mode which is used when foregroundSource is
96        blended over source. Values are case insensitive.
97
98        \table
99        \header
100            \li mode
101            \li description
102        \row
103            \li normal
104            \li The pixel component values from foregroundSource are written
105            over source by using alpha blending.
106        \row
107            \li addition
108            \li The pixel component values from source and foregroundSource are
109            added together and written.
110        \row
111            \li average
112            \li The pixel component values from source and foregroundSource are
113            averaged and written.
114        \row
115            \li color
116            \li The lightness value from source is combined with hue and
117            saturation from foregroundSource and written.
118        \row
119            \li colorBurn
120            \li The darker pixels from source are darkened more, if both source
121            and foregroundSource pixels are light the result is light.
122        \row
123            \li colorDodge
124            \li The lighter pixels from source are lightened more, if both
125            source and foregroundSource pixels are dark the result is dark.
126        \row
127            \li darken
128            \li The darker pixel component value from source and
129            foregroundSource is written.
130        \row
131            \li darkerColor
132            \li The lower luminance pixel rgb-value from source and
133            foregroundSource is written.
134        \row
135            \li difference
136            \li The absolute pixel component value difference between source and
137            foregroundSource is written.
138        \row
139            \li divide
140            \li The pixel component values from source is divided by the value
141            from foregroundSource and written.
142        \row
143            \li exclusion
144            \li The pixel component value difference with reduced contrast
145            between source and foregroundSource is written.
146        \row
147            \li hardLight
148            \li The pixel component values from source are lightened or darkened
149            according to foregroundSource values and written.
150        \row
151            \li hue
152            \li The hue value from foregroundSource is combined with saturation
153            and lightness from source and written.
154        \row
155            \li lighten
156            \li The lightest pixel component value from source and
157            foregroundSource is written.
158        \row
159            \li lighterColor
160            \li The higher luminance pixel rgb-value from source and
161            foregroundSource is written.
162        \row
163            \li lightness
164            \li The lightness value from foregroundSource is combined with hue
165            and saturation from source and written.
166        \row
167            \li multiply
168            \li The pixel component values from source and foregroundSource are
169            multiplied together and written.
170        \row
171            \li negation
172            \li The inverted absolute pixel component value difference between
173            source and foregroundSource is written.
174        \row
175            \li saturation
176            \li The saturation value from foregroundSource is combined with hue
177            and lightness from source and written.
178        \row
179            \li screen
180            \li The pixel values from source and foregroundSource are negated,
181            then multiplied, negated again, and written.
182        \row
183            \li subtract
184            \li Pixel value from foregroundSource is subracted from source and
185            written.
186        \row
187            \li softLight
188            \li The pixel component values from source are lightened or darkened
189            slightly according to foregroundSource values and written.
190
191        \endtable
192
193        \table
194        \header
195            \li Example source
196            \li Example foregroundSource
197        \row
198            \li \image Original_bug.png
199            \li \image Original_butterfly.png
200        \endtable
201
202        \table
203        \header
204        \li Output examples with different mode values
205        \li
206        \li
207        \row
208            \li \image Blend_mode1.png
209            \li \image Blend_mode2.png
210            \li \image Blend_mode3.png
211        \row
212            \li \b { mode: normal }
213            \li \b { mode: addition }
214            \li \b { mode: average }
215        \row
216            \li \image Blend_mode4.png
217            \li \image Blend_mode5.png
218            \li \image Blend_mode6.png
219        \row
220            \li \b { mode: color }
221            \li \b { mode: colorBurn }
222            \li \b { mode: colorDodge }
223        \row
224            \li \image Blend_mode7.png
225            \li \image Blend_mode8.png
226            \li \image Blend_mode9.png
227        \row
228            \li \b { mode: darken }
229            \li \b { mode: darkerColor }
230            \li \b { mode: difference }
231        \row
232            \li \image Blend_mode10.png
233            \li \image Blend_mode11.png
234            \li \image Blend_mode12.png
235        \row
236            \li \b { mode: divide }
237            \li \b { mode: exclusion }
238            \li \b { mode: hardlight }
239        \row
240            \li \image Blend_mode13.png
241            \li \image Blend_mode14.png
242            \li \image Blend_mode15.png
243        \row
244            \li \b { mode: hue }
245            \li \b { mode: lighten }
246            \li \b { mode: lighterColor }
247        \row
248            \li \image Blend_mode16.png
249            \li \image Blend_mode17.png
250            \li \image Blend_mode18.png
251        \row
252            \li \b { mode: lightness }
253            \li \b { mode: negation }
254            \li \b { mode: multiply }
255        \row
256            \li \image Blend_mode19.png
257            \li \image Blend_mode20.png
258            \li \image Blend_mode21.png
259        \row
260            \li \b { mode: saturation }
261            \li \b { mode: screen }
262            \li \b { mode: subtract }
263        \row
264            \li \image Blend_mode22.png
265        \row
266            \li \b { mode: softLight }
267        \endtable
268    */
269    property string mode: "normal"
270
271    /*!
272    This property allows the effect output pixels to be cached in order to
273    improve the rendering performance.
274
275    Every time the source or effect properties are changed, the pixels in the
276    cache must be updated. Memory consumption is increased, because an extra
277    buffer of memory is required for storing the effect output.
278
279    It is recommended to disable the cache when the source or the effect
280    properties are animated.
281
282    By default, the property is set to false.
283
284    */
285    property bool cached: false
286
287    SourceProxy {
288        id: backgroundSourceProxy
289        input: rootItem.source
290    }
291
292    SourceProxy {
293        id: foregroundSourceProxy
294        input: rootItem.foregroundSource
295    }
296
297    ShaderEffectSource {
298        id: cacheItem
299        anchors.fill: parent
300        visible: rootItem.cached
301        smooth: true
302        sourceItem: shaderItem
303        live: true
304        hideSource: visible
305    }
306
307    ShaderEffect {
308        id: shaderItem
309        property variant backgroundSource: backgroundSourceProxy.output
310        property variant foregroundSource: foregroundSourceProxy.output
311        property string mode: rootItem.mode
312        anchors.fill: parent
313
314        fragmentShader: fragmentShaderBegin + blendModeNormal + fragmentShaderEnd
315
316        function buildFragmentShader() {
317            var shader = fragmentShaderBegin
318
319            switch (mode.toLowerCase()) {
320                case "addition" : shader += blendModeAddition; break;
321                case "average" : shader += blendModeAverage; break;
322                case "color" : shader += blendModeColor; break;
323                case "colorburn" : shader += blendModeColorBurn; break;
324                case "colordodge" : shader += blendModeColorDodge; break;
325                case "darken" : shader += blendModeDarken; break;
326                case "darkercolor" : shader += blendModeDarkerColor; break;
327                case "difference" : shader += blendModeDifference; break;
328                case "divide" : shader += blendModeDivide; break;
329                case "exclusion" : shader += blendModeExclusion; break;
330                case "hardlight" : shader += blendModeHardLight; break;
331                case "hue" : shader += blendModeHue; break;
332                case "lighten" : shader += blendModeLighten; break;
333                case "lightercolor" : shader += blendModeLighterColor; break;
334                case "lightness" : shader += blendModeLightness; break;
335                case "negation" : shader += blendModeNegation; break;
336                case "normal" : shader += blendModeNormal; break;
337                case "multiply" : shader += blendModeMultiply; break;
338                case "saturation" : shader += blendModeSaturation; break;
339                case "screen" : shader += blendModeScreen; break;
340                case "subtract" : shader += blendModeSubtract; break;
341                case "softlight" : shader += blendModeSoftLight; break;
342                default: shader += "gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);"; break;
343            }
344
345            shader += fragmentShaderEnd
346            fragmentShader = shader
347
348            // Workaraound for a bug just to make sure display gets updated when the mode changes.
349            backgroundSourceChanged()
350        }
351
352        Component.onCompleted: {
353            buildFragmentShader()
354        }
355
356        onModeChanged: {
357            buildFragmentShader()
358        }
359
360        property string blendModeAddition: "result.rgb = min(rgb1 + rgb2, 1.0);"
361        property string blendModeAverage: "result.rgb = 0.5 * (rgb1 + rgb2);"
362        property string blendModeColor: "result.rgb = HSLtoRGB(vec3(RGBtoHSL(rgb2).xy, RGBtoL(rgb1)));"
363        property string blendModeColorBurn: "result.rgb = clamp(1.0 - ((1.0 - rgb1) / max(vec3(1.0 / 256.0), rgb2)), vec3(0.0), vec3(1.0));"
364        property string blendModeColorDodge: "result.rgb = clamp(rgb1 / max(vec3(1.0 / 256.0), (1.0 - rgb2)), vec3(0.0), vec3(1.0));"
365        property string blendModeDarken: "result.rgb = min(rgb1, rgb2);"
366        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;"
367        property string blendModeDifference: "result.rgb = abs(rgb1 - rgb2);"
368        property string blendModeDivide: "result.rgb = clamp(rgb1 / rgb2, 0.0, 1.0);"
369        property string blendModeExclusion: "result.rgb = rgb1 + rgb2 - 2.0 * rgb1 * rgb2;"
370        property string blendModeHardLight: "result.rgb = vec3(channelBlendHardLight(rgb1.r, rgb2.r), channelBlendHardLight(rgb1.g, rgb2.g), channelBlendHardLight(rgb1.b, rgb2.b));"
371        property string blendModeHue: "result.rgb = HSLtoRGB(vec3(RGBtoHSL(rgb2).x, RGBtoHSL(rgb1).yz));"
372        property string blendModeLighten: "result.rgb = max(rgb1, rgb2);"
373        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;"
374        property string blendModeLightness: "result.rgb = HSLtoRGB(vec3(RGBtoHSL(rgb1).xy, RGBtoL(rgb2)));"
375        property string blendModeMultiply: "result.rgb = rgb1 * rgb2;"
376        property string blendModeNegation: "result.rgb = 1.0 - abs(1.0 - rgb1 - rgb2);"
377        property string blendModeNormal: "result.rgb = rgb2; a = max(color1.a, color2.a);"
378        property string blendModeSaturation: "lowp vec3 hsl1 = RGBtoHSL(rgb1); result.rgb = HSLtoRGB(vec3(hsl1.x, RGBtoHSL(rgb2).y, hsl1.z));"
379        property string blendModeScreen: "result.rgb = 1.0 - (vec3(1.0) - rgb1) * (vec3(1.0) - rgb2);"
380        property string blendModeSubtract: "result.rgb = max(rgb1 - rgb2, vec3(0.0));"
381        property string blendModeSoftLight: "result.rgb = rgb1 * ((1.0 - rgb1) * rgb2 + (1.0 - (1.0 - rgb1) * (1.0 - rgb2)));"
382
383        property string fragmentCoreShaderWorkaround: (GraphicsInfo.profile === GraphicsInfo.OpenGLCoreProfile ? "#version 150 core
384            #define varying in
385            #define texture2D texture
386            out vec4 fragColor;
387            #define gl_FragColor fragColor
388        " : "")
389
390        property string fragmentShaderBegin: fragmentCoreShaderWorkaround + "
391            varying mediump vec2 qt_TexCoord0;
392            uniform highp float qt_Opacity;
393            uniform lowp sampler2D backgroundSource;
394            uniform lowp sampler2D foregroundSource;
395
396            highp float RGBtoL(highp vec3 color) {
397                highp float cmin = min(color.r, min(color.g, color.b));
398                highp float cmax = max(color.r, max(color.g, color.b));
399                highp float l = (cmin + cmax) / 2.0;
400                return l;
401            }
402
403            highp vec3 RGBtoHSL(highp vec3 color) {
404                highp float cmin = min(color.r, min(color.g, color.b));
405                highp float cmax = max(color.r, max(color.g, color.b));
406                highp float h = 0.0;
407                highp float s = 0.0;
408                highp float l = (cmin + cmax) / 2.0;
409                highp float diff = cmax - cmin;
410
411                if (diff > 1.0 / 256.0) {
412                    if (l < 0.5)
413                        s = diff / (cmin + cmax);
414                    else
415                        s = diff / (2.0 - (cmin + cmax));
416
417                    if (color.r == cmax)
418                        h = (color.g - color.b) / diff;
419                    else if (color.g == cmax)
420                        h = 2.0 + (color.b - color.r) / diff;
421                    else
422                        h = 4.0 + (color.r - color.g) / diff;
423
424                    h /= 6.0;
425                }
426                return vec3(h, s, l);
427                }
428
429            highp float hueToIntensity(highp float v1, highp float v2, highp float h) {
430                h = fract(h);
431                if (h < 1.0 / 6.0)
432                    return v1 + (v2 - v1) * 6.0 * h;
433                else if (h < 1.0 / 2.0)
434                    return v2;
435                else if (h < 2.0 / 3.0)
436                    return v1 + (v2 - v1) * 6.0 * (2.0 / 3.0 - h);
437
438                return v1;
439            }
440
441            highp vec3 HSLtoRGB(highp vec3 color) {
442                highp float h = color.x;
443                highp float l = color.z;
444                highp float s = color.y;
445
446                if (s < 1.0 / 256.0)
447                    return vec3(l, l, l);
448
449                highp float v1;
450                highp float v2;
451                if (l < 0.5)
452                    v2 = l * (1.0 + s);
453                else
454                    v2 = (l + s) - (s * l);
455
456                v1 = 2.0 * l - v2;
457
458                highp float d = 1.0 / 3.0;
459                highp float r = hueToIntensity(v1, v2, h + d);
460                highp float g = hueToIntensity(v1, v2, h);
461                highp float b = hueToIntensity(v1, v2, h - d);
462                return vec3(r, g, b);
463            }
464
465            lowp float channelBlendHardLight(lowp float c1, lowp float c2) {
466                return c2 > 0.5 ? (1.0 - (1.0 - 2.0 * (c2 - 0.5)) * (1.0 - c1)) : (2.0 * c1 * c2);
467            }
468
469            void main() {
470                lowp vec4 result = vec4(0.0);
471                lowp vec4 color1 = texture2D(backgroundSource, qt_TexCoord0);
472                lowp vec4 color2 = texture2D(foregroundSource, qt_TexCoord0);
473                lowp vec3 rgb1 = color1.rgb / max(1.0/256.0, color1.a);
474                lowp vec3 rgb2 = color2.rgb / max(1.0/256.0, color2.a);
475                highp float a = max(color1.a, color1.a * color2.a);
476        "
477
478        property string fragmentShaderEnd: "
479                gl_FragColor.rgb = mix(rgb1, result.rgb, color2.a);
480                gl_FragColor.rbg *= a;
481                gl_FragColor.a = a;
482                gl_FragColor *= qt_Opacity;
483            }
484        "
485    }
486}
487