How to invert the color of XAML PNG images using C #?

I am using Visual Studio, C #, XAML, WPF.

In my program, I have XAML buttons with white png icons.

enter image description here

I want you to be able to switch to the black icon theme by selecting the theme from the ComboBox.

Instead of creating a new set of black png images, is there a way with XAML and C # I can invert the color of the white icons?

<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


source to share


1 answer


Thanks for this question. This gave me the opportunity to learn something new. :)

Your goal, when you know what you are doing, is very easy to achieve. WPF supports using GPU shaders to modify images. They are fast at runtime (since they run on your graphics card) and are easy to apply. And in the case of the stated goal, invert colors is very easy to implement.

First you need some shader code. Shaders are written in a language called High Shader Language or HLSL. Here is an HLSL program that inverts the input color:

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

      

But Visual Studio doesn't handle this kind of code directly. You need to make sure you have the DirectX SDK installed, which will give you the fxc.exe compiler used to compile the shader code.

I have compiled the above using this command line:

fxc / T ps_3_0 / E main / Fo <my shader file> .ps <my shader file> .hlsl

Unless, of course, you replace <my shader file>

with your actual filename.

(Note: I did it manually, but you can of course create your own build action in your project to do the same.)

Then you can include the file .ps

in your project by setting "Build Action" to "Resource".

So now you need to create a class ShaderEffect

that will use it. It looks like this:



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

}

      

Indicates the code above:

  • You only need one copy of the shader itself. So I am initializing this in the field static readonly

    . Since the file is .ps

    included as a resource, I can reference it using a schema pack:

    like "pack://application:,,,/<my shader file>.ps"

    . Again, you will need to replace <my shader file>

    with the actual filename of course.
  • In the constructor, you must set a property PixelShader

    for the shader object. You must also call UpdateShaderValue()

    to initialize the shader for each property used as input to the shader (in this case there is only one).
  • The property Input

    is special: registering a dependency property requires using RegisterPixelShaderSamplerProperty()

    .
  • If your shader had other parameters, they would normally be registered with DependencyProperty.Register()

    . But they require a special value PropertyChangedCallback

    obtained by calling ShaderEffect.PixelShaderConstantCallback()

    with the register index declared in the shader code for this parameter.

That's all it takes!

You can use the above in XAML by simply setting the property UIElement.Effect

to an instance of the class InvertEffect

. For example:

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

      

When you run this, you will notice that although the gradient is defined as black in the upper left transition to white in the lower right corner, it renders in reverse, with white in the upper left and black in the lower right corner.

Finally, in an independent situation, you just want to run this job immediately and not have access to the fxc.exe compiler, here is the version above which has the compiled shader code embedded in Base64. It's tiny, so it's a practical alternative to compiling and including the shader as a resource.

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

}

      

Finally, I'll note that the link suggested in Bradley's comment contains a whole bunch of such effects implemented in the shader. The author of those who implemented HLSL objects and ShaderEffect

is somewhat different from the way I show here, so if you want to see other examples of effects and different ways to implement them, looking at this code is a great place to look.

Enjoy!

+5


source







All Articles