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" /> <!---->
<img src="images/turtle.jpg.ashx?h=100" /> <!---->
<img src="images/turtle.jpg.ashx?w=100" /> <!---->
<img src="images/turtle.jpg.ashx?h=100&w=100" /> <!---->
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
Dim Req As HttpRequest = Ctx.Request
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 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
DisplayResizedImage = False
End If
Dim PhysicalPath As String = Regex.Replace(Req.PhysicalPath, _
"\.ashx.*", "")
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
Dim VirtualPath As String = Regex.Replace(Req.Path, "\.ashx.*", "")
Dim ResizedImageName As String = Regex.Replace(VirtualPath, "/", "_")
ResizedImageName = Regex.Replace(ResizedImageName, "_.*?_", "")
ResizedImageName = Width & "_" & Height & _
"_" & ResizedImageName
Dim ri As New ResizedImage
ri = GetResizedImage(Ctx, ResizedImageName, Height, Width, ImgType)
Try
If DisplayResizedImage Then Ctx.Response.WriteFile(Path.Combine(ri.ImagePath, ri.ImageName))
Else
Ctx.Response.WriteFile(PhysicalPath)
End If
Catch ex As Exception
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
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
ResizedImageList = New List(Of ResizedImage)
Else
For Each ri As ResizedImage In ResizedImageList
If ri.ImageName = ImageName And ri.Height = Height _
And ri.Width = Width Then
ResizedImage = ri
ImageFound = True
Exit For
End If
Next
End If
Dim ResizedImagePath As String = _
Ctx.Server.MapPath(ResizedImagesDirectory)
If Not Directory.Exists(ResizedImagePath) Then
Directory.CreateDirectory(ResizedImagePath)
End If
Dim cd As New CacheDependency(ResizedImagePath)
If Not ImageFound Then
Dim ImageFullPath As String = _
Path.Combine(Ctx.Server.MapPath(ResizedImagesDirectory), ImageName)
If File.Exists(ImageFullPath) Then
ResizedImage.ImageName = ImageName
ResizedImage.ImagePath = _
Ctx.Server.MapPath(ResizedImagesDirectory)
ResizedImage.Height = Height
ResizedImage.Width = Width
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
End If
Dim Req As HttpRequest = Ctx.Request
Dim PhysicalPath As String = Regex.Replace(Req.PhysicalPath, _
"\.ashx.*", "")
If ResizedImage.ImageName = "" Then
ResizeImage(PhysicalPath, ResizedImagePath, _
ImageName, Width, Height, ImgType)
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)
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
NewHeight = Math.Floor(OriginalImage.Height / _
(OriginalImage.Width / NewWidth))
End If
If NewHeight > 0 And NewWidth = 0 Then
NewWidth = Math.Floor(OriginalImage.Width / _
(OriginalImage.Height / NewHeight))
End If
If NewHeight > OriginalImage.Height Or _
NewWidth > OriginalImage.Width Then
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)
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
Dim XMLSource As XElement = GetResizedImageKeys(Ctx)
Dim ResizedQuery = From r In XMLSource.Elements("image") _
Where r.Attribute("name") = Key _
Select r
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
xel = ctx.Cache.Get("ResizedImageKeys")
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
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