Let's say you have two or more separate images and you'd like to combine
those into a single image using C#. That's what this tutorial is going
to demonstrate. We're going to create a function that takes multiple
images and combines them into a large panoramic.
I had a collection of 800 images and when I
attempted to import them separately into Deep Zoom Composer, it ate up
all of my system's memory and crashed. I needed to combine several
images together into rows and import those into Deep Zoom Composer.
Here's an example of what the code in this tutorial will produce.
This image was produced by stitching together ten O'Reilly book covers.
The background can be changed to any color, the width is simply the
total width of all the images combined, and the height is set to the
tallest image in the collection.
First off, here's the method signature we'll be filling in the details
for.
public static System.Drawing.Bitmap Combine(string[] files)
{
}
The function will take an array of strings, which are paths to images
files, and returns a
Bitmap
that will be a new image that is the combination of all the images
passed in.
We'll start by pulling in all of the images into memory. If memory is a
concern, the code can be written to only have one image in memory at a
time, however for this example, I chose speed over memory.
public static System.Drawing.Bitmap Combine(string[] files)
{
//read all images into memory
List<System.Drawing.Bitmap> images = new List<System.Drawing.Bitmap>();
System.Drawing.Bitmap finalImage = null;
int width = 0;
int height = 0;
foreach (string image in files)
{
//create a Bitmap from the file and add it to the list
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(image);
//update the size of the final bitmap
width += bitmap.Width;
height = bitmap.Height > height ? bitmap.Height : height;
images.Add(bitmap);
}
//create a bitmap to hold the combined image
finalImage = new System.Drawing.Bitmap(width, height);
}
So here I create a List to hold my Bitmap objects. I also declare the
final image that will be returned by this function. As I create the
images, I update the width that the final image will be. I also update
the height to the tallest image. The last thing I do is create my final
image using the width and height calculated from the input images.
Since Bitmap implements
IDisposable,
we're going to need to add some code to dispose of all these objects
when we're done. Since exceptions can be thrown at any point in this
code, this is a good opportunity to use the finally statement.
public static System.Drawing.Bitmap Combine(string[] files)
{
//read all images into memory
List<System.Drawing.Bitmap> images = new List<System.Drawing.Bitmap>();
System.Drawing.Bitmap finalImage = null;
try
{
int width = 0;
int height = 0;
foreach (string image in files)
{
//create a Bitmap from the file and add it to the list
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(image);
//update the size of the final bitmap
width += bitmap.Width;
height = bitmap.Height > height ? bitmap.Height : height;
images.Add(bitmap);
}
//create a bitmap to hold the combined image
finalImage = new System.Drawing.Bitmap(width, height);
return finalImage;
}
catch(Exception ex)
{
if (finalImage != null)
finalImage.Dispose();
throw ex;
}
finally
{
//clean up memory
foreach (System.Drawing.Bitmap image in images)
{
image.Dispose();
}
}
}
If an exception occurs, the code will enter the catch block and dispose
of the final image before re-throwing the exception up the chain. This
way the calling code has a chance to see what went wrong. The finally
block is always called regardless of how the try statement exits. In the
finally block, we simply loop through each Bitmap in the images List and
dispose them.
So now we've created all the images and made sure we've cleaned up our
memory. All that's left is to actually draw each image onto the final
image.
public static System.Drawing.Bitmap Combine(string[] files)
{
//read all images into memory
List<System.Drawing.Bitmap> images = new List<System.Drawing.Bitmap>();
System.Drawing.Bitmap finalImage = null;
try
{
int width = 0;
int height = 0;
foreach (string image in files)
{
//create a Bitmap from the file and add it to the list
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(image);
//update the size of the final bitmap
width += bitmap.Width;
height = bitmap.Height > height ? bitmap.Height : height;
images.Add(bitmap);
}
//create a bitmap to hold the combined image
finalImage = new System.Drawing.Bitmap(width, height);
//get a graphics object from the image so we can draw on it
using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(finalImage))
{
//set background color
g.Clear(System.Drawing.Color.Black);
//go through each image and draw it on the final image
int offset = 0;
foreach (System.Drawing.Bitmap image in images)
{
g.DrawImage(image,
new System.Drawing.Rectangle(offset, 0, image.Width, image.Height));
offset += image.Width;
}
}
return finalImage;
}
catch(Exception ex)
{
if (finalImage != null)
finalImage.Dispose();
throw ex;
}
finally
{
//clean up memory
foreach (System.Drawing.Bitmap image in images)
{
image.Dispose();
}
}
}
Graphics objects also implement IDisposable, so we have to make sure we
clean those up too. This time I went with a using block. Internally, the
using block is implemented with a try-finally, so no matter what happens
inside the block, the Graphics object will be disposed. You can get more
information about the using statement in our using statementtutorial.
All I do to draw each image onto the final image is call
Graphics.DrawImage. I pass it the image I want to draw and where I'd
like to draw it. Each time I draw an image, I increase the offset so two
images aren't drawn on top of each other.
So that's it - we have now implemented a function that will stitch
together multiple images into one large image. All that's left is to see
how it works.
//get all the files in a directory
string[] files = Directory.GetFiles(@"C:\MyImages");
//combine them into one image
System.Drawing.Bitmap stitchedImage = Combine(files);
//save the new image
stitchedImage.Save(@"C:\stitchedImage.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
When the above code is called, all of the images in C:\MyImages will be
combined into one large image and saved to C:\stitchedImage.jpg.
All-in-all, the code required to do this isn't that difficult. The hard
part is making sure you've properly disposed of your Bitmap objects. Not
disposing objects that implement IDisposable is something very easy to
overlook.
Comments
Post a Comment