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

时间:2017-07-14 01:54:45

标签: c# wpf visual-studio xaml hlsl

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

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

enter image description here

我想拥有它,所以你可以通过从ComboBox中选择主题来切换到带有黑色图标的主题。

有没有一种方法可以使用XAML和C#来反转白色图标的颜色,而不是创建一组新的黑色png图像?

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v4.view.ViewPager.setCurrentItem(int)' on a null object reference

1 个答案:

答案 0 :(得分:9)

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

你的目标是,一旦你知道自己在做什么,就很容易实现。 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<my shader file>.ps <my shader file>.hlsl

当然,您可以使用实际文件名替换<my shader file>

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

然后,您可以在项目中包含.ps文件,将&#34;构建操作&#34; 设置为&#34;资源&#34;

完成后,您现在需要创建将使用它的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对象的作者与我在这里展示的方式略有不同,所以如果你想看到其他效果的例子和实现它们的不同方法,浏览那些代码将是一个好地方看。

享受!