C# Winform版批量壓縮圖片程序

需求

上週,領導給我分配了一個需求:服務器上的圖片文件很是大,天天要用掉兩個G的大小的空間,要作一個自動壓縮圖片的工具處理這些大圖片。領導的思路是這樣的:html

1)打開一個圖片,看它的屬性裏面象素是多少,大於1000就按比例縮小到1000。git

2)再看它的品質屬性,好比咱們標準是50,若是大於這個值再修改品質。github

壓縮後的文件大小不能超過200k。c#

思路

由於服務器上的圖片文件名是加密處理過的,和圖片文件一塊兒存在的還有其它附件,沒有後綴名,用肉眼根本看不出來是不是圖片文件。因此剛開始的時候,個人思路是先批量修改後綴名,再獲取圖片的像素,最後再進行壓縮。後來在作的過程當中,發現不用處理後綴名,直接獲取圖片信息就能識別文件是不是圖片。服務器

因此,最後的作法是:工具

1)遍歷文件夾下的圖片文件的時候,先根據圖片信息,把圖片文件提取到列表測試

2)而後再根據圖片的像素大小進行處理。像素在1000之內的直接修改圖片品質處理,像素大於1000的按尺寸大小壓縮圖片,而後再修改圖片品質處理。ui

(像素大於1000這種狀況之因此有兩步是由於按尺寸大小進行壓縮後,圖片大小大於1M,不符合預期的要求,因此壓縮圖片後再修改圖片品質。這一步爲了不混淆,我把按尺寸大小壓縮圖片放到另外一個文件夾處理,這個文件夾在處理好圖片後,會把壓縮圖片文件進行刪除,因此這個文件夾永遠是空的,不會佔空間)this

//作的時候一聽到是自動壓縮圖片,批量處理文件,覺得很難,很深奧,真正動手後實際上是辦法總比困難多。總有辦法實現的,只是時間問題。加密

代碼片斷

1)由於文件的位置不固定,文件夾下面有圖片,也有文件夾,裏面還有圖片。因此要遍歷子目錄。

/// <summary>
        /// 遍歷文件
        /// </summary>
        /// <param name="di"></param>
        public void ListFiles(DirectoryInfo di)
        {
            if (!di.Exists)
            {
                return;
            }

            if (di == null)
            {
                return;
            }

            //返回當前目錄的文件列表
            FileInfo[] files = di.GetFiles();

            for (int i = 0; i < files.Length; i++)
            {

                try
                {
                    //判斷是否具備照片信息,報錯即不是照片文件
                    GetMetaData.GetExifByMe(files[i].FullName);

                    //把圖片文件添加到列表視圖
                    this.lvSourceFolderList.Items.Add(files[i].FullName);

                    //把圖片文件添加到圖片列表
                    imageList.Add(files[i].FullName);

                }
                catch (Exception)
                {
                    //Logging.Error(System.IO.Path.GetFileName(files[i].FullName) + ",非圖片文件," + ex.Message);
                    continue;
                }

            }
            this.lbInfomation.Text = "共" + this.lvSourceFolderList.Items.Count + "條數據";
            //返回當前目錄的子目錄
            DirectoryInfo[] dis = di.GetDirectories();

            for (int j = 0; j < dis.Length; j++)
            {
                // Console.WriteLine("目錄:" + dis[j].FullName);
                ListFiles(dis[j]);//對於子目錄,進行遞歸調用
            }

        }

2)判斷圖片是否具備照片信息,我用的是MetadataExtractor,直接在nuget裏面添加安裝好,再添加一個GetExifByMe便可。這裏在調用GetExifByMe的時候,不是圖片文件會報錯,報錯的我直接忽略,繼續continue。

//判斷是否具備照片信息,報錯即不是照片文件
   GetMetaData.GetExifByMe(files[i].FullName);
#region   經過metadata-extractor獲取照片參數

        //參考文獻
        //官網: https://drewnoakes.com/code/exif/
        //nuget 官網:https://www.nuget.org/
        //nuget 使用: http://www.cnblogs.com/chsword/archive/2011/09/14/NuGet_Install_OperatePackage.html
        //nuget MetadataExtractor: https://www.nuget.org/packages/MetadataExtractor/

        /// <summary>經過MetadataExtractor獲取照片參數
        /// </summary>
        /// <param name="imgPath">照片絕對路徑</param>
        /// <returns></returns>
        public static Dictionary<string, string> GetExifByMe(string imgPath)
        {
            var rmd = ImageMetadataReader.ReadMetadata(imgPath);

            var rt = new Dictionary<string, string>();
            foreach (var rd in rmd)
            {
                foreach (var tag in rd.Tags)
                {
                    var temp = EngToChs(tag.Name);
                    if (temp == "其餘")
                    {
                        continue;
                    }
                    if (!rt.ContainsKey(temp))
                    {
                        rt.Add(temp, tag.Description);
                    }

                }
            }
            return rt;
        }

        /// <summary>篩選參數並將其名稱轉換爲中文
        /// </summary>
        /// <param name="str">參數名稱</param>
        /// <returns>參數中文名</returns>
        private static string EngToChs(string str)
        {
            var rt = "其餘";
            switch (str)
            {
                case "Exif Version":
                    rt = "Exif版本";
                    break;
                case "Model":
                    rt = "相機型號";
                    break;
                case "Lens Model":
                    rt = "鏡頭類型";
                    break;
                case "File Name":
                    rt = "文件名";
                    break;
                case "File Size":
                    rt = "文件大小";
                    break;
                case "Date/Time":
                    rt = "拍攝時間";
                    break;
                case "File Modified Date":
                    rt = "修改時間";
                    break;
                case "Image Height":
                    rt = "照片高度";
                    break;
                case "Image Width":
                    rt = "照片寬度";
                    break;
                case "X Resolution":
                    rt = "水平分辨率";
                    break;
                case "Y Resolution":
                    rt = "垂直分辨率";
                    break;
                case "Color Space":
                    rt = "色彩空間";
                    break;

                case "Shutter Speed Value":
                    rt = "快門速度";
                    break;
                case "F-Number":
                    rt = "光圈";//Aperture Value也表示光圈
                    break;
                case "ISO Speed Ratings":
                    rt = "ISO";
                    break;
                case "Exposure Bias Value":
                    rt = "曝光補償";
                    break;
                case "Focal Length":
                    rt = "焦距";
                    break;

                case "Exposure Program":
                    rt = "曝光程序";
                    break;
                case "Metering Mode":
                    rt = "測光模式";
                    break;
                case "Flash Mode":
                    rt = "閃光燈";
                    break;
                case "White Balance Mode":
                    rt = "白平衡";
                    break;
                case "Exposure Mode":
                    rt = "曝光模式";
                    break;
                case "Continuous Drive Mode":
                    rt = "驅動模式";
                    break;
                case "Focus Mode":
                    rt = "對焦模式";
                    break;
            }
            return rt;
        }

        #endregion

文件瀏覽完畢後的截圖:

3)文件所有瀏覽完畢後,就開始進行壓縮。

由於文件數量大,原來的簡單壓縮版本老是容易卡死,這裏的新版本用了線程,就沒有卡死的問題了。

這裏的壓縮核心代碼直接參考了
用C#開發一個WinForm版的批量圖片壓縮工具

Thread workThread = new Thread(new ThreadStart(CompressAll));
      workThread.IsBackground = true;
      workThread.Start();

我添加了i標識處理成功的文件數量,壓縮失敗的時候i-=1。

if (CompressPicture(item, fileName))
{
    if (this.InvokeRequired)
    {
        this.Invoke(new DelegateWriteResult(WriteResult), new object[] { item, true });
     }
     else
    {
       this.WriteResult(item, true);
    }
}
 else
{
    i -= 1;

    if (this.InvokeRequired)
    {
        this.Invoke(new DelegateWriteResult(WriteResult), new object[] { item, false });
    }
     else
    {
        this.WriteResult(item, false);
    }
}

改變圖片質量這裏就是第二步思路,分兩步走:

像素在1000之內的直接修改圖片品質處理;

像素大於1000的按尺寸大小壓縮圖片,而後再修改圖片品質處理。

/// <summary>
        /// 改變圖片質量
        /// </summary>
        /// <param name="imgPath">文件路徑</param>
        /// <param name="imgName">文件名</param>
        private static bool VaryQualityLevel(string imgPath, string imgName)
        {

            bool result = false;

            Bitmap bmp1 = new Bitmap(imgPath);

            //獲取照片信息
            // GetExifByMe(imgPath);
            //先獲取圖片的像素
            var imgPixl = RGB2Gray(bmp1);

            //像素超出,先壓縮圖片
            if (imgPixl.Width > 1000 && imgPixl.Height > 1000)
            {
                double width = 0;
                double height = 0;
                if (imgPixl.Width > 2000 && imgPixl.Height > 2000)
                {
                    width = System.Math.Ceiling(Convert.ToDouble(imgPixl.Width / 4));
                    height = System.Math.Ceiling(Convert.ToDouble(imgPixl.Height / 4));
                }
                else if (imgPixl.Width > 1000 && imgPixl.Height > 1000)
                {
                    width = System.Math.Ceiling(Convert.ToDouble(imgPixl.Width / 2));
                    height = System.Math.Ceiling(Convert.ToDouble(imgPixl.Height / 2));
                }
                //cutimg先建立好
                //檢查是否存在文件夾
                string subPath = @"d:/cutimg/";
                if (false == System.IO.Directory.Exists(subPath))
                {
                    //建立pic文件夾
                    System.IO.Directory.CreateDirectory(subPath);
                }
                result = FixSize(imgPath, Convert.ToInt32(width), Convert.ToInt32(height), subPath + imgName, imgName);
            }
            else
            {

                result = SetImgQuality(imgPath, imgPath, imgName);

            }

            return result;

        }

調用按圖片尺寸壓縮方法,先存儲壓縮後的圖片,圖片大小每每還超過1M。再設置圖片的質量,二次處理,壓縮後的圖片大小小於200K。

/// <summary> 按圖片尺寸大小壓縮圖片</summary> 
        /// <param name="sourceFile">原始圖片文件</param> 
        /// <param name="xWidth">圖片width</param>  
        ///  <param name="yWidth">圖片height</param>  
        /// <param name="outputFile">輸出文件名</param>  
        ///  <param name="imgName">文件名</param>  
        /// <returns>成功返回true,失敗則返回false</returns>
        public static bool FixSize(string sourceFile, int xWidth, int yWidth, string outputFile, string imgName)
        {
            try
            {
                Bitmap sourceImage = new Bitmap(sourceFile);

                ImageCodecInfo myImageCodecInfo = GetEncoderInfo("image/jpeg");

                Bitmap newImage = new Bitmap((int)(xWidth), (int)(yWidth));

                Graphics g = Graphics.FromImage(newImage);

                g.DrawImage(sourceImage, 0, 0, xWidth, yWidth);

                sourceImage.Dispose();

                g.Dispose();

                newImage.Save(outputFile);

                //設置圖片質量
                SetImgQuality(sourceFile, outputFile, imgName);

                newImage.Dispose();

                //刪除該圖片文件
                File.Delete(outputFile);

                return true;
            }
            catch (Exception ex)
            {
                Logging.Error("FixSize:" + imgName + "  壓縮出錯:" + ex.Message);
                return false;
            }
        }

調用按圖片尺寸壓縮的時候,這裏發生「GDI+發生通常性錯誤」這個提示,緣由是由於調用了SetImgQuality這個方法,文件尚未釋放出來,在最後加上bmp1.Dispose();就解決了。

//設置圖片質量
 SetImgQuality(sourceFile, outputFile, imgName);

在文件壓縮出錯的時候,我把出錯的文件寫入文本:

for (int j = 0; j < this.lvSourceFolderList.Items.Count; j++)
                {
                    if (fileName == this.lvSourceFolderList.Items[j].Text)
                    {
                        //壓縮失敗的文件寫入文本
                        using (StreamWriter my_writer = new StreamWriter(@"d:\CompressFailFile.txt", true, System.Text.Encoding.Default))
                        {
                            string txtstr = "壓縮失敗:" + fileName + "\r\n";
                            my_writer.Write(txtstr);
                            my_writer.Flush();
                        }
                      
                        this.lvSourceFolderList.Items[j].BackColor = SystemColors.ControlDark;
                    }
                }

在這裏出現「文件正由另外一進程使用,該進程沒法訪問該文件」的錯誤提示,當時在本地上跑沒有任何問題,放在服務器上跑就報錯。後來把服務器上面的文件拿到本地測試,發現是這裏出錯了。換了using後完美解決。

壓縮出錯的文件除了在文本記錄外,我還作了高亮顯示。選中高亮數據的時候,由於沒法複製,添加了SelectedIndexChanged事件以及文本框顯示。

/// <summary>
        /// 選擇行
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvSourceFolderList_SelectedIndexChanged(object sender, EventArgs e)
        {

            ListView.SelectedIndexCollection indexes = lvSourceFolderList.SelectedIndices;//

            string pr = "";

            foreach (int index in indexes)
            {
                pr = lvSourceFolderList.Items[index].Text;
            }

            this.lblChoose.Visible = true;
            this.txtContent.Visible = true;
            this.txtContent.Text = pr;// 顯示選擇的行的內容

        }

最後,貼上個人源碼。由於本身在作的過程當中參考、借鑑了不少前輩的分享,我也把本身完整的代碼分享出來。

源碼 Github地址

參考資源

在作的過程當中,我走了不少彎路,幸虧在這個互聯網發達的時代,在知識共享的時代,我有幸參考了各路前輩分享的資料,才得以完成這個任務。很是感謝如下前輩的分享,還有一個分享當時沒有保存到連接,找不着了。不管如何,我心永存感激。

C#保存圖片設置圖片質量的方法

.net c#經過Exif獲取圖片信息(參數)

用C#開發一個WinForm版的批量圖片壓縮工具

相關文章
相關標籤/搜索