使用 HTTP 模块动态调整图像大小






3.89/5 (8投票s)
通过在图像名称后附加高度和宽度,或者使用具有设定高度和宽度的自定义命名字段,轻松动态地调整整个网站的图像大小。
引言
为您的网站调整图像大小不必很困难。通过 HTTP 模块的魔力,我们可以拦截通过您网站的每一种特定类型的请求(例如图像请求),并在将结果发送到浏览器之前执行任何必要的处理。
当您需要调整整个网站的图像大小的时候,这非常方便。 下面的代码允许您
- 通过在图像后添加宽度和高度来调整图像大小。
- 定义具有特定宽度和高度的图像“键”,这样您就可以在配置文件中更改键的宽度和高度,并且网站上具有该键的每个图像都将自动调整大小。
- 只需在web.config文件中添加几行代码,并且如果您想使用图像键,则添加一个 XML 配置文件,即可进行这些更改。
- 配置设置和调整大小的图像列表被缓存,以使此解决方案尽可能具有可伸缩性。
- 调整大小的图像会保存到磁盘,然后用于所有未来的请求,同样是为了可伸缩性。
查看一个动态图像调整大小的例子,了解图像调整大小可以多么容易。查看源代码,您会发现所有示例都在使用相同的图像名称,并且只有宽度和高度在变化。
背景
了解 HTTP 模块及其工作原理将有助于理解这里发生的事情。您可以通过这篇来自 15 秒的文章简要了解该主题。
使用代码
要将此代码添加到现有网站,请将以下三行代码添加到您的web.config文件的httpHandlers
部分。 这告诉 .NET 处理任何扩展名为.jpg、.gif或.png,后跟.ashx的内容,由ResizeImages
类处理。
<add verb="*" path="*.jpg.ashx" type="ImageHandlers.ResizeImages"/>
<add verb="*" path="*.gif.ashx" type="ImageHandlers.ResizeImages"/>
<add verb="*" path="*.png.ashx" type="ImageHandlers.ResizeImages"/>
接下来,在您的appSettings
部分中,定义您想要保存调整大小的图像的位置,以及您将用于图像键的 XML 文件的位置(如果您不打算使用图像键,则第二个设置是可选的)。
<add key="ResizedImagesDirectory" value="~/images/resized"/>
<add key="ResizedImageKeys" value="~/images/resized.xml"/>
将ResizeImages.vb类放到您的App_Code文件夹中,并将您的图像链接添加到测试页面。
您的链接可以以几种不同的格式显示
<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-->
如果您想定义图像键的高度和宽度,则该信息将保存在resized.xml文件中(或者您选择的任何名称的文件)。格式如下
<ResizedImages>
<image name="thumbnail" width="100" height="100" />
<image name="normal" width="200" />
<image name="large" height="300" />
</ResizedImages>
因此,在此示例中,具有图像键“normal
”的图像的宽度将设置为 200。 如果更改图像键的宽度,则您网站上所有使用“normal
”键的图像都将自动调整大小。
代码
ResizeImages.vb类处理所有图像大小调整。如果您对此如何工作感兴趣,我已经注释了下面的代码,您可以更详细地研究它。如果您只是想开始使用所有内容,请下载该项目并享受吧!
注意:这目前仅适用于 .NET 3.5,因为我使用 LINQ 来解析 XML 中的图像键。 如果您想在 .NET 2.0 中运行它,您可以替换GetImageDimensions
函数中的 LINQ 代码,并改为使用 XPath 查询。
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