aboutsummaryrefslogtreecommitdiffstats
path: root/quickmultieffect/QuickMultiEffect.qml
blob: fee997e934cc0fdea061235c0caf6d6dfeb6f4b7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
/******************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt QuickMultiEffect module.
**
** $QT_BEGIN_LICENSE:GPL-MARKETPLACE-QT$
**
** Marketplace License Usage
** Users, who have licensed the Software under the Qt Marketplace license
** agreement, may use this file in accordance with the Qt Marketplace 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/#marketplace and
** https://www.qt.io/terms-conditions. For further information use the contact
** form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) 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.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-3.0.html.
**
** $QT_END_LICENSE$
**
******************************************************************************/

// GraphicsInfo was introduced in Qt 5.8 / QtQuick 2.8.
// Disabling that, this effect should work with any Qt 5 QtQuick version.
import QtQuick 2.8
import "private"

/*!
    \qmltype QuickMultiEffect
    \inqmlmodule "qrc:/quickmultieffect"
    \since QuickMultiEffect 1.0

    \brief Combination of multiple effects in a single item.

    QuickMultiEffect is an item for adding effects into Qt Quick user interface.
    Compared to Qt Graphical Effects module, QuickMultiEffect combines multiple
    effects (blur, shadow, colorize etc.) into a single item and shader. This
    improves performance in cases where more than one effect per item is desired.
    QuickMultiEffect is designed especially for animated effects, where
    performance is more important than absolute rendering quality.

    To use QuickMultiEffect in your own application UI, follow these steps:

    \list 1

    \li Copy the \c quickmultieffect directory into your project.

    \li In your .pro file, add
        \badcode
        include(quickmultieffect/include.pri)
        \endcode
        or do the equivalent with build systems other than qmake.

    \li Add
        \badcode
        import "qrc:/quickmultieffect"
        \endcode

       and
       \badcode
       QuickMultiEffect {
           ...
       }
       \endcode
       component into your QML file.

    \li Set the \l {QuickMultiEffect}'s \e source property to the item you want
        to add an effect to.

    \li Enable the QuickMultiEffect features you want to use. These are boolean
       properties with a suffix \e Enabled, e.g. blurEnabled, contrastEnabled,
       colorizeEnabled, and so on. To improve performance, shader code will only
       be generated for features that are enabled.

    \li Tweak and/or animate the effect properties. Note that some properties are
       meant to be set beforehand and not during the animations for optimal
       performance. These are documented with \b {Performance note} comments.
    \endlist
*/

Item {
    id: rootItem

    /*!
        \qmlproperty var QuickMultiEffect::source

        Source item for the effect. This does not need to be ShaderEffectSource
        or have \c {layer.enabled} set to \c true as QuickMultiEffect will
        internally generate a ShaderEffectSource as the texture source.
    */
    property var source

    /*!
        \qmlproperty bool QuickMultiEffect::hideSource

        When enabled, source item is hidden. This is often preferred when the
        effect item replaces the source item. Another option is to set the source
        item's \e visibility property to \c false, but that disables also user
        interaction (for example, events from a MouseArea). Therefore, hiding the
        the source item may be preferred.

        By default, this property is set to \c true.
    */
    property bool hideSource: true

    /*!
        \qmlproperty bool QuickMultiEffect::paddingEnabled

        When enabled, item size is padded with paddingRect or automatically
        based on blurMax. Setting this to \c false keeps the item size as original,
        if blur/shadow is not used or the item already contains enough padding to
        fit all required content after applying the effects.

        \include notes.qdocinc performance item size

        \include notes.qdocinc performance item resize
    */
    property bool paddingEnabled: true

    /*!
        \qmlproperty rect QuickMultiEffect::paddingRect

        Set this to increase item size manually so that blur and/or shadows will fit.
        If paddingEnabled is enabled and paddingRect is not set, the item is padded
        to fit maximally blurred item based on blurMax. When enabling the shadow,
        the padding rect typically needs to take shadowHorizontalOffset and
        shadowVerticalOffset into account.

        \include notes.qdocinc performance item size

        \include notes.qdocinc performance item resize
    */
    property rect paddingRect: Qt.rect(0, 0, 0, 0)

    /*!
        \qmlproperty bool QuickMultiEffect::brightnessEnabled

        Enables the brightness effect.

        \include notes.qdocinc performance shader regen
    */
    property bool brightnessEnabled: false

    /*!
        \qmlproperty real QuickMultiEffect::brightness

        This property defines how much the source brightness is increased or
        decreased.

        The value ranges from -1.0 to 1.0. By default, the property is set to \c
        0.0 (no change).
    */
    property real brightness: 0.0

    /*!
        \qmlproperty bool QuickMultiEffect::contrastEnabled

        Enables the contrast effect.

        \include notes.qdocinc performance shader regen
    */
    property bool contrastEnabled: false

    /*!
        \qmlproperty real QuickMultiEffect::contrast

        This property defines how much the source contrast is increased or
        decreased.

        The value ranges from -1.0 to 1.0. By default, the property is set to \c
        0.0 (no change).
    */
    property real contrast: 0.0

    /*!
        \qmlproperty bool QuickMultiEffect::saturationEnabled

        Enables the saturation effect.

        \include notes.qdocinc performance shader regen
    */
    property bool saturationEnabled: false

    /*!
        \qmlproperty real QuickMultiEffect::saturation

        This property defines how much the source saturation is increased or
        decreased.

        The value ranges from -1.0 (totally desaturated) to inf. By default,
        the property is set to \c 0.0 (no change).
    */
    property real saturation: 0.0

    /*!
        \qmlproperty bool QuickMultiEffect::colorizeEnabled

        Enables the colorize effect.

        \include notes.qdocinc performance shader regen
    */
    property bool colorizeEnabled: false

    /*!
        \qmlproperty real QuickMultiEffect::colorize

        This property defines how much the source is colorized with the
        colorizeColor.

        The value ranges from 0.0 (no colorized) to 1.0 (fully colorized).
        By default, the property is set to \c 0.0 (no change).
    */
    property real colorize: 0.0

    /*!
        \qmlproperty color QuickMultiEffect::colorizeColor

        This property defines the RGBA color value which is used to
        colorize the source.

        By default, the property is set to \c  Qt.rgba(1.0, 0.0, 0.0, 1.0) (red).
    */
    property color colorizeColor: Qt.rgba(1.0, 0.0, 0.0, 1.0)

    /*!
        \qmlproperty bool QuickMultiEffect::blurEnabled

        Enables the blur effect.

        \include notes.qdocinc performance shader regen
    */
    property bool blurEnabled: false

    /*!
        \qmlproperty real QuickMultiEffect::blur

        This property defines how much blur (radius) is applied to the source.

        The value ranges from 0.0 (no blur) to 1.0 (full blur). By default,
        the property is set to \c 0.0 (no change). The amount of full blur
        is affected by blurMax and blurMultiplier.

        \b {Performance note:} If you don't need to go close to 1.0 at any point
        of blur animations, consider reducing blurMax or blurMultiplier for
        optimal performance.
    */
    property real blur: 0.0

    /*!
        \qmlproperty int QuickMultiEffect::blurMax

        This property defines the maximum pixel radius that blur with value
        1.0 will reach.

        Meaningful range of this value is from 2 (subtle blur) to 64 (high
        blur). By default, the property is set to \c 32. For the most optimal
        performance, select as small value as you need.

        \note This affects to both blur and shadow effects.

        \include notes.qdocinc performance shader regen

        \include notes.qdocinc performance item resize
    */
    property int blurMax: 32

    /*!
        \qmlproperty real QuickMultiEffect::blurMultiplier

        This property defines a multiplier for extending the blur radius.

        The value ranges from 0.0 (not multiplied) to inf. By default,
        the property is set to \c 0.0. Increasing the multiplier extends the
        blur radius, but decreases the blur quality. This is more performant
        option for a bigger blur radius than blurMax as it doesn't increase
        the amount of texture lookups.

        \note This affects to both blur and shadow effects.

        \include notes.qdocinc performance item resize
    */
    property real blurMultiplier: 0.0

    /*!
        \qmlproperty bool QuickMultiEffect::shadowEnabled

        Enables the shadow effect.

        \include notes.qdocinc performance shader regen
    */
    property bool shadowEnabled: false

    /*!
        \qmlproperty real QuickMultiEffect::shadowOpacity

        This property defines the opacity of the drop shadow.

        The value ranges from 0.0 (fully transparent) to 1.0 (fully opaque).
        By default, the property is set to \c 1.0.
    */
    property real shadowOpacity: 1.0

    /*!
        \qmlproperty real QuickMultiEffect::shadowBlur

        This property defines how much blur (radius) is applied to the shadow.

        The value ranges from 0.0 (no blur) to 1.0 (full blur). By default,
        the property is set to \c 1.0. The amount of full blur
        is affected by blurMax and blurMultiplier.

        \b {Performance note:} The most optimal way to reduce shadow blurring is
        to make blurMax smaller (if it isn't needed for item blur). Just remember
        to not adjust blurMax during animations.
    */
    property real shadowBlur: 1.0

    /*!
        \qmlproperty real QuickMultiEffect::shadowHorizontalOffset

        This property defines the horizontal offset of the shadow from the
        item center.

        The value ranges from -inf to inf. By default, the property is set
        to \c 10.0.

        \note When moving shadow position away from center and adding
        shadowBlur, you possibly also need to increase the paddingRect
        accordingly if you want the shadow to not be clipped.
    */
    property real shadowHorizontalOffset: 10

    /*!
        \qmlproperty real QuickMultiEffect::shadowVerticalOffset

        This property defines the vertical offset of the shadow from the
        item center.

        The value ranges from -inf to inf. By default,
        the property is set to \c 10.0.

        \note When moving shadow position away from center and adding
        shadowBlur, you possibly also need to increase the paddingRect
        accordingly if you want the shadow to not be clipped.
    */
    property real shadowVerticalOffset: 10

    /*!
        \qmlproperty color QuickMultiEffect::shadowColor

        This property defines the RGBA color value which is used to colorize
        the shadow. It is useful for example when a shadow is used for
        simulating a glow effect.

        By default, the property is set to \c {Qt.rgba(0.0, 0.0, 0.0, 1.0)}
        (black).
    */
    property color shadowColor: Qt.rgba(0.0, 0.0, 0.0, 1.0)

    /*!
        \qmlproperty real QuickMultiEffect::shadowScale

        This property defines the scale of the shadow. Scaling is applied from
        the center of the item.

        The value ranges from 0 to inf. By default, the property is set to
        \c 1.0.

        \note When increasing the shadowScale, you possibly also need to
        increase the paddingRect accordingly to avoid the shadow from being
        clipped.
    */
    property real shadowScale: 1.0

    /*!
        \qmlproperty bool QuickMultiEffect::maskEnabled

        Enables the mask effect.

        \include notes.qdocinc performance shader regen
    */
    property bool maskEnabled: false

    /*!
        \qmlproperty var QuickMultiEffect::maskSource

        Source item for the mask effect. Should point to ShaderEffectSource,
        item with \c {layer.enabled} set to \c true, or to an item that can be
        directly used as a texture source (e.g. \l [QML] Image). The alpha
        channel of the source item is used for masking.
    */
    property var maskSource

    /*!
        \qmlproperty real QuickMultiEffect::maskThresholdLow

        This property defines a lower threshold value for the mask pixels.
        The mask pixels that have an alpha value below this property are used
        to completely mask away the corresponding pixels from the source item.
        The mask pixels that have a higher alpha value are used to alphablend
        the source item to the display.

        The value ranges from 0.0 (alpha value 0) to 1.0 (alpha value 255). By
        default, the property is set to \c 0.0.
    */
    property real maskThresholdLow: 0.0

    /*!
        \qmlproperty real QuickMultiEffect::maskSpreadLow

        This property defines the smoothness of the mask edges near the
        maskThresholdLow. Setting higher spread values softens the transition
        from the transparent mask pixels towards opaque mask pixels by adding
        interpolated values between them.

        The value ranges from 0.0 (sharp mask edge) to 1.0 (smooth mask edge).
        By default, the property is set to \c 0.0.
    */
    property real maskSpreadLow: 0.0

    /*!
        \qmlproperty real QuickMultiEffect::maskThresholdUp

        This property defines an upper threshold value for the mask pixels.
        The mask pixels that have an alpha value below this property are used
        to completely mask away the corresponding pixels from the source item.
        The mask pixels that have a higher alpha value are used to alphablend
        the source item to the display.

        The value ranges from 0.0 (alpha value 0) to 1.0 (alpha value 255). By
        default, the property is set to \c 1.0.
    */
    property real maskThresholdUp: 1.0

    /*!
        \qmlproperty real QuickMultiEffect::maskSpreadUp

        This property defines the smoothness of the mask edges near the
        maskThresholdUp. Using higher spread values softens the transition
        from the transparent mask pixels towards opaque mask pixels by adding
        interpolated values between them.

        The value ranges from 0.0 (sharp mask edge) to 1.0 (smooth mask edge).
        By default, the property is set to \c 0.0.
    */
    property real maskSpreadUp: 0.0

    /*!
        \qmlproperty bool QuickMultiEffect::maskInverted

        This property switches the mask to the opposite side; instead of
        masking away the content outside maskThresholdLow and maskThresholdUp,
        content between them will get masked away.

        By default, the property is set to \c false.

        \include notes.qdocinc performance shader regen
    */
    property bool maskInverted: false


    // Read-only access to generated shaders. These can be used for
    // optimization, to debug the behavior and optionally implement custom
    // shader versions.
    readonly property alias fragmentShaderString: shaderItem.fragmentShader
    readonly property alias vertexShaderString: shaderItem.vertexShader

    // Read-only access to effect item rectangle. This can be used e.g. to see
    // the area item covers.
    readonly property rect itemRect: Qt.rect(shaderItem.x, shaderItem.y, shaderItem.width, shaderItem.height)


    QtObject {
        id: priv
        // *** internal properties ***

        // This increases the size of rendered item so blurred content fits there
        readonly property int itemPadding: paddingEnabled && priv.blurItemsNeeded ? blurMax * (1.2 + blurMultiplier) : 0

        readonly property rect paddingRect: paddingEnabled ? rootItem.paddingRect : Qt.rect(0, 0, 0, 0)
        // Controls if blurItems need to be generated
        readonly property bool blurItemsNeeded: (blurEnabled || shadowEnabled)
        readonly property bool generateBlurItems: rootItem.visible && priv.blurItemsNeeded
        property string shaderVersions: shaderItem._isCore ? "core" : "compatibility"
        property string blurItemsVertexShader: "qrc:/quickmultieffect/shaders/" + priv.shaderVersions + "/bluritems.vert"
        property string blurItemsFragmentShader: "qrc:/quickmultieffect/shaders/" + priv.shaderVersions + "/bluritems.frag"

        readonly property bool useBlurItem1: generateBlurItems
        readonly property bool useBlurItem2: generateBlurItems
        readonly property bool useBlurItem3: generateBlurItems && (rootItem.blurMax > 2)
        readonly property bool useBlurItem4: generateBlurItems && (rootItem.blurMax > 8)
        readonly property bool useBlurItem5: generateBlurItems && (rootItem.blurMax > 16)
        readonly property bool useBlurItem6: generateBlurItems && (rootItem.blurMax > 32)
    }

    signal shaderGeneratedSignal
    signal sizeChangedSignal

    // Initial size
    implicitWidth: source ? source.width : 100
    implicitHeight: source ? source.height : 100

    function blurWeight(v) {
        return Math.max(0.0, Math.min(1.0, 1.0 - v * 2.0));
    }

    function calculateBlurWeights(blurLod) {
        var bw1 = blurWeight(Math.abs(blurLod - 0.1));
        var bw2 = blurWeight(Math.abs(blurLod - 0.3));
        var bw3 = blurWeight(Math.abs(blurLod - 0.5));
        var bw4 = blurWeight(Math.abs(blurLod - 0.7));
        var bw5 = blurWeight(Math.abs(blurLod - 0.9));
        var bw6 = blurWeight(Math.abs(blurLod - 1.1));

        var bsum = bw1 + bw2 + bw3 + bw4 + bw5 + bw6;
        shaderItem.blurWeight1 = Qt.vector4d(bw1 / bsum, bw2 / bsum, bw3 / bsum, bw4 / bsum);
        shaderItem.blurWeight2 = Qt.vector2d(bw5 / bsum, bw6 / bsum);
    }

    function calculateShadowWeights(shadowLod) {
        var sw1 = blurWeight(Math.abs(shadowLod - 0.1));
        var sw2 = blurWeight(Math.abs(shadowLod - 0.3));
        var sw3 = blurWeight(Math.abs(shadowLod - 0.5));
        var sw4 = blurWeight(Math.abs(shadowLod - 0.7));
        var sw5 = blurWeight(Math.abs(shadowLod - 0.9));
        var sw6 = blurWeight(Math.abs(shadowLod - 1.1));

        var ssum = sw1 + sw2 + sw3 + sw4 + sw5 + sw6;
        shaderItem.shadowBlurWeight1 = Qt.vector4d(sw1 / ssum, sw2 / ssum, sw3 / ssum, sw4 / ssum);
        shaderItem.shadowBlurWeight2 = Qt.vector2d(sw5 / ssum, sw6 / ssum);
    }

    // Main source item.
    ShaderEffectSource {
        id: itemSource
        readonly property real baseWidth: sourceItem ? sourceItem.width : 1
        readonly property real baseHeight: sourceItem ? sourceItem.height : 1
        // Adjusted paddings for possible item vs. source size difference
        property real _widthMultiplier: baseWidth / rootItem.width
        property real _heightMultiplier: baseHeight / rootItem.height
        property real _xPadding: priv.itemPadding * _widthMultiplier
        property real _yPadding: priv.itemPadding * _heightMultiplier
        property rect _rect: Qt.rect(priv.paddingRect.x * _widthMultiplier,
                                     priv.paddingRect.y * _heightMultiplier,
                                     priv.paddingRect.width * _widthMultiplier,
                                     priv.paddingRect.height * _heightMultiplier)
        sourceItem: rootItem.visible ? rootItem.source : null
        width: shaderItem.width
        height: shaderItem.height
        sourceRect: Qt.rect(-_rect.x - _xPadding,
                            -_rect.y - _yPadding,
                            baseWidth + _rect.x + _rect.width + _xPadding * 2,
                            baseHeight + _rect.y + _rect.height + _yPadding * 2)
        visible: false
        hideSource: rootItem.hideSource
        smooth: true
    }

    ShaderEffectSource {
        id: blurredItemSource1
        width: Math.ceil(shaderItem.width / 64) * 64
        height: Math.ceil(shaderItem.height / 64) * 64
        sourceItem: priv.useBlurItem1 ? itemSource : null
        hideSource: rootItem.visible
        visible: false
        smooth: true
    }

    BlurItem {
        id: blurredItem1
        property var source: blurredItemSource1
        anchors.fill: blurredItemSource2
    }

    ShaderEffectSource {
        id: blurredItemSource2
        width: blurredItemSource1.width / 2
        height: blurredItemSource1.height / 2
        sourceItem: priv.useBlurItem2 ? blurredItem1 : null
        hideSource: rootItem.visible
        visible: false
        smooth: true
    }

    BlurItem {
        id: blurredItem2
        property var source: blurredItemSource2
        anchors.fill: blurredItemSource3
    }

    ShaderEffectSource {
        id: blurredItemSource3
        width: blurredItemSource2.width / 2
        height: blurredItemSource2.height / 2
        sourceItem: priv.useBlurItem3 ? blurredItem2 : null
        hideSource: rootItem.visible
        visible: false
        smooth: true
    }

    BlurItem {
        id: blurredItem3
        property var source: blurredItemSource3
        anchors.fill: blurredItemSource4
    }

    ShaderEffectSource {
        id: blurredItemSource4
        width: blurredItemSource3.width / 2
        height: blurredItemSource3.height / 2
        sourceItem: priv.useBlurItem4 ? blurredItem3 : null
        hideSource: rootItem.visible
        visible: false
        smooth: true
    }

    BlurItem {
        id: blurredItem4
        property var source: blurredItemSource4
        anchors.fill: blurredItemSource5
    }

    ShaderEffectSource {
        id: blurredItemSource5
        width: blurredItemSource4.width / 2
        height: blurredItemSource4.height / 2
        sourceItem: priv.useBlurItem5 ? blurredItem4 : null
        hideSource: rootItem.visible
        visible: false
        smooth: true
    }

    BlurItem {
        id: blurredItem5
        property var source: blurredItemSource5
        anchors.fill: blurredItemSource6
    }

    ShaderEffectSource {
        id: blurredItemSource6
        width: blurredItemSource5.width / 2
        height: blurredItemSource5.height / 2
        sourceItem: priv.useBlurItem6 ? blurredItem5 : null
        hideSource: rootItem.visible
        visible: false
        smooth: true
    }

    ShaderEffect {
        id: shaderItem
        property var src: itemSource
        property alias contrast: rootItem.contrast
        property alias brightness: rootItem.brightness
        property alias saturation: rootItem.saturation
        // Packing some properties into vec4 & vec2 for shader optimization
        property vector4d colorizeColor: Qt.vector4d(rootItem.colorizeColor.r, rootItem.colorizeColor.g,
                                                     rootItem.colorizeColor.b, rootItem.colorize)
        property alias maskSrc: rootItem.maskSource
        property vector4d mask: Qt.vector4d(rootItem.maskThresholdLow, 1.0 + rootItem.maskSpreadLow,
                                            rootItem.maskThresholdUp, 1.0 + rootItem.maskSpreadUp)
        property vector4d shadowColor: Qt.vector4d(rootItem.shadowColor.r, rootItem.shadowColor.g,
                                                   rootItem.shadowColor.b, rootItem.shadowOpacity)
        property real shadowScale: 1.0 / rootItem.shadowScale
        property vector2d shadowOffset: Qt.vector2d(rootItem.shadowHorizontalOffset / width,
                                                    rootItem.shadowVerticalOffset / height)
        // This points to center offset of item without paddings, scaled with shadowScale.
        property vector2d centerOffset: Qt.vector2d((1.0 - shadowScale) * (0.5 + 0.5 * (priv.paddingRect.x - priv.paddingRect.width) / width),
                                                    (1.0 - shadowScale) * (0.5 + 0.5 * (priv.paddingRect.y - priv.paddingRect.height) / height))

        property var blurSrc1: blurredItemSource1
        property var blurSrc2: blurredItemSource2
        property var blurSrc3: blurredItemSource3
        property var blurSrc4: blurredItemSource4
        property var blurSrc5: blurredItemSource5
        property var blurSrc6: blurredItemSource6

        // Pack blurring weights into vec4 & vec2 for shader optimization
        property vector4d blurWeight1
        property vector2d blurWeight2
        property vector4d shadowBlurWeight1
        property vector2d shadowBlurWeight2

        function calculateLod(blurAmount) {
            return Math.sqrt(blurAmount * rootItem.blurMax / 64.0) * 1.2 - 0.2;
        }

        readonly property real blurLod: calculateLod(rootItem.blur)
        readonly property real shadowLod: calculateLod(rootItem.shadowBlur)

        x: -priv.paddingRect.x - priv.itemPadding
        y: -priv.paddingRect.y - priv.itemPadding
        width: parent.width + priv.paddingRect.x + priv.paddingRect.width + (priv.itemPadding * 2)
        height: parent.height + priv.paddingRect.y + priv.paddingRect.height + (priv.itemPadding * 2)

        onWidthChanged: sizeChangedTimer.start();
        onHeightChanged: sizeChangedTimer.start();

        Timer {
            id: sizeChangedTimer
            interval: 0
            onTriggered: {
                if (rootItem.visible) {
                    console.debug("QuickMultiEffect: Item resized.");
                    rootItem.sizeChangedSignal();
                }
            }
        }

        // *** private ***
        // Shader generation supports both compatibility (#version 150) and
        // core (#version 330) GLSL versions
        readonly property bool _isCore: GraphicsInfo.profile === GraphicsInfo.OpenGLCoreProfile
        readonly property string _attribute: _isCore ? "in " : "attribute "
        // Vertex "varying"
        readonly property string _vvarying: _isCore ? "out " : "varying "
        // Fragment "varying"
        readonly property string _fvarying: _isCore ? "in " : "varying "
        readonly property string _texture: _isCore ? "texture" : "texture2D"
        readonly property string _fragColor: _isCore ? "fragColor" : "gl_FragColor"

        readonly property string vShader:
        {
            var s = "";
            if (_isCore) {
                s += "#version 330 core\n";
            }

            s += "uniform highp mat4 qt_Matrix;\n";
            if (rootItem.shadowEnabled) {
                s += "uniform lowp float shadowScale;
uniform lowp vec2 shadowOffset;
uniform lowp vec2 centerOffset;\n";
                s += _vvarying + "highp vec2 shadowTexCoord;\n";
            }

            s += _attribute + "highp vec4 qt_Vertex;\n";
            s += _attribute + "highp vec2 qt_MultiTexCoord0;\n";
            s += _vvarying + "highp vec2 texCoord;\n";

            s += "void main() {
    texCoord = qt_MultiTexCoord0;\n";
            if (rootItem.shadowEnabled) {
                s += "    shadowTexCoord = qt_MultiTexCoord0 - shadowOffset;\n";
                s += "    shadowTexCoord = (shadowTexCoord * shadowScale) + centerOffset;\n";
            }
            s += "    gl_Position = qt_Matrix * qt_Vertex;
}";
            return s;
        }

        readonly property string fShader:
        {
            var s = "";

            if (_isCore) {
                s += "#version 330 core\nout vec4 fragColor;\n";
            }

            s += _fvarying + "highp vec2 texCoord;
uniform sampler2D src;
uniform lowp float qt_Opacity;\n";
            if (rootItem.contrastEnabled)
                s += "uniform lowp float contrast;\n";
            if (rootItem.brightnessEnabled)
                s += "uniform lowp float brightness;\n";
            if (rootItem.saturationEnabled)
                s += "uniform lowp float saturation;\n";
            if (rootItem.colorizeEnabled)
                s += "uniform lowp vec4 colorizeColor;\n";
            if (priv.useBlurItem1)
                s += "uniform sampler2D blurSrc1;\n";
            if (priv.useBlurItem2)
                s += "uniform sampler2D blurSrc2;\n";
            if (priv.useBlurItem3)
                s += "uniform sampler2D blurSrc3;\n";
            if (priv.useBlurItem4)
                s += "uniform sampler2D blurSrc4;\n";
            if (priv.useBlurItem5)
                s += "uniform sampler2D blurSrc5;\n";
            if (priv.useBlurItem6)
                s += "uniform sampler2D blurSrc6;\n";

            if (rootItem.blurEnabled) {
                s += "uniform lowp vec4 blurWeight1;\n";
                if (priv.useBlurItem5)
                    s += "uniform lowp vec2 blurWeight2;\n"
            }
            if (rootItem.maskEnabled) {
                s += "uniform sampler2D maskSrc;\nuniform lowp vec4 mask;\n";
            }
            if (rootItem.shadowEnabled) {
                s += _fvarying + "highp vec2 shadowTexCoord;
uniform lowp vec4 shadowColor;\n";
                s += "uniform lowp vec4 shadowBlurWeight1;\n";
                if (priv.useBlurItem5)
                    s += "uniform lowp vec2 shadowBlurWeight2;\n";
            }

            s += "void main() {
    highp vec4 color;\n";

            // Blur
            if (rootItem.blurEnabled) {
                s += "    color = " + _texture + "(blurSrc1, texCoord) * blurWeight1[0];
    color += " + _texture + "(blurSrc2, texCoord) * blurWeight1[1];\n";
                if (priv.useBlurItem3)
                    s += "    color += " + _texture + "(blurSrc3, texCoord) * blurWeight1[2];\n";
                if (priv.useBlurItem4)
                    s += "    color += " + _texture + "(blurSrc4, texCoord) * blurWeight1[3];\n";
                if (priv.useBlurItem5)
                    s += "    color += " + _texture + "(blurSrc5, texCoord) * blurWeight2[0];\n";
                if (priv.useBlurItem6)
                    s += "    color += " + _texture + "(blurSrc6, texCoord) * blurWeight2[1];\n";
            } else {
                s += "    color = " + _texture + "(src, texCoord);\n";
            }

            // Contrast
            if (rootItem.contrastEnabled) {
                s += "    color.rgb = (color.rgb - 0.5 * color.a) * (1.0 + contrast) + 0.5 * color.a;\n";
            }
            // Brightness
            if (rootItem.brightnessEnabled) {
                s += "    color.rgb += brightness * color.a;\n";
            }

            if (rootItem.saturationEnabled || rootItem.colorizeEnabled) {
                // Calculate gray only once for features that need it
                s += "    lowp float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));\n";
            }

            // Colorize
            if (rootItem.colorizeEnabled) {
                s += "    color.rgb = gray * colorizeColor.rgb * colorizeColor.a + color.rgb * (1.0 - colorizeColor.a);\n";
            }

            // Saturation
            if (rootItem.saturationEnabled) {
                s += "    color.rgb = mix(vec3(gray), color.rgb, 1.0 + saturation);\n";
            }

            // Shadow
            if (rootItem.shadowEnabled) {
                s += "    lowp float shadow = 0.0;
    shadow = " + _texture + "(blurSrc1, shadowTexCoord).a * shadowBlurWeight1[0];
    shadow += " + _texture + "(blurSrc2, shadowTexCoord).a * shadowBlurWeight1[1];\n";
                if (priv.useBlurItem3)
                    s += "    shadow += " + _texture + "(blurSrc3, shadowTexCoord).a * shadowBlurWeight1[2];\n";
                if (priv.useBlurItem4)
                    s += "    shadow += " + _texture + "(blurSrc4, shadowTexCoord).a * shadowBlurWeight1[3];\n";
                if (priv.useBlurItem5)
                    s += "    shadow += " + _texture + "(blurSrc5, shadowTexCoord).a * shadowBlurWeight2[0];\n";
                if (priv.useBlurItem6)
                    s += "    shadow += " + _texture + "(blurSrc6, shadowTexCoord).a * shadowBlurWeight2[1];\n";
                s += "    shadow *= shadowColor.a;
    lowp float aa = (1.0 - color.a) * (1.0 - shadow);
    color.rgb = mix(shadowColor.rgb * shadow, color.rgb, color.a + aa);
    color.a = 1.0 - aa;\n";
            }

            // Mask
            if (rootItem.maskEnabled) {
                s += "    lowp float alphaMask = " + _texture + "(maskSrc, texCoord).a;
    lowp float m1 = smoothstep(mask[0] * mask[1] - (mask[1] - 0.999), mask[0] * mask[1], alphaMask);
    lowp float m2 = smoothstep((1.0 - mask[2]) * mask[3] - (mask[3] - 0.999), (1.0 - mask[2]) * mask[3], (1.0 - alphaMask));\n";
                if (rootItem.maskInverted)
                    s += "    color *= (1.0 - m1 * m2);\n";
                else
                    s += "    color *= m1 * m2;\n";
            }

            s += "    " + _fragColor + " = color * qt_Opacity;\n";
            s += "}";
            return s;
        }


        fragmentShader: fShader
        vertexShader: vShader

        onStatusChanged: {
            if (rootItem.visible && status == ShaderEffect.Compiled) {
                console.debug("QuickMultiEffect: Shader generated.");
                rootItem.shaderGeneratedSignal();
            }
        }

        Component.onCompleted: {
            calculateBlurWeights(blurLod);
            calculateShadowWeights(shadowLod);
        }
        onBlurLodChanged: {
            calculateBlurWeights(blurLod);
        }
        onShadowLodChanged: {
            calculateShadowWeights(shadowLod);
        }
    }
}