对所选容器后面的所有内容应用模糊

时间:2015-11-06 15:53:48

标签: c# wpf xaml

我希望在我的WPF应用程序中有半透明的表单/弹出窗口,而我正在追求的是在Windows 10中实现的同样类似的航空主题深色玻璃模糊,如下所示:< / p>

Aero blur effect example

到目前为止,我只是在网上找到资源来解释如何将这种模糊效果应用到容器的所有内部,这与我之后的相反,或者应用相同的对表单/弹出窗口后面的所有内容进行模糊处理。

我知道SetWindowCompositionAttribute (如下所示:Native Aero Blur without Glass Effect on Borderless WPF Window并在此进一步说明:http://withinrafael.com/adding-the-aero-glass-blur-to-your-windows-10-apps/

但这些解释只是将效果添加到应用程序的整个窗口,这不是我正在追求的。

我想在我的应用程序中将效果应用于所选元素(例如,使用Border);

enter image description here

......就像这样。

我将如何做这样的事情?

1 个答案:

答案 0 :(得分:3)

我希望回答你的问题还为时不晚。在我看来,获得您希望的结果的解决方案由ShaderEffect类表示。

我的方法有一些限制,但我想可以改进它来解决它们。

首先让我们看看结果:

Blur Sample

我的想法是创建一个名为ShaderEffect的自定义BlurRectEffect。要理解本课程,您可以阅读this非常好的教程。

public class RectBlurEffect : ShaderEffect
{
    private static PixelShader pixelShader = new PixelShader();
    private static PropertyInfo propertyInfo;

    public static readonly DependencyProperty InputProperty =
        ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(RectBlurEffect), 0);

    public static readonly DependencyProperty UpLeftCornerProperty =
        DependencyProperty.Register("UpLeftCorner", typeof(Point), typeof(RectBlurEffect),
            new UIPropertyMetadata(new Point(0, 0), PixelShaderConstantCallback(0)));

    public static readonly DependencyProperty LowRightCornerProperty =
        DependencyProperty.Register("LowRightCorner", typeof(Point), typeof(RectBlurEffect),
            new UIPropertyMetadata(new Point(1, 1), PixelShaderConstantCallback(1)));

    public static readonly DependencyProperty FrameworkElementProperty =
        DependencyProperty.Register("FrameworkElement", typeof(FrameworkElement), typeof(RectBlurEffect),
        new PropertyMetadata(null, OnFrameworkElementPropertyChanged));

    static RectBlurEffect()
    {
        pixelShader.UriSource = Global.MakePackUri("RectBlurEffect.ps");
        propertyInfo = typeof(RectBlurEffect).GetProperty("InheritanceContext",
            BindingFlags.Instance | BindingFlags.NonPublic);
    }        

    public RectBlurEffect()
    {
        PixelShader = pixelShader;
        UpdateShaderValue(InputProperty);
        UpdateShaderValue(UpLeftCornerProperty);
        UpdateShaderValue(LowRightCornerProperty);
    }

    public Brush Input
    {
        get { return (Brush)GetValue(InputProperty); }
        set { SetValue(InputProperty, value); }
    }        

    public Point UpLeftCorner
    {
        get { return (Point)GetValue(UpLeftCornerProperty); }
        set { SetValue(UpLeftCornerProperty, value); }
    }        

    public Point LowRightCorner
    {
        get { return (Point)GetValue(LowRightCornerProperty); }
        set { SetValue(LowRightCornerProperty, value); }
    }

    public FrameworkElement FrameworkElement
    {
        get { return (FrameworkElement)GetValue(FrameworkElementProperty); }
        set { SetValue(FrameworkElementProperty, value); }
    }

    private FrameworkElement GetInheritanceContext()
    {
        return propertyInfo.GetValue(this, null) as FrameworkElement;
    }

    private void UpdateEffect(object sender, EventArgs args)
    {
        Rect underRectangle;
        Rect overRectangle;
        Rect intersect;

        FrameworkElement under = GetInheritanceContext();
        FrameworkElement over = this.FrameworkElement; 

        Point origin = under.PointToScreen(new Point(0, 0));
        underRectangle = new Rect(origin.X, origin.Y, under.ActualWidth, under.ActualHeight);

        origin = over.PointToScreen(new Point(0, 0));
        overRectangle = new Rect(origin.X, origin.Y, over.ActualWidth, over.ActualHeight);

        intersect = Rect.Intersect(overRectangle, underRectangle);

        if (intersect.IsEmpty)
        {
            UpLeftCorner = new Point(0, 0);
            LowRightCorner = new Point(0, 0);
        }
        else
        {
            origin = new Point(intersect.X, intersect.Y);
            origin = under.PointFromScreen(origin);

            UpLeftCorner = new Point(origin.X / under.ActualWidth,
                origin.Y / under.ActualHeight);
            LowRightCorner = new Point(UpLeftCorner.X + (intersect.Width / under.ActualWidth),
                UpLeftCorner.Y + (intersect.Height / under.ActualHeight));
        }

    }

    private static void OnFrameworkElementPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
    {
        RectBlurEffect rectBlurEffect = (RectBlurEffect)d;

        FrameworkElement frameworkElement = args.OldValue as FrameworkElement;

        if (frameworkElement != null)
        {
            frameworkElement.LayoutUpdated -= rectBlurEffect.UpdateEffect; 
        }

        frameworkElement = args.NewValue as FrameworkElement;

        if (frameworkElement != null)
        {
            frameworkElement.LayoutUpdated += rectBlurEffect.UpdateEffect;
        }
    }
}

该类计算受影响控件和重叠器之间的交集。然后它将这些信息发送到所谓的“像素着色器”文件( .fx 文件,该文件将被编译为 .ps 文件)。

现在我们需要创建RectBlueEffect.fx文件。它使用HLSL - (i.e. High Level Shading Language)。其内容如下:

sampler2D rectBlurEffect : register(S0);
float2 upperLeftCorner : register(C0);
float2 lowerRightCorner : register(C1);

float Angle : register(C2);
float BlurAmount : register(C3);

float PI = 3.14159265358979323846;
float EPSILON = 0.0001;

float ComputeGaussian(float n)
{
    float theta = 2.0f + EPSILON; //float.Epsilon;

    return theta = (float)((1.0 / sqrt(2 * PI * theta)) *
        exp(-(n * n) / (2 * theta * theta)));
}

float4 gaussianblur(float2 texCoord: TEXCOORD0) : COLOR
{

    float SampleWeights[7];
    float2 SampleOffsets[15];

    // The first sample always has a zero offset.
    float2 initer = { 0.0f, 0.0f };
    SampleWeights[0] = ComputeGaussian(0);
    SampleOffsets[0] = initer;

    // Maintain a sum of all the weighting values.
    float totalWeights = SampleWeights[0];

    // Add pairs of additional sample taps, positioned
    // along a line in both directions from the center.
    for (int i = 0; i < 7 / 2; i++)
    {
        // Store weights for the positive and negative taps.
        float weight = ComputeGaussian(i + 1);

        SampleWeights[i * 2 + 1] = weight;
        SampleWeights[i * 2 + 2] = weight;

        totalWeights += weight * 2;


        float sampleOffset = i * 2 + 1.5f;

        float2 delta = { (1.0f / 512), 0 };
        delta = delta * sampleOffset;

        // Store texture coordinate offsets for the positive and negative taps.
        SampleOffsets[i * 2 + 1] = delta;
        SampleOffsets[i * 2 + 2] = -delta;
    }

    // Normalize the list of sample weightings, so they will always sum to one.
    for (int j = 0; j < 7; j++)
    {
        SampleWeights[j] /= totalWeights;
    }

    float4 color = 0.0f;

    for (int k = 0; k < 7; k++)
    {
        color += tex2D(rectBlurEffect,
            texCoord + SampleOffsets[k]) * SampleWeights[k];
    }

    return color;
}

float4 directionalBlur(float2 uv : TEXCOORD) : COLOR
{
    float4 c = 0;
    float rad = Angle * 0.0174533f;
    float xOffset = cos(rad);
    float yOffset = sin(rad);

    for (int i = 0; i < 12; i++)
    {
        uv.x = uv.x - BlurAmount * xOffset;
        uv.y = uv.y - BlurAmount * yOffset;
        c += tex2D(rectBlurEffect, uv);
    }
    c /= 12;

    return c;
}

float4 main(float2 uv : TEXCOORD) : COLOR
{
    if (uv.x < upperLeftCorner.x || uv.y < upperLeftCorner.y || uv.x > lowerRightCorner.x || uv.y > lowerRightCorner.y)
    {
        return tex2D(rectBlurEffect, uv);
    }

    return gaussianblur(uv);
}

如您所见,如果像素位于矩形之外(两个控制区域之间的交点),则不会产生任何效果。否则main方法使用效果(即高斯模糊)。

您可以找到here我称之为gaussianblur的方法的实现。看看最后的答案。

现在我们必须编译RectBlueEffect.fx文件。您可以在上一个链接(教程)或here中找到一些说明。

XAML是我解决方案的最后一部分:

<Grid>
    <StackPanel Orientation="Vertical" VerticalAlignment="Center">
        <Border Background="Brown"  BorderThickness="0" Name="tb">
            <TextBlock Text="Hello World!" Margin="4" Padding="10"
                        FontSize="30" FontWeight="Bold" Foreground="Yellow"
                        VerticalAlignment="Center" 
                        HorizontalAlignment="Stretch"
                        TextAlignment="Center" />
            <Border.Effect>
                <local:RectBlurEffect FrameworkElement="{Binding ElementName=Border, Mode=OneTime}" />
            </Border.Effect>
        </Border>

        <TextBlock Background="Khaki" Text="I should be partially blurred!" Margin="4" Padding="10"
            Foreground="DarkGreen" FontSize="30" TextWrapping="Wrap" FontFamily="Cambria"
            VerticalAlignment="Center"
            HorizontalAlignment="Stretch" 
            TextAlignment="Center">
            <TextBlock.Effect>
                <local:RectBlurEffect FrameworkElement="{Binding ElementName=Border, Mode=OneTime}" />
            </TextBlock.Effect>
        </TextBlock>
    </StackPanel>

    <Border Name="Border" Width="200" Height="260" BorderThickness="0"
            Background="Black" Opacity=".6" Panel.ZIndex="20">

        <TextBlock Text="Pretend I'm a border dumped over a grid" TextWrapping="Wrap"
                    HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="16"
                    Foreground="AntiqueWhite" Background="Transparent"
                    Margin="8" />

    </Border>
</Grid>

正如您可以假设我的解决方案允许仅重叠矩形(或正方形),但它不适用于椭圆,圆或不规则多边形。当然,一切都取决于您在 .fx 文件中编写的HLSL代码。