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

之前结合网上的一些代码及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;
}

  

 

 

posted @ 2014-03-08 14:52  泉子  阅读(3075)  评论(1编辑  收藏  举报