前言
需求源自项目中的一些应用,比如相册功能,通常用户上传相片后我们都会针对该相片再生成一张缩略图,用于其它页面上的列表显示。随便看一下,大部分网站基本都是将原图等比缩放来生成缩略图。但完美主义者会发现一些问题,比如显示排版时想让相片缩略图列表非常统一、整齐、和美观,比如要求每张缩略图大小固定为120 x 90且不拉伸变形怎么办?再比如用户头像如何让缩略图比原图更清晰?或是如何在上传的图片下加一个半透明的LOGO水印?
OK,本文根据自己的项目代码描述以上问题的解决方案,全部基于.Net Framework类库完成,代码中包含了C#图片处理的一些基础知识.
提高缩略图清晰度
(原图200*200,12.3k)
(处理后80*80,17.7k)
之前一直认为缩略图不可能比原图清晰,直到某天一位产品的同事给我看某网站的效果。于是开始寻找.NET下实现代码,仔细观察缩略图确实比原图更清晰了一些,但代价是缩略图文件比原图更大,所以如果你想让一张占满显示器屏幕的超大图片更清晰,那么图片占用空间和网络流量就必需考虑了,如果是互联网应用,建议缩略图在200像素以内的使用该方法。当然如果哪位有更好的代码即能让图片文件大小变化不大又让图片更清晰还请分享。
吴剑 http://www.cnblogs.com/wu-jian
图片裁剪
(原图256*192)
(裁剪要求100*100)
(原图256*192)
(裁剪要求90*120)
(原图256*192)
(裁剪要求120*90)
(原图146*256)
(裁剪要求100*100)
(原图146*256)
(裁剪要求90*120)
(原图146*256)
(裁剪要求120*90)
算法:以原图中心作为裁剪中心,最大范围的对原图进行裁剪,然后对裁剪结果等比缩放。
吴剑 http://www.cnblogs.com/wu-jian
图片水印


仅演示了效果,如需要变更字体、水印透明度、位置等可自行在代码或方法中扩展。
代码
封装了几个通用的方法,如发现有BUG或漏洞还请及时指正。

1using System; 2using System.Collections.Generic; 3using System.Text; 4using System.IO; 5using System.Drawing; 6using System.Drawing.Drawing2D; 7using System.Drawing.Imaging; 8 9namespace WuJian.Common 10{ 11 /// <summary> 12 /// 图片处理 13 /// http://www.cnblogs.com/wu-jian/ 14 /// 15 /// 吴剑 2011-02-20 创建 16 /// 吴剑 2012-08-08 修改 17 /// </summary> 18 public class Image 19 { 20 #region 正方型裁剪并缩放 21 22 /// <summary> 23 /// 正方型裁剪 24 /// 以图片中心为轴心,截取正方型,然后等比缩放 25 /// 用于头像处理 26 /// </summary> 27 /// <remarks>吴剑 2012-08-08</remarks> 28 /// <param name="fromFile">原图Stream对象</param> 29 /// <param name="fileSaveUrl">缩略图存放地址</param> 30 /// <param name="side">指定的边长(正方型)</param> 31 /// <param name="quality">质量(范围0-100)</param> 32 public static void CutForSquare(System.IO.Stream fromFile, string fileSaveUrl, int side, int quality) 33 { 34 //创建目录 35 string dir = Path.GetDirectoryName(fileSaveUrl); 36 if (!Directory.Exists(dir)) 37 Directory.CreateDirectory(dir); 38 39 //原始图片(获取原始图片创建对象,并使用流中嵌入的颜色管理信息) 40 System.Drawing.Image initImage = System.Drawing.Image.FromStream(fromFile, true); 41 42 //原图宽高均小于模版,不作处理,直接保存 43 if (initImage.Width <= side && initImage.Height <= side) 44 { 45 initImage.Save(fileSaveUrl, System.Drawing.Imaging.ImageFormat.Jpeg); 46 } 47 else 48 { 49 //原始图片的宽、高 50 int initWidth = initImage.Width; 51 int initHeight = initImage.Height; 52 53 //非正方型先裁剪为正方型 54 if (initWidth != initHeight) 55 { 56 //截图对象 57 System.Drawing.Image pickedImage = null; 58 System.Drawing.Graphics pickedG = null; 59 60 //宽大于高的横图 61 if (initWidth > initHeight) 62 { 63 //对象实例化 64 pickedImage = new System.Drawing.Bitmap(initHeight, initHeight); 65 pickedG = System.Drawing.Graphics.FromImage(pickedImage); 66 //设置质量 67 pickedG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; 68 pickedG.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; 69 //定位 70 Rectangle fromR = new Rectangle((initWidth - initHeight) / 2, 0, initHeight, initHeight); 71 Rectangle toR = new Rectangle(0, 0, initHeight, initHeight); 72 //画图 73 pickedG.DrawImage(initImage, toR, fromR, System.Drawing.GraphicsUnit.Pixel); 74 //重置宽 75 initWidth = initHeight; 76 } 77 //高大于宽的竖图 78 else 79 { 80 //对象实例化 81 pickedImage = new System.Drawing.Bitmap(initWidth, initWidth); 82 pickedG = System.Drawing.Graphics.FromImage(pickedImage); 83 //设置质量 84 pickedG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; 85 pickedG.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; 86 //定位 87 Rectangle fromR = new Rectangle(0, (initHeight - initWidth) / 2, initWidth, initWidth); 88 Rectangle toR = new Rectangle(0, 0, initWidth, initWidth); 89 //画图 90 pickedG.DrawImage(initImage, toR, fromR, System.Drawing.GraphicsUnit.Pixel); 91 //重置高 92 initHeight = initWidth; 93 } 94 95 //将截图对象赋给原图 96 initImage = (System.Drawing.Image)pickedImage.Clone(); 97 //释放截图资源 98 pickedG.Dispose(); 99 pickedImage.Dispose(); 100 } 101 102 //缩略图对象 103 System.Drawing.Image resultImage = new System.Drawing.Bitmap(side, side); 104 System.Drawing.Graphics resultG = System.Drawing.Graphics.FromImage(resultImage); 105 //设置质量 106 resultG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; 107 resultG.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; 108 //用指定背景色清空画布 109 resultG.Clear(Color.White); 110 //绘制缩略图 111 resultG.DrawImage(initImage, new System.Drawing.Rectangle(0, 0, side, side), new System.Drawing.Rectangle(0, 0, initWidth, initHeight), System.Drawing.GraphicsUnit.Pixel); 112 113 //关键质量控制 114 //获取系统编码类型数组,包含了jpeg,bmp,png,gif,tiff 115 ImageCodecInfo[] icis = ImageCodecInfo.GetImageEncoders(); 116 ImageCodecInfo ici = null; 117 foreach (ImageCodecInfo i in icis) 118 { 119 if (i.MimeType == "image/jpeg" || i.MimeType == "image/bmp" || i.MimeType == "image/png" || i.MimeType == "image/gif") 120 { 121 ici = i; 122 } 123 } 124 EncoderParameters ep = new EncoderParameters(1); 125 ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, (long)quality); 126 127 //保存缩略图 128 resultImage.Save(fileSaveUrl, ici, ep); 129 130 //释放关键质量控制所用资源 131 ep.Dispose(); 132 133 //释放缩略图资源 134 resultG.Dispose(); 135 resultImage.Dispose(); 136 137 //释放原始图片资源 138 initImage.Dispose(); 139 } 140 } 141 142 #endregion 143 144 #region 自定义裁剪并缩放 145 146 /// <summary> 147 /// 指定长宽裁剪 148 /// 按模版比例最大范围的裁剪图片并缩放至模版尺寸 149 /// </summary> 150 /// <remarks>吴剑 2012-08-08</remarks> 151 /// <param name="fromFile">原图Stream对象</param> 152 /// <param name="fileSaveUrl">保存路径</param> 153 /// <param name="maxWidth">最大宽(单位:px)</param> 154 /// <param name="maxHeight">最大高(单位:px)</param> 155 /// <param name="quality">质量(范围0-100)</param> 156 public static void CutForCustom(System.IO.Stream fromFile, string fileSaveUrl, int maxWidth, int maxHeight, int quality) 157 { 158 //从文件获取原始图片,并使用流中嵌入的颜色管理信息 159 System.Drawing.Image initImage = System.Drawing.Image.FromStream(fromFile, true); 160 161 //原图宽高均小于模版,不作处理,直接保存 162 if (initImage.Width <= maxWidth && initImage.Height <= maxHeight) 163 { 164 initImage.Save(fileSaveUrl, System.Drawing.Imaging.ImageFormat.Jpeg); 165 } 166 else 167 { 168 //模版的宽高比例 169 double templateRate = (double)maxWidth / maxHeight; 170 //原图片的宽高比例 171 double initRate = (double)initImage.Width / initImage.Height; 172 173 //原图与模版比例相等,直接缩放 174 if (templateRate == initRate) 175 { 176 //按模版大小生成最终图片 177 System.Drawing.Image templateImage = new System.Drawing.Bitmap(maxWidth, maxHeight); 178 System.Drawing.Graphics templateG = System.Drawing.Graphics.FromImage(templateImage); 179 templateG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High; 180 templateG.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; 181 templateG.Clear(Color.White); 182 templateG.DrawImage(initImage, new System.Drawing.Rectangle(0, 0, maxWidth, maxHeight), new System.Drawing.Rectangle(0, 0, initImage.Width, initImage.Height), System.Drawing.GraphicsUnit.Pixel); 183 templateImage.Save(fileSaveUrl, System.Drawing.Imaging.ImageFormat.Jpeg); 184 } 185 //原图与模版比例不等,裁剪后缩放 186 else 187 { 188 //裁剪对象 189 System.Drawing.Image pickedImage = null; 190 System.Drawing.Graphics pickedG = null; 191 192 //定位 193 Rectangle fromR = new Rectangle(0, 0, 0, 0);//原图裁剪定位 194 Rectangle toR = new Rectangle(0, 0, 0, 0);//目标定位 195 196 //宽为标准进行裁剪 197 if (templateRate > initRate) 198 { 199 //裁剪对象实例化 200 pickedImage = new System.Drawing.Bitmap(initImage.Width, (int)System.Math.Floor(initImage.Width / templateRate)); 201 pickedG = System.Drawing.Graphics.FromImage(pickedImage); 202 203 //裁剪源定位 204 fromR.X = 0; 205 fromR.Y = (int)System.Math.Floor((initImage.Height - initImage.Width / templateRate) / 2); 206 fromR.Width = initImage.Width; 207 fromR.Height = (int)System.Math.Floor(initImage.Width / templateRate); 208 209 //裁剪目标定位 210 toR.X = 0; 211 toR.Y = 0; 212 toR.Width = initImage.Width; 213 toR.Height = (int)System.Math.Floor(initImage.Width / templateRate); 214 } 215 //高为标准进行裁剪 216 else 217 { 218 pickedImage = new System.Drawing.Bitmap((int)System.Math.Floor(initImage.Height * templateRate), initImage.Height); 219 pickedG = System.Drawing.Graphics.FromImage(pickedImage); 220 221 fromR.X = (int)System.Math.Floor((initImage.Width - initImage.Height * templateRate) / 2); 222 fromR.Y = 0; 223 fromR.Width = (int)System.Math.Floor(initImage.Height * templateRate); 224 fromR.Height = initImage.Height; 225 226 toR.X = 0; 227 toR.Y = 0; 228 toR.Width = (int)System.Math.Floor(initImage.Height * templateRate); 229 toR.Height = initImage.Height; 230 } 231 232 //设置质量 233 pickedG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; 234 pickedG.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; 235 236 //裁剪 237 pickedG.DrawImage(initImage, toR, fromR, System.Drawing.GraphicsUnit.Pixel); 238 239 //按模版大小生成最终图片 240 System.Drawing.Image templateImage = new System.Drawing.Bitmap(maxWidth, maxHeight); 241 System.Drawing.Graphics templateG = System.Drawing.Graphics.FromImage(templateImage); 242 templateG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High; 243 templateG.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; 244 templateG.Clear(Color.White); 245 templateG.DrawImage(pickedImage, new System.Drawing.Rectangle(0, 0, maxWidth, maxHeight), new System.Drawing.Rectangle(0, 0, pickedImage.Width, pickedImage.Height), System.Drawing.GraphicsUnit.Pixel); 246 247 //关键质量控制 248 //获取系统编码类型数组,包含了jpeg,bmp,png,gif,tiff 249 ImageCodecInfo[] icis = ImageCodecInfo.GetImageEncoders(); 250 ImageCodecInfo ici = null; 251 foreach (ImageCodecInfo i in icis) 252 { 253 if (i.MimeType == "image/jpeg" || i.MimeType == "image/bmp" || i.MimeType == "image/png" || i.MimeType == "image/gif") 254 { 255 ici = i; 256 } 257 } 258 EncoderParameters ep = new EncoderParameters(1); 259 ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, (long)quality); 260 261 //保存缩略图 262 templateImage.Save(fileSaveUrl, ici, ep); 263 //templateImage.Save(fileSaveUrl, System.Drawing.Imaging.ImageFormat.Jpeg); 264 265 //释放资源 266 templateG.Dispose(); 267 templateImage.Dispose(); 268 269 pickedG.Dispose(); 270 pickedImage.Dispose(); 271 } 272 } 273 274 //释放资源 275 initImage.Dispose(); 276 } 277 #endregion 278 279 #region 等比缩放 280 281 /// <summary> 282 /// 图片等比缩放 283 /// </summary> 284 /// <remarks>吴剑 2012-08-08</remarks> 285 /// <param name="fromFile">原图Stream对象</param> 286 /// <param name="savePath">缩略图存放地址</param> 287 /// <param name="targetWidth">指定的最大宽度</param> 288 /// <param name="targetHeight">指定的最大高度</param> 289 /// <param name="watermarkText">水印文字(为""表示不使用水印)</param> 290 /// <param name="watermarkImage">水印图片路径(为""表示不使用水印)</param> 291 public static void ZoomAuto(System.IO.Stream fromFile, string savePath, System.Double targetWidth, System.Double targetHeight, string watermarkText, string watermarkImage) 292 { 293 //创建目录 294 string dir = Path.GetDirectoryName(savePath); 295 if (!Directory.Exists(dir)) 296 Directory.CreateDirectory(dir); 297 298 //原始图片(获取原始图片创建对象,并使用流中嵌入的颜色管理信息) 299 System.Drawing.Image initImage = System.Drawing.Image.FromStream(fromFile, true); 300 301 //原图宽高均小于模版,不作处理,直接保存 302 if (initImage.Width <= targetWidth && initImage.Height <= targetHeight) 303 { 304 //文字水印 305 if (watermarkText != "") 306 { 307 using (System.Drawing.Graphics gWater = System.Drawing.Graphics.FromImage(initImage)) 308 { 309 System.Drawing.Font fontWater = new Font("黑体", 10); 310 System.Drawing.Brush brushWater = new SolidBrush(Color.White); 311 gWater.DrawString(watermarkText, fontWater, brushWater, 10, 10); 312 gWater.Dispose(); 313 } 314 } 315 316 //透明图片水印 317 if (watermarkImage != "") 318 { 319 if (File.Exists(watermarkImage)) 320 { 321 //获取水印图片 322 using (System.Drawing.Image wrImage = System.Drawing.Image.FromFile(watermarkImage)) 323 { 324 //水印绘制条件:原始图片宽高均大于或等于水印图片 325 if (initImage.Width >= wrImage.Width && initImage.Height >= wrImage.Height) 326 { 327 Graphics gWater = Graphics.FromImage(initImage); 328 329 //透明属性 330 ImageAttributes imgAttributes = new ImageAttributes(); 331 ColorMap colorMap = new ColorMap(); 332 colorMap.OldColor = Color.FromArgb(255, 0, 255, 0); 333 colorMap.NewColor = Color.FromArgb(0, 0, 0, 0); 334 ColorMap[] remapTable = { colorMap }; 335 imgAttributes.SetRemapTable(remapTable, ColorAdjustType.Bitmap); 336 337 float[][] colorMatrixElements = { 338 new float[] {1.0f, 0.0f, 0.0f, 0.0f, 0.0f}, 339 new float[] {0.0f, 1.0f, 0.0f, 0.0f, 0.0f}, 340 new float[] {0.0f, 0.0f, 1.0f, 0.0f, 0.0f}, 341 new float[] {0.0f, 0.0f, 0.0f, 0.5f, 0.0f},//透明度:0.5 342 new float[] {0.0f, 0.0f, 0.0f, 0.0f, 1.0f} 343 }; 344 345 ColorMatrix wmColorMatrix = new ColorMatrix(colorMatrixElements); 346 imgAttributes.SetColorMatrix(wmColorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); 347 gWater.DrawImage(wrImage, new Rectangle(initImage.Width - wrImage.Width, initImage.Height - wrImage.Height, wrImage.Width, wrImage.Height), 0, 0, wrImage.Width, wrImage.Height, GraphicsUnit.Pixel, imgAttributes); 348 349 gWater.Dispose(); 350 } 351 wrImage.Dispose(); 352 } 353 } 354 } 355 356 //保存 357 initImage.Save(savePath, System.Drawing.Imaging.ImageFormat.Jpeg); 358 } 359 else 360 { 361 //缩略图宽、高计算 362 double newWidth = initImage.Width; 363 double newHeight = initImage.Height; 364 365 //宽大于高或宽等于高(横图或正方) 366 if (initImage.Width > initImage.Height || initImage.Width == initImage.Height) 367 { 368 //如果宽大于模版 369 if (initImage.Width > targetWidth) 370 { 371 //宽按模版,高按比例缩放 372 newWidth = targetWidth; 373 newHeight = initImage.Height * (targetWidth / initImage.Width); 374 } 375 } 376 //高大于宽(竖图) 377 else 378 { 379 //如果高大于模版 380 if (initImage.Height > targetHeight) 381 { 382 //高按模版,宽按比例缩放 383 newHeight = targetHeight; 384 newWidth = initImage.Width * (targetHeight / initImage.Height); 385 } 386 } 387 388 //生成新图 389 //新建一个bmp图片 390 System.Drawing.Image newImage = new System.Drawing.Bitmap((int)newWidth, (int)newHeight); 391 //新建一个画板 392 System.Drawing.Graphics newG = System.Drawing.Graphics.FromImage(newImage); 393 394 //设置质量 395 newG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; 396 newG.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; 397 398 //置背景色 399 newG.Clear(Color.White); 400 //画图 401 newG.DrawImage(initImage, new System.Drawing.Rectangle(0, 0, newImage.Width, newImage.Height), new System.Drawing.Rectangle(0, 0, initImage.Width, initImage.Height), System.Drawing.GraphicsUnit.Pixel); 402 403 //文字水印 404 if (watermarkText != "") 405 { 406 using (System.Drawing.Graphics gWater = System.Drawing.Graphics.FromImage(newImage)) 407 { 408 System.Drawing.Font fontWater = new Font("宋体", 10); 409 System.Drawing.Brush brushWater = new SolidBrush(Color.White); 410 gWater.DrawString(watermarkText, fontWater, brushWater, 10, 10); 411 gWater.Dispose(); 412 } 413 } 414 415 //透明图片水印 416 if (watermarkImage != "") 417 { 418 if (File.Exists(watermarkImage)) 419 { 420 //获取水印图片 421 using (System.Drawing.Image wrImage = System.Drawing.Image.FromFile(watermarkImage)) 422 { 423 //水印绘制条件:原始图片宽高均大于或等于水印图片 424 if (newImage.Width >= wrImage.Width && newImage.Height >= wrImage.Height) 425 { 426 Graphics gWater = Graphics.FromImage(newImage); 427 428 //透明属性 429 ImageAttributes imgAttributes = new ImageAttributes(); 430 ColorMap colorMap = new ColorMap(); 431 colorMap.OldColor = Color.FromArgb(255, 0, 255, 0); 432 colorMap.NewColor = Color.FromArgb(0, 0, 0, 0); 433 ColorMap[] remapTable = { colorMap }; 434 imgAttributes.SetRemapTable(remapTable, ColorAdjustType.Bitmap); 435 436 float[][] colorMatrixElements = { 437 new float[] {1.0f, 0.0f, 0.0f, 0.0f, 0.0f}, 438 new float[] {0.0f, 1.0f, 0.0f, 0.0f, 0.0f}, 439 new float[] {0.0f, 0.0f, 1.0f, 0.0f, 0.0f}, 440 new float[] {0.0f, 0.0f, 0.0f, 0.5f, 0.0f},//透明度:0.5 441 new float[] {0.0f, 0.0f, 0.0f, 0.0f, 1.0f} 442 }; 443 444 ColorMatrix wmColorMatrix = new ColorMatrix(colorMatrixElements); 445 imgAttributes.SetColorMatrix(wmColorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); 446 gWater.DrawImage(wrImage, new Rectangle(newImage.Width - wrImage.Width, newImage.Height - wrImage.Height, wrImage.Width, wrImage.Height), 0, 0, wrImage.Width, wrImage.Height, GraphicsUnit.Pixel, imgAttributes); 447 gWater.Dispose(); 448 } 449 wrImage.Dispose(); 450 } 451 } 452 } 453 454 //保存缩略图 455 newImage.Save(savePath, System.Drawing.Imaging.ImageFormat.Jpeg); 456 457 //释放资源 458 newG.Dispose(); 459 newImage.Dispose(); 460 initImage.Dispose(); 461 } 462 } 463 464 #endregion 465 466 #region 其它 467 468 /// <summary> 469 /// 判断文件类型是否为WEB格式图片 470 /// (注:JPG,GIF,BMP,PNG) 471 /// </summary> 472 /// <param name="contentType">HttpPostedFile.ContentType</param> 473 /// <returns></returns> 474 public static bool IsWebImage(string contentType) 475 { 476 if (contentType == "image/pjpeg" || contentType == "image/jpeg" || contentType == "image/gif" || contentType == "image/bmp" || contentType == "image/png") 477 { 478 return true; 479 } 480 else 481 { 482 return false; 483 } 484 } 485 486 #endregion 487 488 }//end class 489}
