The basic process is to base each pixel's alpha value on its euclidean distance from the transparent color. You could do that directly to the 8-bit alpha channel of a 32bpp ARGB image, but I forsaw a little more math down the line, so I chose to do the processing to a 2 dimensional array of doubles the same size as the image.
Here's the code to generate the map:
public static double[][] GetDistanceMap(Bitmap bm, Color c){ double[][] distanceMap = new double[bm.Width][]; for (int x = 0; x < bm.Width; x++) { distanceMap[x] = new double[bm.Height]; for (int y = 0; y < bm.Height; y++) { distanceMap[x][y] = GetEuclideanDistance(c, bm.GetPixel(x, y)); } } return distanceMap;}private static double GetEuclideanDistance(Color c1, Color c2){ int r1 = Convert.ToInt32(c1.R); int g1 = Convert.ToInt32(c1.G); int b1 = Convert.ToInt32(c1.B); int r2 = Convert.ToInt32(c2.R); int g2 = Convert.ToInt32(c2.G); int b2 = Convert.ToInt32(c2.B); return Math.Sqrt(((r1 * r1) - (r2 * r2)) + ((g1 * g1) - (g2 * g2)) + ((b1 * b1) - (b2 * b2)));}
If you wanted to apply this directly to an image's alpha channel, you would certainly get improved results over GDI+'s MakeTransparent method which just finds a color and turns a pixel on or off. This, on the otherhand, will turn pixels more opaque the farther they are away from the chosen transparent color.
In order to apply our double precision values to an 8-bit (256 value) channel of an image, we're going to need to scale the numbers to the theoretical maximum Euclidean distance. We can calculate that if we know the number of bits per color (not pixel) which were used in our source image. For now, I'm going to assume that we'll be working with 24 bit RGB images and anything that is not will be converted beforehand.
Dmax = SQRT(2552 + 2552 + 2552)
And here's the code we'll use to apply a distance map to an image:
public static Bitmap ApplyDistanceMap(Bitmap sourcebm, double[][] distanceMap){ Bitmap bm = new Bitmap(sourcebm.Width, sourcebm.Height, PixelFormat.Format32bppArgb); double maxDistance = GetMaxDistance(bm); for (int x = 0; x < bm.Width; x++) { for (int y = 0; y < bm.Height; y++) { Color c = sourcebm.GetPixel(x, y); int val = ScaleDoubleTo255(distanceMap[x][y], maxDistance); bm.SetPixel(x, y, Color.FromArgb(val, c)); } } return bm;}
This will turn the following image with a white background into the one with the red background. NOTE: You can't see the transparent layer of the PNG unless your browser supports it. You're out of luck, IE users. Go get Firefox.
Original:
Original with alpha channel:
Not bad. The transparency is smooth, but there's still a halo of white around it. Next time I'll show how to improve on that just a tad.
-
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.