使用GDI+保存帶Alpha通道的圖像

帶Alpha通道的圖像(ARBG)在經過GDIPlus::Bitmap::FromHBITMAP等轉爲GDI+位圖,再存儲時,透明區域會變成純黑(也有多是純白?)。
 
網上找了兩段保持透明的實現代碼,列在下邊,經測試,第一段無效,第二段有效,這兩段代碼正好能夠對比說明:FromHBITMAP在拷貝圖像數據時,原圖中的Alpha數據確實沒有Copy過來,而並不是是未設置圖像屬性的問題。
 
第一段的思路是:直接用 FromHBITMAP建立一個GDI+位圖,新建另外一個帶PixelFormat32bppARGB標識的位圖,再從前者拷貝數據到後者;
第二段的思路是:獲取BITMAP數據,新建一個 PixelFormat32bppARGB標識的位圖,再從前者拷貝數據到後者;  
 
第二段代碼也不夠好,應該先判斷一下位圖是否是32位的,帶Alpha通道的只有ARGB格式,ARGB是32位的,因此不是32位的不須要用這種方式來存儲。若是要拷貝數據的話,不是32位的格式必須處理行邊界對齊的問題。
 
bool   ImageUtil :: CreateGdiplusBmpFromHBITMAP_Alpha (  HBITMAP   hBmp ,  Gdiplus :: Bitmap **  bmp  )
{
     BITMAP   bitmap ;
     GetObject ( hBmp ,  sizeof ( BITMAP ), & bitmap );
     if ( bitmap . bmBitsPixel  != 32)
    {
         return   false ;
    }
     Gdiplus :: Bitmap *  pWrapBitmap  =  Gdiplus :: Bitmap :: FromHBITMAP ( hBmp ,  NULL );
     if  ( pWrapBitmap )
    {
         Gdiplus :: BitmapData   bitmapData ;
         Gdiplus :: Rect   rcImage (0, 0,  pWrapBitmap -> GetWidth (),  pWrapBitmap -> GetHeight ());
         pWrapBitmap -> LockBits (& rcImage ,  Gdiplus :: ImageLockModeRead ,  pWrapBitmap -> GetPixelFormat (), & bitmapData );
        * bmp  =  new   Gdiplus :: Bitmap ( bitmapData . Width ,  bitmapData . Height ,  bitmapData . Stride ,  PixelFormat32bppARGB , ( BYTE *) bitmapData . Scan0 );
         pWrapBitmap -> UnlockBits (& bitmapData );
         delete   pWrapBitmap ;
         return   true ;
    }
     return   false ; 
}

  

Gdiplus :: Bitmap *  ImageUtil :: CreateBitmapFromHBITMAP ( IN   HBITMAP   hBitmap )  
{  
     BITMAP   bmp  = { 0 };  
     if  ( 0 ==  GetObject ( hBitmap ,  sizeof ( BITMAP ), ( LPVOID )& bmp ) )  
    {  
         return   FALSE ;  
    }  
     // Although we can get bitmap data address by bmp.bmBits member of BITMAP   
     // which is got by GetObject function sometime,  
     // we can determine the bitmap data in the HBITMAP is arranged bottom-up   
     // or top-down, so we should always use GetDIBits to get bitmap data.  
     BYTE  * piexlsSrc  =  NULL ;  
     LONG   cbSize  =  bmp . bmWidthBytes  *  bmp . bmHeight ;  
     piexlsSrc  =  new   BYTE [ cbSize ];  
     BITMAPINFO   bmpInfo  = { 0 };  
     // We should initialize the first six members of BITMAPINFOHEADER structure.  
     // A bottom-up DIB is specified by setting the height to a positive number,   
     // while a top-down DIB is specified by setting the height to a negative number.  
     bmpInfo . bmiHeader . biSize  =  sizeof ( BITMAPINFOHEADER );  
     bmpInfo . bmiHeader . biWidth  =  bmp . bmWidth ;  
     bmpInfo . bmiHeader . biHeight  =  bmp . bmHeight ;  // 正數,說明數據從下到上,如未負數,則從上到下 
     bmpInfo . bmiHeader . biPlanes  =  bmp . bmPlanes ;  
     bmpInfo . bmiHeader . biBitCount  =  bmp . bmBitsPixel ;  
     bmpInfo . bmiHeader . biCompression  =  BI_RGB ;  
     HDC   hdcScreen  =  CreateDC ( L "DISPLAY" ,  NULL ,  NULL , NULL );  
     LONG   cbCopied  =  GetDIBits ( hdcScreen ,  hBitmap , 0,  bmp . bmHeight ,   
         piexlsSrc , & bmpInfo ,  DIB_RGB_COLORS );  
     DeleteDC ( hdcScreen );  
     if  ( 0 ==  cbCopied  )  
    {  
         delete  []  piexlsSrc ;  
         return   FALSE ;  
    }  
     // Create an GDI+ Bitmap has the same dimensions with hbitmap  
     Bitmap  * pBitmap  =  new   Bitmap ( bmp . bmWidth ,  bmp . bmHeight ,  PixelFormat32bppPARGB );  
     // Access to the Gdiplus::Bitmap's pixel data  
     BitmapData   bitmapData ;  
     Rect   rect (0, 0,  bmp . bmWidth ,  bmp . bmHeight );  
     if  (  Ok  !=  pBitmap -> LockBits (& rect ,  ImageLockModeRead ,   
         PixelFormat32bppPARGB , & bitmapData ) )  
    {  
         delete  ( pBitmap );  
         return   NULL ;  
    }  
     BYTE  * pixelsDest  = ( BYTE *) bitmapData . Scan0 ;  
     int   nLinesize  =  bmp . bmWidth  *  sizeof ( UINT );  
     int   nHeight  =  bmp . bmHeight ;  
     // Copy pixel data from HBITMAP by bottom-up.  
     for  (  int   y  = 0;  y  <  nHeight ;  y ++ )  
    {  
         // 從下到上覆制數據,由於前面設置高度時是正數。 
         memcpy_s (   
            ( pixelsDest  +  y  *  nLinesize ),   
             nLinesize ,   
            ( piexlsSrc  + ( nHeight  -  y  - 1) *  nLinesize ),   
             nLinesize );  
    }  
     // Copy the data in temporary buffer to pBitmap  
     if  (  Ok  !=  pBitmap -> UnlockBits (& bitmapData ) )  
    {  
         delete   pBitmap ;  
    }  
     delete  []  piexlsSrc ;  
     return   pBitmap ;  
}  

  

使用ATL::CImage來保存圖像也會存在黑底的問題,但這並不是上述丟失Alpha的問題,事實上, ATL::CImage::Attach不會建立位圖的副本。 下面是ATL::CImage在Attach到一個位圖名柄後執行的操做,從ATL::Image::UpdateBitmapInfo的實現中可見,它的問題在於默認認爲圖像是不帶Alpha通道的(置m_bHasAlphaChannel爲false ),繼而在保存操做中,轉爲Gdiplus::Bitmap時沒有使用PixelFormat32bppARGB標誌。
 
最後,我從ATL::CImage裏邊的代碼提取了一個函數,應該比上邊的靠譜而且效率高點。
 
bool ImageUtil::SavePng( HBITMAP hBmp, LPCTSTR lpszFilePath )
{
	DIBSECTION	dibsection;
	int nBytes = ::GetObject( hBmp, sizeof( DIBSECTION ), &dibsection );
	
	Gdiplus::Bitmap* bitmap = 0;
	
	if(nBytes != sizeof(DIBSECTION) || dibsection.dsBm.bmBitsPixel != 32)	
	{
		// Bitmap with plate or non-ARGB(32bpp) 
		bitmap = Gdiplus::Bitmap::FromHBITMAP(hBmp);
	}
	else
	{
		int			width, height, bits_per_pixel, pitch;
		LPVOID		bits;

		width = dibsection.dsBmih.biWidth;
		height = abs( dibsection.dsBmih.biHeight );
		bits_per_pixel = dibsection.dsBmih.biBitCount;
		pitch = (((width*bits_per_pixel)+31)/32)*4;		//計算行寬,四字節對齊 ATL::CImage::ComputePitch
		bits = dibsection.dsBm.bmBits;

		if( dibsection.dsBmih.biHeight > 0 )
		{
			bits = LPBYTE( bits )+((height-1)*pitch);
			pitch = -pitch;
		}

		bitmap = new Gdiplus::Bitmap(width, height, pitch, PixelFormat32bppARGB, static_cast< BYTE* >(bits ));
	}

	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;
}

  

補充關於Gdi+裏圖像編碼器的一點:每一個解碼器都有CLSID和FORMAT兩個數據,實際上都是GUID,不能搞混了,Gdiplus::Bitmap::Save中接收的是CLSID,Gdiplus中提供的預約義ImageFormatXXX是指解碼器的Format,很奇葩,細看了一下代碼才搞清楚。見ATL::CImage::FindCodecForFileType。
 
經測試,此代碼亦存在問題,見下一篇日記。
相關文章
相關標籤/搜索