4. 為啥讀個圖那麼慢?
一般來說,讀圖可以用以下幾種方法:
1 public static Image FromFile(string filename);
2 public static Image FromFile(string filename, bool useEmbeddedColorManagement);
3 public static Bitmap FromHbitmap(IntPtr hbitmap);
4 public static Bitmap FromHbitmap (IntPtr hbitmap, IntPtr hpalette);
5 public static Image FromStream(Stream stream);
6 public static Image FromStream(Stream stream, bool useEmbeddedColorManagement);
7 public static Image FromStream(Stream stream, bool useEmbeddedColorManagement, bool validateImageData);
其中3,4兩種方法主要用在從Windows句柄中拿到原來DIB的Bitmap,經常是用在需要讀取 資源圖像啊,或者GDI圖像的時候。最經常用的,無非是1,2和5,6,7,其中1和5類似,2和 6類似,方法5,6會使用不同的參數調用7。我們可以做一個簡單的性能測試。拿一張 8000*7000大的TIF圖像,這樣的圖像一般大小都在100M以上,用不同的參數調用方法7, 看 到以下結果。
1 {
2 Stopwatch watch = new Stopwatch();
3 watch.Start();
4 FileStream fs = new FileStream(image, FileMode.Open, FileAccess.Read);
5 Image img = Image.FromStream(fs, true, true);
6 Console.WriteLine("Use ICM: {0}. Validate: {1}, ElapsedTicks:{2}.", true, true, watch.ElapsedTicks);
7 watch.Stop ();
8 fs.Close();
9 }
10
11 {
12 Stopwatch watch = new Stopwatch();
13 watch.Start();
14 FileStream fs = new FileStream(image, FileMode.Open, FileAccess.Read);
15 Image img = Image.FromStream(fs, false, true);
16 Console.WriteLine("Use ICM: {0}. Validate: {1}, ElapsedTicks:{2}.", false, true, watch.ElapsedTicks);
17 watch.Stop();
18 fs.Close();
19 }
20
21 {
22 Stopwatch watch = new Stopwatch();
23 watch.Start();
24 FileStream fs = new FileStream(image, FileMode.Open, FileAccess.Read);
25 Image img = Image.FromStream(fs, true, false);
26 Console.WriteLine("Use ICM: {0}. Validate: {1}, ElapsedTicks:{2}.", true, false, watch.ElapsedTicks);
27 watch.Stop();
28 fs.Close();
29 }
30
31 {
32 Stopwatch watch = new Stopwatch();
33 watch.Start();
34 FileStream fs = new FileStream(image, FileMode.Open, FileAccess.Read);
35 Image img = Image.FromStream(fs, false, false);
36 Console.WriteLine("Use ICM: {0}. Validate: {1}, ElapsedTicks: {2}.", false, false, watch.ElapsedTicks);
37 watch.Stop();
38 fs.Close();
39 }
我們來看看執行結果:
Use ICM: True. Validate: True, ElapsedTicks:51853544.
Use ICM: False. Validate: True, ElapsedTicks:52507953.
Use ICM: True. Validate: False, ElapsedTicks:6880.
Use ICM: False. Validate: False, ElapsedTicks:5187.
所以你看到,罪魁禍首是Validate。當然Validate其實是有用的,圖像的格式各種各樣, 就單BMP就有好多種不同的放法,更不要說JPEG, PNG, GIF了。JPEG有普通的用離散余弦變換 生成的最早的,還有用小波生成的JPEG 2000。 PNG沒啥研究,不知道裡面什麼名堂。GIF有 靜態的和能動的GIF 98.所以後面的數據流出問題是很正常的事情,驗證在很多情況下是需要 的,但是造成的性能損失實在是很大。 如果你確定這些照片一般不會錯,而且讀圖又需要很 好的性能,比如獲得一個目錄裡面所有圖像的縮略圖,那還是用最後一種方法吧。
這裡再提一下ICM. ICM是色彩管理中的一個概念,對於相同圖像不同設備而言,呈現的色 彩可能是不同的。比如一個液晶顯示器和一個CRT顯示器,先是一張相同的圖片,色彩可能相 差十萬八千裡。這主要是因為不同設備能夠顯示的色彩空間是不同的。色彩空間又是個值得 研究的話題,這裡就不多說了。對於色彩的研究可以寫一本好厚的書。這個世界上最權威的 一個網站http://www.color.org/ 可以告訴你很多有用的信息和數學公式,包括Gamma,色彩 密度的概念,等等諸如此類。有些圖像格式是可以把ICM的信息寫在文件中的,比如JPEG,所 以這個參數是說是否使用文件內嵌的ICM信息還是用設備默認的信息。
其實第7個函數是在.NET 2.0以後才出現的。1.1出來以後讀圖慢的這個問題被別人罵得要 死,所以新版本中加了一個函數解決了這個問題。當時有個叫Justin Rogers的人寫了個類叫 ImageFast (http://weblogs.asp.net/justin_rogers/articles/131704.aspx),自己用 Interop去調用GDI+的方法,跳過了icm和validation。在.NET 2.0之後,這個問題才算是被 解決了。這又是.NET Framework對GDI+封裝不完善的一個例子。