Can you change one colour to another in an Bitmap image?

10,641

Solution 1

Through curiosity to Yorye Nathan's comment, This is an extension that I created by modifying http://msdn.microsoft.com/en-GB/library/ms229672(v=vs.90).aspx.

It can turn all pixels in a bitmap from one colour to another.

public static class BitmapExt
{
    public static void ChangeColour(this Bitmap bmp, byte inColourR, byte inColourG, byte inColourB, byte outColourR, byte outColourG, byte outColourB)
    {
        // Specify a pixel format.
        PixelFormat pxf = PixelFormat.Format24bppRgb;

        // Lock the bitmap's bits.
        Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        BitmapData bmpData =
        bmp.LockBits(rect, ImageLockMode.ReadWrite,
                     pxf);

        // Get the address of the first line.
        IntPtr ptr = bmpData.Scan0;

        // Declare an array to hold the bytes of the bitmap. 
        // int numBytes = bmp.Width * bmp.Height * 3; 
        int numBytes = bmpData.Stride * bmp.Height;
        byte[] rgbValues = new byte[numBytes];

        // Copy the RGB values into the array.
        Marshal.Copy(ptr, rgbValues, 0, numBytes);

        // Manipulate the bitmap
        for (int counter = 0; counter < rgbValues.Length; counter += 3)
        {
            if (rgbValues[counter] == inColourR &&
                rgbValues[counter + 1] == inColourG &&
                rgbValues[counter + 2] == inColourB)

             {
                rgbValues[counter] = outColourR;
                rgbValues[counter + 1] = outColourG;
                rgbValues[counter + 2] = outColourB;
             }
        }

        // Copy the RGB values back to the bitmap
        Marshal.Copy(rgbValues, 0, ptr, numBytes);

        // Unlock the bits.
        bmp.UnlockBits(bmpData);
    }
}

called by bmp.ChangeColour(0,128,0,0,0,0);

Solution 2

Lifting the code from this answer:

public static class BitmapExtensions
{
    public static Bitmap ChangeColor(this Bitmap image, Color fromColor, Color toColor)
    {
        ImageAttributes attributes = new ImageAttributes();
        attributes.SetRemapTable(new ColorMap[]
        {
            new ColorMap()
            {
                OldColor = fromColor,
                NewColor = toColor,
            }
        }, ColorAdjustType.Bitmap);

        using (Graphics g = Graphics.FromImage(image))
        {
            g.DrawImage(
                image,
                new Rectangle(Point.Empty, image.Size),
                0, 0, image.Width, image.Height,
                GraphicsUnit.Pixel,
                attributes);
        }

        return image;
    }
}

While I haven't benchmarked it, this should be faster than any solution that's doing GetPixel/SetPixel in a loop. It's also a bit more straightforward.

Share:
10,641
Bob.
Author by

Bob.

MVC3/Razor, WPF, EF 4/5, C#

Updated on July 19, 2022

Comments

  • Bob.
    Bob. almost 2 years

    For Bitmap, there is a MakeTransparent method, is there one similar for changing one color to another?

    // This sets Color.White to transparent
    Bitmap myBitmap = new Bitmap(sr.Stream);
    myBitmap.MakeTransparent(System.Drawing.Color.White);
    

    Is there something that can do something like this?

    Bitmap myBitmap = new Bitmap(sr.Stream);
    myBitmap.ChangeColor(System.Drawing.Color.Black, System.Drawing.Color.Gray);
    
  • SimpleVar
    SimpleVar about 11 years
    Inefficient, and totally to short as an answer.
  • Bob.
    Bob. about 11 years
    So, if I had a picture, I'd have to do get GetPixel and SetPixel n times where n is the number of pixels. So for a regular image, even a logo, that'll probably be n = 150k+...
  • Robert S.
    Robert S. about 9 years
    Thanks. Helped me. You also can modify your code to work with transparent PNGs. Just change the pixel format to PixelFormat.Format32bppArgb and increase counter by 4 instead of 3. I needed that cause otherwise the code messed up the transparent background.
  • Rodrigo Perez Burgues
    Rodrigo Perez Burgues over 8 years
    Thank you. Very usefull. But bits red and blue are inversed. ;-)
  • Szabolcs Antal
    Szabolcs Antal over 8 years
    If there would already exist a method in the language for this, wouldn't it do the same iteration in the background? ...
  • Nyerguds
    Nyerguds over 6 years
    Would probably be more user-friendly to make the args Color objects ;)
  • Nyerguds
    Nyerguds over 6 years
    Note that you should really take the pixel format from the image and check it, rather than just assuming it.
  • Nyerguds
    Nyerguds over 6 years
    Another note: this ignores the stride when traversing bytes. Bad idea. Go over the image line by line, or your manipulations will shift after the first line if your stride doesn't match the exact data length for one line of pixels. The stride is generally aligned to a multiple of four bytes.