4

I'm looking to have semi-transparent forms / popups in my WPF application, and what i'm after exactly is the same kind of aero-theme dark-glass-like blur achieved in windows 10 as shown here:

Aero blur effect example

Thus far I've only either found resources online to explain how to apply this blur effect to everything inside of a container which is quite the opposite of what I'm after, or apply the same sort of blur broad-brush to everything behind the form/popup.

I am aware of the SetWindowCompositionAttribute (shown here: Native Aero Blur without Glass Effect on Borderless WPF Window and further explained here: http://withinrafael.com/adding-the-aero-glass-blur-to-your-windows-10-apps/)

But these explain only adding the effect to an entire window of an application which isn't precisely what I'm after.

I want to apply the effect to a chosen element (let's say a Border, for example) inside my application;

enter image description here

... Like so.

How would i go about doing such a thing?

Community
  • 1
  • 1
Logan
  • 806
  • 8
  • 17
  • My idea would be to take "screenshot" then blur it and put it as a control's background. I've seen that a lot of developers are eager to have such feature like Background="Blur" but I didn't pass any solutions for such problem. – Michal Kania Nov 06 '15 at 19:04
  • @MichalKozak of course that's wrong. – Federico Berasategui Nov 06 '15 at 19:09
  • 2
    http://jmorrill.hjtcentral.com/Home/tabid/428/EntryId/403/Glass-Behavior-for-WPF.aspx – Federico Berasategui Nov 06 '15 at 19:09
  • @Highcore - this has been quite useful. Although it's old i managed to essentially grab the class in his project and achieve a measure of what i was after with just that. – Logan Nov 09 '15 at 15:00
  • Now having tried to use it inside my main project, it appears to be rather limited. It basically takes a screenshot of whatever you point it towards (and only that), then makes a blurred copy to display. – Logan Nov 09 '15 at 18:47

1 Answers1

7

I hope it is not too late for answering to your question. The solution for obtaining the result that you wish, in my opinion, is represented by the ShaderEffect class.

My approach has some limitations, but I guess it can be improved for workarounding them.

First of all let's see the result:

Blur Sample

My idea is to create a custom ShaderEffect, that I called BlurRectEffect. For understanding this class your can read this very good tutorial.

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;
        }
    }
}

This class computes the intersection between the effected control and the overlapper one. Then it sends those informations to the so called "pixel shader" file (a .fx file, that will be compiled into a .ps file).

Now we need to create the RectBlueEffect.fx file. It uses the HLSL - (i.e. High Level Shading Language). Here its content:

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);
}

As you can see, if a pixel is outside of a rectangle (the intersection between the two controls areas) no effect is applied. Otherwise the main method uses an effect (i.e. a gaussian blur).

You can find here an implementation of the method that I called gaussianblur. Just take a look to the last answer.

Now we have to compile the RectBlueEffect.fx file. You can find some instructions either in the previous link (the tutorial) or here.

The XAML is the last part of my solution:

<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>

As you can suppose my solution allows to overlap just rectangles (or squares), but it does not work for ellipses, circles or irregular polygons. Of course everything depends on HLSL code that you write in the .fx file.

Il Vic
  • 5,576
  • 4
  • 26
  • 37
  • Could you please provide the full .fx file I can use to compile the .ps. The current one I am using is throwing "Invalid user-specified pixel shader. Register a PixelShader.InvalidPixelShaderEncountered event handler to avoid this exception being raised". Thanks for your time. – MoonKnight Jan 12 '18 at 17:55
  • 1
    @MoonKnight, I have just added the whole code of my _.fx_ file. I hope mine can be more useful for you – Il Vic Jan 21 '18 at 17:06
  • Thanks very much for your time. – MoonKnight Jan 22 '18 at 11:19