3
votes

I have an image where I need to change the background colour (E.g. changing the background of the example image below to blue).

However, the image is anti-aliased so I cannot simply do a replace of the background colour with a different colour.

One way I have tried is creating a second image that is just the background and changing the colour of that and merging the two images into one, however this does not work as the border between the two images is fuzzy.

Is there any way to do this, or some other way to achieve this that I have no considered?

Example image

4
You would be better off with a transparent background to start with. Then you can overlay the image on any coloured background you wantmusefan
If you have control over the original image then @musefan 's comment is the way to do this.James Barrass
If you don't have control over the original image then... If all your source images are only black and white, and the only time a pixel colour is not solid black (#000) or solid white (#FFF) then you can process each pixel individually, if the pixel colour is solid black then ignore, if the pixel is solid white then change to blue. If it is a greyscale (i.e. part of the anti-aliasing), then I reckon you can workout the percentage of black involved, create a blue pixel with the same percentage of black mixed in, and use thatmusefan
@musefan I thought that as the image is anti-aliased, even with a transparent background the edges have to contain some pixels that merge into the desired background colour otherwise it ends up with jagged edges?Richard Dalton
@RichardDalton: Yeah, but I would expect them to be merged with transparent (i.e. have a alpha value of less than 100%). I think it would work. Do a quick test, using a photo editor, have your transparent background version as the top layer and then paint a blue layer underneath. See how smooth it looks. Paint.net is free if you don't have anything like photoshop (its not as good, but can manage this)musefan

4 Answers

8
votes

Just using GDI+

Image image = Image.FromFile("cloud.png");

Bitmap bmp = new Bitmap(image.Width, image.Height);
using (Graphics g = Graphics.FromImage(bmp)) {
  g.Clear(Color.SkyBlue);
  g.InterpolationMode = InterpolationMode.NearestNeighbor;
  g.PixelOffsetMode = PixelOffsetMode.None;
  g.DrawImage(image, Point.Empty);
}

resulted in:

enter image description here

4
votes

Abstractly

Each pixel in your image is a (R, G, B) vector, where each component is in the range [0, 1]. You want a transform, T, that will convert all of the pixels in your image to a new (R', G', B') under the following constraints:

  • black should stay black
    • T(0, 0, 0) = (0, 0, 0)
  • white should become your chosen color C*
    • T(1, 1, 1) = C*

A straightforward way to do this is to choose the following transform T:

T(c) = C* .* c  (where .* denotes element-wise multiplication)

This is just standard image multiplication.

Concretely

If you're not worried about performance, you can use the (very slow) methods GetPixel and SetPixel on your Bitmap to apply this transform for each pixel in it. If it's not clear how to do this, just say so in a comment and I'll add a detailed explanation for that part.

Comparison

Compare this to the method presented by LarsTech. The method presented here is on the top; the method presented by LarsTech is on the bottom. Notice the undesirable edge effects on the bottom icon (white haze on the edges).

Comparison

And here is the image difference of the two:

Difference

Afterthought

If your source image has a transparent (i.e. transparent-white) background and black foreground (as in your example), then you can simply make your transform T(a, r, g, b) = (a, 0, 0, 0) then draw your image on top of whatever background color you want, as LarsTech suggested.

1
votes

If it is a uniform colour you want to replace you could convert this to an alpha. I wouldn't like to code it myself!

You could use GIMP's Color To Alpha source code (It's GPL), here's a version of it

P.S. Not sure how to get the latest.

1
votes

Background removal /replacement, IMO is more art than science, you’ll not find one algorithm fit all solution for this BUT depending on how desperate or interested you are in solving this problem, you may want to consider the following explanation:

Let’s assume you have a color image.

Use your choice of decoding mechanism and generate a gray scale / luminosity image of your color image.

Plot a graph (metaphorically speaking) of numeric value of the pixel(x) vs number of pixels in the image for that value(y). Aka. a luminosity histogram.

Now if your background is large enough (or small), you’d see a part of the graph representing the distribution of a range of pixels which constitute your background. You may want to select a slightly wider range to handle the anti-aliasing (based on a fixed offset that you define if you are dealing with similar images) and call it the luminosity range for your background.

It would make your life easier if you know at least one pixel (sample/median pixel value) out of the range of pixels which defines your background, that way you can ‘look up’ the part of the graph which defines your background.

Once you have the range of luminosity pixels for the background, you may run through the original image pixels, compare their luminosity values with the range you have, if it falls within, replace the pixel in the original image with the desired color, preferably luminosity shifted based on the original pixel and the sample pixel, so that the replaced background looks anti-aliased too.

This is not a perfect solution and there are a lot of scenarios where it might fail / partially fail, but again it would work for the sample image that you had attached with your question.

Also there are a lot of performance improvement opportunities, including GPGPU etc.

Another possible solution would be to use some of the pre-built third party image processing libraries, there are a few open source such as Camellia but I am not sure of what features are provided and how sophisticated they are.