Fading Images Together using C#
I recently began a project that required me to fade two images together.
When I say "fade," I mean that I want to color an image based on another. Some people call this blending, or simply layering one image on top of the other and setting the opacity/transparency of the upper image. I'm not really sure what the formal term for it is, but this is basically accomplished by averaging the pixel values between the two images where they overlap. Don't be intimidated if you're unfamiliar with images or pixels - read on. The effect produced is pretty cool.
Up until now, I hadn't used any image manipulation classes in .NET, but I had done some simple image splicing in Java (using the excellent Graphics package). So, I was excited to see what facilities were going to be provided by .NET for doing similar operations. Honestly, I didn't even know what class to use, so I set out by dropping a PictureBox control onto a Windows Forms application. One of the properties I saw in the Property Editor for this control was the Image property, which was of type System.Drawing.Image. Bingo!
I switch to my C# code and set about creating an Image instance. Now, if I know the .NET framework (and I do), I'd be willing to bet System.Drawing.Image is an abstract base class, but its a good starting point nonetheless. Indeed, creating an instance using the new keyword fails because, as expected, it is an abstract class.
The next thing I generally do is check to see if there is a static Factory method within the base class that will create an instance for me. I type Image. (note the dot) and let Visual Studio's Intellisense go to work for me.
It looks like the FromFile() method will do what I want. I call this method with the filename of an existing image, and move on to the next line. Again, I will use my trusty Intellisense to find out what I can do with this Image object. To my horror, I find that the Image class does not provide me any information about the pixels (individual colored dots) in the underlying image, nor does it show the robust list of image transformations I expected to be able to perform. Hell, it doesn't even let you set the Width and Height properties. Who are you, and what have you done with .NET?
This is obviously going to take a little bit more research. You might've noticed in the first screenshot that the abstract Image class provides functionality for its descendants (as is the purpose of an abstract class), including System.Drawing.Bitmap. I was in a sporting mood, so I decided to check out the class hierarchy. Normally, this would be done using a combination of the Object Browser and the Image class summary, which gives us the hint that its descendent classes are System.Drawing.Bitmap and System.Drawing.Imaging.Metafile. However, an addin I use called Resharper allows me to right click on the Image class in my code and find all types that derive from it, like so:
This confirms that Bitmap and Metafile are the only classes that inherit from Image in the .NET framework. I refer to the Object Browser again to inspect these classes, starting with Bitmap.
From this, I can see that the Bitmap class is intended to be used where it is necessary to work with pixel data, and also that it has methods for getting and setting individual pixels (as well as some of the other more complicated image manipulation behavior I expected to see in Image). This looks promising, so I can move forward from here by instantiating both of my Images as Bitmaps, and attempting to fade their pixel data together.
Given that I have two Bitmap objects that I want to fade together, aptly named image1 and image2, I can use the following code to "average" the overlapping area of the images:
for (int i = 0; i < image1.Width &amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp; i < image2.Width; i++)
{
for (int j = 0; j < image1.Height &amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp; j < image2.Height; j++)
{
Color image1Pixel = image1.GetPixel(i, j), image2Pixel = image2.GetPixel(i, j);
Color newColor = Color.FromArgb((image1Pixel.R + image2Pixel.R)/2, (image1Pixel.G + image2Pixel.G)/2, (image1Pixel.B + image2Pixel.B)/2);
image1.SetPixel(i, j, newColor);
}
}
Let me walk you through what's going on here. Each Bitmap is essentially a two-dimensional array of pixels (represented in your code by a System.Drawing.Color object). Thus, we use a nested loop to iterate through them. Note that our for loop will only proceed while both i and j are within the bounds of both images. This will make our code look a little nice by not requiring us to check if each pixel we extract is within its image's bounds (the code can be easily extended to handle non-overlapping areas, if required). Anyway, within our loops, we extract the pixels present in both images at our current position to Color objects by calling GetPixel(i,j) on each image. Then, we construct a new Color object using the Color.Argb() factory method. The overload of the Argb() method I used takes in three integer parameters: a red, a green, and a blue, to determine the overall color of the pixel. These are given by the R, G, and B integer properties of the two Color objects we created, so we simply average them, respectively. Finally, we set the pixel value at that location with this new Color object (I arbitrarily chose to do so in image1 here, overwriting its contents).
Simply enough, yes? This will give us a nice overall average for the two images, or what I'm calling the "fade" effect. Since this worked out so nicely, what if we got a little bit deeper? The project I'm doing actually requires my "fade" effect to be weighted heavily in favor of one of the images, which is the same as giving less transparency to the image that is laid on top of the other. I know that my idea of averaging the two pixel values worked out just fine, so I should be able to calculate a weighted average just as easily, given some parameter to tell me how strongly to do so.
I extracted this code to a static method in another class. It could've been done simply with a helper method, or by subclassing Bitmap, but I prefer to have this helper class present in case I decide to add any further functionality. I specify that I'll take two Bitmap objects and an integer parameter, which will represent how heavily to weight the first image over the second. I have also changed the code to be slightly more "proper," in that I create a new Bitmap object instead of changing image1's data. Finally, you'll see that I compute the new RGB values using a weighted average algorithm, where the original values are multiplied by their weights and their sum is divided by the total weight.
I thought for a moment here about whether or not I could get an integer overflow here. However, it turns out that image file formats store, at most, 32-bits of information per pixel. Since these 32-bits are divided up across the red, green, and blue components of the pixel, we are usually going to be working with 8-bit integers (which range from 0-255). The int data type is 32-bit, so we should never run into this problem.
The updated code looks like this:
public static Bitmap Fade(Bitmap image1, Bitmap image2, int opacity)
{
Bitmap newBmp = new Bitmap(Math.Min(image1.Width,image2.Width), Math.Min(image1.Height,image2.Height));
for (int i = 0; i < image1.Width &amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp; i < image2.Width; i++)
{
for (int j = 0; j < image1.Height &amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp; j < image2.Height; j++)
{
Color image1Pixel = image1.GetPixel(i, j), image2Pixel = image2.GetPixel(i, j);
Color newColor = Color.FromArgb( (image1Pixel.R * opacity + image2Pixel.R * (100 - opacity) ) / 100, (image1Pixel.G * opacity + image2Pixel.G * (100 - opacity) ) / 100, (image1Pixel.B * opacity + image2Pixel.B * (100 - opacity) ) / 100);
newBmp.SetPixel(i, j, newColor);
}
}
return newBmp;
}
One thing to note in the code that will call this method is that you no longer need to use the Image.FromFile() method. If you do, you'll have to cast that Image instance down to Bitmap, like so:
pictureBox1.Image = ImageFader.Fade((Bitmap) Image.FromFile(label1.Text), (Bitmap) Image.FromFile(label2.Text), trackBar1.Value);
Instead, you can use the more natural Bitmap constructor:
pictureBox1.Image = ImageFader.Fade(new Bitmap(label1.Text), new Bitmap(label2.Text), trackBar1.Value);
Well, that about wraps it up. I didn't touch the rather robust GDI+ Graphics class (which is what I was familiar with from Java), and it certainly isn't the fastest code I've ever written. However, for a first stab at the problem, I am pretty happy to have such a simple solution. I'll look more into using a Graphics object to speed up execution in the future, but for now, if anyone has any suggestions on improving this code, I'm all ears (eyes?) Here's a screenshot of the Windows Forms application I built to test this algorithm out:
Download
The source code and Windows binary for this project can be downloaded here: Image Fader Windows Forms Project
Links
Since the .NET framework is closed source, check out the Microsoft Developer's Network for class documentation and examples:






Subscribe to this category
I'm curious. Where you asked to watermark an image? I'm having trouble thinking of another scenario for this, maybe a collage?
March 17th, 2009 at 4:33 PM
No, not watermarking exactly. This project was very similar to a collage. After assembling images into a collage, I wanted to select a group of them and color them like another image. This was something that was inspired by Silverlight's Deep Zoom, but with an emphasis on finding new images as you zoom in further.
March 17th, 2009 at 4:37 PM