Friday, June 02, 2006

Offroading in Morocco

I'm finally getting around to putting up these pictures that have been sitting around far too long. It was about this time last year when Barb and I were tooling around Morocco in a land rover with Trailmasters.

I have to admit I was VERY nervous, but the trip was amazing. Anyone who has the ability to do something like this should do it without hesitation. I've divided the pictures up into three sections to make navigating them a little easier.

#    Comments [0] |
 Monday, April 10, 2006

Smooth Alpha Layers with GDI+ [Part III]

Previously, I had applied an RGB Euclidean distance vector to the alpha channel of a jpeg and ended up with better results than Bitmap's MakeTransparent. There was, however, still a slight halo around the opaque parts of the image and I promised to show you a way that you might be able to improve upon that.

The plan to help get rid of the halo is to truncate the map at a lower an upper threshold and rescale the values in between. In other words, we know that there are lots of colors close to white that we want to throw away, and there's only a few colors around blue that we want to keep.

I chose to scale my thresholds to 8 bits instead of making the user guess (or know) the range of possible values for the distance map. Here's the code to truncate and rescale:

public static void SetThreshold(Bitmap bm, double[][] distanceMap, int limit1,
int limit2)
{
double maxDistance = GetMaxDistance(bm);
double lim1 = Scale255ToDouble(limit1, maxDistance);
double lim2 = Scale255ToDouble(limit2, maxDistance);

for (int x = 0; x < distanceMap.Length; x++)
{
for (int y = 0; y < distanceMap[x].Length; y++)
{
distanceMap[x][y] = ScaleDistanceToRange(distanceMap[x][y],
maxDistance, lim1, lim2);
}
}
}

private static double ScaleDistanceToRange(double val, double maxVal,
double lim1, double lim2)
{
if (val <= lim1) return 0;
if (val >= lim2) return maxVal;

return ((val - lim1) / (lim2 - lim1)) * maxVal;
}

And here's the results... (Again, this doesn't work fully in IE because it won't make PNGs transparent!)

Original:

Original with alpha channel:

This is about as good as you can expect, however, I haven't tried with a wider array of colors. I have a feeling that with more colors, it might be worth transforming the image to CSV and processing based on one or more of those channels.

#    Comments [0] |
 Saturday, April 08, 2006

Smooth Alpha Layers with GDI+ [Part II]

As I was saying before, I need to make an image with a smooth transparency. In other words, pick a transparent color and base an alpha layer on that with smooth edges.

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.

#    Comments [0] |
 Friday, April 07, 2006

Smooth Alpha Layers with GDI+

I'm working on a project with a Flash programmer friend, Jack. In one of those seemingly innocuous discussions that inevitably start with the phrase "Shouldn't you be able to...". and end up with me furiously flipping through books or googling for algorithms.

Jack needs to be able to upload an image in any reasonable format (gif, jpeg, tiff, png) and have it converted to a png. That's easy enough, but then he needs to be able to supply an x,y coordinate and have the color of that pixel be made transparent. Again, that's not so difficult in and of itself, but to make it look GOOD, you need to make sure that you grab not only that color, but a range of colors.

That's all well and good, but by the time someone is uploading an image, it's most likely got some anti-aliasing between the color you want to be transparent and the colors you want to keep. That will certainly cause some artifacts that we want to try to avoid...

So here's my plan of attack:

1. Calcualte the Euclidean distance of each of the pixels from the color to be made transparent. There are a number of good websites with information about this. The formula is:

     d = SQRT((R1 - R2)2 + (G1 - G2)2 + (B1 - B2)2)

This will effectively make a grayscale bitmap whose pixels are black if the colors are exactly the same as the target color and get lighter as they stray away.

2. The next step is similar to what you do when you tweak the histogram of an image in Photoshop. Any pixel whose value is less than a given threshold will be made completely transparent (alpha value of 0%). Any pixel whose value is greater than another number becomes fully opaque (alpha 100%). In between, the values are weighted to a scale from 0-100%

3. Producing a transparent PNG from there is a simple matter of applying that grayscale image to the alpa channel of the original image! Getting that image into Flash 7 is another matter. (But that's another show)

What about Bitmap.MakeTransparent(Color c) I hear you say? I'll be it works great on images you have full control over and started off as bitmaps, but what about jpegs which need to be supplied by anyone and their dog? Here's what MakeTransparent does to a jpeg: (NOTE: this sample doesn't work with IE because it doesn't properly display alpha channels for PNGs)

Original:

Original with MakeTransparent(Color.White):

#    Comments [0] |

-