• 使用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;
    }
    

      

  • 相关阅读:
    Log4net简介
    LOG4NET用法
    RabbitMQ 使用参考
    消息队列 RabbitMQ 入门介绍
    RabbitMQ三种Exchange模式(fanout,direct,topic)的性能比较
    RabbitMQ学习之:(十二)在Node.js环境下使用RabbitMQ
    RabbitMQ学习之:(十一)AMQP.0-10规范,中文翻译1,2,3章 (转载)
    谷歌如何测试软件 —— 第三部分
    【转】在淘宝一年测试工作的感悟
    青春年少,风华正茂
  • 原文地址:https://www.cnblogs.com/yedaoq/p/3587913.html
Copyright © 2020-2023  润新知