首页 > 其他 > 详细

使用GDI+保存带Alpha通道的图像

时间:2014-03-04 12:14:47      阅读:921      评论:0      收藏:0      [点我收藏+]
带Alpha通道的图像(ARBG)在通过GDIPlus::Bitmap::FromHBITMAP等转为GDI+位图,再存储时,透明区域会变成纯黑(也有可能是纯白?)。
 
网上找了两段保持透明的实现代码,列在下边,经测试,第一段无效,第二段有效,这两段代码正好可以对比说明:FromHBITMAP在拷贝图像数据时,原图中的Alpha数据确实没有Copy过来,而并非是未设置图像属性的问题。
 
第一段的思路是:直接用FromHBITMAP创建一个GDI+位图,新建另一个带PixelFormat32bppARGB标识的位图,再从前者拷贝数据到后者;
第二段的思路是:获取BITMAP数据,新建一个PixelFormat32bppARGB标识的位图,再从前者拷贝数据到后者; 
 
第二段代码也不够好,应该先判断一下位图是不是32位的,带Alpha通道的只有ARGB格式,ARGB是32位的,所以不是32位的不需要用这种方式来存储。如果要拷贝数据的话,不是32位的格式必须处理行边界对齐的问题。
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 ;
}

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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里边的代码提取了一个函数,应该比上边的靠谱并且效率高点。这个函数存在的问题是根据ATL::CImage中代码的执行路径,不能证明这个做法也适用于非DIB格式,所以我先把非DIB屏掉了。
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
bool ImageUtil::SavePng( HBITMAP hBmp, LPCTSTR lpszFilePath )
{
    DIBSECTION    dibsection;
    bool        m_bIsDIBSection;
    int            m_nWidth, m_nHeight, m_nBPP, m_nPitch;
    LPVOID        m_pBits;
    int nBytes = ::GetObject( hBmp, sizeof( DIBSECTION ), &dibsection );
    // 1. I find no way to save nonDIB from ATL::CImage::Save. 2. Only ARGB(32) has Alpha channel
    if(nBytes != sizeof(DIBSECTION) || dibsection.dsBm.bmBitsPixel != 32)   
    {
        return false;
    }
    m_bIsDIBSection = true;
    m_nWidth = dibsection.dsBmih.biWidth;
    m_nHeight = abs( dibsection.dsBmih.biHeight );
    m_nBPP = dibsection.dsBmih.biBitCount;
    m_nPitch = (((m_nWidth*m_nBPP)+31)/32)*4;        //计算行宽,四字节对齐ATL::CImage::ComputePitch
    m_pBits = dibsection.dsBm.bmBits;
    if( dibsection.dsBmih.biHeight > 0 )
    {
        m_pBits = LPBYTE( m_pBits )+((m_nHeight-1)*m_nPitch);
        m_nPitch = -m_nPitch;
    }
    Gdiplus::Bitmap bm( m_nWidth, m_nHeight, m_nPitch, PixelFormat32bppARGB, static_cast< BYTE* >(m_pBits ));
    CLSID clsid = GetGdiplusEncoderClsid(NULL, &Gdiplus::ImageFormatPNG);
    if(clsid != CLSID_NULL)
    {
        return Gdiplus::Ok == bm.Save(lpszFilePath, &clsid, NULL);
    }
     
    return false;
}

  

 
补充关于Gdi+里图像编码器的一点:每个解码器都有CLSID和FORMAT两个数据,实际上都是GUID,不能搞混了,Gdiplus::Bitmap::Save中接收的是CLSID,Gdiplus中提供的预定义ImageFormatXXX是指解码器的Format,很奇葩,细看了一下代码才搞清楚。见ATL::CImage::FindCodecForFileType。

使用GDI+保存带Alpha通道的图像,布布扣,bubuko.com

使用GDI+保存带Alpha通道的图像

原文:http://www.cnblogs.com/yedaoq/p/3579036.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!