如何使用C#反转XAML PNG图像的颜色?

10

我正在使用Visual Studio、C#、XAML和WPF。

我的程序中有带有白色png图标的XAML按钮。

输入图像描述

我想让用户可以通过从ComboBox中选择主题来切换到具有黑色图标的主题。

除了创建一组新的黑色png图片外,是否有一种方法可以通过XAML和C#来反转白色图标的颜色?

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

3
这些链接可能会有所帮助:使用ColorMatrix创建负片图像WPF - 在运行时修改图像颜色 (C#) - Keyur PATEL
1
这个库附带了几个预定义的WPF着色器效果,包括颜色反转。 - Bradley Uffner
1个回答

18

谢谢您的问题。这让我有机会学到一些新知识。:)

一旦您知道自己在做什么,达成您的目标就非常容易。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对象上略有不同地实现了它们,所以如果你想看到其他效果和不同的实现方式,浏览那些代码将是一个很好的选择。
享受吧!

1
非常感谢。以后参考这里中建议的方法,首先通过color.rgb /= color.a计算像素颜色上的alpha印象,然后应用反转color.rgb = 1 - color.rgb,最后通过color.rgb *= color.a添加alpha,对带有alpha通道的像素产生了更好的效果。 - Hosein

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接