之前結合網上的一些代碼及ATL::CImage的實現,自己寫了一個將HBITMAP以PNG格式保存到文件到函數。見上一篇日記。
不過,后來換了個環境又發現了問題,昨天和今天上午把《Windows程序設計》中位圖處理相關的部分又粗略瞄了一下,然后把之前的函數改了一下,現在在新環境下也可以了,當然,這個函數也並不十分嚴謹,但是考慮到位圖格式的歷史淵源和復雜性,測試起來目測會相當麻煩,還是不要深究的好。而且,現在基本上都是32位圖像,老的格式中很多東西都已無用武之地,所以且將就用着。
首先,幸好需要處理的只是帶Alpha通道的圖像,而Alpha通道只有ARGB有,ARGB又不需要顏色表(每個像素值都是真實的顏色值,而非顏色表索引)。對於ARGB來說,位圖數據可以隨意拷貝,不需要先針對目標DC或目標DDB/DIB進行轉換。
所以,鑒於只要解決Alpha通道失效的問題,所以處理辦法可以簡化一下,首先判斷位圖每像素的位數(ARGB為32位),如果不是32位,則必定不是ARGB,不存在Alpha通道,因此可以直接使用Gdiplus::Bitmap::FromHBITMAP再保存。這樣,就可以把非32位的位圖可能的種種情況(對齊、顏色掩碼、顏色表、坐標系)通通丟給GDI+去處理。
如果是32位,即可認為是ARGB,由於不存在顏色表,所以也可以用采比較粗糙的辦法處理:用ARGB格式來建立一個新的Bitmap對象,然后不考慮原位圖是DIB還是DDB,直接將它的圖像數據填充到新Bitmap對象。
上一篇日記里的函數其實也是這個思路,但是有個小問題:通過GetObject得到的BITMAP,其中的bmBits成員(即圖像數據塊的指針),只有在圖像是DIB時才是有效的,如果圖像是DDB,得到的指針將是NULL。之前沒有在GetObject的說明中留意到這一點,又在ATL::CImage中找不到DDB的處理代碼,所以就直接放棄了DDB。
新函數糾正了這個問題,對於DDB,通過GetDIBits可以拿到圖像數據再填充到Bitmap。由於ARGB不存在顏色表,所以可以用更簡單的GetBitmapBits來代替GetDIBits。
新函數中依然存一個不確定性問題:
在從ATL::CImage中提取出來DIB處理代碼中,會處理坐標系。當取到的BITMAPINFOHEADER.biHeight為正數時,會把位數據塊的指針移到最后一行,並把行寬置為負值(這樣每次通過+行寬值的操作就能得到上一行的數據地址)。
而對DDB來說,我不知道是不是也存在坐標系的問題,而沒有BITMAPINFOHEADER數據,而根據MSDN,BITMAP中的bmHeight是必須為正值的,它不能用來指示坐標系。所以,我沒有考慮這個問題,用GetBitmapBits把位圖數據取出來並直接填到Bitmap對象中去了。目前看到的結果是圖像沒有發生倒置,但是我不確定是否會存在其它情況。
代碼在此:
bool ImageUtil::SavePng( HBITMAP hBmp, LPCTSTR lpszFilePath ) { DIBSECTION dibsection = {0}; int nBytes = ::GetObject( hBmp, sizeof( DIBSECTION ), &dibsection ); Gdiplus::Bitmap* bitmap = 0; if(dibsection.dsBm.bmBitsPixel != 32) { bitmap = Gdiplus::Bitmap::FromHBITMAP(hBmp, NULL); } else { int width, height, pitch; LPVOID bits; width = dibsection.dsBm.bmWidth; height = abs( dibsection.dsBm.bmHeight ); pitch = (((width*dibsection.dsBm.bmBitsPixel)+31)/32)*4; //計算行寬,四字節對齊 ATL::CImage::ComputePitch // 32位位圖不存在對齊問題,so其實沒必要 bits = dibsection.dsBm.bmBits; if( dibsection.dsBmih.biHeight > 0 ) // 對於DDB,不會取到dsBmih數據,所以biHeight成員始終為0 { bits = LPBYTE( bits )+((height-1)*pitch); pitch = -pitch; } bitmap = new Gdiplus::Bitmap(width, height, pitch, PixelFormat32bppARGB, static_cast< BYTE* >(bits )); if(0 == bits) { BitmapData bitmapData; Rect rc(0, 0, width, height); bitmap->LockBits(&rc, ImageLockModeWrite, PixelFormat32bppARGB, &bitmapData); GetBitmapBits(hBmp, pitch * height, bitmapData.Scan0); // 上面的bits在biHeight>0時要倒置的,但是這里不知道要不要,也不好測試 bitmap->UnlockBits(&bitmapData); } } bool ret = false; CLSID clsid = GetGdiplusEncoderClsid(NULL, &Gdiplus::ImageFormatPNG); if(clsid != CLSID_NULL) { ret = (Gdiplus::Ok == bitmap->Save(lpszFilePath, &clsid, NULL)); } delete bitmap; return ret; }