其實我一直都是喜歡自己去做映像格式的解碼的(目前我自己解碼的映像格式大概有15種),但是寫本文主要原因是基於CSDN的這個文章的:
http://bbs.csdn.net/topics/390510431 用pictureBox顯示一個黑白8bit映像,如何消除顆粒感
用於測試的原始的JPG映像: http://files.cnblogs.com/Imageshop/img01.rar
這個文章中,作者的需要載入一副灰階的8位的PG格式映像,但是利用.net的Bitmap類載入的映像會出現明顯顆粒感,由於.net中的Bitmap類是基於GDI+操作的,因此我也是試著用我的Imageshop開啟這幅映像(Imageshop內部也是用GDI+的API實現的),同樣有顆粒感。因此,我們需要從其他的手段來解決這個問題。
.net下載入的效果 Photoshop開啟的效果
首先,我用了VS6.0中的Stdpicture對象來載入這幅映像,能得到正確的結果。然後用PS開啟它,也能得到較好的效果,最後用微軟的圖片查看器,也是可以正確顯示的。最後用mspaint(畫圖)工具開啟,則出現了和在.net中一樣的效果。
因此,我們的第一理想方案是使用com裡的Stdpicture來解決這個問題,其實在VB6.0下,一個LoadPicture函數就可以解決它,但是在C#下要使用它,需要很多API函數來處理,我自己試著搞了下,覺得過於繁瑣,因此放棄了。
因此,我把希望投向了比較有名的映像解碼的軟體FreeImage中,經過實驗,發現FreeImage的解碼是和PS一致的。
我們先來看看百度對FreeImage的介紹:
FreeImage是一款免費的、開源的、跨平台(Windows 、Linux 和Mac OS X )的,支援20 多種映像類型的(如BMP 、JPEG 、GIF 、PNG 、TIFF 等)影像處理庫。其最大優點就是採用外掛程式驅動型架構,具有快速、靈活、簡單易用的特點,得到了廣泛使用。 FreeImage 的主要功能有多格式位元影像的讀寫;方便的位元影像資訊擷取;位元深度轉換;位元影像頁面訪問;基本的幾何變換和點處理;通道合并與混合等。FreeImage 暫時不支援向量圖形和進階影像處理,位元影像繪製需要由使用者來完成。 FreeImage 中的所有函數都以FreeImage_ 開頭,像檔案的讀寫函數分別為FreeImage_Load 和FreeImage_Save 。FIBITMAP 資料結構儲存著位元影像資訊和像素資料,是FreeImage
的核心。
由上述可見,FreeImage的側重點偏向於映像的解碼和編碼,顯示映像則需要使用者自己負責,而這正是我們所需要的。
為了能在.NET中使用FreeImage,我知道的有兩種方式,一種是直接使用FreeImage 的Flat API,而這需要對使用的API函數進行聲明。另外一種方式就是使用FreeImage 提供的FreeImageNET.dll中提供的類庫(其實就是對FreeImage.dll中函數的封裝)。 我這裡把兩種方式的實現都簡單的描述下:
public static Bitmap LoadImageFormFreeImage(string FileName) { Bitmap Bmp = null; FREE_IMAGE_FORMAT fif = FREE_IMAGE_FORMAT.FIF_UNKNOWN; ; fif = FreeImage_GetFileType(FileName, 0); if (fif == FREE_IMAGE_FORMAT.FIF_UNKNOWN) { fif = FreeImage_GetFIFFromFilename(FileName); } if ((fif != FREE_IMAGE_FORMAT.FIF_UNKNOWN) && (FreeImage_FIFSupportsReading(fif) != 0)) { IntPtr Dib = FreeImage_Load(fif, FileName, 0); int Bpp = FreeImage_GetBPP(Dib); PixelFormat PF; int Width, Height, Stride; switch (Bpp) { case 1: PF = PixelFormat.Format1bppIndexed; break; case 4: PF = PixelFormat.Format4bppIndexed; break; case 8: PF = PixelFormat.Format8bppIndexed; break; case 16: PF = PixelFormat.Format16bppRgb555; break; case 24: PF = PixelFormat.Format24bppRgb; break; case 32: PF = PixelFormat.Format32bppArgb; break; default: FreeImage_Free(Dib); return null; } Width = FreeImage_GetWidth(Dib); // 映像寬度 Height = FreeImage_GetHeight(Dib); // 映像高度 Stride = FreeImage_GetPitch(Dib); // 映像掃描行的大小,必然是4的整數倍 /** 方案1:存在記憶體泄露 * FreeImage_FlipVertical(Dib); // 因為FreeImage的認為的映像的起點在左下角,不進行翻轉則映像的倒過來的 * IntPtr Bits = FreeImage_GetBits(Dib); // 得到映像資料在記憶體中的地址 * Bmp = new Bitmap(Width, Height, Stride, PF, Bits); // 實際上調用的GdipCreateBitmapFromScan0函數從記憶體建立位元影像 **/ //方案2: IntPtr Bits = FreeImage_GetBits(Dib); Bmp = new Bitmap(Width, Height, Stride, PF, Bits); Bmp.RotateFlip(RotateFlipType.RotateNoneFlipY); // 調用GDI+自己的旋轉函數 if (Bpp <= 8) { ColorPalette Pal = Bmp.Palette; // 設定調色盤 RGBQUAD* GdiPal = FreeImage_GetPalette(Dib); int ClrUsed = FreeImage_GetColorsUsed(Dib); for (int I = 0; I < ClrUsed; I++) { Pal.Entries[I] = Color.FromArgb(255, GdiPal[I].Red, GdiPal[I].Green, GdiPal[I].Blue); } Bmp.Palette = Pal; } FreeImage_Free(Dib); // 使用方式情節2則可以立馬釋放 return Bmp; } return null; } }
上述代碼中,我們對方案1為什麼存在記憶體泄露做一定的說明。
方案1中,Bmp = new Bitmap(Width, Height, Stride, PF, Bits)這條語句實際上調用了GDI+的函數GdipCreateBitmapFromScan0從記憶體建立位元影像,通過此種方式建立的位元影像並沒有新分配一塊記憶體給建立的位元影像,而是和Bits對應的記憶體綁定的。您可以用如下的代碼驗證這一點:
BitmapData BmpData = Bmp.LockBits(new Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.ReadWrite, Bmp.PixelFormat); if (BmpData.Scan0 == Bits ) MessageBox.Show ("通過GDI+建立的映像和FreeImage的DIB對象公用同一記憶體.") Bmp.UnlockBits(BmpData);
正是由於這個原因的存在,如果採用方案1,我們不能在建立GDI+的位元影像後立馬釋放FreeImage的建立的DIB對象,即不能調用FreeImage_Free(Dib);如果調用了,對應的 Bmp對象實際上是個Null 物件了。
這樣的話也許可能沒有關係,我們只要在適當的地方調用Bmp.Dispose,不就可以了嗎,你可以做個實驗,使用這段代碼,然後不斷的開啟新映像,你會發現程式佔用的記憶體會不斷的增加,而沒有釋放。因此,我們看看MSDN對GdipCreateBitmapFromScan0這個函數是怎麼解釋的,特別是最後一個參數:
-
scan0 [in]
-
Type: BYTE*
Pointer to an array of bytes that contains the pixel data. The caller is responsible for allocating and freeing the block of memory pointed to by this parameter.
上述文字表示使用者需要對分配的記憶體進行釋放,也就是說Dispose方法無法釋放該部分記憶體。
有了上述的問題,我們轉而使用方式情節2,方案2使用了一句Bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);這個語句會建立一副新的位元影像,也就是說進行旋轉後的映像已經不再同FreeImage使用同一塊記憶體了。那麼此時就可以放心的釋放掉FreeImage的DIB對象了。
本以為RotateFlip函數會降低效率,測試表面微軟對這個函數的執行效率還是很高的,其實這個函數的函數完全可以藉助於CopyMemory函數來高速實現。
當映像的位深小於8時,需要擷取調色盤的資料。但是我對認為上述擷取調色盤的FreeImage_GetPalette函數存在記憶體泄露,無法釋放這些RGBQUAD*分配的記憶體的。FreeImage應該考慮使用類似於GDI+中擷取調色盤資料那種方式。
使用FreeImageNET.dll中提供的類庫,則編寫代碼更為方便,推薦使用第二種方式,朋友們可以參考附件。
實際上FreeImage還有很多強大的功能,比如色深轉換、充分利用它洗看圖軟體,格式批處理那是很快捷方便的。
附件中的拖動映像的方式我認為也是值得作為大家學習的。
http://files.cnblogs.com/Imageshop/FreeImage.rar
http://files.cnblogs.com/Imageshop/FreeImageApi.rar
**********基本上我不提供原始碼,但是我會盡量用文字把對應的演算法描述清楚或提供參考文檔***************
**********因為靠自己的努力和實踐寫出來的效果才真正是自己的東西,人一定要靠自己*******************
*********作者: laviewpbt 時間: 2013.7.5 聯絡QQ: 33184777 轉載請保留本行資訊*************