原文:
做了一個照片批量壓縮工具,其實核心代碼幾分鐘就完成了,但整個小工具做下來還是花了一天的時間。中間遇到了大堆問題,并尋求最好的解決方案予以解決。現在就分享一下這個看似簡單的小工具所使用的技術。
軟體界面如下:

要做真實場景的測試,拿的都是單反照的大相片:圖檔尺寸3888*2592 圖檔大小5.37m:
其中遇到的問題與解決方案分享:
1.用listview顯示圖檔縮略圖非常慢的問題
這個問題是始料未及的,如果不做也可以,但是沒有縮略圖就有損軟體體驗,這是所有最求完美的程式員所不能容忍的,我當然也不例外。
最初的代碼如下:(此方法加載每張5m左右的圖檔需要200-500ms)
<code>listview1.items.clear();</code>
<code>imagelist1.images.clear();</code>
<code>directoryinfo thefolder =</code><code>new</code>
<code>directoryinfo(folderbrowserdialog1.selectedpath);</code><code>//檔案路徑</code>
<code>list<</code><code>string</code><code>> imgnames =</code><code>new</code>
<code>list<</code><code>string</code><code>>();</code>
<code>string</code>
<code>allowimg =</code><code>".jpg.jpeg.png.bmp"</code><code>;</code>
<code>fileinfo[] files = thefolder.getfiles();</code>
<code>for</code> <code>(</code><code>int</code>
<code>i = 0; i < files.length; i++)</code><code>//周遊檔案夾</code>
<code>{</code>
<code> </code><code>if</code>
<code>(files[i].length > 0 &&allowimg.indexof(files[i].extension.tolower())>-1)</code><code>//或者jpg,png 檔案大小要大于0且是圖檔檔案</code>
<code> </code><code>{</code>
<code> </code><code>image image = image.fromfile(files[i].directoryname +</code><code>"\\"</code>
<code>+ files[i].name); </code><code>//擷取檔案 </code>
<code> </code><code>imgnames.add(files[i].name);</code><code>//添加檔案名</code>
<code> </code><code>imagelist1.images.add(image);</code><code>//添加圖檔</code>
<code> </code><code>}</code>
<code>}</code>
<code>//初始化設定</code>
<code>this</code><code>.listview1.view = view.largeicon;</code>
<code>this</code><code>.listview1.largeimagelist =</code><code>this</code><code>.imagelist1;</code>
<code>//開始綁定</code>
<code>this</code><code>.listview1.beginupdate();</code>
<code>for</code>
<code>(</code><code>int</code>
<code>i = 0; i < imgnames.count; i++)</code>
<code> </code><code>listviewitem lvi =</code><code>new</code>
<code>listviewitem();</code>
<code> </code><code>lvi.imageindex = i;</code>
<code> </code><code>lvi.text = imgnames[i];</code>
<code> </code><code>this</code><code>.listview1.items.add(lvi);</code>
<code>this</code><code>.listview1.endupdate();</code>
解決辦法是用微軟提供的windows
api code pack
1.0.1庫,通過該庫可以直接使用到win7/vista/win8系統的一些特性功能,如資料總管、桌面、工作列等等。詳細介紹見
本程式使用windowsapicode完成對檔案夾下的圖檔迅速建縮略圖的代碼如下:
先在界面上添加一個該庫提供的explorerbrowser控件,然後初始化該控件:
<code>//設定圖檔展示控件屬性</code>
<code>explorerbrowser1.contentoptions.viewmode = explorerbrowserviewmode.list;</code>
<code>explorerbrowser1.navigationoptions.panevisibility.navigation = panevisibilitystate.hide;</code>
<code>explorerbrowser1.navigationoptions.panevisibility.commandsview = panevisibilitystate.hide;</code>
<code>explorerbrowser1.navigationoptions.panevisibility.commandsorganize = panevisibilitystate.hide;</code>
<code>explorerbrowser1.navigationoptions.panevisibility.commands = panevisibilitystate.hide;</code>
<code>explorerbrowser1.selectionchanged +=</code><code>new</code>
<code>eventhandler(explorerbrowser1_selectionchanged);</code>
完成打開檔案夾并顯示圖檔縮略圖的代碼非常簡單:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<code>//打開圖檔檔案夾</code>
<code> </code><code>private</code>
<code>void</code> <code>btnopendir_click(</code><code>object</code>
<code>sender, eventargs e)</code>
<code> </code><code>{</code>
<code> </code><code>// 建立打開檔案夾對話框</code>
<code> </code><code>commonopenfiledialog cfd =</code><code>new</code>
<code>commonopenfiledialog();</code>
<code> </code><code>// 設定對話框屬性</code>
<code> </code><code>cfd.isfolderpicker =</code><code>true</code><code>;</code>
<code> </code><code>cfd.allownonfilesystemitems =</code><code>true</code><code>;</code>
<code> </code><code>// 彈出對話框并傳回使用者的選擇</code>
<code> </code><code>commonfiledialogresult result = cfd.showdialog();</code>
<code> </code><code>//如果使用者确定</code>
<code> </code><code>if</code>
<code>(result == commonfiledialogresult.ok)</code>
<code> </code><code>{</code>
<code> </code><code>// 擷取選擇對象的shellobject形式</code>
<code> </code><code>shellobject resultitem = cfd.fileasshellobject;</code>
<code> </code><code>//用explorerbrowser控件顯示圖檔清單</code>
<code> </code><code>explorerbrowser1.navigate(resultitem);</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
采用這種方法打開圖檔縮略圖清單時間可以忽略不計。
2.好看的圖檔界面庫
從前面的界面可以看出,本工具的界面并不醜,可以說還很精美,這也是花了心思的。
本工具的界面我采用的是
官方首頁為
3.充分利用多核并行計算,提高圖檔處理速度
處理批量任務當然要考慮速度,否則就失去了工具的意義了
.netframework4.0裡面提供了parallel系列、task系列來支援并行運算,讓并行計算變得如此簡單(為什麼不跟着微軟走呢,後悔了吧
^_^)。
并行指的是利用現在的cup多核,同時開啟多個任務。跟以往的并發計算不同的是,并發的多個線程其實并非真正同時在運作,他們隻是按照時間片,走走停停,邏輯上在同時進行,而并行則是在多個完全獨立的核上同時運作任務,是真正的同時在跑。
本程式中并行進行圖檔壓縮的代碼如下:
<code>paralleloptions po =</code><code>new</code>
<code>paralleloptions();</code>
<code>po.maxdegreeofparallelism = 15;</code><code>//最多并發15個任務</code>
<code>//并行進行圖檔壓縮</code>
<code>parallel.foreach(imgtocomp, po, (o) =></code>
<code> </code><code>system.drawing.image sourceimg = system.drawing.image.fromfile(o.parsingname);</code>
<code> </code><code>int</code>
<code>iwidth = 0;</code>
<code>iheight = 0;</code>
<code>(rbtper.checked)</code>
<code> </code><code>int</code>
<code>per =</code><code>int</code><code>.parse(txtper.text);</code>
<code> </code><code>iwidth = sourceimg.width * per / 100;</code>
<code> </code><code>iheight = sourceimg.height * per / 100;</code>
<code>(rbtheight.checked)</code><code>//最大高度</code>
<code> </code><code>iheight =</code><code>int</code><code>.parse(txtheight.text);</code>
<code> </code><code>iwidth = iheight * sourceimg.width / sourceimg.height;</code>
<code>(rbtwidth.checked)</code><code>//最大寬度</code>
<code> </code><code>iwidth =</code><code>int</code><code>.parse(txtwidth.text);</code>
<code> </code><code>iheight = iwidth * sourceimg.height / sourceimg.width;</code>
<code> </code><code>system.drawing.image thumbimg = imgcompress.getimagethumb(sourceimg, iwidth, iheight);</code>
<code>(rbtpng.checked) thumbimg.save(filesavepath+o.name,system.drawing.imaging.imageformat.png);</code>
<code>(rbtgif.checked) thumbimg.save(filesavepath + o.name, system.drawing.imaging.imageformat.gif);</code>
<code>(rbtjpg.checked) thumbimg.save(filesavepath + o.name, system.drawing.imaging.imageformat.jpeg);</code>
<code> </code><code>sourceimg.dispose();</code>
<code> </code><code>thumbimg.dispose();</code>
<code> </code><code>ifinish++;</code>
<code> </code><code>this</code><code>.invoke(</code><code>this</code><code>.mysetfinish,</code><code>new</code>
<code>object[] { ifinish });</code><code>//重新整理進度條等</code>
<code>});</code>
這裡主要強調一下并發任務數量的設定、以及資源的顯示釋放。
并發數量通過paralleloptions參數的maxdegreeofparallelism來設定,這裡必須設定,否則幾百張5m的圖檔同時跑,立馬記憶體就占滿了。
資源的顯式釋放:sourceimg.dispose(); thumbimg.dispose();
這點也非常重要,處理大圖檔是非常耗記憶體的,測試過程中就因為沒有顯式釋放記憶體,偷懶想着.net的自動垃圾回收機制會幫忙善後,結果跑到40多張圖檔的時候就記憶體不足了。顯式處理資源釋放後,壓縮圖檔的速度也因為空餘的記憶體比較多而變快了。
下載下傳本程式