感谢您提出这个问题。它给了我一个学习新东西的机会。 :)
您的目标是,一旦您知道自己在做什么,就很容易实现。 WPF 支持使用 GPU 着色器来修改图像。它们在运行时速度很快(因为它们在您的视频卡中执行)并且易于应用。并且在反转颜色的既定目标的情况下,也很容易实现。
首先,您需要着色器代码。着色器是用一种称为High Level Shader Language 或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,它将为您提供 fxc.exe 编译器,用于编译着色器代码。
我用这个命令行编译了上面的代码:
fxc /T ps_3_0 /E main /Fo.ps .hlsl
当然,您将 <my shader file> 替换为您的实际文件名。
(注意:我手动完成了此操作,但您当然可以在项目中创建自定义构建操作来执行相同操作。)
然后您可以在项目中包含.ps 文件,将“构建操作” 设置为“资源”。
完成后,您现在需要创建将使用它的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 对象的作者与我在此处展示的方式略有不同,因此如果您想查看其他效果示例和实现它们的不同方式,浏览该代码将是一个很好的地方.
享受吧!