仿淘宝头像上传功能(二)——程序篇
目录:
源码下载:
这里先定义了一个类,来专门处理图片的:
public class ImageManager { }
以下是其中的方法:
/// <summary> /// 获得文件名并去掉后缀。 /// </summary> /// <param name="path">文件完所路径。</param> /// <returns>不带路径与后缀的文件名。</returns> private string GetFileName(string path) { string fileName = Path.GetFileName(path); return fileName.Substring(0, fileName.Length - Path.GetExtension(fileName).Length); }
/// <summary> /// 生成图片。 /// </summary> /// <param name="image">要生成的图片对象。</param> /// <param name="savePath">保存位置。</param> /// <param name="high">是否设置绘制高质量。</param> private void SaveImage(Image image, string savePath, bool high) { string fileExt = Path.GetExtension(savePath); ImageFormat imageFormat = ImageFormat.Jpeg; if (fileExt.Equals(".gif", StringComparison.OrdinalIgnoreCase)) { imageFormat = ImageFormat.Gif; } if (fileExt.Equals(".png", StringComparison.OrdinalIgnoreCase)) { imageFormat = ImageFormat.Png; } var directory = Directory.GetParent(savePath).FullName; if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } if (high) { //以下代码为保存图片时,设置压缩质量。 EncoderParameters encoderParams = new EncoderParameters(); long[] quality = new long[1]; quality[0] = 100; EncoderParameter encoderParam = new EncoderParameter(Encoder.Quality, quality); encoderParams.Param[0] = encoderParam; //获得包含有关内置图像编码解码器的信息的 ImageCodecInfo 对象。 ImageCodecInfo[] arrayICI = ImageCodecInfo.GetImageEncoders(); ImageCodecInfo jpegICI = null; foreach (ImageCodecInfo imageCodecInfo in arrayICI) { if (imageCodecInfo.FormatDescription.Equals("JPEG")) { jpegICI = imageCodecInfo; //设置 JPEG 编码 break; } } if (jpegICI != null) { image.Save(savePath, jpegICI, encoderParams); } else { image.Save(savePath, imageFormat); } } else { //保存图片。 image.Save(savePath, imageFormat); } }
/// <summary> /// 获得重新绘制的缩略图对象。 /// </summary> /// <param name="image">原图对象。</param> /// <param name="maxWidth">缩略图最大宽度。</param> /// <param name="maxHeight">缩略图最大高度。</param> /// <param name="high">是否设置绘制高质量。</param> /// <returns>绘制缩略图对象。</returns> private Bitmap GetThumbnailImage(Image image, int maxWidth, int maxHeight, bool high) { //缩略图尺寸 Size newSize = this.GetImageSize(new Size(image.Width, image.Height), new Size(maxWidth, maxHeight)); //创建一张缩略图对象。 var targetImage = new Bitmap(newSize.Width, newSize.Height); Graphics graphics = Graphics.FromImage(targetImage); if (high) { graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.SmoothingMode = SmoothingMode.HighQuality; } //缩略图的矩形。 var toRectangle = new Rectangle(0, 0, newSize.Width, newSize.Height); //缩略图在原图的位置的矩形。 var fromRectangle = new Rectangle(0, 0, image.Width, image.Height); //将要原图绘制到 targetImage 对象里,该对象表示的是缩小后的图片。 graphics.DrawImage(image, toRectangle, fromRectangle, GraphicsUnit.Pixel); graphics.Dispose(); return targetImage; }
/// <summary> /// 裁剪图片。 /// </summary> /// <param name="sourceImage">原图对象。</param> /// <param name="cutRectangle">裁剪矩形信息。</param> /// <param name="high">是否设置绘制高质量。</param> /// <returns>裁剪后的图片对象。</returns> private Bitmap CutImage(Image sourceImage, Rectangle cutRectangle, bool high) { //根据传入的裁剪坐标与宽高来裁剪出图片。 var targetImage = new Bitmap(cutRectangle.Width, cutRectangle.Height); Graphics graphics = Graphics.FromImage(targetImage); if (high) { graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.SmoothingMode = SmoothingMode.HighQuality; } //裁剪图片的矩形。 var toRectangle = new Rectangle(0, 0, cutRectangle.Width, cutRectangle.Height); //裁剪图片在原图的位置的矩形。 var fromRectangle = new Rectangle(cutRectangle.X, cutRectangle.Y, cutRectangle.Width, cutRectangle.Height); //将要裁剪的区域绘制到 image 对象里,该对象表示的是裁剪后的图片。 graphics.DrawImage(sourceImage, toRectangle, cutRectangle, GraphicsUnit.Pixel); graphics.Dispose(); return targetImage; }
/// <summary> /// 获得裁剪正方形区域矩形。 /// </summary> /// <remarks> /// 根据 cutRectangle 与原图显示区域的比例来计算出原图的裁剪区域范围矩形位置与大小。 /// </remarks> /// <param name="width">原图宽度。</param> /// <param name="height">原图高度。</param> /// <param name="areaWidth">原图显示区域宽度。</param> /// <param name="areaHieght">原图显示区域高度。</param> /// <param name="cutRectangle">原图显示区域的裁剪区域范围矩形。</param> /// <returns>原图的裁剪区域范围矩形。</returns> private Rectangle GetCutRectangle(double width, double height, double areaWidth, double areaHieght, Rectangle cutRectangle) { double w = 0.0; double h = 0.0; //计算出源图片缩小到显示区域范围内的宽度和高度。 if (width < areaWidth && height < areaHieght) { //没超出,不做计算。 w = width; h = height; } else if ((width / height) > (areaWidth / areaHieght)) { w = areaWidth; h = (w * height) / width; } else { h = areaHieght; w = (h * width) / height; } //计算出显示区域的图片的宽度与裁剪图片的宽度的比例。 double widthScale = w / cutRectangle.Width; //计算出显示区域的图片的高度与裁剪图片的高度的比例。 double heightScale = h / cutRectangle.Height; //计算出 X 坐标的比例(公式:显示区域宽度 / X 坐标) double smallWidthScale = w / cutRectangle.X; //计算出 Y 坐标的比例(公式:显示区域高度 / Y 坐标) double smallHeightScale = h / cutRectangle.Y; Rectangle resultRectangle = new Rectangle { //裁剪源图片的 X 坐标是:源图片宽度 / X 坐标比例。 X = (int)(width / smallWidthScale), //裁剪源图片的 Y 坐标是:源图片高度 / Y 坐标比例。 Y = (int)(height / smallHeightScale), //裁剪源图片的宽度是:源图片宽度 / 宽度比例。 Width = (int)(width / widthScale), //裁剪源图片的高度是:源图片高度 / 高度比例。 Height = (int)(height / heightScale) }; return resultRectangle; }
/// <summary> /// 获得指定图片尺寸的缩放尺寸。 /// </summary> /// <param name="imageSize">原图尺寸。</param> /// <param name="maxSize">最大尺寸。</param> /// <returns>缩放后的尺寸。</returns> private Size GetImageSize(Size imageSize, Size maxSize) { double w = 0.0; double h = 0.0; double sw = Convert.ToDouble(imageSize.Width); double sh = Convert.ToDouble(imageSize.Height); double mw = Convert.ToDouble(maxSize.Width); double mh = Convert.ToDouble(maxSize.Height); if (sw < mw && sh < mh) { w = sw; h = sh; } else if ((sw / sh) > (mw / mh)) { w = maxSize.Width; h = (w * sh) / sw; } else { h = maxSize.Height; w = (h * sw) / sh; } int width = Convert.ToInt32(w); int height = Convert.ToInt32(h); return new Size(width, height); }
/// <summary> /// 裁剪并缩放正方形图片。 /// </summary> /// <remarks> /// 1.先在原图上根据 x,y 坐标开始裁剪,裁剪的宽和高就是width,height。 /// 2.在将裁剪出来后的图片,按照传入 side 缩略图尺寸来将裁剪出来的图片进行缩小或者放大。 /// 3.保存图片。 /// </remarks> /// <param name="sourcePath">原图位置(包含文件名)。</param> /// <param name="savePath">保存位置(目录)。</param> /// <param name="side">裁剪后的正方型缩略图尺寸。</param> /// <param name="high">是否设置绘制高质量。</param> /// <param name="cutImage">裁剪图片信息。</param> /// <param name="fileName">文件名(不带后缀)。</param> /// <param name="sizes">缩略图尺寸。</param> public void CutImage(string sourcePath, string savePath, int side, bool high, CutImageModel cutImage, string fileName, params int[] sizes) { ////判断原文件是否存在。 //if(!File.Exists(sourcePath)) //{ // throw new IOException(string.Format("路径:“{0}”文件不存在。", sourcePath)); //} if(cutImage == null) { throw new ArgumentNullException("cutImage", "cutImage 对象不可以是空的。"); } if (cutImage.Width <= 0 || cutImage.Height <= 0) { throw new TBGException("裁剪图片区域的宽度或者高度必须是大于 0 的。"); } if (cutImage.AreaWidth <= 0 || cutImage.AreaHeight <= 0) { throw new TBGException("原图预览区域的宽度或者高度必须是大于 0 的。"); } using (Image sourceImage = Image.FromFile(sourcePath)) { //计算并返回原图的裁剪区域范围矩形。 var cutRectangle = this.GetCutRectangle( sourceImage.Width, sourceImage.Height, cutImage.AreaWidth, cutImage.AreaHeight, new Rectangle(cutImage.X, cutImage.Y, cutImage.Width, cutImage.Height)); //裁剪原图。 var targetImage = this.CutImage(sourceImage, cutRectangle, high); //按 side 尺寸缩放裁剪后的图片。 var resultImage = this.GetThumbnailImage(targetImage, side, side, high); //获得原图扩展名。 string fileExt = Path.GetExtension(sourcePath); //组合保存图片的路径。 string saveImagePath = savePath.EndsWith("\\") ? savePath + fileName + "_" + side + fileExt : savePath + "\\" + fileName + "_" + side + fileExt; //保存图片(只支付 jpg, gif, png)。 SaveImage(resultImage, saveImagePath, high); //生成缩略图 if (sizes != null && sizes.Length > 0) { foreach (int size in sizes) { var thumbnailImage = this.GetThumbnailImage(resultImage, size, size, high); if (thumbnailImage != null) { saveImagePath = savePath.EndsWith("\\") ? savePath + fileName + "_" + size + fileExt : savePath + "\\" + fileName + "_" + size + fileExt; SaveImage(thumbnailImage, saveImagePath, high); thumbnailImage.Dispose(); } } } targetImage.Dispose(); resultImage.Dispose(); } ////删除原图片。 //File.Delete(sourcePath); }
/// <summary> /// 上传并按比例缩放图片。 /// </summary> /// <param name="uploadImage">图片上传信息。</param> /// <param name="newFileNameAction">生成新图片名称。</param> /// <param name="newFileName">带后缀新图片名称。</param> public ViewUploadImage LoadImage(UploadImage uploadImage, Func<string> newFileNameAction, out string newFileName) { if (string.IsNullOrWhiteSpace(uploadImage.FileName)) { throw new ArgumentNullException("fileName", "文件名不可以是空的。"); } string fileExt = Path.GetExtension(uploadImage.FileName); if (string.IsNullOrWhiteSpace(fileExt)) { throw new ArgumentNullException("fileExt", "文件名不可以没有后缀。"); } newFileName = newFileNameAction() + fileExt.ToLower(); string filePath = Path.Combine(uploadImage.SavePath, newFileName); if (!Directory.Exists(uploadImage.SavePath)) { Directory.CreateDirectory(uploadImage.SavePath); } ViewUploadImage view = new ViewUploadImage(); view.FileName = this.GetFileName(filePath); view.FileExt = fileExt; Image sourceImage = Image.FromStream(uploadImage.Stream); var targetImage = this.GetThumbnailImage(sourceImage, uploadImage.Width, uploadImage.Height, true); var size = this.GetImageSize(new Size(sourceImage.Width, sourceImage.Height), new Size(uploadImage.Width, uploadImage.Height)); view.Width = sourceImage.Width; view.Height = sourceImage.Height; view.ViewWidth = size.Width; view.ViewHeight = size.Height; this.SaveImage(targetImage, filePath, true); sourceImage.Dispose(); targetImage.Dispose(); return view; }
/// <summary> /// 裁剪图片模型信息。 /// </summary> public class CutImageModel { /// <summary> /// 裁剪图片在原图里的左上角开始的 X 坐标。 /// </summary> public int X { get; set; } /// <summary> /// 裁剪图片在原图里的左上角开始的 Y 坐标。 /// </summary> public int Y { get; set; } /// <summary> /// 裁剪图片宽度。 /// </summary> public int Width { get; set; } /// <summary> /// 裁剪图片高度。 /// </summary> public int Height { get; set; } /// <summary> /// 加载区域的宽度。 /// </summary> /// <remarks> /// 加载区域表示的是显示原图片的区域。 /// 由于窗口中不可能显示完整某些图片, /// 所以大于这个区域的图片会先缩小到 /// 不超出该区域的大小之后才显示出来。 /// </remarks> public int AreaWidth { get; set; } /// <summary> /// 加载区域的高度。 /// </summary> public int AreaHeight { get; set; } }
/// <summary> /// 上传图片信息。 /// </summary> public class UploadImage { /// <summary> /// 文件流。 /// </summary> public Stream Stream { get; set; } /// <summary> /// 原图片名称。 /// </summary> public string FileName { get; set; } /// <summary> /// 保存目录。 /// </summary> public string SavePath { get; set; } /// <summary> /// 最大宽度。 /// </summary> public int Width { get; set; } /// <summary> /// 最大高度。 /// </summary> public int Height { get; set; } }
/// <summary> /// 输出显示上传图片信息。 /// </summary> public class ViewUploadImage { /// <summary> /// 文件名。 /// </summary> public string FileName { get; set; } /// <summary> /// 扩展名。 /// </summary> public string FileExt { get; set; } /// <summary> /// 原图片宽度。 /// </summary> public int Width { get; set; } /// <summary> /// 原图片高度。 /// </summary> public int Height { get; set; } /// <summary> /// 预览图片宽度。 /// </summary> public int ViewWidth { get; set; } /// <summary> /// 预览图片高度。 /// </summary> public int ViewHeight { get; set; } }
实际使用方法:
头像辅助类:
/// <summary> /// 修改头像操作状态。 /// </summary> public enum ChangeIconStatus { /// <summary> /// 成功。 /// </summary> Success = 1, /// <summary> /// 失败。 /// </summary> Error = 0, /// <summary> /// 登录失效。 /// </summary> LoginInvalid = -1, /// <summary> /// 未上传头像。 /// </summary> NotUpload = -2, }
这里是仿Discuz论坛的头像上传保存路径。
SiteFactory.Current.IconDefaultUrl
表示头像的访问地址。
public class UserIconManager { private static readonly UserIconManager _current = new UserIconManager(); public static UserIconManager Current { get { return _current; } } /// <summary> /// 获得用户头像。 /// </summary> /// <param name="userId">用户编号。</param> /// <param name="fileName">头像名称(带后缀)。</param> /// <param name="size">头像尺寸。</param> /// <returns></returns> public string GetIcon(int userId, string fileName, IconSize size) { string image = string.Empty; string icon = string.Empty; string uid = userId.ToString(); int len = uid.Length; if (len < 11) { for (int i = 0; i < 11 - len; i++) { uid = "0" + uid; } } uid = uid.Substring(0, 3) + "/" + uid.Substring(3, 3) + "/" + uid.Substring(6, 3) + "/" + uid.Substring(9, 2); image = SiteFactory.Current.IconDefaultUrl.EndsWith("/") ? SiteFactory.Current.IconDefaultUrl + uid + "/" : SiteFactory.Current.IconDefaultUrl + "/" + uid + "/"; string ext = Path.GetExtension(fileName); switch (size) { case IconSize.Normal: icon = userId.ToString() + ext; break; case IconSize.Big: icon = userId.ToString() + "_" + ((int)IconSize.Big) + ext; break; case IconSize.Small: icon = userId.ToString() + "_" + ((int)IconSize.Small) + ext; break; default: icon = userId.ToString() + "_" + ((int)IconSize.Big) + ext; break; } return image + icon; } /// <summary> /// /// </summary> /// <param name="userId"></param> /// <returns></returns> public string GetSaveDirectory(int userId) { string uid = userId.ToString(); int len = uid.Length; if (len < 11) { for (int i = 0; i < 11 - len; i++) { uid = "0" + uid; } } uid = uid.Substring(0, 3) + "\\" + uid.Substring(3, 3) + "\\" + uid.Substring(6, 3) + "\\" + uid.Substring(9, 2); return SiteFactory.Current.SaveIconPath.EndsWith("\\") ? SiteFactory.Current.SaveIconPath + uid + "\\" : SiteFactory.Current.SaveIconPath + "\\" + uid + "\\"; } } /// <summary> /// 输出显示上传头像信息。 /// </summary> public class ViewIconImage { /// <summary> /// 文件名(带后缀)。 /// </summary> public string FileName { get; set; } /// <summary> /// 原图片宽度。 /// </summary> public int Width { get; set; } /// <summary> /// 原图片高度。 /// </summary> public int Height { get; set; } /// <summary> /// 预览图片宽度。 /// </summary> public int ViewWidth { get; set; } /// <summary> /// 预览图片高度。 /// </summary> public int ViewHeight { get; set; } /// <summary> /// 原图头像地址。 /// </summary> public string Image { get; set; } /// <summary> /// 小图片头像地址。 /// </summary> public string SmallImage { get; set; } /// <summary> /// 大图片头像地址。 /// </summary> public string BigImage { get; set; } }
/// <summary> /// 头像大小。 /// </summary> public enum IconSize { /// <summary> /// 通常。 /// </summary> Normal = 0, /// <summary> /// 大图。 /// </summary> Big = 110, /// <summary> /// 缩略图。 /// </summary> Small = 37 }
上传头像:
/// <summary> /// 上传头像。 /// </summary> /// <param name="userId">用户编号。</param> /// <param name="uploadImage">上传图片信息。</param> /// <returns></returns> public ViewIconImage UploadIcon(int userId, UploadImage uploadImage) { string newFileName; var view = ImageManager.Current.LoadImage(uploadImage, () => UserIconManager.Current.GetSaveDirectory(userId) + userId, out newFileName); if (view == null) { throw new Exception("头像上传失败。"); } //保存头像后缀名。 var result = this._userRepository.ChangeIcon(userId, view.FileExt); if (result) { ViewIconImage iconImage = new ViewIconImage(); iconImage.ViewWidth = view.ViewWidth; iconImage.ViewHeight = view.ViewHeight; iconImage.Image = UserIconManager.Current.GetIcon(userId, newFileName, IconSize.Normal); iconImage.BigImage = UserIconManager.Current.GetIcon(userId, newFileName, IconSize.Big); iconImage.SmallImage = UserIconManager.Current.GetIcon(userId, newFileName, IconSize.Small); return iconImage; } return null; }
修改头像:
/// <summary> /// 修改头像。 /// </summary> /// <param name="accountNumber">用户账号。</param> /// <param name="cutImage">裁剪图片输入信息。</param> public ChangeIconStatus ChangeIcon(string accountNumber, CutImageModel cutImage) { var user = this.GetUser(accountNumber); if(user == null) { return ChangeIconStatus.LoginInvalid; } string sourceImagePath = string.Empty; if (!string.IsNullOrWhiteSpace(user.Image)) { string savePath = UserIconManager.Current.GetSaveDirectory(user.Id); sourceImagePath = savePath + user.Id.ToString() + user.Image; ImageManager.Current.CutImage(sourceImagePath, savePath, (int)IconSize.Big, true, cutImage, user.Id.ToString(), (int)IconSize.Small); return ChangeIconStatus.Success; } return ChangeIconStatus.NotUpload; }