使用FreeImage庫實作将Bmp、JPG、PNG、PCX、TGA、PSD等多種格式的圖像轉換為 .NET下的Bitmap對象。
其實我一直都是喜歡自己去做圖像格式的解碼的(目前我自己解碼的圖像格式大概有15種),但是寫本文主要原因是基于CSDN的這個文章的:
http://bbs.csdn.net/topics/390510431 用pictureBox顯示一個黑白8bit圖像,如何消除顆粒感
用于測試的原始的JPG圖像: https://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對象實際上是個空對象了。
這樣的話也許可能沒有關系,我們隻要在适當的地方調用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還有很多強大的功能,比如色深轉換、充分利用它洗看圖軟體,格式批處理那是很快捷友善的。
附件中的拖動圖像的方式我認為也是值得作為大家學習的。
https://files.cnblogs.com/Imageshop/FreeImage.rar
https://files.cnblogs.com/Imageshop/FreeImageApi.rar
***************************作者: laviewpbt 時間: 2013.7.7 聯系QQ: 33184777 轉載請保留本行資訊*************************