One of the common suggestions for improving the speed of your website is to combine all your images into a single image file, or sprite file, and use CSS to display only the specific sprite you want to display.
As I start to incorporate a number of icons and other small images into my websites, I decided it was time for me to generate a single sprite image, along with the CSS file to access each of the images.
The problem was that I had a large number of image files. Most of the online generators topped out at about 500 images, and I was well past that number for all the icons I had collected. At this point I had 2 options – I could either take the time to narrow down all my icons to only those I was using and might use in the future, or I could write my own generator to handle everything for me.
Since the basic concept was pretty straightforward, I decided to write my own.
Generating a Single Image File
The first thing I needed to do was combine all my images into a single image file that I could address using CSS. The .NET framework provides a couple of easy to use image and drawing libraries, so I decided to write my generator in C#. Besides, a windows-based utility would let me easily tweak some of the settings later on down the road.
Getting the List of Images
I decided to make my generator work on an entire directory. This simplified a lot of code, since all I needed to do was point it at a specific directory and run it.
DirectoryInfo info = new DirectoryInfo(this.textBoxFolder.Text);List<Image> images = new List<Image>();var files = info.GetFiles();
int maxWidth = 0;
int maxHeight = 0;
foreach (var file in files) {var img = Image.FromFile(file.FullName);maxWidth = Math.Max(img.Width, maxWidth);
maxHeight = Math.Max(img.Height, maxHeight);
img.Tag = Path.GetFileNameWithoutExtension(file.FullName);
images.Add(img);}maxWidth++;maxHeight++;System.Drawing.Bitmap sprite = GenerateSprite(images, maxWidth, maxHeight);
string fileName = Path.ChangeExtension(this.outputTextBox.Text, "png");sprite.Save(Path.Combine(info.FullName, fileName), System.Drawing.Imaging.ImageFormat.Png);
This code does a couple of things all at once. First, it goes out to the directory specified by the UserControl textBoxFolder, and retrieves all the files in that directory.
Next, it iterates over each of those files and creates an Image instance. It then compares the current image’s width and height to the maximum width and height it has found so far and updates the max width and height as appropriate. Finally, it adds the image instance to a list of images (for later processing).
After the list of images is built, we pad the max width and height by one, to give the images a little separation. Then we generate our sprite image from the list of images we just created. Of course, this does not handle any of the various error conditions that could occur, and is certainly not the most efficient use of memory, but it is fast and works well for its purpose.
Combining the Images
Once we have a list of images, all we need to do is draw them to a new image in a sequential manner. In the sample above, all the heavy lifting is done in the GenerateSprite method (see *Note):
private static System.Drawing.Bitmap GenerateSprite(List<Image> images, int maxWidth, int maxHeight) {int numColumns = (int)Math.Sqrt(images.Count);int numRows = (images.Count / numColumns) + 1;
System.Drawing.Bitmap sprite = new System.Drawing.Bitmap(maxWidth * numColumns, maxHeight * numRows);System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(sprite);int x = 0;
int y = 0;
for (int i = 0; i < images.Count; i++) {var img = images[i];
g.DrawImage(img, x * maxWidth, y * maxHeight,Math.Min(maxWidth, img.Width), Math.Min(maxHeight, img.Height));x++;if (x == numColumns) {
x = 0;y++;}}return sprite;
}
This code has a couple of interesting things going on. First, we calculate the number of columns as the square root of the number of images. This will give us a roughly square image. There is no need for this, we could create a large horizontal or vertical image with all the constituent images stacked together, but I find the more square form makes it easier to view in editors if you ever need to do touchup work.
After we determine the number of rows and columns, we generate a new blank image and create a graphics object we can use to draw on the image. The image is sized such that each of the individual images has a maxWidth by maxHeight rectangle the image. If the images being combined are of different sizes this will not result in an efficient packing algorithm, but it is very fast and easy to calculate.
Finally, we loop over each image and draw it to our sprite, calculating its position in the sprite based on the current row and column (x and y) position. Since each image gets a rectangle sized maxWidth by maxHeight, the row and column is all we need to ensure that we will not overwrite any other image.
Conclusion
That is all it takes to generate a single sprite image from a directory full of images. However, you may have noticed that we are still missing the CSS file, and there are some other things going on in the code that prevents you from simply copying the examples as is.
Have no fear, I’ll discuss generating the corresponding CSS classes and a sample HTML file to view all the images in a future post.
*Note:
This articles was modified to correct an off by one error in the number of rows calculation (it did not handle a final partial row of icons). The new code will correctly allocate enough space for a final partial row.