谢谢您的问题。这让我有机会学到一些新知识。:)
一旦您知道自己在做什么,达成您的目标就非常容易。WPF支持使用GPU着色器来修改图像。它们在运行时非常快(因为它们在您的显卡中执行),并且易于应用。而在反转颜色的情况下,实现起来也非常容易。
首先,您需要着色器代码。着色器是用一种称为高级着色语言(HLSL)的语言编写的。以下是一个HLSL“程序”,它将反转输入颜色:
sampler2D input : register(s0);
float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 color = tex2D(input, uv);
float alpha = color.a;
color = 1 - color;
color.a = alpha;
color.rgb *= alpha;
return color;
}
但是,Visual Studio不能直接处理这种类型的代码。您需要确保已安装DirectX SDK,该SDK将提供用于编译着色器代码的fxc.exe编译器。
我使用以下命令行编译上述代码:
fxc /T ps_3_0 /E main /Fo<my shader file>.ps <my shader file>.hlsl
当然,您需要将<my shader file>
替换为实际的文件名。
(注意:我手动执行了此操作,但您当然可以在项目中创建自定义构建操作来执行相同的操作。)
然后,您可以将.ps
文件包含在项目中,并将其“Build Action”设置为“Resource”。
完成后,现在需要创建ShaderEffect
类来使用它。代码如下:
class InvertEffect : ShaderEffect
{
private static readonly PixelShader _shader =
new PixelShader { UriSource = new Uri("pack://application:,,,/<my shader file>.ps") };
public InvertEffect()
{
PixelShader = _shader;
UpdateShaderValue(InputProperty);
}
public Brush Input
{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}
public static readonly DependencyProperty InputProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(InvertEffect), 0);
}
上述代码的关键点:
- 只需要一个着色器本身的副本。因此,我将其初始化为
static readonly
字段。由于.ps
文件作为资源包含在内,我可以使用pack:
方案引用它,例如"pack://application:,,,/<my shader file>.ps"
。当然,你需要用实际的文件名替换<my shader file>
。
- 在构造函数中,您必须将
PixelShader
属性设置为着色器对象。您还必须对每个用作着色器输入的属性(在这种情况下只有一个)调用UpdateShaderValue()
来初始化着色器。
Input
属性是特殊的:它需要使用RegisterPixelShaderSamplerProperty()
注册依赖属性。
- 如果你的着色器有其他参数,则可以使用
DependencyProperty.Register()
正常注册它们。但是,它们需要一个特殊的PropertyChangedCallback
值,该值通过在着色器代码中声明该参数的寄存器索引来调用ShaderEffect.PixelShaderConstantCallback()
获得。
这就是全部内容!您可以通过将
UIElement.Effect
属性设置为
InvertEffect
类的实例来在XAML中使用它。例如:
<Window x:Class="TestSO45093399PixelShader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:TestSO45093399PixelShader"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Rectangle Width="100" Height="100">
<Rectangle.Fill>
<LinearGradientBrush>
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
<Rectangle.Effect>
<l:InvertEffect/>
</Rectangle.Effect>
</Rectangle>
</Grid>
</Window>
当你运行代码时,你会发现即使渐变在左上角定义为黑色,向右下角过渡为白色,它的显示方式是相反的,白色在左上角,黑色在右下角。
最后,如果你想立即让它工作,但没有访问fxc.exe编译器,这里有一个以上的版本,其中包含编译的着色器代码作为Base64。它很小,所以这是将着色器编译并作为资源包含的实用替代方法。
class InvertEffect : ShaderEffect
{
private const string _kshaderAsBase64 =
@"AAP///7/HwBDVEFCHAAAAE8AAAAAA///AQAAABwAAAAAAQAASAAAADAAAAADAAAAAQACADgAAAAA
AAAAaW5wdXQAq6sEAAwAAQABAAEAAAAAAAAAcHNfM18wAE1pY3Jvc29mdCAoUikgSExTTCBTaGFk
ZXIgQ29tcGlsZXIgMTAuMQCrUQAABQAAD6AAAIA/AAAAAAAAAAAAAAAAHwAAAgUAAIAAAAOQHwAA
AgAAAJAACA+gQgAAAwAAD4AAAOSQAAjkoAIAAAMAAAeAAADkgQAAAKAFAAADAAgHgAAA/4AAAOSA
AQAAAgAICIAAAP+A//8AAA==";
private static readonly PixelShader _shader;
static InvertEffect()
{
_shader = new PixelShader();
_shader.SetStreamSource(new MemoryStream(Convert.FromBase64String(_kshaderAsBase64)));
}
public InvertEffect()
{
PixelShader = _shader;
UpdateShaderValue(InputProperty);
}
public Brush Input
{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}
public static readonly DependencyProperty InputProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(InvertEffect), 0);
}
最后,我要指出
Bradley's comment中提供的链接包含许多这些种类的着色器实现效果。那些实现者只是在HLSL和
ShaderEffect
对象上略有不同地实现了它们,所以如果你想看到其他效果和不同的实现方式,浏览那些代码将是一个很好的选择。
享受吧!