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