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

Posted

技术标签:

【中文标题】如何使用 C# 反转 XAML PNG 图像的颜色?【英文标题】:How to Invert Color of XAML PNG Images using C#? 【发布时间】:2017-12-18 23:45:43 【问题描述】:

我正在使用 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>

【问题讨论】:

这些链接可能会有所帮助:Using ColorMatrix for Creating Negative Image 和 WPF - Modifying Image Colors on the Fly (C#) This library 带有几个预定义的 WPF 着色器效果,包括颜色反转。 【参考方案1】:

感谢您提出这个问题。它给了我一个学习新东西的机会。 :)

您的目标是,一旦您知道自己在做什么,就很容易实现。 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

当然,您将 &lt;my shader file&gt; 替换为您的实际文件名。

(注意:我手动完成了此操作,但您当然可以在项目中创建自定义构建操作来执行相同操作。)

然后您可以在项目中包含.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:,,,/&lt;my shader file&gt;.ps"。同样,您当然需要将 &lt;my shader file&gt; 替换为实际的文件名。 在构造函数中,您必须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 对象的作者与我在此处展示的方式略有不同,因此如果您想查看其他效果示例和实现它们的不同方式,浏览该代码将是一个很好的地方.

享受吧!

【讨论】:

非常感谢。对于将来的参考,使用here 中建议的方法首先通过color.rgb /= color.a 计算像素颜色的alpha 印象,然后通过color.rgb = 1 - color.rgb 应用invert,然后通过color.rgb *= color.a 添加alpha,对于具有alpha 通道的像素可以获得更好的结果。

以上是关于如何使用 C# 反转 XAML PNG 图像的颜色?的主要内容,如果未能解决你的问题,请参考以下文章

自定义 UIActivity :图像图标反转颜色

C# XAML Flyout 更改标题背景颜色

WPF C# 图像源

FFMPEG在将tiff图片转换为png时反转颜色

如何使用背景图像切换背景颜色[重复]

如何使用 CSS 替换 PNG 图像的颜色? [复制]