Click here to Skip to main content
15,886,724 members
Articles / Web Development / HTML

Generate CSS sprites and thumbnail images on the fly in ASP.NET sites

Rate me:
Please Sign up or sign in to vote.
4.82/5 (40 votes)
9 Jun 2012CPOL64 min read 117.1K   2.8K   85  
Reduces page load times of ASP.NET web sites by combining page images and CSS background images into CSS sprites. Compresses and physically resizes images to make thumbnails. Caters for repeating background images.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mapper;
using System.Drawing;
using System.Drawing.Imaging;
using System.Diagnostics;
using System.IO;

namespace CssSpriteGenerator
{    
    /// <summary>
    /// Represents the contents of a sprite image.
    /// 
    /// Note that this doesn't hold the image itself. That only gets generated and written to disc
    /// by method WriteSpriteImage.
    /// </summary>
     
    // TODO: automatically increase pixel depth of the sprite if the constituent images all have an indexed color scheme
    // but with widely different palettes.

    public class SpriteInfoWritable : Sprite
    {
        /// <summary>
        /// Generates an image containing the individual images as described in spriteMappings.
        /// Writes the image to disc.
        /// 
        /// This method determines the actual name of the sprite image itself.
        /// It ensures that that name doesn't clash with the names of other sprite images.
        /// </summary>
        /// <param name="fileSystem">
        /// This object will be used to write the sprite image to the sprites folder.
        /// </param>
        /// <param name="spriteImageType">
        /// The image type of the sprite, such as Png or Gif.
        /// </param>
        /// <param name="pixelFormatOverride">
        /// If this is not PixelFormat.DontCare or PixelFormat.Undefined, than this pixel format is 
        /// used for the final sprite image.
        /// 
        /// Otherwise, the pixel format is based on the pixel formats of the individual images going
        /// into the sprite image - using the pixel format of the image that requires the hightest 
        /// number of bits per pixel.
        /// </param>
        /// <param name="paletteAlgorithm">
        /// Algorithm to be used when the color depth of the sprite gets changed to
        /// an indexed color format (Format1bppIndexed, Format4bppIndexed or Format8bppIndexed).
        /// 
        /// This not only happens when pixelFormatOverride is set. The sprite gets constructed using
        /// a non-indexed format. It then gets the highest pixel format of all constituing images.
        /// That may be an index format.
        /// </param>
        /// <param name="jpegQuality">
        /// If the output format of the sprite is jpg, than this parameter can be used to reduce its quality
        /// and therefore its file size.
        /// 
        /// This parameter can be between 0 and 100. 100 gives you highest quality. 0 means that the sprite gets saved
        /// as is, without changing its quality.
        /// </param>
        /// <param name="groupUniqueOutputId">
        /// Add this string to the file name of the sprite.
        /// It encodes the output properties of the group that this sprite belongs to, such as JpegQuality.
        /// A sprite with the same constituent images but with different output settings needs to have a different name and url.
        /// </param>
        /// <returns>
        /// Url of the sprite image that was written to disc.
        /// </returns>
        public string WriteSpriteImage(
            IFileSystem fileSystem, ImageType spriteImageType,
            PixelFormat pixelFormatOverride, PaletteAlgorithm paletteAlgorithm, int jpegQuality, string groupUniqueOutputId)
        {
            string urlSpriteImage = null;

            // We'll use the combined file paths of all images in the sprite as the basis of the
            // the name of the sprite image. This because the file paths identify
            // the bitmaps that go into the sprite (the bitmaps are loaded from the image files).

            StringBuilder combinedImageFilePaths = new StringBuilder();

            // We'll also work out the PixelFormat with the highest number of bits to accommodate all images.
            // The lower the PixelFormat we settle on, the smaller the sprite will be in bytes.

            PixelFormat highestPixelFormat = PixelFormat.Format1bppIndexed;

            // Create a bitmap for the entire sprite
            using (Bitmap spriteBitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb))
            {
                using (Graphics spriteGraphics = Graphics.FromImage(spriteBitmap))
                {
                    // Draw each image in turn onto the sprite
                    foreach (IMappedImageInfo mappedImageInfo in this.MappedImages)
                    {
                        try
                        {
                            ImageInfo imageInfo = mappedImageInfo.ImageInfo as ImageInfo;
                            if (imageInfo == null)
                            {
                                throw new Exception("WriteSpriteImage: mappedImageInfo contains null ImageInfo");
                            }

                            if (imageInfo.FilePathBroken) { continue; }

                            highestPixelFormat = PixelFormatUtils.HigherPixelFormat(highestPixelFormat, imageInfo.ImageBitmap.PixelFormat);

                            // Create Rectangle specifying portion of image to copy to the sprite. Ensure this rectangle
                            // takes in the entire image.

                            Rectangle imageRect = new Rectangle(0, 0, imageInfo.Width, imageInfo.Height);

                            // Create a clone of the image bitmap, and give that clone the same horizontal and vertical resolution
                            // as the sprite bitmap. This way, the image bitmap will be copied into the sprite pixel by pixel.
                            // If the resolutions are different, the same number of pixels corresponds to different physical sizes,
                            // and DrawImage takes physical size into account when drawing an image. 
                            Bitmap clonedImageBitmap = imageInfo.ImageBitmap.Clone(imageRect, spriteBitmap.PixelFormat);
                            clonedImageBitmap.SetResolution(spriteBitmap.HorizontalResolution, spriteBitmap.VerticalResolution);

                            // Draw the image onto the sprite
                            spriteGraphics.DrawImage(
                                clonedImageBitmap,
                                mappedImageInfo.X, mappedImageInfo.Y,
                                imageRect,
                                GraphicsUnit.Pixel);

                            // Add the UniqueId of the ImageInfo, consisting of file path of the image and its width and height.
                            // Need the dimensions, in case you have the image multiple times on different pages,
                            // resized at different sizes.
                            combinedImageFilePaths.Append(imageInfo.UniqueId());

                            // Now that the ImageInfo's bitmap has been used, it can be disposed
                            imageInfo.DisposeBitmap();
                        }
                        catch (FileNotFoundException)
                        {
                            // We get here if the image's bitmap could not be read from disk.

                            // Note that if ExceptionOnMissingFile had been active, this situation would have been
                            // checked by MapPath, so in that case we would never have gotten here.
                        }
                    }

                    // Create file name of the sprite

                    string spriteFilename =
                        groupUniqueOutputId +
                        UrlUtils.StringHash(combinedImageFilePaths.ToString()) +
                        UrlUtils.ExtensionForImageType(spriteImageType);

                    if ((pixelFormatOverride != PixelFormat.DontCare) && (pixelFormatOverride != PixelFormat.Undefined))
                    {
                        highestPixelFormat = pixelFormatOverride;
                    }

                    // ----------------------------------------
                    // Create bitmap with optimized pixel format (fewer bits per pixel means that the sprite takes fewer bytes).
                    // If that fails due to an exception or otherwise, just write the sprite in its current pixel format.

                    Bitmap colorAdjustedBitmap = null;

                    try
                    {
                        colorAdjustedBitmap =
                            ImageUtils.ChangePixelFormat(spriteBitmap, highestPixelFormat, paletteAlgorithm);

                        spriteBitmap.Dispose();
                    }
                    catch (Exception)
                    {
                        // Just use current bit map
                        colorAdjustedBitmap = spriteBitmap;
                    }

                    urlSpriteImage = fileSystem.WriteImageFile(spriteFilename, colorAdjustedBitmap, spriteImageType, jpegQuality);
                }
            }

            return urlSpriteImage;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
Australia Australia
Twitter: @MattPerdeck
LinkedIn: au.linkedin.com/in/mattperdeck
Current project: JSNLog JavaScript Logging Package

Matt has over 9 years .NET and SQL Server development experience. Before getting into .Net, he worked on a number of systems, ranging from the largest ATM network in The Netherlands to embedded software in advanced Wide Area Networks and the largest ticketing web site in Australia. He has lived and worked in Australia, The Netherlands, Slovakia and Thailand.

He is the author of the book ASP.NET Performance Secrets (www.amazon.com/ASP-NET-Site-Performance-Secrets-Perdeck/dp/1849690685) in which he shows in clear and practical terms how to quickly find the biggest bottlenecks holding back the performance of your web site, and how to then remove those bottlenecks. The book deals with all environments affecting a web site - the web server, the database server and the browser.

Matt currently lives in Sydney, Australia. He recently worked at Readify and the global professional services company PwC. He now works at SP Health, a global provider of weight loss web sites such at CSIRO's TotalWellBeingDiet.com and BiggestLoserClub.com.

Comments and Discussions