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 ZoomBlur
45    \inqmlmodule QtGraphicalEffects
46    \since QtGraphicalEffects 1.0
47    \inherits QtQuick2::Item
48    \ingroup qtgraphicaleffects-motion-blur
49    \brief Applies directional blur effect towards source items center point.
50
51    Effect creates perceived impression that the source item appears to be
52    moving towards the center point in Z-direction or that the camera appears
53    to be zooming rapidly. Other available motion blur effects are
54    \l{QtGraphicalEffects::DirectionalBlur}{DirectionalBlur}
55    and \l{QtGraphicalEffects::RadialBlur}{RadialBlur}.
56
57    \table
58    \header
59        \li Source
60        \li Effect applied
61    \row
62        \li \image Original_bug.png
63        \li \image ZoomBlur_bug.png
64    \endtable
65
66    \note This effect is available when running with OpenGL.
67
68    \section1 Example
69
70    The following example shows how to apply the effect.
71    \snippet ZoomBlur-example.qml example
72
73*/
74Item {
75    id: rootItem
76
77    /*!
78        This property defines the source item that is going to be blurred.
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 maximum perceived amount of movement for each
87        pixel. The amount is smaller near the center and reaches the specified
88        value at the edges.
89
90        The quality of the blur depends on \l{ZoomBlur::samples}{samples}
91        property. If length value is large, more samples are needed to keep the
92        visual quality at high level.
93
94        The value ranges from 0.0 to inf. By default the property is set to \c
95        0.0 (no blur).
96
97        \table
98        \header
99        \li Output examples with different length values
100        \li
101        \li
102        \row
103            \li \image ZoomBlur_length1.png
104            \li \image ZoomBlur_length2.png
105            \li \image ZoomBlur_length3.png
106        \row
107            \li \b { length: 0.0 }
108            \li \b { length: 32.0 }
109            \li \b { length: 48.0 }
110        \row
111            \li \l samples: 24
112            \li \l samples: 24
113            \li \l samples: 24
114        \row
115            \li \l horizontalOffset: 0
116            \li \l horizontalOffset: 0
117            \li \l horizontalOffset: 0
118        \row
119            \li \l verticalOffset: 0
120            \li \l verticalOffset: 0
121            \li \l verticalOffset: 0
122        \endtable
123
124    */
125    property real length: 0.0
126
127    /*!
128        This property defines how many samples are taken per pixel when blur
129        calculation is done. Larger value produces better quality, but is slower
130        to render.
131
132        This property is not intended to be animated. Changing this property may
133        cause the underlying OpenGL shaders to be recompiled.
134
135        Allowed values are between 0 and inf (practical maximum depends on GPU).
136        By default the property is set to \c 0 (no samples).
137
138    */
139    property int samples: 0
140
141    /*!
142        \qmlproperty real QtGraphicalEffects::ZoomBlur::horizontalOffset
143        \qmlproperty real QtGraphicalEffects::ZoomBlur::verticalOffset
144
145        These properties define an offset in pixels for the blur direction
146        center point.
147
148        The values range from -inf to inf. By default these properties are set
149        to \c 0.
150
151        \table
152        \header
153        \li Output examples with different horizontalOffset values
154        \li
155        \li
156        \row
157            \li \image ZoomBlur_horizontalOffset1.png
158            \li \image ZoomBlur_horizontalOffset2.png
159            \li \image ZoomBlur_horizontalOffset3.png
160        \row
161            \li \b { horizontalOffset: 100.0 }
162            \li \b { horizontalOffset: 0.0 }
163            \li \b { horizontalOffset: -100.0 }
164        \row
165            \li \l samples: 24
166            \li \l samples: 24
167            \li \l samples: 24
168        \row
169            \li \l length: 32
170            \li \l length: 32
171            \li \l length: 32
172        \row
173            \li \l verticalOffset: 0
174            \li \l verticalOffset: 0
175            \li \l verticalOffset: 0
176        \endtable
177    */
178    property real horizontalOffset: 0.0
179    property real verticalOffset: 0.0
180
181    /*!
182        This property defines the blur behavior near the edges of the item,
183        where the pixel blurring is affected by the pixels outside the source
184        edges.
185
186        If the property is set to \c true, the pixels outside the source are
187        interpreted to be transparent, which is similar to OpenGL
188        clamp-to-border extension. The blur is expanded slightly outside the
189        effect item area.
190
191        If the property is set to \c false, the pixels outside the source are
192        interpreted to contain the same color as the pixels at the edge of the
193        item, which is similar to OpenGL clamp-to-edge behavior. The blur does
194        not expand outside the effect item area.
195
196        By default, the property is set to \c false.
197
198    */
199    property bool transparentBorder: false
200
201    /*!
202        This property allows the effect output pixels to be cached in order to
203        improve the rendering performance.
204
205        Every time the source or effect properties are changed, the pixels in
206        the cache must be updated. Memory consumption is increased, because an
207        extra buffer of memory is required for storing the effect output.
208
209        It is recommended to disable the cache when the source or the effect
210        properties are animated.
211
212        By default, the property is set to \c false.
213    */
214    property bool cached: false
215
216    SourceProxy {
217        id: sourceProxy
218        input: rootItem.source
219        sourceRect: rootItem.transparentBorder ? Qt.rect(-1, -1, parent.width + 2.0, parent.height + 2.0) : Qt.rect(0, 0, 0, 0)
220    }
221
222    ShaderEffectSource {
223        id: cacheItem
224        anchors.fill: shaderItem
225        visible: rootItem.cached
226        smooth: true
227        sourceItem: shaderItem
228        live: true
229        hideSource: visible
230    }
231
232    ShaderEffect {
233        id: shaderItem
234        property variant source: sourceProxy.output
235        property variant center: Qt.point(0.5 + rootItem.horizontalOffset / width, 0.5 + rootItem.verticalOffset / height)
236        property real len: rootItem.length
237        property bool transparentBorder: rootItem.transparentBorder
238        property real samples: rootItem.samples
239        property real weight: 1.0 / Math.max(1.0, rootItem.samples)
240        property variant expandPixels: transparentBorder ? Qt.size(rootItem.samples, rootItem.samples) : Qt.size(0,0)
241        property variant expand: transparentBorder ? Qt.size(expandPixels.width / width, expandPixels.height / height) : Qt.size(0,0)
242        property variant delta: Qt.size(1.0 / rootItem.width, 1.0 / rootItem.height)
243
244        x: transparentBorder ? -expandPixels.width - 1 : 0
245        y: transparentBorder ? -expandPixels.height - 1 : 0
246        width: transparentBorder ? parent.width + 2.0 * expandPixels.width + 2 : parent.width
247        height: transparentBorder ? parent.height + 2.0 * expandPixels.height + 2 : parent.height
248
249        property string fragmentShaderSkeleton: "
250            varying highp vec2 qt_TexCoord0;
251            uniform highp float qt_Opacity;
252            uniform lowp sampler2D source;
253            uniform highp float len;
254            uniform highp float weight;
255            uniform highp float samples;
256            uniform highp vec2 center;
257            uniform highp vec2 expand;
258            uniform highp vec2 delta;
259
260            void main(void) {
261                mediump vec2 texCoord = qt_TexCoord0;
262                mediump vec2 centerCoord = center;
263
264                PLACEHOLDER_EXPAND_STEPS
265
266                highp vec2 dir = vec2(centerCoord.x - texCoord.s, centerCoord.y - texCoord.t);
267                dir /= max(1.0, length(dir) * 2.0);
268                highp vec2 shift = delta * len * dir * 2.0 / max(1.0, samples - 1.0);
269                gl_FragColor = vec4(0.0);
270
271                PLACEHOLDER_UNROLLED_LOOP
272
273                gl_FragColor *= weight * qt_Opacity;
274            }
275        "
276
277        function buildFragmentShader() {
278            var shader = ""
279            if (GraphicsInfo.profile == GraphicsInfo.OpenGLCoreProfile)
280                shader += "#version 150 core\n#define varying in\n#define gl_FragColor fragColor\n#define texture2D texture\nout vec4 fragColor;\n"
281            shader += fragmentShaderSkeleton
282            var expandSteps = ""
283
284            if (transparentBorder) {
285                expandSteps += "centerCoord = (centerCoord - expand) / (1.0 - 2.0 * expand);"
286                expandSteps += "texCoord = (texCoord - expand) / (1.0 - 2.0 * expand);"
287             }
288
289             var unrolledLoop = "gl_FragColor += texture2D(source, texCoord);\n"
290
291             if (rootItem.samples > 1) {
292                 unrolledLoop = ""
293                 for (var i = 0; i < rootItem.samples; i++)
294                      unrolledLoop += "gl_FragColor += texture2D(source, texCoord); texCoord += shift;\n"
295             }
296
297             shader = shader.replace("PLACEHOLDER_EXPAND_STEPS", expandSteps)
298             fragmentShader = shader.replace("PLACEHOLDER_UNROLLED_LOOP", unrolledLoop)
299        }
300
301        onFragmentShaderChanged: sourceChanged()
302        onSamplesChanged: buildFragmentShader()
303        onTransparentBorderChanged: buildFragmentShader()
304        Component.onCompleted: buildFragmentShader()
305    }
306}
307