Jan
12
2012

Optimize web page - Resizing image using http handler and custom server control

Introduction

In this (not so) short article, I am going to discuss about role of image size in web page performance and how easily we can create a custom image control and with the help of custom http handler how can we dramatically improve the performance.

We are going to follow two steps to optmize the images used on web page

  • Use HTTP handler to resize the image and pass to web page
  • Creating custom server control to take care of background processing (i.e. The control should be used as normal Image control)

Background

I know, there are many tools/packages/components available freely to take care of image optimization for your web site. Nuget packages like AspSprite are brilliant in optimizing the images. However, my focus for this article is about reducing the image size so as to reduce the bandwidth and which should work behind the scene for me. That means, I dont want to reduce image size by passing static width etc.

Article Body

To begin with, consider this scenario. I want to create a sample picture gallery web page. Where lots of images are displayed in tabular form. For demo, I am picking up few images from Windows folder and add them in web site project. After formatting the page as a table I use <asp:Image> tag to display image like this

<asp:Image runat="server" ID="Image1" 
ImageUrl="~/Images/img16.jpg" width="170px" 
Height="150px">
</asp:Image>

If you are using images carefully designed for your web site with considering size of the image to be displayed on page. Then you would have images with exact height and width as they are going to be presented. However, mostly it is not the case (Even if you are using custom created images, It is likely that you use them in different places on the page with different size)

In summary, we pass width and height attribute to image tag to determine what area that image will cover on the page. Now, consider the image tag above. I have used some of the standard wallpaper images from Windows folder to create image gallery and this is what displayed.

Now, using Firebug let us see all the requests that were sent to display the page.

It can be seen that all images used in gallery are around 1.5 MB in size. Going back to image file in web site folder and checking its properties reveal that the actual image size is 300x300.

That means, we are displaying 300x300 image by compressing image tag size as 250x180. But still the full image of 1.5 MB is being transferred over the wire.

We can of course create a smaller versions of these images using image editing tools like PhotoShop etc. but that would create a constraint of image size. Generally, developers use http handler to send resized images to browser based on width and height parameter passed to handler.

We are going to see this general apprch coupled with creating custom image control so that other developers in your project dont have go into details of calling http handler for evey image. Instead of that the custom image control will take care of it.

Creating HTTP handler for resizing image

To demonstrate this, let us create new web site project. We will come back to above example in the later part of this article.

After creating new website, right click on project in solution explorer and go to Add New Item -> Generic handler

Name the handler class as ImageResizehandler.ashx (If you are not aware about what is generic handler and how it works, Please refer this MSDN article)

Now, we need to add the code in public method ProcessRequest which will take following steps.

  • Check if width and height paramters are passed as querystring
  • If not then send original image as it is (the image path should be passed as querystring. Since, the handler will be used by our own custom control, I am not adding check to make sure whether image path is provided in query string or not.
  • If both width and height parameter are found in the querystring and are valid then resize image and store resized image into byte array.
  • send byte array with response.

Below code snippet replicates the above logic which is to be added in handler class.

<%@ WebHandler Language="C#" Class="ImageResizeHandler" %>

using System;
using System.Web;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;

public class ImageResizeHandler : IHttpHandler {
    
    public void ProcessRequest (HttpContext context) {
        //get image path from querystring
        string imgPath = context.Request.QueryString["imgPath"];
        //get image width to be resized from querystring
        string imgwidth = context.Request.QueryString["width"];
        //get image height to be resized from querystring
        string imgHeight = context.Request.QueryString["height"];
        //check that height and width is passed as querystring. then only image resizing will happen
        if (imgPath != string.Empty && imgHeight != string.Empty && imgwidth != string.Empty && (imgHeight!=null && imgwidth!=null))
        {
            if (!System.Web.UI.WebControls.Unit.Parse(imgwidth).IsEmpty && !System.Web.UI.WebControls.Unit.Parse(imgHeight).IsEmpty)
            {
                
                //create callback handler
                Image.GetThumbnailImageAbort myCallback = new Image.GetThumbnailImageAbort(ThumbnailCallback);
                //creat BitMap object from image path passed in querystring
                Bitmap myBitmap = new Bitmap(context.Server.MapPath(imgPath));
                //create unit object for height and width. This is to convert parameter passed in differen unit like pixel, inch into generic unit.
                System.Web.UI.WebControls.Unit widthUnit = System.Web.UI.WebControls.Unit.Parse(imgwidth);
                System.Web.UI.WebControls.Unit heightUnit = System.Web.UI.WebControls.Unit.Parse(imgHeight);
                //Resize actual image using width and height paramters passed in querystring
                Image myThumbnail = myBitmap.GetThumbnailImage(Convert.ToInt16(widthUnit.Value), Convert.ToInt16(heightUnit.Value), myCallback, IntPtr.Zero);
                //Create memory stream and save resized image into memory stream
                MemoryStream objMemoryStream = new MemoryStream();
                myThumbnail.Save(objMemoryStream, System.Drawing.Imaging.ImageFormat.Png);
                //Declare byte array of size memory stream and read memory stream data into array
                byte[] imageData = new byte[objMemoryStream.Length];
                objMemoryStream.Position = 0;
                objMemoryStream.Read(imageData, 0, (int)objMemoryStream.Length);

                //send contents of byte array as response to client (browser)
                context.Response.BinaryWrite(imageData);                
            }
        }
        else
        {
            //if width and height was not passed as querystring, then send image as it is from path provided.
            context.Response.WriteFile(context.Server.MapPath(imgPath));
        }
    }
    

    public bool ThumbnailCallback()
    {
        return false;
    }    

}

Once we have this generic handler in our project, we can use it by adding tag simillar to this in aspx design.

<asp:Image ID="Image1" runat="server" 
ImageUrl="~/ImageResizeHandler.ashx?imgPath=~/Images/old.jpg&width=150&height=150" />

After running the page, the old.jpg file would be displayed at the place of this image tag also, by observing FireBug we can see downloaded image size is only 58 KB in size.

This is all good. But I dont want that all my developers to write a call to ImageResizeHandler.ashx handler. Instead what we will do is, build a custom image control and then you can simply use the custom image control as we use standard one and call to generic handler and image resizing will happen transparently.

Writing custom image control to use HTTP generic handler

We can either create custom control and build it as dll and use in any other project or we can simply create custom control class in App_Code directory of the project and refer it in same project. For this demo purpose we will follow later approach.

Create a new directory in same sample application where we have create generic handler and name it as App_Code. Add a new class in this folder and name it is ResizeImage.cs. The custom image control class will do following.

  • Inherit from standard Image web control class
  • Accept one more boolean properties ResizeToCompress to indicate whether to resize image or not.
  • Override Render function to amend ImageURL property to add call to ImageResizeHandler.ashx handler and pass width, height property values to it as query string.

The compete code of custom image control class looks like this

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.IO;

namespace CustomImage
{

    public class ResizeImage : System.Web.UI.WebControls.Image
    {
        private bool _resizeToCompress;
        private bool _modifyImageControl;
        #region Image

        [Category("Layout"), Bindable(true)]
        public  bool ResizeToCompress
        {
            get
            {
                return _resizeToCompress;
            }
            set
            {
                _resizeToCompress = value;
            }
        }

        #endregion

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
        }


        protected override void Render(HtmlTextWriter writer)
        {
            if (_resizeToCompress == true)
                base.ImageUrl = "ImageResizeHandler.ashx?imgpath=" + base.ImageUrl + "&height=" + base.Height.ToString() + "&width=" + base.Width.ToString();
            else
                base.ImageUrl = "ImageResizeHandler.ashx?imgpath=" + base.ImageUrl;            
            base.Render(writer);
        }

    }
}

Note: If you notice in render method above, I have hard coded name of HTTP handler class. Instead this can accepted as a separate public property and that property value can be used here to avoid hardcoding

Putting it all together

After creating the custom control class, we need to add reference to it in .aspx (or in web.config) file. To see the complete code in action and monitor comparative performance, First of all copy the HTTP hadler as well as custom control class (with App_Code directory) in our original example of image gallery.

Now, register custom control for image gallery page.

<%@ Register Namespace="CustomImage" 
TagPrefix="CustomImage" %>

And simply replace our original image tag with this

<CustomImage:ResizeImage runat="server" ID="Image1" 
ImageUrl="~/Images/img16.jpg" 
width="170px" 
Height="150px" 
ResizeToCompress="true" >
</CustomImage:ResizeImage>

Note that, for the developers in your team, only difference is to use CustomImage tag and passing ResizeToCompress attribute as true.

Finally run the web site. There would not be any difference in the display of web page. However, after monitoring requests in Firebug, it is clear that we have significantly reduced size of images being downloaded.

Conclusion

By combining HTTP handler and custom image control, we can creat transparent mechanism of resizing images as per width and height of image tag therby reducimg unnecessary response traffic. we can further improve its perfomance by adopting caching mechanism to avoid image requests once it is cached in browser.

About Me

You are visiting personal website of Kedar (KK)

Please go here to know more about me

Disclaimer

The opinions expressed here represent my own and not those of my past or present employers.

The concept/code provided on this site may not work as described. If you are using any code provided on this site. Then, please test it thoroughly. I shall not be responsible for any issues arising in the code. 

Month List