惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
博客园 - 三生石上(FineUI控件)
S
Securelist
U
Unit 42
The Cloudflare Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Simon Willison's Weblog
Simon Willison's Weblog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
B
Blog
T
Tenable Blog
The Hacker News
The Hacker News
The Register - Security
The Register - Security
IT之家
IT之家
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
P
Privacy & Cybersecurity Law Blog
博客园_首页
T
Tailwind CSS Blog
人人都是产品经理
人人都是产品经理
C
Cybersecurity and Infrastructure Security Agency CISA
Know Your Adversary
Know Your Adversary
NISL@THU
NISL@THU
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
T
Tor Project blog
C
CERT Recently Published Vulnerability Notes
Apple Machine Learning Research
Apple Machine Learning Research
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
V
Vulnerabilities – Threatpost
A
Arctic Wolf
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
V
V2EX
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
Cyberwarzone
Cyberwarzone
V
Visual Studio Blog
月光博客
月光博客
爱范儿
爱范儿
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
G
GRAHAM CLULEY
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
H
Heimdal Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

博客园 - 泉子

算法题:求一个序列S中所有包含T的子序列(distinct sub sequence) Django模板输出Dict所有Value的效率问题 笔记:LNK2001不代表链接器真的需要链接相关符号 Windows Restart Manager 重启管理器 advapi32.dll kernel32.dll 中的两套注册表API 使用GDI+保存带Alpha通道的图像(续) 窗口销毁的相关函数与消息,ActiveX控件的销毁 为什么不能将ptr_fun(operator delete)传递给For_Each 两个数之间的最短路径问题 C/C++函数中局部对象的构造与析构时机 四年记——身在中小企业 C++ Const总结 [转载]怎样用VB编写.DLL动态链接库文件 Visual Studio 6.0编译PWLib1.12总结 在Windows上编译PWLib 金山词霸没落了么? SendInput模拟键盘输入的问题 数组变量在取地址时回归:不再是指针 创建用于非Visual C++工具的DLL——VB调用C++ DLL为何必须使用def文件 - 泉子
使用GDI+保存带Alpha通道的图像
泉子 · 2014-03-03 · via 博客园 - 泉子

带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。

经测试,此代码亦存在问题,见下一篇日记。