问题描述
我希望在WPF应用程序中具有半透明的窗体/弹出窗口,而我想要的正是在Windows 10中实现的类似航空主题的深色玻璃样模糊效果,如下所示:
到目前为止,我只是在网上找到资源来解释如何将这种模糊效果应用于容器的所有内部(与我所追求的相反),或者应用相同的方法对表单/弹出窗口后面的所有内容都模糊不清.
我知道SetWindowCompositionAttribute(此处显示:
...像这样
我将如何做这样的事情?
我希望回答您的问题还为时不晚.我认为,
我的想法是创建一个自定义的 ShaderEffect
,我将其称为 BlurRectEffect
.要了解此类,您可以阅读此很好的教程.
公共类RectBlurEffect:ShaderEffect{私有静态PixelShader pixelShader = new PixelShader();私有静态PropertyInfo propertyInfo;公共静态只读DependencyProperty InputProperty =ShaderEffect.RegisterPixelShaderSamplerProperty("Input",typeof(RectBlurEffect),0);公共静态只读DependencyProperty UpLeftCornerProperty =DependencyProperty.Register("UpLeftCorner",typeof(Point),typeof(RectBlurEffect),new UIPropertyMetadata(new Point(0,0),PixelShaderConstantCallback(0)));公共静态只读DependencyProperty LowRightCornerProperty =DependencyProperty.Register("LowRightCorner",typeof(Point),typeof(RectBlurEffect),new UIPropertyMetadata(new Point(1,1),PixelShaderConstantCallback(1)));公共静态只读DependencyProperty FrameworkElementProperty =DependencyProperty.Register("FrameworkElement",typeof(FrameworkElement),typeof(RectBlurEffect),新的PropertyMetadata(null,OnFrameworkElementPropertyChanged));静态RectBlurEffect(){pixelShader.UriSource = Global.MakePackUri("RectBlurEffect.ps");propertyInfo = typeof(RectBlurEffect).GetProperty("InheritanceContext",BindingFlags.Instance |BindingFlags.NonPublic);}公共RectBlurEffect(){PixelShader = pixelShader;UpdateShaderValue(InputProperty);UpdateShaderValue(UpLeftCornerProperty);UpdateShaderValue(LowRightCornerProperty);}公共画笔输入{get {return(Brush)GetValue(InputProperty);}设置{SetValue(InputProperty,value);}}公共Point UpLeftCorner{获取{return(Point)GetValue(UpLeftCornerProperty);}设置{SetValue(UpLeftCornerProperty,value);}}公共点LowRightCorner{获取{return(Point)GetValue(LowRightCornerProperty);}设置{SetValue(LowRightCornerProperty,value);}}公共FrameworkElement FrameworkElement{get {return(FrameworkElement)GetValue(FrameworkElementProperty);}设置{SetValue(FrameworkElementProperty,value);}}私有FrameworkElement GetInheritanceContext(){返回propertyInfo.GetValue(this,null)作为FrameworkElement;}私人无效UpdateEffect(对象发送者,EventArgs args){矩形下矩形;矩形矩形;矩形相交;下的FrameworkElement = GetInheritanceContext();FrameworkElement over = this.FrameworkElement;点原点= 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);如果(相交IsEmpty){UpLeftCorner = new Point(0,0);LowRightCorner = new Point(0,0);}别的{origin =新的Point(intersect.X,intersect.Y);origin = under.PointFromScreen(origin);UpLeftCorner = new Point(origin.X/under.ActualWidth,origin.Y/under.ActualHeight);LowRightCorner =新点(UpLeftCorner.X +(相交宽度/实际宽度以下),UpLeftCorner.Y +(相交高度/实际高度以下);}}私有静态无效OnFrameworkElementPropertyChanged(DependencyObject d,DependencyPropertyChangedEventArgs args){RectBlurEffect rectBlurEffect =(RectBlurEffect)d;FrameworkElement frameworkElement = args.OldValue作为FrameworkElement;如果(frameworkElement!= null){frameworkElement.LayoutUpdated-= rectBlurEffect.UpdateEffect;}frameworkElement = args.NewValue作为FrameworkElement;如果(frameworkElement!= null){frameworkElement.LayoutUpdated + = rectBlurEffect.UpdateEffect;}}}
此类计算受影响的控件和重叠控件之间的交集.然后它将这些信息发送到所谓的像素着色器"文件(一个 .fx 文件,该文件将被编译为一个 .ps 文件).
现在,我们需要创建RectBlueEffect.fx文件.它使用 HLSL-(即高级着色语言).这里是它的内容:
sampler2D rectBlurEffect:寄存器(S0);float2 upperLeftCorner:寄存器(C0);float2 lowerRightCorner:寄存器(C1);浮角:寄存器(C2);float BlurAmount:寄存器(C3);浮点PI = 3.14159265358979323846;浮动EPSILON = 0.0001;float ComputeGaussian(float n){浮点数= 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):颜色{float SampleWeights [7];float2 SampleOffsets [15];//第一个样本始终具有零偏移.float2 initer = {0.0f,0.0f};SampleWeights [0] = ComputeGaussian(0);SampleOffsets [0] =初始化程序;//保持所有权重值的总和.float totalWeights = SampleWeights [0];//添加成对的附加样本抽头,//从中心沿两个方向沿着一条线.对于(int i = 0; i< 7/2; i ++){//存储正分接头和负分接头的权重.浮点重量= ComputeGaussian(i + 1);SampleWeights [i * 2 +1] =重量;SampleWeights [i * 2 + 2] =重量;totalWeights + =重量* 2;float sampleOffset = i * 2 + 1.5f;float2 delta = {(1.0f/512),0};delta = delta * sampleOffset;//存储正拍和负拍的纹理坐标偏移量.SampleOffsets [i * 2 +1] =增量;SampleOffsets [i * 2 + 2] = -delta;}//归一化样本权重列表,因此它们将总和为1.对于(int j = 0; j< 7; j ++){SampleWeights [j]/= totalWeights;}float4颜色= 0.0f;对于(int k = 0; k
如您所见,如果像素在矩形(两个控件区域之间的交集)之外,则不会应用任何效果.否则, main
方法使用一种效果(即高斯模糊).
您可以找到此处我称为 gaussianblur
的方法的实现.只需看看最后一个答案即可.
现在,我们必须编译 RectBlueEffect.fx
文件.您可以在上一个链接(本教程)或此处.
XAML是我解决方案的最后一部分:
< Grid>< StackPanel Orientation ="Vertical" VerticalAlignment ="Center"><边框背景=棕色" BorderThickness ="0"名称="tb">< TextBlock Text ="Hello World!"Margin ="4" Padding ="10"FontSize ="30" FontWeight ="Bold"前景="Yellow"VerticalAlignment ="Center"HorizontalAlignment =拉伸"TextAlignment ="Center"/><边界效果>< local:RectBlurEffect FrameworkElement ="{Binding ElementName = Border,Mode = OneTime}""/></Border.Effect></Border>< TextBlock Background ="Khaki" Text =我应该被部分模糊!Margin ="4" Padding ="10"Foreground ="DarkGreen" FontSize ="30" TextWrapping =包装" FontFamily ="Cambria"VerticalAlignment ="Center"HorizontalAlignment =拉伸"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 =假装我是一个被网格覆盖的边框" TextWrapping ="Wrap"HorizontalAlignment ="Left" VerticalAlignment ="Top" FontSize ="16"Foreground ="AntiqueWhite" Background ="Transparent"Margin ="8"/></Border></Grid>
您可以假设我的解决方案只允许重叠矩形(或正方形),但不适用于椭圆形,圆形或不规则多边形.当然,一切都取决于您在 .fx 文件中编写的HLSL代码.
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:
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;
... Like so.
How would i go about doing such a thing?
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:
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.
这篇关于对所选容器后面的所有内容应用模糊处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!