Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Dynamic Image Resizing Using HTTP Modules

0.00/5 (No votes)
24 Aug 2008 1  
Easily and dynamically re-size images throughout your site by appending the height and width to the image name, or by using custom named fields with a set height and width.

Introduction

Resizing images for your website doesn't have to be difficult. Through the magic of HTTP modules, we can intercept every request of a certain type that comes through your site (such as image requests), and perform any necessary processing before sending the result down to the browser.

This is very handy when you need to resize images throughout your website. The code below will allow you to:

  • Resize images by adding the width and height after the image.
  • Define image "keys" with a certain width and height, so you can change the width and height of the key in a configuration file, and every image throughout the site that has that key will be automatically resized.
  • Make these changes with nothing more than adding a few lines to the web.config file, and adding an XML configuration file if you want to use image keys.
  • The configuration settings and list of resized images are cached to make this solution as scalable as possible.
  • The resized image is saved to disk and then used for all future requests, again for scalability purposes.

See a working example of how easy image resizing can be. View the source to see that the same image name is being used in all examples, and only the width and height are changing.

Background

A knowledge of HTTP modules and how they work will come in handy to understanding what's going on here. You can get a brief overview of the subject with this article at 15 seconds.

Using the code

To add this code to an existing site, add the following three lines to the httpHandlers section of your web.config file. This tells .NET that anything it finds with an extension of .jpg, .gif, or .png, followed by .ashx, should be handled by the ResizeImages class.

<add verb="*" path="*.jpg.ashx" type="ImageHandlers.ResizeImages"/>
<add verb="*" path="*.gif.ashx" type="ImageHandlers.ResizeImages"/>
<add verb="*" path="*.png.ashx" type="ImageHandlers.ResizeImages"/>

Next, in your appSettings section, define where you want the resized images to be saved, and the location of the XML file you'll use for your image keys (the second setting is optional if you don't plan on using image keys).

<add key="ResizedImagesDirectory" value="~/images/resized"/>
<add key="ResizedImageKeys" value="~/images/resized.xml"/>

Drop the ResizeImages.vb class in your App_Code folder, and add your image links to a test page.

Your links can be displayed in several different formats:

<img src="images/turtle.jpg.ashx?k=normal" />     <!--With an image key-->
<img src="images/turtle.jpg.ashx?h=100" />    <!--Height only (width is scaled)-->
<img src="images/turtle.jpg.ashx?w=100" />    <!--Width only (height is scaled)-->
<img src="images/turtle.jpg.ashx?h=100&w=100" />  <!--Height and width set-->

If you want to define the height and width of your image keys, that information goes in the resized.xml file (or whatever you choose to name your file). The format looks like this:

<ResizedImages>
  <image name="thumbnail" width="100" height="100" />
  <image name="normal" width="200" />  
  <image name="large" height="300" />  
</ResizedImages>

So, in this example, an image that has an image key of "normal" would have its width set to 200. If you change the width of the image key, every image on your site that is using the "normal" key will be automatically resized.

Code

The ResizeImages.vb class handles all the image resizing. If you're interested in how this works, I have commented the code below and you can investigate it in more detail. If you just want to start using everything, download the project and enjoy!

Note: This currently only works with .NET 3.5, since I'm using LINQ to parse through the XML for the image keys. If you want to run this in .NET 2.0, you can replace the LINQ code in the GetImageDimensions function and use XPath queries instead.

Imports System
Imports System.Text
Imports System.Web
Imports System.Drawing
Imports System.Web.SessionState
Imports System.IO
Imports System.Xml
Imports System.Collections.Generic
Imports System.Linq
Imports System.Xml.Linq

Namespace ImageHandlers
    Public Class ResizeImages
        Implements IHttpHandler

        Enum ImageType As Integer
            JPG
            PNG
            GIF
        End Enum

        Public ReadOnly Property IsReusable() As Boolean _
               Implements System.Web.IHttpHandler.IsReusable
            Get
                Return True
            End Get
        End Property

        Dim ResizedImagesDirectory As String = _
          ConfigurationManager.AppSettings("ResizedImagesDirectory")

        Public Sub ProcessRequest(ByVal Ctx As System.Web.HttpContext) _
               Implements System.Web.IHttpHandler.ProcessRequest

            'Get the current request
            Dim Req As HttpRequest = Ctx.Request


            'Set the width and height for the resized image
            Dim Width As Integer = 0
            Dim Height As Integer = 0
            Dim Key As String = ""
            If Not IsNothing(Req.QueryString("w")) And _
                   IsNumeric(Req.QueryString("w")) Then
                Width = Req.QueryString("w")
            End If
            If Not IsNothing(Req.QueryString("h")) And _
                   IsNumeric(Req.QueryString("h")) Then
                Height = Req.QueryString("h")
            End If
            If Not IsNothing(Req.QueryString("k")) Then
                Key = Req.QueryString("k")
            End If

            'If we have a key stored in an xml file, use it to determine 
            'the width and height of the image instead
            If Key.Length > 0 Then
                Dim KeyImage As New ResizedImage
                KeyImage = GetImageDimensions(Ctx, Key)
                Height = KeyImage.Height
                Width = KeyImage.Width
            End If

            Dim DisplayResizedImage As Boolean = True
            If Width = 0 And Height = 0 Then
                'They didn't set a height or width, 
                'so don't create or display a resized image
                'Use the original image instead
                DisplayResizedImage = False
            End If

            'Get the path of the file, without the .ashx extension
            Dim PhysicalPath As String = Regex.Replace(Req.PhysicalPath, _
                                           "\.ashx.*", "")

            'Determine the content type, and save 
            'what image type we have for later use
            Dim ImgType As ImageType
            If PhysicalPath.EndsWith(".jpg") Or _
                   PhysicalPath.EndsWith(".jpeg") Then
                Ctx.Response.ContentType = "image/jpeg"
                ImgType = ImageType.JPG
            ElseIf PhysicalPath.EndsWith(".gif") Then
                Ctx.Response.ContentType = "image/gif"
                ImgType = ImageType.GIF
            ElseIf PhysicalPath.EndsWith(".png") Then
                Ctx.Response.ContentType = "image/png"
                ImgType = ImageType.PNG
            End If


            'Name the images based on their width, height, 
            'and path to ensure they're unique.
            'The image name starts out looking like 
            '/HttpModule/images/turtle.jpg (the virtual path), and gets
            'converted to 400_200_images_turtle.jpg.
            'The 400 is the width, and the 200 is the height.
            'If a width or height is not specified,
            'it will look like 0_200_images_turtle.jpg (an example
            'where the width is not specified).
            Dim VirtualPath As String = Regex.Replace(Req.Path, "\.ashx.*", "")
            Dim ResizedImageName As String = Regex.Replace(VirtualPath, "/", "_")
            ResizedImageName = Regex.Replace(ResizedImageName, "_.*?_", "")
            ResizedImageName = Width & "_" & Height & _
                               "_" & ResizedImageName

            'Get the resized image
            Dim ri As New ResizedImage
            ri = GetResizedImage(Ctx, ResizedImageName, Height, Width, ImgType)

            Try
                If DisplayResizedImage Then 'display resized image
                    Ctx.Response.WriteFile(Path.Combine(ri.ImagePath, ri.ImageName))
                Else
                    'display original image
                    Ctx.Response.WriteFile(PhysicalPath)
                End If

            Catch ex As Exception
                'You can add logging here if you want,
                'but most like the image path can't be found,
                'so don't do anything
            End Try

        End Sub

        Private Function GetResizedImage(ByVal Ctx As System.Web.HttpContext, _
                ByVal ImageName As String, ByVal Height As Integer, _
                ByVal Width As Integer, ByVal ImgType As ImageType) As ResizedImage

            'Look in the cache first for a list of images that have been resized
            Dim ResizedImageList As New List(Of ResizedImage)
            ResizedImageList = Ctx.Cache.Get("ResizedImageList")

            Dim ResizedImage As New ResizedImage
            Dim ImageFound As Boolean = False

            If IsNothing(ResizedImageList) Then
                'Nothing in the cache, start a new image list
                ResizedImageList = New List(Of ResizedImage)
            Else
                'Let's see if an image with this name and size is already created
                For Each ri As ResizedImage In ResizedImageList
                    If ri.ImageName = ImageName And ri.Height = Height _
                        And ri.Width = Width Then
                        'The image already exists, no need to create it.
                        ResizedImage = ri
                        ImageFound = True
                        Exit For
                    End If
                Next
            End If

            'Create the folder where we want to save 
            'the resized images if it's not already there
            Dim ResizedImagePath As String = _
                Ctx.Server.MapPath(ResizedImagesDirectory)
            If Not Directory.Exists(ResizedImagePath) Then
                Directory.CreateDirectory(ResizedImagePath)
            End If

            'Clear the cache anytime the resized image folder 
            'changes (in case items were removed from it)
            Dim cd As New CacheDependency(ResizedImagePath)

            If Not ImageFound Then
                'We didn't find the image in the list of resized 
                'images...look in the resized folder
                'and see if it's there
                Dim ImageFullPath As String = _
                    Path.Combine(Ctx.Server.MapPath(ResizedImagesDirectory), ImageName)
                If File.Exists(ImageFullPath) Then
                    'The image has already been created, 
                    'set the properties for the image
                    'and add it to the cached image list
                    ResizedImage.ImageName = ImageName
                    ResizedImage.ImagePath = _
                           Ctx.Server.MapPath(ResizedImagesDirectory)
                    ResizedImage.Height = Height
                    ResizedImage.Width = Width
                    ResizedImageList.Add(ResizedImage)

                    'Keep the cache for a day, unless new 
                    'images get added to or deleted from
                    'the resized image folder
                    Dim ts As New TimeSpan(24, 0, 0)
                    Ctx.Cache.Add("ResizedImageList", ResizedImageList, cd, _
                                  Cache.NoAbsoluteExpiration, ts, _
                                  CacheItemPriority.Default, Nothing)
                End If
            End If

            Dim Req As HttpRequest = Ctx.Request
            Dim PhysicalPath As String = Regex.Replace(Req.PhysicalPath, _
                                         "\.ashx.*", "")
            If ResizedImage.ImageName = "" Then
                'The image isn't already created, 
                'we need to create it add it to the cache
                ResizeImage(PhysicalPath, ResizedImagePath, _
                            ImageName, Width, Height, ImgType)

                'Now update the cache since we've added a new resized image 
                ResizedImage.Width = Width
                ResizedImage.Height = Height
                ResizedImage.ImageName = ImageName
                ResizedImage.ImagePath = ResizedImagePath
                ResizedImageList.Add(ResizedImage)

                Dim ts As New TimeSpan(24, 0, 0)
                Ctx.Cache.Add("ResizedImageList", ResizedImageList, cd, _
                              Cache.NoAbsoluteExpiration, ts, _
                              CacheItemPriority.Default, Nothing)
            End If

            Return ResizedImage
        End Function

        Private Sub ResizeImage(ByVal ImagePath As String, _
                    ByVal ResizedSavePath As String, ByVal ResizedImageName As String, _
                    ByVal NewWidth As Integer, ByVal NewHeight As Integer, _
                    ByVal ImgType As ImageType)

            'Make sure the image exists before trying to resize it
            If File.Exists(ImagePath) And Not (NewHeight = 0 And NewWidth = 0) Then
                Using OriginalImage As New Bitmap(ImagePath)

                    If NewWidth > 0 And NewHeight = 0 Then
                        'The user only set the width, calculate the new height
                        NewHeight = Math.Floor(OriginalImage.Height / _
                                    (OriginalImage.Width / NewWidth))
                    End If

                    If NewHeight > 0 And NewWidth = 0 Then
                        'The user only set the height, calculate the width
                        NewWidth = Math.Floor(OriginalImage.Width / _
                                             (OriginalImage.Height / NewHeight))
                    End If

                    If NewHeight > OriginalImage.Height Or _
                                      NewWidth > OriginalImage.Width Then
                        'Keep the original height and width 
                        'to avoid losing image quality
                        NewHeight = OriginalImage.Height
                        NewWidth = OriginalImage.Width
                    End If


                    Using ResizedImage As New Bitmap(OriginalImage, NewWidth, NewHeight)
                        ResizedImage.SetResolution(72, 72)

                        Dim newGraphic As Graphics = Graphics.FromImage(ResizedImage)
                        newGraphic.Clear(Color.White)
                        newGraphic.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
                        newGraphic.InterpolationMode = _
                           Drawing2D.InterpolationMode.HighQualityBicubic
                        newGraphic.DrawImage(OriginalImage, 0, 0, NewWidth, NewHeight)

                        'Save the image as the appropriate type
                        Select Case ImgType
                            Case ImageType.GIF
                                ResizedImage.Save(System.IO.Path.Combine(ResizedSavePath, _
                                                  ResizedImageName), Imaging.ImageFormat.Gif)
                            Case ImageType.JPG
                                ResizedImage.Save(System.IO.Path.Combine(ResizedSavePath, _
                                                  ResizedImageName), Imaging.ImageFormat.Jpeg)
                            Case ImageType.PNG
                                ResizedImage.Save(System.IO.Path.Combine(ResizedSavePath, _
                                                  ResizedImageName), Imaging.ImageFormat.Png)
                        End Select

                    End Using
                End Using
            End If
        End Sub

        Private Function GetImageDimensions(ByVal Ctx As HttpContext, _
                         ByVal Key As String) As ResizedImage


            Dim ri As New ResizedImage

            'If I enter the key "thumbnail", this function 
            'will go to the xml file to find out
            'what the width and height of a thumbnail should be.

            'The xml that we're reading from looks like this.
            'You can set the width, the height, or both
            '<ResizedImages>
            '  <image name="thumbnail" width="100" height="100" />
            '  <image name="normal" width="200" />  
            '  <image name="large" height="300" />  
            '</ResizedImages>
            
            'Load the xml file
            Dim XMLSource As XElement = GetResizedImageKeys(Ctx)

            'Get all nodes where the name equals the key
            'To make this code work in .Net 2.0, use an xpath query to get the height
            'and width values instead of a LINQ query
            Dim ResizedQuery = From r In XMLSource.Elements("image") _
                            Where r.Attribute("name") = Key _
                            Select r

            'Set the resized image we're returning with the width and height
            For Each r As XElement In ResizedQuery
                If Not IsNothing(r.Attribute("height")) Then
                    ri.Height = r.Attribute("height")
                End If
                If Not IsNothing(r.Attribute("width")) Then
                    ri.Width = r.Attribute("width")
                End If
            Next

            Return ri
        End Function

        Private Function GetResizedImageKeys(ByVal ctx As HttpContext) As XElement
            Dim xel As XElement = Nothing
            Dim ResizedImageKeys As String = _
                ctx.Server.MapPath(ConfigurationManager.AppSettings("ResizedImageKeys"))
            If Not IsNothing(ResizedImageKeys) Then
                'Try to get the xml from the cache first
                xel = ctx.Cache.Get("ResizedImageKeys")

                'If it's not there, load the xml document and then add it to the cache
                If IsNothing(xel) Then
                    xel = XElement.Load(ResizedImageKeys)
                    Dim cd As New CacheDependency(ResizedImageKeys)
                    Dim ts As New TimeSpan(24, 0, 0)
                    ctx.Cache.Add("ResizedImageKeys", xel, cd, _
                                  Cache.NoAbsoluteExpiration, ts, _
                                  CacheItemPriority.Default, Nothing)
                End If
            End If
            Return xel
        End Function

        'This class is used to keep track of which images are resized.
        'We save this in a cached list and look here first,
        'so we don't have to look through the folder on the file
        'system every time we want to see if the resized image
        'exists or not
        Private Class ResizedImage
            Private _ImageName As String
            Private _ImagePath As String
            Private _Width As Integer
            Private _Height As Integer
            Public Property ImageName() As String
                Get
                    Return _ImageName
                End Get
                Set(ByVal value As String)
                    _ImageName = value
                End Set
            End Property
            Public Property ImagePath() As String
                Get
                    Return _ImagePath
                End Get
                Set(ByVal value As String)
                    _ImagePath = value
                End Set
            End Property
            Public Property Width() As Integer
                Get
                    Return _Width
                End Get
                Set(ByVal value As Integer)
                    _Width = value
                End Set
            End Property
            Public Property Height() As Integer
                Get
                    Return _Height
                End Get
                Set(ByVal value As Integer)
                    _Height = value
                End Set
            End Property
            Public Sub New()
                Width = 0
                Height = 0
                ImagePath = ""
                ImageName = ""
            End Sub
        End Class
    End Class

End Namespace

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here