码迷,mamicode.com
首页 > 编程语言 > 详细

使用MFC CImage类和GDI+ Image加载并绘制PNG图片

时间:2015-07-18 15:42:08      阅读:5483      评论:0      收藏:0      [点我收藏+]

标签:

一、使用MFC CImage类加载PNG图片       

        为了测试CImage绘制PNG图片的效果,我们用截图软件截得一张360的界面,然后使用PhotoShop等工具在图片的周边加上了透明的区域,然后保存成PNG图片文件。CImage首先从文件中加载,即

    CImage* m_pImgBk;
    ......
    
    m_pImgBk = new CImage;
    m_pImgBk->Load( _T("res\\bk.png"));
    if ( m_pImgBk->IsNull() ) // 图片加载失败
    {
        delete m_pImgBk;
        m_pImgBk = NULL;
    }

        然后再到测试对话框的OnPaint中绘制,即

void CTestCImageDrawDlg::OnPaint()
{
    CDialogEx::OnPaint();

    CWindowDC dc(this);
    if ( m_pImgBk != NULL )
    {
	m_pImgBk->Draw(dc.GetSafeHdc(), 30, 30, m_pImgBk->GetWidth(), m_pImgBk->GetHeight() );
    }
}

        结果发现了下面的一些问题。

       1、直接使用CImage来绘制带透明部分的PNG图片,透明区域并没有透掉(非缩放)

        按照图片的原始尺寸绘制到测试对话框界面上,结果透明区域没有透掉,代码如下所示。

    CWindowDC dc(this);
    if ( m_pImgBk != NULL )
    {
	m_pImgBk->Draw(dc.GetSafeHdc(), 30, 30, m_pImgBk->GetWidth(), m_pImgBk->GetHeight() );
    }
        显示的效果图如下所示。

技术分享

        经查阅,对于带透明区域的PNG图片需要做额外的处理,判断是否启用了Alpha透明通道,若启用则要对之做如下处理:

	if ( m_pImgBk->GetBPP() == 32 )
	{
		for(int i = 0; i < m_pImgBk->GetWidth(); i++)   
		{   
			for(int j = 0; j < m_pImgBk->GetHeight(); j++)   
			{   
				unsigned char* pucColor = reinterpret_cast<unsigned char *>(m_pImgBk->GetPixelAddress(i , j));   
				pucColor[0] = pucColor[0] * pucColor[3] / 255;   
				pucColor[1] = pucColor[1] * pucColor[3] / 255;   
				pucColor[2] = pucColor[2] * pucColor[3] / 255;   
			}   
		}
	}

        经处理该透掉的区域均被透掉,如下所示。

技术分享

        2、使用CImage::Draw直接绘制缩放的PNG图片时,则显示不全、失真严重

        考虑到在某些情况下,要对PNG图片进行缩放,所以对缩放绘制效果进行了测试。缩放时要做到长度和宽度的等比例缩放,相关代码如下所示。

	CWindowDC dc(this);
	if ( m_pImgBk != NULL )
	{
		int nDstWidth = 450;
		int nDstHeight = (int)( (nDstWidth*1.0/m_pImgBk->GetWidth())*m_pImgBk->GetHeight() ); // 宽和高等比例缩放
		m_pImgBk->Draw(dc.GetSafeHdc(), 30, 30, nDstWidth, nDstHeight );
	}

技术分享

        查阅MSDN,看是否有相关接口或参数能较好的处理这种缩放的问题。发现在CImage::Draw中可以添加Gdiplus::InterpolationMode的参数,GO过去看了一下,可以选用Gdiplus::InterpolationModeHighQuality高质量类型,发现缩放失真改善了许多,但本该透掉的透明部分却变黑了,如下所示。

技术分享技术分享

        所以,CImage处理带透明部分的PNG图片,特别是缩放时是有缺陷的。后来改用gdi+的Image类,则没有类似的问题其实CImage内部也是使用gdi+实现的,具体为什么会出现上述问题尚不明确。可以直接使用gdi+的Image类来处理PNG图片。使用Image类是借助Gdiplus::Graphics来绘制的,即使用Image来加载图片,使用Gdiplus::Graphics将Image中的图片绘制到界面DC中。

二、使用GDI+ Image类加载PNG图片    
        1、使用Image类遇到的两个问题

         (1)直接使用Image来定义对象,如下所示:

Image img;
提示这样的错误:error C2248: “Gdiplus::Image::Image”: 无法访问 protected 成员(在“Gdiplus::Image”类中声明)   c:\program files\microsoft sdks\windows\v7.0a\include\gdiplusheaders.h(471) : 参见“Gdiplus::Image::Image”的声明。

查看Image的声明,得知Image不带参数的构造函数是protected的,不能在外部访问的,所以不行,如下所示:

技术分享

由于还有两个下面的构造函数:

技术分享

所以下面的使用方法是可以的:

Image img( L"res\\bk.png" );
当然这只适合局部变量,不适合定义成员变量,然后在初始化时去加载图片,供后续使用。       

      (2)为了在Image*成员变量指针,在初始化时new一个Image对象,并加载图片,代码如下:

m_pImgBk = new Image( L"res\\bk.png" );
结果编译出现问题:error C2660: “Gdiplus::GdiplusBase::operator new”: 函数不接受 3 个参数
于是到网上搜索了一下,主要是是微软MFC的 DEBUG_NEW 和 GDI+ 不匹配造成的,有以下几个方法:

方法1:
注释掉cpp中下面的代码这就好了:

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

方法2:
::new Bitmap(cx,cy,PixelFormat32bppRGB); //加上全局作用域说明符

方法3:
详细见:
Microsoft Foundation Classes DEBUG_NEW Does Not Work with GDI+

http://support.microsoft.com/kb/317799/

        2、使用Image静态函数来加载PNG图片

        使用如下的FromFile和FromStream函数来加载,返回Image指针,正好保存在成员变量Image* m_pImgBK中。函数如下所示:

技术分享

使用FromFile函数比较简单,直接通过图片路径去加载,代码如下:

m_pImgBk = Image::FromFile( L"res\\bk.png" );
但是使用FromFile有个问题,会将图片文件“锁住”,其他对象将无法加载,所以还是使用FromStream,但是使用FromStream比较复杂,其大体思路为:先将图片文件数据读到内存中,然后用GlobalAlloc分配同样大的内存hGlobal,然后将图片数据拷贝到hGlobal中,然后再利用hGlobal调用CreateStreamOnHGlobal来创建IStream,组后调用Image::FromStream最终将图片加载到Image对象中。

        使用Image::FromStream有两种情况,一是直接加载磁盘上的图片文件,二是加载资源中的图片文件数据。在实现时,都是先将图片文件数据读到内存中,按照上述方法操作。代码要实现这两种情况对应的接口。

        (1)加载磁盘中的图片文件

Image* LoadFromFile( LPCTSTR lpszFile )
{
	Image* pImage = NULL;
	TCHAR achErrInfo[512] = { 0 };

	HANDLE hFile = ::CreateFile( lpszFile, GENERIC_READ, FILE_SHARE_READ, NULL, 
		OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
	if( hFile == INVALID_HANDLE_VALUE )
	{
		memset( achErrInfo, 0, sizeof(achErrInfo) );
		_stprintf( achErrInfo, _T("Load (file): Error opening file %s\n"), lpszFile );
		::OutputDebugString( achErrInfo );
		return NULL;
	}

	DWORD dwSize;
	dwSize = ::GetFileSize( hFile, NULL );
	HGLOBAL hGlobal = GlobalAlloc( GMEM_MOVEABLE | GMEM_NODISCARD, dwSize );
	if ( !hGlobal )
	{
		::OutputDebugString( _T("Load (file): Error allocating memory\n") );
		::CloseHandle( hFile );
		return NULL;
	};

	char *pData = reinterpret_cast<char*>(GlobalLock(hGlobal));
	if ( !pData )
	{
		::OutputDebugString( _T("Load (file): Error locking memory\n") );
		GlobalFree( hGlobal );
		::CloseHandle( hFile );
		return NULL;
	};

	try
	{
		DWORD dwReadBytes = 0;
		::ReadFile( hFile, pData, dwSize, &dwReadBytes, NULL );
	}
	catch( ... )                                         
	{
		memset( achErrInfo, 0, sizeof(achErrInfo) );
		_stprintf( achErrInfo, _T("Load (file): An exception occured while reading the file %s\n"), 
			lpszFile );
		::OutputDebugString( achErrInfo );

		GlobalFree( hGlobal );
		::CloseHandle( hFile );
		return NULL;
	}

	GlobalUnlock( hGlobal );
	::CloseHandle( hFile );

	IStream *pStream = NULL;
	if ( CreateStreamOnHGlobal( hGlobal, TRUE, &pStream ) != S_OK )
	{
		return NULL;
	}

	pImage = Image::FromStream( pStream );

	// 要加上这一句,否则由GlobalAlloc得来的hGlobal内存没有被释放,导致内存泄露,由于
	// CreateStreamOnHGlobal第二个参数被设置为TRUE,所以调用pStream->Release()会自动
	// 将hGlobal内存(参见msdn对CreateStreamOnHGlobal的说明)
	pStream->Release();

	return pImage;
}

        (2)加载资源中的图片文件

Image* LoadFromRes( UINT nResID, LPCTSTR lpszResType, HINSTANCE hInstance )
{
        Image* pImage = NULL;

	ASSERT( lpszResType );

	HRSRC hPic = FindResource( hInstance, MAKEINTRESOURCE(nResID), lpszResType );
	HANDLE hResData = NULL;
	if ( !hPic || !( hResData = LoadResource( hInstance,hPic ) ) )
	{
		::OutputDebugString( _T( "Load (resource): Error loading resource: %d\n" ) );
		return NULL;
	}

	DWORD dwSize = SizeofResource( hInstance, hPic );

	// hResData is not the real HGLOBAL (we can't lock it)
	// let's make it real
	HGLOBAL hGlobal = GlobalAlloc( GMEM_MOVEABLE | GMEM_NODISCARD, dwSize );
	if ( !hGlobal )
	{
		::OutputDebugString( _T("Load (resource): Error allocating memory\n" ) );
		FreeResource( hResData );
		return NULL;
	}

	char *pDest = reinterpret_cast<char *> (GlobalLock(hGlobal));
	char *pSrc = reinterpret_cast<char *> (LockResource(hResData));
	if ( !pSrc || !pDest )
	{
		::OutputDebugString( _T( "Load (resource): Error locking memory\n" ) );
		GlobalFree( hGlobal );
		FreeResource( hResData );
		return NULL;
	};

	memcpy( pDest, pSrc, dwSize );
	FreeResource( hResData );
	GlobalUnlock( hGlobal );

	IStream *pStream = NULL;
	if ( CreateStreamOnHGlobal( hGlobal, TRUE, &pStream ) != S_OK )
	{
		return NULL;
	}

	pImage = Image::FromStream( pStream );

	// 要加上这一句,否则由GlobalAlloc得来的hGlobal内存没有被释放,导致内存泄露,由于
	// CreateStreamOnHGlobal第二个参数被设置为TRUE,所以调用pStream->Release()会自动
	// 将hGlobal内存(参见msdn对CreateStreamOnHGlobal的说明)
	pStream->Release();

	return pImage;
}

图片加载到Image对象中后,使用GDI+中的Graphics对象绘制即可,代码如下:

    Gdiplus::Graphics graphics( dc );  
    graphics.DrawImage( m_pImgBK, 30, 30, nDstWidth, nDstHeight )         

        最后需要说明的是,Image::FromFile和Image::FromStream函数返回的指针是new出来的Image对象,在使用完后要在外部将Image对象delete掉。msdn中有相关的说明,如下:

技术分享

从msdn中给出的示例代码也能看的出来,返回的Image对象也是要delete的,如下:

VOID Example_FromStream(HDC hdc)
{
   Graphics graphics(hdc);

   Image* pImage1 = NULL;
   Image* pImage2 = NULL;

   IStorage* pIStorage = NULL;
   IStream* pIStream1 = NULL;
   IStream* pIStream2 = NULL;
   HRESULT hr;
   Status stat;

   // Open an existing compound file, and get a pointer 
   // to its IStorage interface.
   hr = StgOpenStorage(
      L"CompoundFile.cmp",
      NULL, 
      STGM_READ|STGM_SHARE_EXCLUSIVE, 
      NULL,
      0, 
      &pIStorage);

   if(FAILED(hr))
      goto Exit;

   // Get a pointer to the stream StreamImage1 in the compound file.
   hr = pIStorage->OpenStream(
      L"StreamImage1", 
      NULL, 
      STGM_READ|STGM_SHARE_EXCLUSIVE,
      0,
      &pIStream1);

   if(FAILED(hr))
      goto Exit;

   // Get a pointer to the stream StreamImage2 in the compound file.
   hr = pIStorage->OpenStream(
      L"StreamImage2", 
      NULL, 
      STGM_READ|STGM_SHARE_EXCLUSIVE,
      0,
      &pIStream2);

   if(FAILED(hr))
      goto Exit;

   // Create a new Image object based on StreamImage1.
   pImage1 = Image::FromStream(pIStream1);
   stat = pImage1->GetLastStatus();  
   if(stat != Ok)
      goto Exit;

   graphics.DrawImage(pImage1, 10, 10);

   // Create a new Image object based on StreamImage2.
   pImage2 = Image::FromStream(pIStream2);
   stat = pImage2->GetLastStatus();
   if(stat != Ok)
      goto Exit;

   graphics.DrawImage(pImage2, 200, 10);

Exit:
   if(pImage1)
      delete pImage1;
   if(pImage2)
      delete pImage2;
   if(pIStream1)
      pIStream1->Release(); 
   if(pIStream2)
      pIStream2->Release();
   if(pIStorage)
      pIStorage->Release();
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

使用MFC CImage类和GDI+ Image加载并绘制PNG图片

标签:

原文地址:http://blog.csdn.net/chenlycly/article/details/46941621

(0)
(2)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!