如何使用 C# 反转 XAML PNG 图像的颜色?
How to Invert Color of XAML PNG Images using C#?
我正在使用 Visual Studio、C#、XAML、WPF。
在我的程序中,我有 XAML 个带有白色 png 图标的按钮。
我想要它,这样您就可以通过从组合框中选择主题来切换到带有黑色图标的主题。
有没有办法用 XAML 和 C# 反转白色图标的颜色,而不是创建一组新的黑色 png 图像?
<Button x:Name="btnInfo" HorizontalAlignment="Left" Margin="10,233,0,0" VerticalAlignment="Top" Width="22" Height="22" Cursor="Hand" Click="buttonInfo_Click" Style="{DynamicResource ButtonSmall}">
<Image Source="Resources/Images/info.png" Width="5" Height="10" Stretch="Uniform" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="1,0,0,0"/>
</Button>
感谢您提出这个问题。它给了我学习新东西的机会。 :)
您的目标是,一旦您知道自己在做什么,就很容易实现。 WPF 支持使用 GPU 着色器修改图像。它们在 运行 时速度很快(因为它们在您的视频卡中执行)并且易于应用。并且在既定目标反转颜色的情况下,也很容易实现。
首先,您需要着色器代码。着色器是用一种称为 High Level Shader Language 或 HLSL 的语言编写的。这是一个 HLSL "program",它将反转输入颜色:
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<my shader file>.ps <my shader file>.hlsl
当然,您将 <my shader file>
替换为您的实际文件名。
(注意:我是手动完成的,但您当然可以在项目中创建自定义构建操作来执行相同的操作。)
然后您可以在项目中包含 .ps
文件,将 "Build Action" 设置为 "Resource".
完成后,您现在需要创建将使用它的 ShaderEffect
class。看起来像这样:
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
class 的实例,即可在 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);
}
最后,我会注意到 中提供的 link 确实有一大堆此类着色器实现的效果。那些实现 HLSL 和 ShaderEffect
对象的作者与我在这里展示的方式略有不同,所以如果您想查看其他效果示例和不同的实现方式,浏览该代码将是一个很好的地方看看。
尽情享受吧!
我正在使用 Visual Studio、C#、XAML、WPF。
在我的程序中,我有 XAML 个带有白色 png 图标的按钮。
我想要它,这样您就可以通过从组合框中选择主题来切换到带有黑色图标的主题。
有没有办法用 XAML 和 C# 反转白色图标的颜色,而不是创建一组新的黑色 png 图像?
<Button x:Name="btnInfo" HorizontalAlignment="Left" Margin="10,233,0,0" VerticalAlignment="Top" Width="22" Height="22" Cursor="Hand" Click="buttonInfo_Click" Style="{DynamicResource ButtonSmall}">
<Image Source="Resources/Images/info.png" Width="5" Height="10" Stretch="Uniform" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="1,0,0,0"/>
</Button>
感谢您提出这个问题。它给了我学习新东西的机会。 :)
您的目标是,一旦您知道自己在做什么,就很容易实现。 WPF 支持使用 GPU 着色器修改图像。它们在 运行 时速度很快(因为它们在您的视频卡中执行)并且易于应用。并且在既定目标反转颜色的情况下,也很容易实现。
首先,您需要着色器代码。着色器是用一种称为 High Level Shader Language 或 HLSL 的语言编写的。这是一个 HLSL "program",它将反转输入颜色:
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<my shader file>.ps <my shader file>.hlsl
当然,您将 <my shader file>
替换为您的实际文件名。
(注意:我是手动完成的,但您当然可以在项目中创建自定义构建操作来执行相同的操作。)
然后您可以在项目中包含 .ps
文件,将 "Build Action" 设置为 "Resource".
完成后,您现在需要创建将使用它的 ShaderEffect
class。看起来像这样:
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
class 的实例,即可在 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);
}
最后,我会注意到 ShaderEffect
对象的作者与我在这里展示的方式略有不同,所以如果您想查看其他效果示例和不同的实现方式,浏览该代码将是一个很好的地方看看。
尽情享受吧!