Conserve Bandwidth With Thumbnails

The solution discussed in the accompanying article works on a smaller scale, but more sophisticated solutions offer more consistent performance and more robust features. One such solution starts by loading the TreeView with folders and subfolders, but not with files. The TreeView then creates nodes only for the folders, which is usually much more manageable in number.

However, this technique means that another approach is required to display the JPG images. Instead of linking the file nodes (which don't exist in this solution) to render the JPG images in an iframe, set the folder nodes to load all the images within that folder in the iframe. However, the images could be huge in dimension and file size. Loading several of them in an iframe would likely result in insufficient real estate to display them all—not to mention the tremendous amount of bandwidth it could require (especially if the images are large and of high resolution). You can solve both the real-estate problem and the bandwidth problem by reducing the images in both dimension and file size. Simply create thumbnails of these images and display them using an ASP.NET DataList (see Figure 3 in the accompanying article). My sample app reduces all the images in size so you can see and load them quickly.

The codebehind still loads the TreeView folder nodes, but it skips loading the files. However, you set the NavigateUrl property of each folder node before you create the folder nodes and add them to the TreeView's Nodes collection. The NavigateUrl property still targets the iframe, but instead of loading a single image, it loads another ASPX—ThumbNailDisplay2.aspx—and passes to it the path to the folder. The ThumbNailDisplay2.aspx's codebehind iterates through the given path and retrieves the images in it. The ThumbNailDisplay2.aspx contains a form that in turn contains an ASP.NET DataList. The DataList is bound to an array of images through the codebehind. Its codebehind (ThumbNailDisplay2.aspx.cs) loops through all the JPG files in the given path and builds a <a> tag with an embedded <img> tag to show the JPG's image. The hyperlink links to a full-size version of the JPG in a new window.

The source of the <img> tag is not simply the path to the full size JPG. Instead, the source of the <img> is a call to the ImageStreamer.aspx, which creates a thumbnail version of the JPG and streams it back to the source of the <img> tag.

The codebehind (ImageStreamer.aspx.cs) reads into memory the given image path, desired thumbnail width, and desired thumbnail height through the Request.QueryString object. The codebehind runs some security code first to make sure the images it loads truly come from your Web site. It aborts the image stream, and the <img> tag from the source ASPX won't display an image for that call if it finds this is not the case. This guards against a rogue Web application spoofing your application. In fact, you might even want to expand this code to hard-code in your Web site's URL and other unique aspects of your Web site if this is a concern (sometimes it's good to be overly careful):

// Make sure image URL doesn't contain /'s or \'s. 
// If it does, then get out. This limits the code to
// only work for the current directory.
if ((sImageURL.IndexOf("/") >= 0) || 
   (sImageURL.IndexOf("\\") >= 0))
{
   // Slashes exist—now check to see if the folder 
   // begins with "images/", which is ok
   string sGlobalImageFolder = 
      ConfigurationSettings.AppSettings.Get
      ("ImageFolder");
   if ((sImageURL.Substring(0, 
      sGlobalImageFolder.Length).ToLower() != 
      sGlobalImageFolder.ToLower()))
   {
      // We found a slash of some sorts (/ or \)
      // and it is not because of 
      Response.End();
   }
}

The code creates a delegate to represent the GetThumbnailImageAbort method. This delegate gets invoked if a problem occurs while the thumbnail is being created by the GetThumbnailImage method of the System.Drawing.Image object. Then an instance of the Image object is created, and the full-sized image is associated with it:

// Grab full-sized image
System.Drawing.Image oFullImg;
oFullImg = 
   System.Drawing.Image.FromFile
   (MapPath(sImageURL));

Now rotate the full-sized image 360 degrees before creating the thumbnail image from the full-sized image. This ritual does nothing to the orientation of the image; however, it ensures that it doesn't use the full-sized image's embedded thumbnail (if one exists) when the thumbnail is retrieved from it. I'm not sure why this works, but it does, and it does so reliably.

Some digital photos embed a thumbnail in the image. The GetThumbnailImage method uses and retrieves this embedded image if it is available and the original image is not manipulated in some fashion (such as rotating it 360 degrees). Rotating the image forces GetThumbnailImage to use the full-sized image to create a thumbnail image on its own.

It's important not to use the embedded thumbnail image because it generally has a low resolution and can appear blurry. You get a much better quality thumbnail by making it not use the embedded one:

oFullImg.RotateFlip
   (RotateFlipType.Rotate180FlipNone);
oFullImg.RotateFlip
   (RotateFlipType.Rotate180FlipNone);

Finally, the image is streamed back to the <img> tag's src attribute. The code tells the Response object to send back its content in the image/JPG format. Then the Image object's Save method is invoked, sending the JPG thumbnail to the Response object's OutputStream as its destination. In the end, a smaller dimension and file size are created and streamed on the fly to the user's browser:

   // Stream image to Response object
   // as output of ASPX
   Response.ContentType = "image/jpeg";
   oThumbNailImg.Save(Response.OutputStream, 
      ImageFormat.Jpeg);

This technique scales better because it doesn't load as many nodes in the TreeView. It can load several images in the DataList, but the images are small (around 4K in my sample app).

You can enhance this application by modifying the thumbnail page to allow paging—possibly using a DataGrid. This way, you could modify the app to only display only 10 images at a time on a page if there are hundreds of image files in a given folder. Of course, you can tweak this code in other ways, but the key is to make sure your approach balances performance, scalability, and appearance optimally.

The System.Drawing and System.IO namespaces have many more features that make manipulating and enumerating files and images easy. I encourage you to explore them now and see what else you can achieve with them in your own apps.