C# - Combining Images [Beginner]


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.

Example Sticked
Image

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