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.
Comments
-
Bob. almost 2 years
For
Bitmap
, there is aMakeTransparent
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 about 11 yearsInefficient, and totally to short as an answer.
-
Bob. about 11 yearsSo, if I had a picture, I'd have to do get
GetPixel
andSetPixel
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. about 9 yearsThanks. Helped me. You also can modify your code to work with transparent PNGs. Just change the pixel format to
PixelFormat.Format32bppArgb
and increasecounter
by 4 instead of 3. I needed that cause otherwise the code messed up the transparent background. -
Rodrigo Perez Burgues over 8 yearsThank you. Very usefull. But bits red and blue are inversed. ;-)
-
Szabolcs Antal over 8 yearsIf there would already exist a method in the language for this, wouldn't it do the same iteration in the background? ...
-
Nyerguds over 6 yearsWould probably be more user-friendly to make the args
Color
objects ;) -
Nyerguds over 6 yearsNote that you should really take the pixel format from the image and check it, rather than just assuming it.
-
Nyerguds over 6 yearsAnother 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.