在页面上显示数据库中的图片时如何设置图片的大小并且不让图片有明显的失真和变形?采用Image对象的GetThumbnailImage方法对图片进行缩略图设置,让输出的图片按照指定大小显示。本文给出了一个较为完整的解决方案。
在开发Web应用程序时,常常都会遇到图片上传和浏览的问题,有两种方案可以用来解决这个问题:
1. 将图片直接上传到服务器的硬盘上。优点是代码执行起来比较方便,给ASP.NET的执行进程赋予一定的权限,直接将操作的客户端文件保存到远程服务器上即可,并且在页面上读取图片非常方便,还可以直接设置图片的显示样式,如图片的大小、边框、链接地址等;缺点是安全性较差,从客户端访问服务器的目录存在一定的安全隐患,另外就是图片不便于管理,如用户删除图片、修改图片、图片上传后的文件名管理等,尤其是共享图片的管理。
2. 将图片转存到数据库中。优点是图片便于管理,DBA可以统一对图片进行操作,用户也可以非常方便地修改和删除已有的图片;缺点是代码执行起来稍微麻烦,而且针对不同的数据库还需要编写不同的执行代码,另外就是在处理页面中一组连续的图片时会有些麻烦,可能需要通过多次访问数据库来获取到页面上的所有图片,以及读取图片时同时设置图片的显示样式、对上传图片的文件大小限制等。
总之,两种解决方案各有利弊,但终究要选用哪种解决方案往往也是由项目需求所决定的,作为程序员本身,当然是希望越简单越好。其实个人觉得如果项目对图片的要求不是很高,而且数据量不太大,采用第一种方案比较合适,毕竟操作简单,实现起来比较容易,如果要对图片进行修改和删除的话,也可以增加一些补救措施。如用户上传图片的同时将图片的相关信息保存到数据库中,但图片文件本身保存到服务器的硬盘上,可以编写一个文件名生成程序用于在服务器上创建唯一的文件名,这样就可以避免当用户上传的图片与服务器上已有的图片文件名相同时文件被覆盖掉,数据库中保存文件的一个映射表用于进行图片的检索,当图片被修改或删除时根据映射表对磁盘上的文件进行相应的操作,操作成功后同时更新映射表。
第一种方案在上传和读取图片时都很容易,相关的代码我就不再具体给出来了,这里具体看一下第二种方案的实现。
首先是图片的上传。在ASP.NET中,我们在页面上可以直接使用Visual Studio提供的FileUpload控件,也可以使用input的file类型的标签,其中这两种控件在本质上并没有区别,只是微软提供的控件封装了一些属性和方法便于调用罢了(我个人推荐直接在页面上使用input type='file'的标签)。《由Stream.Position问题而引发的思考》一文中介绍了如何上传图片,在服务端得到要上传的图片后,可以通过调用SQL的存储过程将图片保存进数据库中。
读取图片时可以根据关键字调用数据库的存储过程得到图片实体,然后操作该实体将图片的二进制数据Write到页面上。一个图片的实体类可以仅有与图片相关的属性,类似于下面这样:
public class ImageEntity
{
public ImageEntity()
{
}
public ImageEntity(int id, string title, Byte[] imageBlob, string type)
{
ID = id;
Title = title;
ImageBlob = imageBlob;
Type = type;
}
public int ID { get; set; }
public string Title { get; set; }
public string Type { get; set; }
public Byte[] ImageBlob { get; set; }
}
问题的关键是得到图片的实体后怎样将图片Write到页面上指定的地方,并设置图片的大小呢?
将图片Write到页面上可以通过MemoryStream的WriteTo方法,大致是这样的:
Byte[] imageBlob = imageEntity.ImageBlob;
if (imageBlob.Length > 0)
{
using (MemoryStream stream = new MemoryStream(imageBlob))
{
Response.ContentType = imageEntity.Type;
stream.WriteTo(Response.OutputStream);
stream.Close();
}
}
代码比较简单,没有什么很特别的地方,与在页面上输出其它内容的方式基本相同。不过这种方法只能将图片的内容输出到页面上,而不能在页面上指定的地方进行输出。要做到这一点,我们只能将输出图片的代码写在单独的一个页面中,然后在要显示图片的页面的指定位置写上<img>标签,并将它的src指向这个用来输出图片的页面的地址。这个方法很奏效!但仍然面临着一些问题,如设置图片显示时的大小。也许你会说直接设置<img>标签的Width和Height属性不就可以了吗?跟在第一种方案里面一样!事实上这种方法的效果并不是很好,第一种方案在实际应用中也会碰到类似的问题,如果我们仅仅只是设置了图片的高和宽,那么图片在显示时可能会变形甚至失真。解决这个问题的办法是采用.NET提供的图片缩略图功能,即采用Image对象的GetThumbnailImage方法。下面给出了一个完整的解决方案。
ThumbnailPage
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
using BLL;
namespace SEL.SonySource.Views.Home
{
/// <summary>
/// This page is used to create the thumbnail images.
/// Parameters: width - the width value for thumbnail image.
/// height - the height value for thumbnail image.
/// imageId - the image key to indentify the only one image.
/// isthumbnail - true is thumbnail image (default), false is original image.
/// </summary>
public partial class Thumbnail : System.Web.UI.Page
{
private const int WIDTH = 196;
private const int HEIGHT = 102;
private int _width = WIDTH;
private int _height = HEIGHT;
private int _imageID = 0;
private string _isThumbnail = bool.TrueString;
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void Page_Load(object sender, EventArgs e)
{
GetRequestParms();
ImageEntity image = imageBll.GetImageById(_imageID);
if (image != null)
{
try
{
GetThumbNail(_width, _height, _isThumbnail.ToLower().Equals(bool.TrueString.ToLower()), image.Type, image.ImageBlob);
}
catch (Exception ex)
{
throw ex;
}
}
}
private void GetRequestParms()
{
// Request parameter Width
if (Request["width"] != null)
{
try
{
_width = int.Parse(Request["width"]);
}
catch { }
}
// Request parameter Height
if (Request["height"] != null)
{
try
{
_height = int.Parse(Request["height"]);
}
catch { }
}
// Request parameter baseCommentID
if (Request["imageid"] != null)
{
try
{
_imageID = int.Parse(Request["imageid"]);
}
catch { }
}
// Request parameter isThumbnail
if (Request["isthumbnail"] != null)
{
_isThumbnail = Request["isthumbnail"];
}
}
/// <summary>
/// Get ImageFormat from the image content type.
/// </summary>
/// <param name="strContentType">The string value of the image content type (e.g. image/bmp).</param>
/// <returns>Return the matched ImageFormat object.</returns>
private ImageFormat GetImageType(object strContentType)
{
switch (strContentType.ToString().ToLower())
{
case "image/jpeg":
case "image/pjpeg":
return ImageFormat.Jpeg;
case "image/gif":
return ImageFormat.Gif;
case "image/bmp":
return ImageFormat.Bmp;
case "image/tiff":
return ImageFormat.Tiff;
case "image/x-icon":
return ImageFormat.Icon;
case "image/x-png":
case "image/png":
return ImageFormat.Png;
case "iamge/x-emf":
return ImageFormat.Emf;
case "image/x-exif":
return ImageFormat.Exif;
case "image/x-wmf":
return ImageFormat.Wmf;
default:
return ImageFormat.MemoryBmp;
}
}
/// <summary>
/// Get encoder from image format.
/// </summary>
/// <param name="format"></param>
/// <returns></returns>
private ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID.Equals(format.Guid))
{
return codec;
}
}
return null;
}
/// <summary>
/// Create thumbnail image to the current page.
/// </summary>
/// <param name="iWidth">Thumbnail width.</param>
/// <param name="iHeight">Thumbnail height.</param>
/// <param name="isThumbnail">Whether or not create thumbnail image.</param>
/// <param name="contentType">The image content type.</param>
/// <param name="imageBlob">The byte array of the image.</param>
private void GetThumbNail(int iWidth, int iHeight, bool isThumbnail, string contentType, Byte[] imageBlob)
{
if (imageBlob.Length > 0)
{
using (MemoryStream memStreamOri = new MemoryStream(imageBlob))
{
Response.ContentType = contentType;
// Create thumbnail image.
if (isThumbnail)
{
System.Drawing.Image img = System.Drawing.Image.FromStream(memStreamOri);
img = img.GetThumbnailImage(iWidth, iHeight, null, IntPtr.Zero);
using (MemoryStream memStreamThumb = new MemoryStream())
{
System.Drawing.Imaging.Encoder encoder = System.Drawing.Imaging.Encoder.Quality;
EncoderParameters encoderParms = new EncoderParameters(1);
EncoderParameter encoderParm = new EncoderParameter(encoder, 50L);// Specified the value of thumbnail image.
encoderParms.Param[0] = encoderParm;
ImageCodecInfo imageCodec = GetEncoder(GetImageType(contentType));
img.Save(memStreamThumb, imageCodec, encoderParms);
memStreamThumb.WriteTo(Response.OutputStream);
memStreamThumb.Close();
}
}
// Create orginal image.
else
{
memStreamOri.WriteTo(Response.OutputStream);
}
memStreamOri.Close();
}
}
}
}
}
其中缺省使用了BLL命名空间下的imageBll.GetImageById方法,这个方法需要自己编写,主要功能就是根据传入的图片ID从数据库中读取图片,并返回一个图片的实体对象。页面接收四个参数,包括图片压缩的高和宽、是否以缩略图方式显示图片(如果为否则不用指定高和宽),图片ID。代码的核心部分为GetThumbNail方法,读者可以参考msdn中给出的例子http://msdn.microsoft.com/zh-cn/library/bb882583.aspx 。EncoderParameter构造函数中的第二个参数(例子中的50L)为指定图片的压缩级别,最高为100,数值越高表示压缩得越少,越接近原图片质量。按照指定高和宽压缩后的图片会根据你所指定的压缩级别丢失一部分像素(图片原尺寸大于压缩后的尺寸),或者补充像素(图片原尺寸小于压缩后的尺寸)。
这里有一个小小的缺陷,因为图片是从数据库中读取的,如果页面上需要连续显示一组图片,并且要显示的图片数量比较多的话,采用此方法输出图片的时候就需要多次调用输出图片的页面,同时也就需要多次访问数据库,效率可能会有所降低,适当地采用数据库缓存可以来解决这个问题。还有就是当gif格式的图片存在多帧的动画时,被压缩后的图片只会显示第一帧。
另外,有一些问题需要引起注意,在使用MemoryStream.WriteTo方法往页面上输出图片时可能会有异常抛出,使用前必须确保MemoryStream的实例在使用完后被显示关闭并释放(如使用Using语句),并且保存图片原始数据的MemoryStream与压缩后保存图片数据的MemorySream对象不是同一个对象。通过GetImageType方法得到正确的ImageFormat,有的时候图片的ContentType会有一些细微的区别,各种不同的ContentType是否代表了同一种ImageFormat需要认真确认,这些因素都可能导致图片显示时导致异常。异常可能并不会直接在页面上反映出来,在调用的地方,要显示的图片在发生异常时会显示一个红色的小叉,表示图片显示失败。