簡介
筆者剛開始接觸電腦時是在學校裡,學校的網速你懂的,學校區域網路裡能有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 < 0 || maxHeight < 0) { 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 != 0 && destWidth > maxWidth) { destWidth = maxWidth; } //如果輸入的最大寬為0,則不限制寬度 //如果不為0,且原圖高度大於該值,則附值為最大高度 if (maxHeight != 0 && 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(0, 0, destWidth, destHeight), new Rectangle(0, 0, 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 < 0) { 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 > 0) { //原高寬比 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(0, 0, destWidth, destHeight), new Rectangle(0, 0, 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 = 0.18;/// <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 <= 0 || quality > 100) quality = 95; //建立儲存的檔案夾 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[0] = 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)影像處理系列之前言》