10

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);
Bob.
  • 3,894
  • 4
  • 44
  • 76

4 Answers4

11

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

Sayse
  • 42,633
  • 14
  • 77
  • 146
  • 4
    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. – Robert S. Apr 02 '15 at 07:51
  • 1
    Thank you. Very usefull. But bits red and blue are inversed. ;-) – Rodrigo Perez Burgues Sep 16 '15 at 16:04
  • Would probably be more user-friendly to make the args `Color` objects ;) – Nyerguds Jan 09 '18 at 11:49
  • 1
    Note that you should _really_ take the pixel format _from the image_ and _check it_, rather than just assuming it. – Nyerguds Jan 09 '18 at 11:50
  • 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. – Nyerguds Jan 09 '18 at 11:52
4

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.

flndr
  • 455
  • 1
  • 6
  • 16
-1

You can use SetPixel for that:

private void ChangeColor(Bitmap s, System.Drawing.Color source, System.Drawing.Color target)
{
    for (int x = 0; x < s.Width; x++)
    {
        for (int y = 0; y < s.Height; y++)
        {
            if (s.GetPixel(x, y) == source)
                s.SetPixel(x, y, target);
        }                
    }
}

GetPixel and SetPixel are wrappers around gdiplus.dll functions GdipBitmapGetPixel and GdipBitmapSetPixel accordingly

Remarks:

Depending on the format of the bitmap, GdipBitmapGetPixel might not return the same value as was set by GdipBitmapSetPixel. For example, if you call GdipBitmapSetPixel on a Bitmap object whose pixel format is 32bppPARGB, the pixel's RGB components are premultiplied. A subsequent call to GdipBitmapGetPixel might return a different value because of rounding. Also, if you call GdipBitmapSetPixel on a Bitmap object whose color depth is 16 bits per pixel, information could be lost during the conversion from 32 to 16 bits, and a subsequent call to GdipBitmapGetPixel might return a different value.

volody
  • 6,946
  • 3
  • 42
  • 54
  • 1
    Inefficient, and totally to short as an answer. – SimpleVar May 02 '13 at 19:34
  • 1
    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+... – Bob. May 02 '13 at 19:37
  • 2
    If there would already exist a method in the language for this, wouldn't it do the same iteration in the background? ... – Szabolcs Antal Sep 24 '15 at 12:20
-2

You need a library that provides a way to modify the color space of an image without having to work with pixels. LeadTools has a pretty extensive image library that you can use that supports color space modifications, including swapping colors.

Tombala
  • 1,660
  • 9
  • 11