Document recursion limitations of the graphical effects.
[qt:qtgraphicaleffects.git] / src / effects / RadialBlur.qml
1 /****************************************************************************
2 **
3 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the Qt Graphical Effects module.
7 **
8 ** $QT_BEGIN_LICENSE:BSD$
9 ** You may use this file under the terms of the BSD license as follows:
10 **
11 ** "Redistribution and use in source and binary forms, with or without
12 ** modification, are permitted provided that the following conditions are
13 ** met:
14 **   * Redistributions of source code must retain the above copyright
15 **     notice, this list of conditions and the following disclaimer.
16 **   * Redistributions in binary form must reproduce the above copyright
17 **     notice, this list of conditions and the following disclaimer in
18 **     the documentation and/or other materials provided with the
19 **     distribution.
20 **   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
21 **     of its contributors may be used to endorse or promote products derived
22 **     from this software without specific prior written permission.
23 **
24 **
25 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
36 **
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40
41 import QtQuick 2.0
42 import "private"
43
44 /*!
45     \qmltype RadialBlur
46     \inqmlmodule QtGraphicalEffects 1.0
47     \since QtGraphicalEffects 1.0
48     \inherits QtQuick2::Item
49     \ingroup qtgraphicaleffects-motion-blur
50     \brief Applies directional blur in a circular direction around the items
51     center point.
52
53     Effect creates perceived impression that the source item appears to be
54     rotating to the direction of the blur.
55
56     Other available motionblur effects are
57     \l{QtGraphicalEffects1::ZoomBlur}{ZoomBlur} and
58     \l{QtGraphicalEffects1::DirectionalBlur}{DirectionalBlur}.
59
60     \table
61     \header
62         \li Source
63         \li Effect applied
64     \row
65         \li \image Original_bug.png
66         \li \image RadialBlur_bug.png
67     \endtable
68
69     \section1 Example Usage
70
71     The following example shows how to apply the effect.
72     \snippet RadialBlur-example.qml example
73 */
74 Item {
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 direction for the blur and at the same time
87         the level of blurring. The larger the angle, the more the result becomes
88         blurred. The quality of the blur depends on
89         \l{RadialBlur::samples}{samples} property. If angle value is large, more
90         samples are needed to keep the visual quality at high level.
91
92         Allowed values are between 0.0 and 360.0. By default the property is set
93         to \c 0.0.
94
95         \table
96         \header
97         \li Output examples with different angle values
98         \li
99         \li
100         \row
101             \li \image RadialBlur_angle1.png
102             \li \image RadialBlur_angle2.png
103             \li \image RadialBlur_angle3.png
104         \row
105             \li \b { angle: 0.0 }
106             \li \b { angle: 15.0 }
107             \li \b { angle: 30.0 }
108         \row
109             \li \l samples: 24
110             \li \l samples: 24
111             \li \l samples: 24
112         \row
113             \li \l horizontalOffset: 0
114             \li \l horizontalOffset: 0
115             \li \l horizontalOffset: 0
116         \row
117             \li \l verticalOffset: 0
118             \li \l verticalOffset: 0
119             \li \l verticalOffset: 0
120         \endtable
121     */
122     property real angle: 0.0
123
124     /*!
125         This property defines how many samples are taken per pixel when blur
126         calculation is done. Larger value produces better quality, but is slower
127         to render.
128
129         This property is not intended to be animated. Changing this property may
130         cause the underlying OpenGL shaders to be recompiled.
131
132         Allowed values are between 0 and inf (practical maximum depends on GPU).
133         By default the property is set to \c 0 (no samples).
134
135     */
136     property int samples: 0
137
138     /*!
139         \qmlproperty real QtGraphicalEffects1::RadialBlur::horizontalOffset
140         \qmlproperty real QtGraphicalEffects1::RadialBlur::verticalOffset
141
142         These properties define the offset in pixels for the perceived center
143         point of the rotation.
144
145         Allowed values are between -inf and inf.
146         By default these properties are set to \c 0.
147
148         \table
149         \header
150         \li Output examples with different horizontalOffset values
151         \li
152         \li
153         \row
154             \li \image RadialBlur_horizontalOffset1.png
155             \li \image RadialBlur_horizontalOffset2.png
156             \li \image RadialBlur_horizontalOffset3.png
157         \row
158             \li \b { horizontalOffset: 75.0 }
159             \li \b { horizontalOffset: 0.0 }
160             \li \b { horizontalOffset: -75.0 }
161         \row
162             \li \l samples: 24
163             \li \l samples: 24
164             \li \l samples: 24
165         \row
166             \li \l angle: 20
167             \li \l angle: 20
168             \li \l angle: 20
169         \row
170             \li \l verticalOffset: 0
171             \li \l verticalOffset: 0
172             \li \l verticalOffset: 0
173         \endtable
174     */
175     property real horizontalOffset: 0.0
176     property real verticalOffset: 0.0
177
178     /*!
179         This property defines the blur behavior near the edges of the item,
180         where the pixel blurring is affected by the pixels outside the source
181         edges.
182
183         If the property is set to \c true, the pixels outside the source are
184         interpreted to be transparent, which is similar to OpenGL
185         clamp-to-border extension. The blur is expanded slightly outside the
186         effect item area.
187
188         If the property is set to \c false, the pixels outside the source are
189         interpreted to contain the same color as the pixels at the edge of the
190         item, which is similar to OpenGL clamp-to-edge behavior. The blur does
191         not expand outside the effect item area.
192
193         By default, the property is set to \c false.
194     */
195     property bool transparentBorder: false
196
197     /*!
198         This property allows the effect output pixels to be cached in order to
199         improve the rendering performance.
200
201         Every time the source or effect properties are changed, the pixels in
202         the cache must be updated. Memory consumption is increased, because an
203         extra buffer of memory is required for storing the effect output.
204
205         It is recommended to disable the cache when the source or the effect
206         properties are animated.
207
208         By default, the property is set to \c false.
209
210     */
211     property bool cached: false
212
213     SourceProxy {
214         id: sourceProxy
215         input: rootItem.source
216         sourceRect: shaderItem.transparentBorder ? Qt.rect(-1, -1, parent.width + 2.0, parent.height + 2.0) : Qt.rect(0, 0, 0, 0)
217     }
218
219     ShaderEffectSource {
220         id: cacheItem
221         anchors.fill: shaderItem
222         visible: rootItem.cached
223         smooth: true
224         sourceItem: shaderItem
225         live: true
226         hideSource: visible
227     }
228
229     ShaderEffect {
230         id: shaderItem
231         property variant source: sourceProxy.output
232         property variant center: Qt.point(0.5 + rootItem.horizontalOffset / parent.width, 0.5 + rootItem.verticalOffset / parent.height)
233         property bool transparentBorder: rootItem.transparentBorder && rootItem.samples > 1
234         property int samples: rootItem.samples
235         property real weight: 1.0 / Math.max(1.0, rootItem.samples)
236         property real angleSin: Math.sin(rootItem.angle/2 * Math.PI/180)
237         property real angleCos: Math.cos(rootItem.angle/2 * Math.PI/180)
238         property real angleSinStep: Math.sin(-rootItem.angle * Math.PI/180 / Math.max(1.0, rootItem.samples - 1))
239         property real angleCosStep: Math.cos(-rootItem.angle * Math.PI/180 / Math.max(1.0, rootItem.samples - 1))
240         property variant expandPixels: transparentBorder ? Qt.size(0.5 * parent.height, 0.5 * parent.width) : 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         property real w: parent.width
244         property real h: parent.height
245
246         x: transparentBorder ? -expandPixels.width - 1 : 0
247         y: transparentBorder ? -expandPixels.height - 1 : 0
248         width: transparentBorder ? parent.width + expandPixels.width * 2.0 + 2 : parent.width
249         height: transparentBorder ? parent.height + expandPixels.height * 2.0 + 2 : parent.height
250
251         property string fragmentShaderSkeleton: "
252             varying highp vec2 qt_TexCoord0;
253             uniform highp float qt_Opacity;
254             uniform lowp sampler2D source;
255             uniform highp float angleSin;
256             uniform highp float angleCos;
257             uniform highp float angleSinStep;
258             uniform highp float angleCosStep;
259             uniform highp float weight;
260             uniform highp vec2 expand;
261             uniform highp vec2 center;
262             uniform highp vec2 delta;
263             uniform highp float w;
264             uniform highp float h;
265
266             void main(void) {
267                 highp mat2 m;
268                 gl_FragColor = vec4(0.0);
269                 mediump vec2 texCoord = qt_TexCoord0;
270
271                 PLACEHOLDER_EXPAND_STEPS
272
273                 highp vec2 dir = vec2(texCoord.s * w - w * center.x, texCoord.t * h - h * center.y);
274                 m[0] = vec2(angleCos, -angleSin);
275                 m[1] = vec2(angleSin, angleCos);
276                 dir *= m;
277
278                 m[0] = vec2(angleCosStep, -angleSinStep);
279                 m[1] = vec2(angleSinStep, angleCosStep);
280
281                 PLACEHOLDER_UNROLLED_LOOP
282
283                 gl_FragColor *= weight * qt_Opacity;
284             }
285         "
286
287         function buildFragmentShader() {
288             var shader = fragmentShaderSkeleton
289             var expandSteps = ""
290
291             if (transparentBorder) {
292                 expandSteps += "texCoord = (texCoord - expand) / (1.0 - 2.0 * expand);"
293             }
294
295             var unrolledLoop = "gl_FragColor += texture2D(source, texCoord);\n"
296
297             if (rootItem.samples > 1) {
298                  unrolledLoop = ""
299                  for (var i = 0; i < rootItem.samples; i++)
300                      unrolledLoop += "gl_FragColor += texture2D(source, center + dir * delta); dir *= m;\n"
301             }
302
303             shader = shader.replace("PLACEHOLDER_EXPAND_STEPS", expandSteps)
304             fragmentShader = shader.replace("PLACEHOLDER_UNROLLED_LOOP", unrolledLoop)
305         }
306
307         onFragmentShaderChanged: sourceChanged()
308         onSamplesChanged: buildFragmentShader()
309         onTransparentBorderChanged: buildFragmentShader()
310         Component.onCompleted: buildFragmentShader()
311     }
312 }