使用MFC CImage类和GDI+ Image加载并绘制PNG图片
一、使用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();
}
版权声明:本文为博主原创文章,未经博主允许不得转载。