标签:
一、使用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 个参数
方法1:
注释掉cpp中下面的代码这就好了:
#ifdef _DEBUG #define new DEBUG_NEW #endif
::new Bitmap(cx,cy,PixelFormat32bppRGB); //加上全局作用域说明符
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