簡介
筆者剛開始接觸計算機時是在學校裡,學校的網速你懂的,學校區域網路裡能有5MB/s,而通路學校以外的網站時能有256KB/s就相當滿意了。那時候筆者在開發網站時,處理圖檔時就特别小心,能用gif的不用jpeg,而且反複的優化。
随着技術的進步、網絡裝置的不斷更新,現在大家家用的光纖寬帶基本上都是10MB/s以上了,但是圖檔優化的工作還是得繼續做,因為壓力轉移到伺服器一邊,對于一個通路量超級大的Web系統而言,網絡帶寬是仍然是非常珍貴的,即使是某東、某寶,給使用者看到的圖檔基本上都優化到了極緻。然而,在實際業務中,系統背景操作人員并不都具備圖檔優化的技術能力,可能幾MB的bmp或者png的原圖就直接上傳到背景了,筆者見過某個商品的詳情頁的圖檔累計有6MB的(因為隻能限制單張圖檔大小,業務,業務人員上傳了好幾張圖檔),是以需要在背景直接實作圖檔的壓縮優化。
本文将重點向大家介紹怎麼使用GDI+(Graphics)對圖檔進行壓縮優化。
圖檔壓縮優化
對圖檔進行壓縮優化過程中生成高品質圖檔的原理與縮略圖一樣,可以參考
C#中基于GDI+(Graphics)圖像處理系列之高品質縮略圖
相對縮略圖,不同點是生成圖檔寬度和高度的算法不一樣和當圖檔大小不變化時算一下是否需要進行壓縮優化,代碼中有詳細的注釋,這裡不再贅述。
主要有兩種情況
限制生成圖檔的最大寬度和最大高度
這種情況可能出現在新聞内容中的圖檔、商品詳情中的圖檔。主要是在批量的壓縮優化時限制生成圖檔的最大寬度或者最大高度,如同時限制了最大寬度和最大高度,進行計算對比,取小值;如果原圖的寬和高都小于限定值,則計算平均每個像素所占檔案大小,看是否需要進行優化。代碼如下:
/// <summary>
/// 對圖檔進行壓縮優化(限制寬高),始終保持原寬高比
/// </summary>
/// <param name="destPath">目标儲存路徑</param>
/// <param name="srcPath">源檔案路徑</param>
/// <param name="max_Width">壓縮後的圖檔寬度不大于這值,如果為0,表示不限制寬度</param>
/// <param name="max_Height">壓縮後的圖檔高度不大于這值,如果為0,表示不限制高度</param>
/// <param name="quality">1~100整數,無效值則取預設值95</param>
/// <param name="mimeType">如 image/jpeg</param>
public bool GetCompressImage(string destPath, string srcPath, int maxWidth, int maxHeight, int quality, out string error,string mimeType = "image/jpeg")
{
bool retVal = false;
error = string.Empty;
//寬高不能小于0
if (maxWidth < || maxHeight < )
{
error = "目标寬高不能小于0";
return retVal;
}
Image srcImage = null;
Image destImage = null;
Graphics graphics = null;
try
{
//擷取源圖像
srcImage = Image.FromFile(srcPath, false);
FileInfo fileInfo = new FileInfo(srcPath);
//目标寬度
var destWidth = srcImage.Width;
//目标高度
var destHeight = srcImage.Height;
//如果輸入的最大寬為0,則不限制寬度
//如果不為0,且原圖寬度大于該值,則附值為最大寬度
if (maxWidth != && destWidth > maxWidth)
{
destWidth = maxWidth;
}
//如果輸入的最大寬為0,則不限制寬度
//如果不為0,且原圖高度大于該值,則附值為最大高度
if (maxHeight != && destHeight > maxHeight)
{
destHeight = maxHeight;
}
float srcD = (float)srcImage.Height / srcImage.Width;
float destD = (float)destHeight / destWidth;
//目的高寬比 大于 原高寬比 即目的高偏大,是以按照原比例計算目的高度
if (destD > srcD)
{
destHeight = Convert.ToInt32(destWidth * srcD);
}
else if (destD < srcD) //目的高寬比 小于 原高寬比 即目的寬偏大,是以按照原比例計算目的寬度
{
destWidth = Convert.ToInt32(destHeight / srcD);
}
//如果維持原寬高,則判斷是否需要優化
if (destWidth == srcImage.Width && destHeight == srcImage.Height && fileInfo.Length < destWidth * destHeight * sizePerPx)
{
error = "圖檔不需要壓縮優化";
return retVal;
}
//定義畫布
destImage = new Bitmap(destWidth, destHeight);
//擷取高清Graphics
graphics = GetGraphics(destImage);
//将源圖像畫到畫布上,注意最後一個參數GraphicsUnit.Pixel
graphics.DrawImage(srcImage, new Rectangle(, , destWidth, destHeight), new Rectangle(, , srcImage.Width, srcImage.Height), GraphicsUnit.Pixel);
//如果是覆寫則先釋放源資源
if (destPath == srcPath)
{
srcImage.Dispose();
}
//儲存到檔案,同時進一步控制品質
SaveImage2File(destPath, destImage, quality, mimeType);
retVal = true;
}
catch (Exception ex)
{
error = ex.Message;
}
finally
{
if (srcImage != null)
srcImage.Dispose();
if (destImage != null)
destImage.Dispose();
if (graphics != null)
graphics.Dispose();
}
return retVal;
}
限制生成圖檔長邊的最大值
這種情況可能出現在對相片進行壓縮優化,主要是在進行批量壓縮優化時限制生成圖檔的最長邊,有可能是寬也有可能是高(相片有的寬大于高,有的寬小于高);如果原圖的寬和高都小于限定值,則計算平均每個像素所占檔案大小,看是否需要進行優化。代碼如下:
/// <summary>
/// 對圖檔進行壓縮優化,始終保持原寬高比,限制長邊長度,常用場景:相片
/// </summary>
/// <param name="destPath">目标儲存路徑</param>
/// <param name="srcPath">源檔案路徑</param>
/// <param name="max_Length">壓縮後的圖檔邊(寬或者高)長變不大于這值,為0表示不限制</param>
/// <param name="quality">1~100整數,無效值,則取預設值95</param>
/// <param name="mimeType">如 image/jpeg</param>
public bool GetCompressImage(string destPath, string srcPath, int maxLength, int quality, out string error, string mimeType = "image/jpeg")
{
bool retVal = false;
error = string.Empty;
//最大邊長不能小于0
if (maxLength < )
{
error = "最大邊長不能小于0";
return retVal;
}
Image srcImage = null;
Image destImage = null;
Graphics graphics = null;
try
{
//擷取源圖像
srcImage = Image.FromFile(srcPath, false);
FileInfo fileInfo = new FileInfo(srcPath);
//目标寬度
var destWidth = srcImage.Width;
//目标高度
var destHeight = srcImage.Height;
//如果限制
if (maxLength > )
{
//原高寬比
float srcD = (float)srcImage.Height / srcImage.Width;
//如果寬>高,且大于 限制
if (destWidth > destHeight && destWidth > maxLength)
{
destWidth = maxLength;
destHeight = Convert.ToInt32(destWidth * srcD);
}
else
{
if (destHeight > maxLength)
{
destHeight = maxLength;
destWidth = Convert.ToInt32(destHeight / srcD);
}
}
}
//如果維持原寬高,則判斷是否需要優化
if (destWidth == srcImage.Width && destHeight == srcImage.Height && fileInfo.Length < destWidth * destHeight * sizePerPx)
{
error = "圖檔不需要壓縮優化";
return retVal;
}
//定義畫布
destImage = new Bitmap(destWidth, destHeight);
//擷取高清Graphics
graphics = GetGraphics(destImage);
//将源圖像畫到畫布上,注意最後一個參數GraphicsUnit.Pixel
graphics.DrawImage(srcImage, new Rectangle(, , destWidth, destHeight), new Rectangle(, , srcImage.Width, srcImage.Height), GraphicsUnit.Pixel);
//如果是覆寫則先釋放源資源
if (destPath == srcPath)
{
srcImage.Dispose();
}
//儲存到檔案,同時進一步控制品質
SaveImage2File(destPath, destImage, quality, mimeType);
retVal = true;
}
catch (Exception ex)
{
error = ex.Message;
}
finally
{
if (srcImage != null)
srcImage.Dispose();
if (destImage != null)
destImage.Dispose();
if (graphics != null)
graphics.Dispose();
}
return retVal;
}
其他需要的代碼
下面的代碼在本系列文章中反複使用,建議去《C#中基于GDI+(Graphics)圖像處理系列之前言》擷取整個圖像處理工具類的源碼
//優化良好的圖檔每個像素平均占用檔案大小,經驗值,可根據需要修改
private static readonly double sizePerPx = ;
/// <summary>
/// 擷取高清的Graphics
/// </summary>
/// <param name="img"></param>
/// <returns></returns>
public Graphics GetGraphics(Image img)
{
var g = Graphics.FromImage(img);
//設定品質
g.SmoothingMode = SmoothingMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
//InterpolationMode不能使用High或者HighQualityBicubic,如果是灰色或者部分淺色的圖像是會在邊緣處出一白色透明的線
//用HighQualityBilinear卻會使圖檔比其他兩種模式模糊(需要肉眼仔細對比才可以看出)
g.InterpolationMode = InterpolationMode.Default;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
return g;
}
/// <summary>
/// 将Image執行個體儲存到檔案,注意此方法不執行 img.Dispose()
/// 圖檔儲存時本可以直接使用destImage.Save(path, ImageFormat.Jpeg),但是這種方法無法進行進一步控制圖檔品質
/// </summary>
/// <param name="path"></param>
/// <param name="img"></param>
/// <param name="quality">1~100整數,無效值,則取預設值95</param>
/// <param name="mimeType"></param>
public void SaveImage2File(string path, Image destImage, int quality, string mimeType = "image/jpeg")
{
if (quality <= || quality > ) quality = ;
//建立儲存的檔案夾
FileInfo fileInfo = new FileInfo(path);
if (!Directory.Exists(fileInfo.DirectoryName))
{
Directory.CreateDirectory(fileInfo.DirectoryName);
}
//設定儲存參數,儲存參數裡進一步控制品質
EncoderParameters encoderParams = new EncoderParameters();
long[] qua = new long[] { quality };
EncoderParameter encoderParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
encoderParams.Param[] = encoderParam;
//擷取指定mimeType的mimeType的ImageCodecInfo
var codecInfo = ImageCodecInfo.GetImageEncoders().FirstOrDefault(ici => ici.MimeType == mimeType);
destImage.Save(path, codecInfo, encoderParams);
}
完整示例程式源碼
http://download.csdn.net/detail/lhtzbj12/9730116
示例程式截圖
如果想查閱本系列其他文章,請移步《C#中基于GDI+(Graphics)圖像處理系列之前言》