这两天在做UI,以前做过一点,但是不太熟悉,于是乎就遇到很多的问题。以前用BITMAP的时候,做的就是像素COPY,再多一点就是像素运算,这一切的东西都是有自己控制的。也就是说不参杂alpha通道。
话说今天我用PNG图片,但是用到一个小图片的时候,图片总是不能正常显示。代码如下:
CImage image;//CImage类有自己的Dc
if (image.Load(imagePath)!=S_OK)
{
AfxMessageBox(_T("加载图片失败"));
}
image.BitBlt(m_dc.GetSafeHdc(),rect,CPoint(0,0),SRCCOPY);
在这种情况下进行位COPY,不能正常显示。我就在琢磨这是什么原因呢?想了半天,也想不到一个所以然来,于是不得不借助于网络搜索,正好看到下面的一篇文章。
它是直接用CImage::Draw()直接进行绘图的。下面是查询的MSDN:
Draw performs the same operation as StretchBlt, unless the image contains a transparent color or alpha channel. In that case, Draw performs the same operation as either TransparentBlt or AlphaBlend as required.
For versions of Draw that do not specify a source rectangle, the entire source image is the default. For the version of Draw that does not specify a size for the destination rectangle, the size of the source image is the default and no stretching or shrinking occurs.
从上面看出,如果图片是含有alpha通道,那么这个图片就应该用TransparentBlt or AlphaBlend这两个函数,否则用StretchBlt,Bitblt。从这里就可以看出我用Bitblt操作含有alpha通道的图片是错误的.
正确方式:
- CImage image;//CImage类有自己的Dc
- if (image.Load(imagePath)!=S_OK)
- {
- AfxMessageBox(_T("加载图片失败"));
- }
- CRect src(0,0,image.GetWidth(),image.GetHeight());
- image.AlphaBlend(m_dc.GetSafeHdc(),rect,src);
CImage image;//CImage类有自己的Dc
if (image.Load(imagePath)!=S_OK)
{
AfxMessageBox(_T("加载图片失败"));
}
CRect src(0,0,image.GetWidth(),image.GetHeight());
image.AlphaBlend(m_dc.GetSafeHdc(),rect,src);
下面转载:
先看看GDI+的方法方法1:
1.GDI+画透明图层(alpha)的png图片
stdafx加入如下:
- #include <comdef.h>//初始化一下com口
- #include "GdiPlus.h"
- using namespace Gdiplus;
- #pragma comment(lib,"gdiplus.lib")
#include <comdef.h>//初始化一下com口
#include "GdiPlus.h"
using namespace Gdiplus;
#pragma comment(lib,"gdiplus.lib")
开始初始化:
在app类的声明里(.h)加入:
- ULONG_PTR m_gdiplusToken;
- InitInstance()里加入://若没有usingnamespace Gdiplus; 就要在前面加Gdiplus::
- GdiplusStartupInput gdiplusStartupInput;
- GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
- 重载ExitInstance,加入GdiplusShutdown(m_gdiplusToken);
- int CxxxApp::ExitInstance()
- {
- // TODO: 在此添加专用代码和/或调用基类
- GdiplusShutdown(m_gdiplusToken);
- return CWinApp::ExitInstance();
- }
ULONG_PTR m_gdiplusToken;
InitInstance()里加入://若没有usingnamespace Gdiplus; 就要在前面加Gdiplus::
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
重载ExitInstance,加入GdiplusShutdown(m_gdiplusToken);
int CxxxApp::ExitInstance()
{
// TODO: 在此添加专用代码和/或调用基类
GdiplusShutdown(m_gdiplusToken);
return CWinApp::ExitInstance();
}
显示图片的过程如下
- CClientDC *pDC = new CClientDC(GetDlgItem(IDC_STATIC_PIC));
- CRect rect;
- GetDlgItem(IDC_STATIC_PIC)->GetWindowRect(&rect);
- Graphics graphics(pDC->m_hDC); // Create a GDI+ graphics object
- Image image(_T("1.png")); // Construct an image
- graphics.DrawImage(&image, 0, 0, image.GetWidth(), image.GetHeight());
- delete pDC;
CClientDC *pDC = new CClientDC(GetDlgItem(IDC_STATIC_PIC));
CRect rect;
GetDlgItem(IDC_STATIC_PIC)->GetWindowRect(&rect);
Graphics graphics(pDC->m_hDC); // Create a GDI+ graphics object
Image image(_T("1.png")); // Construct an image
graphics.DrawImage(&image, 0, 0, image.GetWidth(), image.GetHeight());
delete pDC;
这是用GDI+来显示图片。
2.CImage绘制带alpha透明图层的png图片
用MFC自带的CImage也可以显示,不过要稍微进行转换才能得到正常的带α通道的png图片!
在画图前进行一次转换,其中Image是CImage的对象
PNG图片的透明背景总是一片白色,后来才发现这其实是微软GDI+的设计问题,PNG图片是ARGB,使用GDI+载入图片的时候,GDI+会默认已经进行了预剩运算(PARGB),即每象素的实际值是已经和ALPHA值按比例相乘的结果,实际上它根本就没有做预乘,在使用透明图片的象素ALPHA通道的时候,CImage内部正是调用的AlphaBlend,没有预乘的图当作预乘的图片处理的结果就是这相当于一张和纯白背景进行了预乘,所以图象总是出现白色背景。
最后的解决方法,写一个小程序对PNG图片每个象素进行预乘运算,然后保存成PNG图片,实际效果良好。
具体方法如下:
- HWND hwnd = GetSafeHwnd(); //获取窗口的HWND
- ::InvalidateRect( hwnd, NULL, true ); //或者 ::InvalidateRect( hwnd, NULL, false );
- ::UpdateWindow(hwnd);
- //若使用前不想把原来绘制的图片去掉,可以删去上面那三段
- CDC *pDC = GetDC();
- CImage Image;
- Image.Load(strPath);
- if (Image.IsNull())
- {
- MessageBox(_T("没加载成功"));
- return -1;
- }
- if (Image.GetBPP() == 32) //确认该图像包含Alpha通道
- {
- int i;
- int j;
- for (i = 0; i < Image.GetWidth(); i++)
- {
- for (j = 0; j < Image.GetHeight(); j++)
- {
- //TT:一直在推测一段代码的意思,既然系统已经支持了alpha,应该系统自己计算,为什么还要我们计算呢?真心不知道。
- //我知道这段代码是计算透明度的方法
- byte *pByte = (byte *)Image.GetPixelAddress(i, j);
- pByte[0] = pByte[0] * pByte[3] / 255;
- pByte[1] = pByte[1] * pByte[3] / 255;
- pByte[2] = pByte[2] * pByte[3] / 255;
- }
- }
- }
- Image.Draw(pDC->m_hDC, 0, 0);
- Image.Destroy();
- ReleaseDC(pDC);
HWND hwnd = GetSafeHwnd(); //获取窗口的HWND
::InvalidateRect( hwnd, NULL, true ); //或者 ::InvalidateRect( hwnd, NULL, false );
::UpdateWindow(hwnd);
//若使用前不想把原来绘制的图片去掉,可以删去上面那三段
CDC *pDC = GetDC();
CImage Image;
Image.Load(strPath);
if (Image.IsNull())
{
MessageBox(_T("没加载成功"));
return -1;
}
if (Image.GetBPP() == 32) //确认该图像包含Alpha通道
{
int i;
int j;
for (i = 0; i < Image.GetWidth(); i++)
{
for (j = 0; j < Image.GetHeight(); j++)
{
//TT:一直在推测一段代码的意思,既然系统已经支持了alpha,应该系统自己计算,为什么还要我们计算呢?真心不知道。
//我知道这段代码是计算透明度的方法
byte *pByte = (byte *)Image.GetPixelAddress(i, j);
pByte[0] = pByte[0] * pByte[3] / 255;
pByte[1] = pByte[1] * pByte[3] / 255;
pByte[2] = pByte[2] * pByte[3] / 255;
}
}
}
Image.Draw(pDC->m_hDC, 0, 0);
Image.Destroy();
ReleaseDC(pDC);
代码中内部的框架是对图像的再次处理,对原来进行了修正,这样得到的更加正常,代码实测如下