Skip to main content

...

....

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

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >();

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as

C# WPF Tutorial - Implementing IScrollInfo [Advanced]

The ScrollViewer in WPF is pretty handy (and quite flexible) - especially when compared to what you had to work with in WinForms ( ScrollableControl ). 98% of the time, I can make the ScrollViewer do what I need it to for the given situation. Those other 2 percent, though, can get kind of hairy. Fortunately, WPF provides the IScrollInfo interface - which is what we will be talking about today. So what is IScrollInfo ? Well, it is a way to take over the logic behind scrolling, while still maintaining the look and feel of the standard ScrollViewer . Now, first off, why in the world would we want to do that? To answer that question, I'm going to take a an example from a tutorial that is over a year old now - Creating a Custom Panel Control . In that tutorial, we created our own custom WPF panel (that animated!). One of the issues with that panel though (and the WPF WrapPanel in general) is that you have to disable the horizontal scrollbar if you put the panel in a ScrollV