Dynamic Image Resizing Using HTTP Modules






3.89/5 (8 votes)
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