One frequent task is to take images and convert them into thumbnails. This is certainly nothing new, but seeing this question is so frequently asked on newsgroups and message boards bears reviewing this topic here again.

 

I was getting tired of constantly repeating this code for specific situations, so I created a generic page in my apps to handle resizing images from the current site dynamically in a page called CreateThumbnail. You call this page with a relative image name from the Web site on the querystring and it returns the image as a thumbnail.

 

An example of how this might work looks like this:

 

http://www.west-wind.com/wwStore/demos/CreateThumbnail.aspx?image=images/WebStoreLogo_big.jpg&size=400

 

Size is an optional second parameter – it defaults to 120.

 

Here’s what the implementation of this generic page looks like:

 

using System.Drawing;

using System.Drawing.Imaging;

 

public class CreateThumbNail : System.Web.UI.Page

{

private void Page_Load(object sender, System.EventArgs e)

      {

            string Image = Request.QueryString["Image"];

            if (Image == null) 

            {

                  this.ErrorResult();

                  return;

            }

 

            string sSize = Request["Size"];

            int Size = 120;

            if (sSize != null)

                  Size = Int32.Parse(sSize);

 

            string Path = Server.MapPath(Request.ApplicationPath) + "\\" + Image;

            Bitmap bmp = CreateThumbnail(Path,Size,Size);

 

            if (bmp == null)

            {

                  this.ErrorResult();

                  return;

            }

 

            string OutputFilename = null;

            OutputFilename = Request.QueryString["OutputFilename"];

 

            if (OutputFilename != null)

            {

                  if (this.User.Identity.Name == "") 

                  {

                        // *** Custom error display here

                        bmp.Dispose();

                        this.ErrorResult();

                  }

                  try

                  {

                        bmp.Save(OutputFilename);

                  }

                  catch(Exception ex)

                  {

                        bmp.Dispose();

                        this.ErrorResult();

                        return;

                  }

            }

 

            // Put user code to initialize the page here

            Response.ContentType = "image/jpeg";

            bmp.Save(Response.OutputStream,System.Drawing.Imaging.ImageFormat.Jpeg);

            bmp.Dispose();

      }

 

      private void ErrorResult()

      {

            Response.Clear();

            Response.StatusCode = 404;

            Response.End();

      }

 

      ///

      /// Creates a resized bitmap from an existing image on disk.

      /// Call Dispose on the returned Bitmap object

      ///

      ///

      ///

      ///

      /// Bitmap or null

      public static Bitmap CreateThumbnail(string lcFilename,int lnWidth, int lnHeight)

      {

     

            System.Drawing.Bitmap bmpOut = null;

            try

            {

                  Bitmap loBMP = new Bitmap(lcFilename);

                  ImageFormat loFormat = loBMP.RawFormat;

 

                  decimal lnRatio;

                  int lnNewWidth = 0;

                  int lnNewHeight = 0;

 

                  //*** If the image is smaller than a thumbnail just return it

                  if (loBMP.Width < lnWidth && loBMP.Height < lnHeight)

                        return loBMP;

           

 

                  if (loBMP.Width > loBMP.Height)

                  {

                        lnRatio = (decimal) lnWidth / loBMP.Width;

                        lnNewWidth = lnWidth;

                        decimal lnTemp = loBMP.Height * lnRatio;

                        lnNewHeight = (int)lnTemp;

                  }

                  else

                  {

                        lnRatio = (decimal) lnHeight / loBMP.Height;

                        lnNewHeight = lnHeight;

                        decimal lnTemp = loBMP.Width * lnRatio;

                        lnNewWidth = (int) lnTemp;

                  }

 

                  // System.Drawing.Image imgOut =

                  //      loBMP.GetThumbnailImage(lnNewWidth,lnNewHeight,

                  //                              null,IntPtr.Zero);

                 

                  // *** This code creates cleaner (though bigger) thumbnails and properly

                  // *** and handles GIF files better by generating a white background for

                  // *** transparent images (as opposed to black)

                  bmpOut = new Bitmap(lnNewWidth, lnNewHeight);

                  Graphics g = Graphics.FromImage(bmpOut);

                  g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;

                  g.FillRectangle( Brushes.White,0,0,lnNewWidth,lnNewHeight);

                  g.DrawImage(loBMP,0,0,lnNewWidth,lnNewHeight);

 

                  loBMP.Dispose();

            }

            catch

            {

                  return null;

            }

     

            return bmpOut;

      }

 

}

 

This code doesn’t use the CreateThumbnail method of GDI+ because it doesn’t properly convert transparent GIF images as it draws the background color black. The code above compensates for this by first drawing the canvas white then loading the GIF image on top of it. Transparency is lost – unfortunately GDI+ does not handle transparency automatically and keeping Transparency intact requires manipulating the palette of the image which is beyond this demonstration.

 

The Bitmap object is returned as the result. You can choose what to do with this object. In this example it’s directly streamed in the ASP. Net Output stream by default. If you specify another query string value of OutputFilename you can also force the file to be written to disk *if* you are logged in. This is definitely not something that you want to allow just ANY user access to as anything that writes to disk is potentially dangerous in terms of overloading your disk space. Writing files out in this fashion also requires that the ASPNET or NETWORK SERVICE or whatever account the ASP. Net app runs under has rights to write the file in the specified directory.  I’ve provided this here as an example, but it’s probably best to stick file output functionality into some other more isolated component or page that is more secure.

 

Notice also that all errors return a 404 file not found error. This is so that images act on failure just as if an image file is not available which gives the browser an X’d out image to display. Realistically this doesn’t matter – browsers display the X anyway even if you send back an HTML error message, but this is the expected response the browser would expect.

 

In my West Wind Web Store I have several admin routines that allow to resize images on the fly and display them in a preview window. It’s nice to preview them before writing them out to disk optionally. You can also do this live in an application *if* the number of images isn’t very large and you’re not pushing your server to its limits already. Image creation on the fly is always slower than static images on disk. However, ASP. Net can be pretty damn efficient using Caching and this scenario is made for it. You can specify:

<%@ OutputCache duration="10000" varybyparam="Image;Size" %>

 

in the ASPX page to force images to cache once they’ve been generated. This will work well, but keep in mind that bitmap images can be memory intensive and caching them can add up quickly especially if you have large numbers of them.

 

If you create images dynamically frequently you might also consider using an HTTP Handler to perform this task since raw Handlers have less overhead than the ASP.Net Page handler. For example, the Whidbey Dynamic Image control relies on an internal handler that provides image presentation 'dynamically' without having to save files to disk first.