C# – Pixel by Pixel Color Conversion WriteableBitmap => PNG Black only to Color with Transparency

alphac++colorspixelwriteablebitmap

I am working on a silverlight application, where all my icons are PNGs.

The color of all these icons is black, or rather black to gray, depending on the alpha value. Every PNG has a transparent background.

Inside my application, I want to do a pixel by pixel color change from black or gray, let's say to red (before black) or light red (before gray).

Walking through the bytes of every pixel, I recognized, that every full black pixel has an alpha value of 255. Those, who are gray, have an an alpha value between 1 and 254. An alpha value of 0 seem to be transparent. The RGB values are all 0 for Black.

My idea was, that I change the color of every pixel, but keep the alpha value, so that the PNGs keep their original look.

For this purpose, I have written a little function, that looks like this:

private static WriteableBitmap ColorChange(WriteableBitmap wbmi, System.Windows.Media.Color color)
    {
        for (int pixel = 0; pixel < wbmi.Pixels.Length; pixel++)
        {
            if (wbmi.Pixels[pixel] != 0)
            {
                byte[] colorArray   = BitConverter.GetBytes(wbmi.Pixels[pixel]);
                byte blue           = colorArray[0];
                byte green          = colorArray[1];
                byte red            = colorArray[2];
                byte alpha          = colorArray[3];

                if (alpha > 0)
                {

                    //HERE, I change the color, but keep the alpha
                    //
                    wbmi.Pixels[pixel] = Gui.Colors.ToInt.Get(color,alpha);

                    colorArray  = BitConverter.GetBytes(wbmi.Pixels[pixel]);
                    blue        = colorArray[0];
                    green       = colorArray[1];
                    red         = colorArray[2];
                    alpha       = colorArray[3];

                    String colorString = "blue: " + blue.ToString() + "green: " + green.ToString() + "red: " + red.ToString() + "alpha: " + alpha.ToString();
                    System.Diagnostics.Debug.WriteLine(colorString);
                }
            }
        }
        return wbmi;
    }

And a little helper class to change the color but keep the alpha:

namespace Gui.Colors
{
public class ToInt
{

    public static int Get(System.Windows.Media.Color color, Byte alpha)
    {
        color.A = alpha;
        return color.A << 24 | color.R << 16 | color.G << 8 | color.B;
    }
}
}

My problem is, that I can clearly see, that the pixels with an alpha less then 255 (gray), are the problem. Although the alpha value is kept and the color has changed, the pixels look different, they are not transparent any more. This makes the edges look scraggy.

Long talk, short question: Looking at my code – what is the problem? How can I achieve a pixel by pixel color conversion with the transparency of the original PNG?

Please excuse my English. I am not a native English speaker.

Best Solution

This case is solved. I found the WriteableBitmapExtensions class written by Rene Schulte.

This extension class offers this function (and many many more...):

private const float PreMultiplyFactor = 1 / 255f;

public static void SetPixeli(this WriteableBitmap bmp, int index, byte a, Color color)
  {
     float ai = a * PreMultiplyFactor;
     bmp.Pixels[index] = (a << 24) | ((byte)(color.R * ai) << 16) | ((byte)(color.G * ai) << 8) | (byte)(color.B * ai);
  }

The routine to change the color looks now like this:

    /// <summary>
    /// Changes the color of every pixel in a WriteableBitmap with an alpha value > 0 to the desired color.
    /// </summary>
    /// <param name="wbmi"></param>
    /// <param name="color"></param>
    /// <returns></returns>
    private static WriteableBitmap ColorChange(WriteableBitmap wbmi, System.Windows.Media.Color color)
    {
        for (int pixel = 0; pixel < wbmi.Pixels.Length; pixel++)
        {
            byte[] colorArray = BitConverter.GetBytes(wbmi.Pixels[pixel]);
            byte blue         = colorArray[0];
            byte green        = colorArray[1];
            byte red          = colorArray[2];
            byte alpha        = colorArray[3];

            if (alpha > 0)
            {
                wbmi.SetPixeli(pixel, alpha, color);
            }
        }

        wbmi.Invalidate();
        return wbmi;
    }

You can download the work of Rene Schulte here:

http://dotnet.dzone.com/sites/all/files/WriteableBitmapExtensions.zip

The whole project can be found here:

http://writeablebitmapex.codeplex.com/

As no one answered this question here on SO, big gun salutes to Rene Schulte. YMMD! :)

By the way: I am using SVGs of the NounProject, convert them to PNGs in the desired size and then I use this methods to color them. As all the symbols in the NounProject are black, it's a good way to get high quality symbols.

Related Question