Revision control
++ // Copyright (C) 2022 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qtypes.h"
++ #include <QtQml/qqmlcomponent.h>
++ #include <private/qquickmultieffect_p_p.h>
++ #include <private/qquickshadereffect_p.h>
++ #include <private/qquickshadereffectsource_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_STATIC_LOGGING_CATEGORY(lcQuickEffect, "qt.quick.effects")
++
++ /*!
++ \keyword Qt Quick Effects
++ \qmlmodule QtQuick.Effects
++ \title Qt Quick Effects QML Types
++ \ingroup qmlmodules
++ \brief Provides QML types for applying one or more simple graphical effects to Qt Quick items.
++
++ To use the types in this module, import the module with the following line:
++
++ \qml
++ import QtQuick.Effects
++ \endqml
++ */
++
++ /*!
++ \qmltype MultiEffect
++ \inqmlmodule QtQuick.Effects
++ \inherits Item
++ \ingroup qtquick-effects
++ \brief Applies post-processing effect to an item.
++
++ The MultiEffect type, the successor to the \l{Qt 5 Compatibility APIs: Graphical Effects}{Qt Graphical Effects}
++ from Qt 5, applies a post-processing effect to the \l source item. In contrast to the
++ \l{Qt 5 Compatibility APIs: Graphical Effects}{Qt Graphical Effects} module, MultiEffect makes
++ it possible to combine multiple effects (blur, shadow, colorization etc.) into a single item and
++ shader. You can use \l{MultiEffect} if you only need a single effect at no additional overhead,
++ but you can also use it to apply multiple effects to an item without the cost of multiple render
++ passes.
++
++ MultiEffect is designed specifically for most common effects and can be easily animated.
++ If the MultiEffect doesn't contain the effect you need, consider implementing a custom
++ effect using \l {Qt Quick Effect Maker}. For more information about shader effects,
++ see the \l ShaderEffect reference documentation.
++
++ Note that MultiEffect type renders a new visual item alongside the source
++ item. To apply the effect to the source item, you need to place the new
++ MultiEffect item at the position of the source item. If the source item and
++ the MultiEffect item are not opaque, both the items can be visible, thus you
++ may not get the desired effect. To hide the source item, do any of
++ these:
++
++ \list
++ \li Set \c{visible: false} for the source item. In this case, the source
++ item is not rendered at all and cannot receive touch or click input.
++ \li Set \c{opacity: 0} for the source item. In this case, the source
++ item is completely transparent, but still can receive touch or click
++ input.
++ \endlist
++
++ \section1 Example Usage
++
++ The following example shows how to apply a saturation effect to an item:
++
++ \table 70%
++ \row
++ \li \image multieffect-example1.png
++ {Qt logo in grayscale showing full desaturation effect}
++ \li \qml
++ import QtQuick
++ import QtQuick.Effects
++
++ ...
++ Image {
++ id: sourceItem
++ source: "qt_logo_green_rgb.png"
++ // Hide the source item, otherwise both the source item and
++ // MultiEffect will be rendered
++ visible: false
++ // or you can set:
++ // opacity: 0
++ }
++ // Renders a new item with the specified effects rendered
++ // at the same position where the source item was rendered
++ MultiEffect {
++ source: sourceItem
++ anchors.fill: sourceItem
++ saturation: -1.0
++ }
++ \endqml
++ \endtable
++
++ The following example shows how to apply a saturation effect to
++ a \l [QML]{Item#Item Layers}{layered Item}:
++
++ \table 70%
++ \row
++ \li \image multieffect-example1.png
++ {Qt logo in grayscale showing full desaturation effect}
++ \li \qml
++ import QtQuick
++ import QtQuick.Effects
++
++ ...
++ Image {
++ id: sourceItem
++ source: "qt_logo_green_rgb.png"
++ layer.enabled: true
++ // For the layered items, you can assign a MultiEffect directly
++ // to layer.effect.
++ layer.effect: MultiEffect {
++ saturation: -1.0
++ }
++ }
++ \endqml
++ \endtable
++
++ The following example shows how to apply multiple effects at the same time:
++
++ \table 70%
++ \row
++ \li \image multieffect-example2.png
++ {Qt logo with green glow showing brightness, saturation, and
++ blur effects combined}
++ \li \qml
++ import QtQuick
++ import QtQuick.Effects
++
++ ...
++ MultiEffect {
++ source: sourceItem
++ anchors.fill: sourceItem
++ brightness: 0.4
++ saturation: 0.2
++ blurEnabled: true
++ blurMax: 64
++ blur: 1.0
++ }
++ \endqml
++ \endtable
++
++ Below is an example of how to use the mask, colorization and brightness effects together to
++ fade away an element. This kind of hiding/showing of elements can be, for example, bind
++ to a slider value or animations like NumberAnimation. Note how the \c visible property is
++ false when the item is totally faded away, to avoid unnecessary rendering of the effect.
++
++ \table 70%
++ \row
++ \li \image multieffect-example3.png
++ {Four stages of Qt logo fading with mask and colorization,
++ from visible green to dissolved pink particles}
++ \li \qml
++ import QtQuick
++ import QtQuick.Effects
++ import QtQuick.Controls.Material
++
++ ...
++ MultiEffect {
++ property real effectAmount: effectSlider.value
++ source: sourceItem
++ anchors.fill: sourceItem
++ brightness: effectAmount
++ colorizationColor: "#ff20d0"
++ colorization: effectAmount
++ maskEnabled: true
++ maskSource: Image {
++ source: "mask.png"
++ }
++ maskSpreadAtMin: 0.2
++ maskThresholdMin: effectAmount
++ visible: effectAmount < 1.0
++ }
++ Slider {
++ id: effectSlider
++ anchors.bottom: parent.bottom
++ anchors.horizontalCenter: parent.horizontalCenter
++ }
++ \endqml
++ \endtable
++
++ \section1 Performance
++ There are a few things to consider for optimal performance:
++ \list
++ \li To get the most optimal shader, enable only the effects which you actually use
++ (see \l blurEnabled, \l shadowEnabled, \l maskEnabled). Simple color effects (\l brightness,
++ \l contrast, \l saturation, \l colorization) are always enabled, so using them doesn't
++ add extra overhead.
++ \li See the \b {Performance notes} of the properties which may change the shader or the effect
++ item size and don't modify these during animations.
++ \li When the MultiEffect isn't used, remember to set its \c visible property to be false to
++ avoid rendering the effects in the background.
++ \li Blur and shadow are the heaviest effects. With these, prefer increasing \l blurMultiplier
++ over \l blurMax and avoid using \l source items which animate, so blurring doesn't need
++ to be regenerated in every frame.
++ \li Apply effects to optimally sized QML elements as more pixels means more work for
++ the GPU. When applying the blur effect to the whole background, remember to set
++ \l autoPaddingEnabled false or the effect grows "outside" the window / screen.
++ \endlist
++
++ \include notes.qdocinc shadereffectsource and multieffect
++ */
++
++ /*!
++ \qmlsignal QtQuick.Effects::MultiEffect::shaderChanged()
++
++ This signal is emitted when the used shader changes.
++ \sa fragmentShader, vertexShader
++ */
++
++ QQuickMultiEffect::QQuickMultiEffect(QQuickItem *parent)
++ : QQuickItem(*new QQuickMultiEffectPrivate, parent)
++ {
++ setFlag(ItemHasContents);
++ }
++
++ QQuickMultiEffect::~QQuickMultiEffect()
++ {
++ Q_D(QQuickMultiEffect);
++
++ // Disconnect the change listeners before we destroy the base types. Destroying the base types
++ // may still trigger changes we're listening to. The private object is only destroyed by the
++ // QObject dtor.
++
++ if (d->m_sourceItem) {
++ QQuickItemPrivate::get(d->m_sourceItem)
++ ->removeItemChangeListener(d, QQuickItemPrivate::ChangeType::Destroyed);
++ }
++ if (d->m_maskSourceItem) {
++ QQuickItemPrivate::get(d->m_maskSourceItem)
++ ->removeItemChangeListener(d, QQuickItemPrivate::ChangeType::Destroyed);
++ }
++ }
++
++ /*!
++ \qmlproperty Item QtQuick.Effects::MultiEffect::source
++
++ This property holds the item to be used as a source for the effect.
++ If needed, MultiEffect will internally generate a \l ShaderEffectSource
++ as the texture source.
++
++ \note It is not supported to let the effect include itself, for instance
++ by setting source to the effect's parent.
++
++ \note If the source item has \l {QtQuick::Item::} {layer.enabled} set to true,
++ it will be used directly. This is good for the performance and often desired, when the source
++ is hidden. But if the source remains visible and the effect adds padding (autoPaddingEnabled,
++ paddingRect), that padding can affect the appearance of the source item.
++
++ \sa hasProxySource
++ */
++ QQuickItem *QQuickMultiEffect::source() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->source();
++ }
++
++ void QQuickMultiEffect::setSource(QQuickItem *item)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setSource(item);
++ }
++
++ /*!
++ \qmlproperty bool QtQuick.Effects::MultiEffect::autoPaddingEnabled
++
++ When blur or shadow effects are enabled and this is set to true (default),
++ the item size is padded automatically based on blurMax and blurMultiplier.
++ Note that \l paddingRect is always added to the size.
++
++ \image multieffect-example4.png
++ {Blurred images comparing autoPaddingEnabled true with extra
++ space versus false with clipped edges}
++
++ \sa paddingRect
++
++ \include notes.qdocinc performance item size
++
++ \include notes.qdocinc performance item resize
++ */
++ bool QQuickMultiEffect::autoPaddingEnabled() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->autoPaddingEnabled();
++ }
++
++ void QQuickMultiEffect::setAutoPaddingEnabled(bool enabled)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setAutoPaddingEnabled(enabled);
++ }
++
++ /*!
++ \qmlproperty rect QtQuick.Effects::MultiEffect::paddingRect
++
++ Set this to increase item size manually so that blur and/or shadows will fit.
++ If autoPaddingEnabled is true and paddingRect is not set, the item is padded
++ to fit maximally blurred item based on blurMax and blurMultiplier. When
++ enabling the shadow, you typically need to take \l shadowHorizontalOffset and
++ \l shadowVerticalOffset into account and adjust this paddingRect accordingly.
++
++ Below is an example of adjusting paddingRect with autoPaddingEnabled set to
++ false so that the shadow fits inside the MultiEffect item.
++
++ \image multieffect-example5.png
++ {Two Qt logos with shadows comparing paddingRect values, showing
++ clipped shadow at zero versus full shadow with padding}
++
++ \sa autoPaddingEnabled
++
++ \include notes.qdocinc performance item size
++
++ \include notes.qdocinc performance item resize
++ */
++ QRectF QQuickMultiEffect::paddingRect() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->paddingRect();
++ }
++
++ void QQuickMultiEffect::setPaddingRect(const QRectF &rect)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setPaddingRect(rect);
++ }
++
++ /*!
++ \qmlproperty real QtQuick.Effects::MultiEffect::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).
++ */
++ qreal QQuickMultiEffect::brightness() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->brightness();
++ }
++
++ void QQuickMultiEffect::setBrightness(qreal brightness)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setBrightness(brightness);
++ }
++
++ /*!
++ \qmlproperty real QtQuick.Effects::MultiEffect::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).
++ */
++ qreal QQuickMultiEffect::contrast() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->contrast();
++ }
++
++ void QQuickMultiEffect::setContrast(qreal contrast)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setContrast(contrast);
++ }
++
++ /*!
++ \qmlproperty real QtQuick.Effects::MultiEffect::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).
++ */
++ qreal QQuickMultiEffect::saturation() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->saturation();
++ }
++
++ void QQuickMultiEffect::setSaturation(qreal saturation)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setSaturation(saturation);
++ }
++
++ /*!
++ \qmlproperty real QtQuick.Effects::MultiEffect::colorization
++
++ This property defines how much the source is colorized with the
++ colorizationColor.
++
++ The value ranges from 0.0 (not colorized) to 1.0 (fully colorized).
++ By default, the property is set to \c 0.0 (no change).
++ */
++ qreal QQuickMultiEffect::colorization() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->colorization();
++ }
++
++ void QQuickMultiEffect::setColorization(qreal colorization)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setColorization(colorization);
++ }
++
++ /*!
++ \qmlproperty color QtQuick.Effects::MultiEffect::colorizationColor
++
++ 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).
++
++ \sa colorization
++ */
++ QColor QQuickMultiEffect::colorizationColor() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->colorizationColor();
++ }
++
++ void QQuickMultiEffect::setColorizationColor(const QColor &color)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setColorizationColor(color);
++ }
++
++ /*!
++ \qmlproperty bool QtQuick.Effects::MultiEffect::blurEnabled
++
++ Enables the blur effect.
++
++ \include notes.qdocinc performance shader regen
++ */
++ bool QQuickMultiEffect::blurEnabled() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->blurEnabled();
++ }
++
++ void QQuickMultiEffect::setBlurEnabled(bool enabled)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setBlurEnabled(enabled);
++ }
++
++ /*!
++ \qmlproperty real QtQuick.Effects::MultiEffect::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.
++ */
++ qreal QQuickMultiEffect::blur() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->blur();
++ }
++
++ void QQuickMultiEffect::setBlur(qreal blur)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setBlur(blur);
++ }
++
++ /*!
++ \qmlproperty int QtQuick.Effects::MultiEffect::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
++ */
++ int QQuickMultiEffect::blurMax() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->blurMax();
++ }
++
++ void QQuickMultiEffect::setBlurMax(int blurMax)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setBlurMax(blurMax);
++ }
++
++ /*!
++ \qmlproperty real QtQuick.Effects::MultiEffect::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. Incresing 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
++ */
++ qreal QQuickMultiEffect::blurMultiplier() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->blurMultiplier();
++ }
++
++ void QQuickMultiEffect::setBlurMultiplier(qreal blurMultiplier)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setBlurMultiplier(blurMultiplier);
++ }
++
++ /*!
++ \qmlproperty bool QtQuick.Effects::MultiEffect::shadowEnabled
++
++ Enables the shadow effect.
++
++ \include notes.qdocinc performance shader regen
++ */
++ bool QQuickMultiEffect::shadowEnabled() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->shadowEnabled();
++ }
++
++ void QQuickMultiEffect::setShadowEnabled(bool enabled)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setShadowEnabled(enabled);
++ }
++
++ /*!
++ \qmlproperty real QtQuick.Effects::MultiEffect::shadowOpacity
++
++ This property defines the opacity of the drop shadow. This value
++ is multiplied with the \c shadowColor alpha value.
++
++ The value ranges from 0.0 (fully transparent) to 1.0 (fully opaque).
++ By default, the property is set to \c 1.0.
++ */
++ qreal QQuickMultiEffect::shadowOpacity() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->shadowOpacity();
++ }
++
++ void QQuickMultiEffect::setShadowOpacity(qreal shadowOpacity)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setShadowOpacity(shadowOpacity);
++ }
++
++ /*!
++ \qmlproperty real QtQuick.Effects::MultiEffect::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.
++ */
++ qreal QQuickMultiEffect::shadowBlur() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->shadowBlur();
++ }
++
++ void QQuickMultiEffect::setShadowBlur(qreal shadowBlur)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setShadowBlur(shadowBlur);
++ }
++
++ /*!
++ \qmlproperty real QtQuick.Effects::MultiEffect::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 0.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.
++ */
++ qreal QQuickMultiEffect::shadowHorizontalOffset() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->shadowHorizontalOffset();
++ }
++
++ void QQuickMultiEffect::setShadowHorizontalOffset(qreal offset)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setShadowHorizontalOffset(offset);
++ }
++
++ /*!
++ \qmlproperty real QtQuick.Effects::MultiEffect::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 0.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.
++ */
++ qreal QQuickMultiEffect::shadowVerticalOffset() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->shadowVerticalOffset();
++ }
++
++ void QQuickMultiEffect::setShadowVerticalOffset(qreal offset)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setShadowVerticalOffset(offset);
++ }
++
++ /*!
++ \qmlproperty color QtQuick.Effects::MultiEffect::shadowColor
++
++ This property defines the RGBA color value which is used for
++ 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).
++ */
++ QColor QQuickMultiEffect::shadowColor() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->shadowColor();
++ }
++
++ void QQuickMultiEffect::setShadowColor(const QColor &color)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setShadowColor(color);
++ }
++
++ /*!
++ \qmlproperty real QtQuick.Effects::MultiEffect::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.
++ */
++ qreal QQuickMultiEffect::shadowScale() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->shadowScale();
++ }
++
++ void QQuickMultiEffect::setShadowScale(qreal shadowScale)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setShadowScale(shadowScale);
++ }
++
++ /*!
++ \qmlproperty bool QtQuick.Effects::MultiEffect::maskEnabled
++
++ Enables the mask effect.
++
++ \include notes.qdocinc performance shader regen
++ */
++ bool QQuickMultiEffect::maskEnabled() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->maskEnabled();
++ }
++
++ void QQuickMultiEffect::setMaskEnabled(bool enabled)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setMaskEnabled(enabled);
++ }
++
++ /*!
++ \qmlproperty Item QtQuick.Effects::MultiEffect::maskSource
++
++ Source item for the mask effect. Should point to ShaderEffectSource,
++ item with \l {QtQuick::Item::} {layer.enabled} set to \c true,
++ or to an item that can be directly used as a texture source (for example,
++ \l [QML] Image). The alpha channel of the source item is used for masking.
++
++ If the maskSource and the source have different dimensions, the maskSource
++ image is stretched to match the source size.
++ */
++ QQuickItem *QQuickMultiEffect::maskSource() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->maskSource();
++ }
++
++ void QQuickMultiEffect::setMaskSource(QQuickItem *item)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setMaskSource(item);
++ }
++
++ /*!
++ \qmlproperty real QtQuick.Effects::MultiEffect::maskThresholdMin
++
++ 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.
++ */
++ qreal QQuickMultiEffect::maskThresholdMin() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->maskThresholdMin();
++ }
++
++ void QQuickMultiEffect::setMaskThresholdMin(qreal threshold)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setMaskThresholdMin(threshold);
++ }
++
++ /*!
++ \qmlproperty real QtQuick.Effects::MultiEffect::maskSpreadAtMin
++
++ This property defines the smoothness of the mask edges near the
++ maskThresholdMin. 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.
++ */
++ qreal QQuickMultiEffect::maskSpreadAtMin() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->maskSpreadAtMin();
++ }
++
++ void QQuickMultiEffect::setMaskSpreadAtMin(qreal spread)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setMaskSpreadAtMin(spread);
++ }
++
++ /*!
++ \qmlproperty real QtQuick.Effects::MultiEffect::maskThresholdMax
++
++ 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.
++ */
++ qreal QQuickMultiEffect::maskThresholdMax() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->maskThresholdMax();
++ }
++
++ void QQuickMultiEffect::setMaskThresholdMax(qreal threshold)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setMaskThresholdMax(threshold);
++ }
++
++ /*!
++ \qmlproperty real QtQuick.Effects::MultiEffect::maskSpreadAtMax
++
++ This property defines the smoothness of the mask edges near the
++ maskThresholdMax. 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.
++ */
++ qreal QQuickMultiEffect::maskSpreadAtMax() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->maskSpreadAtMax();
++ }
++
++ void QQuickMultiEffect::setMaskSpreadAtMax(qreal spread)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setMaskSpreadAtMax(spread);
++ }
++
++ /*!
++ \qmlproperty bool QtQuick.Effects::MultiEffect::maskInverted
++
++ This property switches the mask to the opposite side; instead of
++ masking away the content outside maskThresholdMin and maskThresholdMax,
++ content between them will get masked away.
++
++ By default, the property is set to \c false.
++ */
++ bool QQuickMultiEffect::maskInverted() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->maskInverted();
++ }
++
++ void QQuickMultiEffect::setMaskInverted(bool inverted)
++ {
++ Q_D(QQuickMultiEffect);
++ d->setMaskInverted(inverted);
++ }
++
++ /*!
++ \qmlproperty rect QtQuick.Effects::MultiEffect::itemRect
++ \readonly
++
++ Read-only access to effect item rectangle. This can be used e.g. to see
++ the area item covers.
++
++ \sa paddingRect, autoPaddingEnabled
++ */
++ QRectF QQuickMultiEffect::itemRect() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->itemRect();
++ }
++
++ /*!
++ \qmlproperty string QtQuick.Effects::MultiEffect::fragmentShader
++ \readonly
++
++ Read-only access to filename of the currently used fragment shader.
++ */
++ QString QQuickMultiEffect::fragmentShader() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->fragmentShader();
++ }
++
++ /*!
++ \qmlproperty string QtQuick.Effects::MultiEffect::vertexShader
++ \readonly
++
++ Read-only access to filename of the currently used vertex shader.
++ */
++ QString QQuickMultiEffect::vertexShader() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->vertexShader();
++ }
++
++ /*!
++ \qmlproperty bool QtQuick.Effects::MultiEffect::hasProxySource
++ \readonly
++
++ Returns true when the MultiEffect internally creates \l ShaderEffectSource
++ for the \l source item and false when \l source item is used as-is.
++ For example when source is \l Image element or \l Item with
++ \l {QtQuick::Item::} {layer.enabled} set to \c true,
++ this additional proxy source is not needed.
++ */
++ bool QQuickMultiEffect::hasProxySource() const
++ {
++ Q_D(const QQuickMultiEffect);
++ return d->hasProxySource();
++ }
++
++ // *** protected ***
++
++ void QQuickMultiEffect::componentComplete()
++ {
++ Q_D(QQuickMultiEffect);
++ QQuickItem::componentComplete();
++ d->initialize();
++ }
++
++ void QQuickMultiEffect::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
++ {
++ Q_D(QQuickMultiEffect);
++ QQuickItem::geometryChange(newGeometry, oldGeometry);
++ if (width() > 0 && height() > 0)
++ d->handleGeometryChange(newGeometry, oldGeometry);
++ }
++
++ void QQuickMultiEffect::itemChange(ItemChange change, const ItemChangeData &value)
++ {
++ Q_D(QQuickMultiEffect);
++ d->handleItemChange(change, value);
++ QQuickItem::itemChange(change, value);
++ }
++
++ // *** private ***
++
++ QQuickMultiEffectPrivate::QQuickMultiEffectPrivate()
++ {
++ }
++
++ QQuickMultiEffectPrivate::~QQuickMultiEffectPrivate()
++ {
++ }
++
++ void QQuickMultiEffectPrivate::handleGeometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
++ {
++ Q_UNUSED(oldGeometry);
++ Q_UNUSED(newGeometry);
++ initialize();
++ if (!m_shaderEffect)
++ return;
++ updateBlurItemSizes();
++ updateSourcePadding();
++ }
++
++ void QQuickMultiEffectPrivate::handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
++ {
++ Q_UNUSED(value);
++ if (change == QQuickItem::ItemSceneChange)
++ initialize();
++ }
++
++
++ QQuickItem *QQuickMultiEffectPrivate::source() const
++ {
++ return m_sourceItem;
++ }
++
++ void QQuickMultiEffectPrivate::setSource(QQuickItem *item)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (item == m_sourceItem)
++ return;
++
++ if (m_sourceItem)
++ QQuickItemPrivate::get(m_sourceItem)->removeItemChangeListener(this, ChangeType::Destroyed);
++ if (item)
++ QQuickItemPrivate::get(item)->addItemChangeListener(this, ChangeType::Destroyed);
++
++ m_sourceItem = item;
++ if (m_shaderSource)
++ m_shaderSource->setInput(m_sourceItem);
++
++ updateSourcePadding();
++ q->update();
++ Q_EMIT q->sourceChanged();
++ }
++
++ bool QQuickMultiEffectPrivate::autoPaddingEnabled() const
++ {
++ return m_autoPaddingEnabled;
++ }
++
++ void QQuickMultiEffectPrivate::setAutoPaddingEnabled(bool enabled)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (enabled == m_autoPaddingEnabled)
++ return;
++
++ m_autoPaddingEnabled = enabled;
++ updateSourcePadding();
++ q->update();
++ Q_EMIT q->autoPaddingEnabledChanged();
++ }
++
++ QRectF QQuickMultiEffectPrivate::paddingRect() const
++ {
++ return m_paddingRect;
++ }
++
++ void QQuickMultiEffectPrivate::setPaddingRect(const QRectF &rect)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (rect == m_paddingRect)
++ return;
++ m_paddingRect = rect;
++ updateCenterOffset();
++ updateSourcePadding();
++ q->update();
++ emit q->paddingRectChanged();
++ }
++
++ qreal QQuickMultiEffectPrivate::brightness() const
++ {
++ return m_brightness;
++ }
++
++ void QQuickMultiEffectPrivate::setBrightness(qreal brightness)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (brightness == m_brightness)
++ return;
++
++ m_brightness = brightness;
++ if (m_shaderEffect)
++ m_shaderEffect->setProperty("brightness", m_brightness);
++
++ q->update();
++ Q_EMIT q->brightnessChanged();
++ }
++
++ qreal QQuickMultiEffectPrivate::contrast() const
++ {
++ return m_contrast;
++ }
++
++ void QQuickMultiEffectPrivate::setContrast(qreal contrast)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (contrast == m_contrast)
++ return;
++
++ m_contrast = contrast;
++ if (m_shaderEffect)
++ m_shaderEffect->setProperty("contrast", m_contrast);
++
++ q->update();
++ Q_EMIT q->contrastChanged();
++ }
++
++ qreal QQuickMultiEffectPrivate::saturation() const
++ {
++ return m_saturation;
++ }
++
++ void QQuickMultiEffectPrivate::setSaturation(qreal saturation)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (saturation == m_saturation)
++ return;
++
++ m_saturation = saturation;
++ if (m_shaderEffect)
++ m_shaderEffect->setProperty("saturation", m_saturation);
++
++ q->update();
++ Q_EMIT q->saturationChanged();
++ }
++
++ qreal QQuickMultiEffectPrivate::colorization() const
++ {
++ return m_colorization;
++ }
++
++ void QQuickMultiEffectPrivate::setColorization(qreal colorization)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (colorization == m_colorization)
++ return;
++
++ m_colorization = colorization;
++ updateColorizationColor();
++
++ q->update();
++ Q_EMIT q->colorizationChanged();
++ }
++
++ QColor QQuickMultiEffectPrivate::colorizationColor() const
++ {
++ return m_colorizationColor;
++ }
++
++ void QQuickMultiEffectPrivate::setColorizationColor(const QColor &color)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (color == m_colorizationColor)
++ return;
++
++ m_colorizationColor = color;
++ updateColorizationColor();
++
++ q->update();
++ Q_EMIT q->colorizationColorChanged();
++ }
++
++ bool QQuickMultiEffectPrivate::blurEnabled() const
++ {
++ return m_blurEnabled;
++ }
++
++ void QQuickMultiEffectPrivate::setBlurEnabled(bool enabled)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (enabled == m_blurEnabled)
++ return;
++
++ m_blurEnabled = enabled;
++ updateSourcePadding();
++ updateBlurLevel();
++ updateEffectShaders();
++
++ q->update();
++ Q_EMIT q->blurEnabledChanged();
++ }
++
++ qreal QQuickMultiEffectPrivate::blur() const
++ {
++ return m_blur;
++ }
++
++ void QQuickMultiEffectPrivate::setBlur(qreal blur)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (blur == m_blur)
++ return;
++
++ m_blur = blur;
++ updateBlurWeights();
++
++ q->update();
++ Q_EMIT q->blurChanged();
++ }
++
++ int QQuickMultiEffectPrivate::blurMax() const
++ {
++ return m_blurMax;
++ }
++
++ void QQuickMultiEffectPrivate::setBlurMax(int blurMax)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (blurMax == m_blurMax)
++ return;
++
++ m_blurMax = blurMax;
++ updateSourcePadding();
++ updateBlurLevel();
++ updateBlurItemSizes();
++ updateBlurWeights();
++ updateShadowBlurWeights();
++ updateEffectShaders();
++
++ q->update();
++ Q_EMIT q->blurMaxChanged();
++ }
++
++ qreal QQuickMultiEffectPrivate::blurMultiplier() const
++ {
++ return m_blurMultiplier;
++ }
++
++ void QQuickMultiEffectPrivate::setBlurMultiplier(qreal blurMultiplier)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (blurMultiplier == m_blurMultiplier)
++ return;
++
++ m_blurMultiplier = blurMultiplier;
++ updateSourcePadding();
++ updateBlurItemSizes(true);
++ updateBlurWeights();
++ updateShadowBlurWeights();
++
++ q->update();
++ Q_EMIT q->blurMultiplierChanged();
++ }
++
++ bool QQuickMultiEffectPrivate::shadowEnabled() const
++ {
++ return m_shadowEnabled;
++ }
++
++ void QQuickMultiEffectPrivate::setShadowEnabled(bool enabled)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (enabled == m_shadowEnabled)
++ return;
++
++ m_shadowEnabled = enabled;
++ updateSourcePadding();
++ updateBlurLevel();
++ updateEffectShaders();
++
++ q->update();
++ Q_EMIT q->shadowEnabledChanged();
++ }
++
++ qreal QQuickMultiEffectPrivate::shadowOpacity() const
++ {
++ return m_shadowOpacity;
++ }
++
++ void QQuickMultiEffectPrivate::setShadowOpacity(qreal shadowOpacity)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (shadowOpacity == m_shadowOpacity)
++ return;
++
++ m_shadowOpacity = shadowOpacity;
++ updateShadowColor();
++
++ q->update();
++ Q_EMIT q->shadowOpacityChanged();
++ }
++
++ qreal QQuickMultiEffectPrivate::shadowBlur() const
++ {
++ return m_shadowBlur;
++ }
++
++ void QQuickMultiEffectPrivate::setShadowBlur(qreal shadowBlur)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (shadowBlur == m_shadowBlur)
++ return;
++
++ m_shadowBlur = shadowBlur;
++ updateShadowBlurWeights();
++
++ q->update();
++ Q_EMIT q->shadowBlurChanged();
++ }
++
++ qreal QQuickMultiEffectPrivate::shadowHorizontalOffset() const
++ {
++ return m_shadowHorizontalOffset;
++ }
++
++ void QQuickMultiEffectPrivate::setShadowHorizontalOffset(qreal offset)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (offset == m_shadowHorizontalOffset)
++ return;
++
++ m_shadowHorizontalOffset = offset;
++ updateShadowOffset();
++
++ q->update();
++ Q_EMIT q->shadowHorizontalOffsetChanged();
++ }
++
++ qreal QQuickMultiEffectPrivate::shadowVerticalOffset() const
++ {
++ return m_shadowVerticalOffset;
++ }
++
++ void QQuickMultiEffectPrivate::setShadowVerticalOffset(qreal offset)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (offset == m_shadowVerticalOffset)
++ return;
++
++ m_shadowVerticalOffset = offset;
++ updateShadowOffset();
++
++ q->update();
++ Q_EMIT q->shadowVerticalOffsetChanged();
++ }
++
++ QColor QQuickMultiEffectPrivate::shadowColor() const
++ {
++ return m_shadowColor;
++ }
++
++ void QQuickMultiEffectPrivate::setShadowColor(const QColor &color)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (color == m_shadowColor)
++ return;
++
++ m_shadowColor = color;
++ updateShadowColor();
++
++ q->update();
++ Q_EMIT q->shadowColorChanged();
++ }
++
++ qreal QQuickMultiEffectPrivate::shadowScale() const
++ {
++ return m_shadowScale;
++ }
++
++ void QQuickMultiEffectPrivate::setShadowScale(qreal shadowScale)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (shadowScale == m_shadowScale)
++ return;
++
++ m_shadowScale = shadowScale;
++ updateCenterOffset();
++ if (m_shaderEffect)
++ m_shaderEffect->setProperty("shadowScale", 1.0 / m_shadowScale);
++
++ q->update();
++ Q_EMIT q->shadowScaleChanged();
++ }
++
++ bool QQuickMultiEffectPrivate::maskEnabled() const
++ {
++ return m_maskEnabled;
++ }
++
++ void QQuickMultiEffectPrivate::setMaskEnabled(bool enabled)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (enabled == m_maskEnabled)
++ return;
++
++ m_maskEnabled = enabled;
++ updateEffectShaders();
++
++ q->update();
++ Q_EMIT q->maskEnabledChanged();
++ }
++
++ QQuickItem *QQuickMultiEffectPrivate::maskSource() const
++ {
++ return m_maskSourceItem;
++ }
++
++ void QQuickMultiEffectPrivate::setMaskSource(QQuickItem *item)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (item == m_maskSourceItem)
++ return;
++
++ if (m_maskSourceItem) {
++ QQuickItemPrivate::get(m_maskSourceItem)
++ ->removeItemChangeListener(this, ChangeType::Destroyed);
++ }
++ if (item)
++ QQuickItemPrivate::get(item)->addItemChangeListener(this, ChangeType::Destroyed);
++
++ m_maskSourceItem = item;
++ if (m_shaderEffect) {
++ auto maskSourceVariant = QVariant::fromValue<QQuickItem*>(m_maskSourceItem);
++ m_shaderEffect->setProperty("maskSrc", maskSourceVariant);
++ }
++
++ q->update();
++ Q_EMIT q->maskSourceChanged();
++ }
++
++ qreal QQuickMultiEffectPrivate::maskThresholdMin() const
++ {
++ return m_maskThresholdMin;
++ }
++
++ void QQuickMultiEffectPrivate::setMaskThresholdMin(qreal threshold)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (threshold == m_maskThresholdMin)
++ return;
++
++ m_maskThresholdMin = threshold;
++ updateMaskThresholdSpread();
++
++ q->update();
++ Q_EMIT q->maskThresholdMinChanged();
++ }
++
++ qreal QQuickMultiEffectPrivate::maskSpreadAtMin() const
++ {
++ return m_maskSpreadAtMin;
++ }
++
++ void QQuickMultiEffectPrivate::setMaskSpreadAtMin(qreal spread)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (spread == m_maskSpreadAtMin)
++ return;
++
++ m_maskSpreadAtMin = spread;
++ updateMaskThresholdSpread();
++
++ q->update();
++ Q_EMIT q->maskSpreadAtMinChanged();
++ }
++
++ qreal QQuickMultiEffectPrivate::maskThresholdMax() const
++ {
++ return m_maskThresholdMax;
++ }
++
++ void QQuickMultiEffectPrivate::setMaskThresholdMax(qreal threshold)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (threshold == m_maskThresholdMax)
++ return;
++
++ m_maskThresholdMax = threshold;
++ updateMaskThresholdSpread();
++
++ q->update();
++ Q_EMIT q->maskThresholdMaxChanged();
++ }
++
++ qreal QQuickMultiEffectPrivate::maskSpreadAtMax() const
++ {
++ return m_maskSpreadAtMax;
++ }
++
++ void QQuickMultiEffectPrivate::setMaskSpreadAtMax(qreal spread)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (spread == m_maskSpreadAtMax)
++ return;
++
++ m_maskSpreadAtMax = spread;
++ updateMaskThresholdSpread();
++
++ q->update();
++ Q_EMIT q->maskSpreadAtMaxChanged();
++ }
++
++ bool QQuickMultiEffectPrivate::maskInverted() const
++ {
++ return m_maskInverted;
++ }
++
++ void QQuickMultiEffectPrivate::setMaskInverted(bool inverted)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (inverted == m_maskInverted)
++ return;
++
++ m_maskInverted = inverted;
++ if (m_shaderEffect)
++ m_shaderEffect->setProperty("maskInverted", float(m_maskInverted));
++
++ q->update();
++ Q_EMIT q->maskInvertedChanged();
++ }
++
++ QRectF QQuickMultiEffectPrivate::itemRect() const
++ {
++ if (!m_shaderEffect || !m_shaderSource)
++ return QRectF();
++
++ QRectF sourceRect = m_shaderSource->sourceRect();
++ if (sourceRect.width() > 0 && sourceRect.height() > 0)
++ return sourceRect;
++ else
++ return m_shaderEffect->boundingRect();
++ }
++
++ QString QQuickMultiEffectPrivate::fragmentShader() const
++ {
++ return m_fragShader;
++ }
++
++ QString QQuickMultiEffectPrivate::vertexShader() const
++ {
++ return m_vertShader;
++ }
++
++ bool QQuickMultiEffectPrivate::hasProxySource() const
++ {
++ return m_shaderSource && m_shaderSource->isActive();
++ }
++
++ // This initializes the component. It will be ran once, when
++ // the component is ready and it has a valid size.
++ void QQuickMultiEffectPrivate::initialize()
++ {
++ Q_Q(QQuickMultiEffect);
++ if (m_initialized)
++ return;
++ if (!q->isComponentComplete())
++ return;
++ if (!q->window())
++ return;
++ if (q->width() <= 0 || q->height() <= 0)
++ return;
++
++ m_shaderEffect = new QQuickShaderEffect(q);
++ m_shaderSource = new QGfxSourceProxyME(q);
++ QObject::connect(m_shaderSource, &QGfxSourceProxyME::outputChanged, q, [this] { proxyOutputChanged(); });
++ QObject::connect(m_shaderSource, &QGfxSourceProxyME::activeChanged, q, &QQuickMultiEffect::hasProxySourceChanged);
++
++ m_shaderEffect->setParentItem(q);
++ m_shaderEffect->setSize(q->size());
++
++ m_shaderSource->setParentItem(q);
++ m_shaderSource->setSize(q->size());
++ m_shaderSource->setInput(m_sourceItem);
++
++ updateCenterOffset();
++ updateMaskThresholdSpread();
++ updateBlurWeights();
++ updateShadowBlurWeights();
++ updateColorizationColor();
++ updateShadowColor();
++ updateShadowOffset();
++
++ // Create properties
++ auto sourceVariant = QVariant::fromValue<QQuickItem*>(m_shaderSource->output());
++ m_shaderEffect->setProperty("src", sourceVariant);
++ m_shaderEffect->setProperty("brightness", m_brightness);
++ m_shaderEffect->setProperty("contrast", m_contrast);
++ m_shaderEffect->setProperty("saturation", m_saturation);
++ m_shaderEffect->setProperty("shadowScale", 1.0 / m_shadowScale);
++ auto maskSourceVariant = QVariant::fromValue<QQuickItem*>(m_maskSourceItem);
++ m_shaderEffect->setProperty("maskSrc", maskSourceVariant);
++ m_shaderEffect->setProperty("maskInverted", float(m_maskInverted));
++
++ updateBlurLevel();
++ updateBlurItemSizes();
++ updateSourcePadding();
++
++ updateEffectShaders();
++
++ m_initialized = true;
++ }
++
++ void QQuickMultiEffectPrivate::updateMaskThresholdSpread()
++ {
++ if (!m_shaderEffect)
++ return;
++
++ // Calculate threshold and spread values for mask
++ // smoothstep, keeping always edge0 < edge1.
++ const qreal c0 = 0.0001;
++ const qreal c1 = 1.0 - c0;
++ const qreal mt1 = m_maskThresholdMin + c0;
++ const qreal ms1 = m_maskSpreadAtMin + 1.0;
++ const qreal mt2 = c1 - m_maskThresholdMax;
++ const qreal ms2 = m_maskSpreadAtMax + 1.0;
++ const QVector4D maskThresholdSpread = QVector4D(
++ mt1 * ms1 - (ms1 - c1),
++ mt1 * ms1,
++ mt2 * ms2 - (ms2 - c1),
++ mt2 * ms2);
++ m_shaderEffect->setProperty("mask", maskThresholdSpread);
++ }
++
++ void QQuickMultiEffectPrivate::updateCenterOffset()
++ {
++ if (!m_shaderEffect)
++ return;
++
++ const qreal scale = 1.0 / m_shadowScale;
++ QVector2D centerOffset((1.0 - scale) * (0.5 + 0.5 * (m_paddingRect.x() - m_paddingRect.width()) / m_shaderEffect->width()),
++ (1.0 - scale) * (0.5 + 0.5 * (m_paddingRect.y() - m_paddingRect.height()) / m_shaderEffect->height()));
++ m_shaderEffect->setProperty("centerOffset", centerOffset);
++ }
++
++ void QQuickMultiEffectPrivate::updateShadowOffset()
++ {
++ if (!m_shaderEffect)
++ return;
++
++ QVector2D shadowOffset = QVector2D(m_shadowHorizontalOffset / m_shaderEffect->width(), m_shadowVerticalOffset / m_shaderEffect->height());
++ m_shaderEffect->setProperty("shadowOffset", shadowOffset);
++ }
++
++ void QQuickMultiEffectPrivate::updateColorizationColor()
++ {
++ if (!m_shaderEffect)
++ return;
++
++ float alpha = std::clamp(float(m_colorizationColor.alphaF() * m_colorization), 0.0f, 1.0f);
++ QVector4D colorizationColor(m_colorizationColor.redF(),
++ m_colorizationColor.greenF(),
++ m_colorizationColor.blueF(),
++ alpha);
++ m_shaderEffect->setProperty("colorizationColor", colorizationColor);
++ }
++
++ void QQuickMultiEffectPrivate::updateShadowColor()
++ {
++ if (!m_shaderEffect)
++ return;
++
++ // Shader shadowColor has premultiplied alpha
++ float alpha = std::clamp(float(m_shadowOpacity), 0.0f, 1.0f);
++ QVector4D shadowColor(m_shadowColor.redF() * alpha,
++ m_shadowColor.greenF() * alpha,
++ m_shadowColor.blueF() * alpha,
++ m_shadowColor.alphaF() * alpha);
++ m_shaderEffect->setProperty("shadowColor", shadowColor);
++ }
++
++ float QQuickMultiEffectPrivate::calculateLod(float blurAmount)
++ {
++ return qSqrt(blurAmount * float(m_blurMax) / 64.0f) * 1.2f - 0.2f;
++ }
++
++ float QQuickMultiEffectPrivate::blurWeight(float v)
++ {
++ return std::max(0.0f, std::min(1.0f, 1.0f - v * 2.0f));
++ }
++
++ void QQuickMultiEffectPrivate::getBlurWeights(float blurLod, QVector4D &blurWeight1, QVector2D &blurWeight2)
++ {
++ float bw1 = blurWeight(std::fabs(blurLod - 0.1f));
++ float bw2 = blurWeight(std::fabs(blurLod - 0.3f));
++ float bw3 = blurWeight(std::fabs(blurLod - 0.5f));
++ float bw4 = blurWeight(std::fabs(blurLod - 0.7f));
++ float bw5 = blurWeight(std::fabs(blurLod - 0.9f));
++ float bw6 = blurWeight(std::fabs(blurLod - 1.1f));
++ float bsum = bw1 + bw2 + bw3 + bw4 + bw5 + bw6;
++ blurWeight1 = QVector4D(bw1 / bsum, bw2 / bsum, bw3 / bsum, bw4 / bsum);
++ blurWeight2 = QVector2D(bw5 / bsum, bw6 / bsum);
++ }
++
++ void QQuickMultiEffectPrivate::updateBlurWeights()
++ {
++ if (!m_shaderEffect)
++ return;
++ float blurLod = calculateLod(m_blur);
++ getBlurWeights(blurLod, m_blurWeight1, m_blurWeight2);
++ m_shaderEffect->setProperty("blurWeight1", m_blurWeight1);
++ m_shaderEffect->setProperty("blurWeight2", m_blurWeight2);
++ }
++
++ void QQuickMultiEffectPrivate::updateShadowBlurWeights()
++ {
++ if (!m_shaderEffect)
++ return;
++ float blurLod = calculateLod(m_shadowBlur);
++ getBlurWeights(blurLod, m_shadowBlurWeight1, m_shadowBlurWeight2);
++ m_shaderEffect->setProperty("shadowBlurWeight1", m_shadowBlurWeight1);
++ m_shaderEffect->setProperty("shadowBlurWeight2", m_shadowBlurWeight2);
++ }
++
++ void QQuickMultiEffectPrivate::updateBlurItemSizes(bool forceUpdate)
++ {
++ if (m_blurEffects.isEmpty() || !m_shaderSource || !m_sourceItem)
++ return;
++
++ // First blur item size to be half of th source item
++ // extended size, rounded to next divisible by 16.
++ QSizeF sourceSize = itemRect().size();
++ QSizeF firstItemSize(std::ceil(sourceSize.width() / 16) * 8,
++ std::ceil(sourceSize.height() / 16) * 8);
++
++ if (!forceUpdate && m_firstBlurItemSize == firstItemSize)
++ return;
++
++ qCDebug(lcQuickEffect) << "Source size:" << sourceSize;
++ m_firstBlurItemSize = firstItemSize;
++
++ for (int i = 0; i < m_blurEffects.size(); i++) {
++ auto *blurEffect = m_blurEffects[i];
++ QSizeF itemSize = (i == 0) ? firstItemSize : m_blurEffects[i - 1]->size() * 0.5;
++ qCDebug(lcQuickEffect) << "Blur item" << i << ":" << itemSize;
++ blurEffect->setSize(itemSize);
++
++ const QVector2D offset((1.0 + m_blurMultiplier) / itemSize.width(),
++ (1.0 + m_blurMultiplier) / itemSize.height());
++ blurEffect->setProperty("offset", offset);
++ }
++ }
++
++ void QQuickMultiEffectPrivate::updateEffectShaders()
++ {
++ Q_Q(QQuickMultiEffect);
++ if (!q->isComponentComplete() || !m_shaderEffect)
++ return;
++
++ QString vShader = QStringLiteral("multieffect_c");
++ if (m_shadowEnabled)
++ vShader += QStringLiteral("s");
++
++ QString fShader = QStringLiteral("multieffect_c");
++ if (m_maskEnabled)
++ fShader += QStringLiteral("m");
++ if (m_blurEnabled && m_blurMax > 0)
++ fShader += QStringLiteral("b");
++ if (m_shadowEnabled)
++ fShader += QStringLiteral("s");
++
++ fShader += QString::number(m_blurLevel);
++
++ bool shaderChanged = false;
++ if (fShader != m_fragShader) {
++ shaderChanged = true;
++ m_fragShader = fShader;
++ QUrl fs = QUrl(QStringLiteral("qrc:/data/shaders/%1.frag.qsb").arg(m_fragShader));
++ m_shaderEffect->setFragmentShader(fs);
++ Q_EMIT q->fragmentShaderChanged();
++ }
++ if (vShader != m_vertShader) {
++ shaderChanged = true;
++ m_vertShader = vShader;
++ QUrl vs = QUrl(QStringLiteral("qrc:/data/shaders/%1.vert.qsb").arg(m_vertShader));
++ m_shaderEffect->setVertexShader(vs);
++ Q_EMIT q->vertexShaderChanged();
++ }
++ if (shaderChanged) {
++ qCDebug(lcQuickEffect) << this << "Shaders: " << m_fragShader << m_vertShader;
++ Q_EMIT q->shaderChanged();
++ }
++ }
++
++ void QQuickMultiEffectPrivate::updateBlurLevel(bool forceUpdate)
++ {
++ int blurLevel = 0;
++ if ((m_blurEnabled || m_shadowEnabled) && m_blurMax > 0) {
++ if (m_blurMax > 32)
++ blurLevel = 3;
++ else if (m_blurMax > 16)
++ blurLevel = 2;
++ else
++ blurLevel = 1;
++ }
++
++ if (blurLevel != m_blurLevel || (blurLevel > 0 && m_blurEffects.isEmpty()) || forceUpdate) {
++ // Blur level has changed or blur items need to be
++ // initially created.
++ updateBlurItemsAmount(blurLevel);
++ // When the level grows, new items must be resized
++ if (blurLevel > m_blurLevel)
++ updateBlurItemSizes(true);
++ }
++ m_blurLevel = blurLevel;
++ }
++
++ void QQuickMultiEffectPrivate::updateBlurItemsAmount(int blurLevel)
++ {
++ Q_Q(QQuickMultiEffect);
++ if (!m_shaderEffect)
++ return;
++
++ const auto engine = qmlEngine(q);
++ if (!engine)
++ return;
++
++ // Lowest blur level uses 3 items, highest 5 items.
++ int itemsAmount = blurLevel == 0 ? 0 : blurLevel + 2;
++
++ if (m_blurEffects.size() < itemsAmount) {
++ // Add more blur items.
++ // Note that by design blur items are only added and never reduced
++ // during the lifetime of the effect component.
++ QUrl blurVs = QUrl(QStringLiteral("qrc:/data/shaders/bluritems.vert.qsb"));
++ QUrl blurFs = QUrl(QStringLiteral("qrc:/data/shaders/bluritems.frag.qsb"));
++ QQmlComponent blurComponent(
++ engine,
++ QUrl(QStringLiteral("qrc:/qt-project.org/imports/QtQuick/Effects/BlurItem.qml")));
++ for (int i = m_blurEffects.size(); i < itemsAmount; i++) {
++ auto blurEffect = qobject_cast<QQuickShaderEffect*>(blurComponent.create());
++ blurEffect->setParent(q);
++ blurEffect->setParentItem(q);
++ auto sourceVariant = QVariant::fromValue<QQuickItem*>(blurEffect);
++ QString sourceProperty = QStringLiteral("blurSrc%1").arg(i + 1);
++ m_shaderEffect->setProperty(sourceProperty.toUtf8(), sourceVariant);
++ // Initial value to avoid "'source' does not have a matching property" warning.
++ // Will be updated with the correct one few lines forward.
++ blurEffect->setProperty("source", sourceVariant);
++ QQuickItemPrivate *priv = QQuickItemPrivate::get(blurEffect);
++ priv->layer()->setEnabled(true);
++ priv->layer()->setSmooth(true);
++ blurEffect->setVertexShader(blurVs);
++ blurEffect->setFragmentShader(blurFs);
++ m_blurEffects << blurEffect;
++ }
++ }
++
++ // Set the blur items source components
++ if (!m_dummyShaderSource)
++ m_dummyShaderSource = new QQuickShaderEffectSource(q);
++ for (int i = 0; i < m_blurEffects.size(); i++) {
++ auto *blurEffect = m_blurEffects[i];
++ auto sourceItem = (i >= itemsAmount) ?
++ static_cast<QQuickItem *>(m_dummyShaderSource) : (i == 0) ?
++ static_cast<QQuickItem *>(m_shaderSource->output()) :
++ static_cast<QQuickItem *>(m_blurEffects[i - 1]);
++ auto sourceVariant = QVariant::fromValue<QQuickItem*>(sourceItem);
++ blurEffect->setProperty("source", sourceVariant);
++ }
++ }
++
++ void QQuickMultiEffectPrivate::updateSourcePadding()
++ {
++ Q_Q(QQuickMultiEffect);
++ if (!m_shaderEffect || !m_shaderSource)
++ return;
++
++ const bool blurItemsNeeded = (m_blurEnabled || m_shadowEnabled) && (m_blurMax > 0);
++ const int itemPadding = m_autoPaddingEnabled && blurItemsNeeded ? m_blurMax * (1.0 + m_blurMultiplier) : 0;
++
++ // Set the shader effect size
++ if (m_paddingRect != QRectF() || itemPadding > 0) {
++ QRectF effectRect(-m_paddingRect.x() - itemPadding,
++ -m_paddingRect.y() - itemPadding,
++ q->width() + m_paddingRect.x() + m_paddingRect.width() + (itemPadding * 2),
++ q->height() + m_paddingRect.y() + m_paddingRect.height() + (itemPadding * 2));
++ m_shaderEffect->setX(effectRect.x());
++ m_shaderEffect->setY(effectRect.y());
++ m_shaderEffect->setWidth(effectRect.width());
++ m_shaderEffect->setHeight(effectRect.height());
++
++ // Set the source size
++ m_shaderSource->setSize(m_shaderEffect->size());
++
++ // When m_sourceItem is set and has size, use that as the base size.
++ // When effect is used as a component in Item "layer.effect", source
++ // doesn't have a size and then we follow the effect item size.
++ const qreal baseWidth = m_sourceItem && m_sourceItem->width() > 0 ? m_sourceItem->width() : q->width();
++ const qreal baseHeight = m_sourceItem && m_sourceItem->height() > 0 ? m_sourceItem->height() : q->height();
++
++ // Set the source rect
++ const qreal widthMultiplier = q->width() > 0 ? baseWidth / q->width() : 1.0;
++ const qreal heightMultiplier = q->height() > 0 ? baseHeight / q->height() : 1.0;
++ const qreal xPadding = itemPadding * widthMultiplier;
++ const qreal yPadding = itemPadding * heightMultiplier;
++ QRectF rect = QRectF(m_paddingRect.x() * widthMultiplier,
++ m_paddingRect.y() * heightMultiplier,
++ m_paddingRect.width() * widthMultiplier,
++ m_paddingRect.height() * heightMultiplier);
++ QRectF sourceRect = QRectF(-rect.x() - xPadding,
++ -rect.y() - yPadding,
++ baseWidth + rect.x() + rect.width() + xPadding * 2,
++ baseHeight + rect.y() + rect.height() + yPadding * 2);
++ m_shaderSource->setSourceRect(sourceRect);
++ } else {
++ m_shaderEffect->setX(0);
++ m_shaderEffect->setY(0);
++ m_shaderEffect->setSize(q->size());
++ m_shaderSource->setSize(q->size());
++ m_shaderSource->setSourceRect(QRectF());
++ }
++
++ updateShadowOffset();
++ updateProxyActiveCheck();
++ updateBlurItemSizes();
++ Q_EMIT q->paddingRectChanged();
++ Q_EMIT q->itemRectChanged();
++ Q_EMIT q->itemSizeChanged();
++ }
++
++ void QQuickMultiEffectPrivate::proxyOutputChanged()
++ {
++ if (!m_shaderSource)
++ return;
++
++ auto sourceVariant = QVariant::fromValue<QQuickItem*>(m_shaderSource->output());
++ m_shaderEffect->setProperty("src", sourceVariant);
++
++ // Force updating the blur items since the source output has changed
++ updateBlurLevel(true);
++ updateBlurItemSizes();
++ updateSourcePadding();
++ }
++
++ void QQuickMultiEffectPrivate::itemDestroyed(QQuickItem *item)
++ {
++ if (item == m_sourceItem)
++ setSource(nullptr);
++ else if (item == m_maskSourceItem)
++ setMaskSource(nullptr);
++ }
++
++ void QQuickMultiEffectPrivate::updateProxyActiveCheck()
++ {
++ if (!m_shaderSource)
++ return;
++
++ m_shaderSource->polish();
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qquickmultieffect_p.cpp"