Alpha图象合成的方法:合成图象的各点像素值是由用来制作合成图的两张图片的相应点的像素值按一定比例混合而成的,这个比例由Alpha值决定,具体算式见下: newPixeValR= (pixel1ValR*(255-Alpha)+pixel2ValR*Alpha)/255; // Alpha取值范围从0到255
newPixeValG= (pixel1ValG*(255-Alpha)+pixel2ValG*Alpha)/255; // Alpha取值范围从0到255
newPixeValB= (pixel1ValB*(255-Alpha)+pixel2ValB*Alpha)/255; // Alpha取值范围从0到255 从上面的算式可以看出,只要修改Alpha的值,就可以改变合成后的图象中用来合成的两张图片各自所占的比值,改变合成后的显示效果。利用这个方法,我们就可以很轻易的制作出生动的淡入淡出效果和图片间的平滑过度特效。下面给出一个制作合成图的具体源码:BOOL CompoundDIB(HANDLE hDIB,HANDLE hDIBSrc,int alpha)
{
LPVOID lpvBuf=NULL; // 目标图象数据指针
LPVOID lpvBufSrc=NULL; // 源图数据指针// // 源图象信息 //
LPBITMAPINFO lpbmif=(LPBITMAPINFO)hDIBSrc;
LPBITMAPINFOHEADER lpbmifh=(LPBITMAPINFOHEADER)lpbmif;
// 计算图象数据偏移量
UINT nColors=lpbmifh->biClrUsed ? lpbmifh->biClrUsed : 1<<lpbmifh->biBitCount;
if ( nColors >256 )
nColors=0; // 如果颜色数大于256色,则没有调色板
lpvBufSrc=(LPVOID)((LPBYTE)lpbmif->bmiColors+nColors*sizeof(RGBQUAD));
int cxSrc=lpbmifh->biWidth; // 源图象宽度
int cySrc=lpbmifh->biHeight; // 源图象高度
// 计算图象每行的字节数(图象位数 x 图象宽度,如果不能被2整除则在每行后面添加一个0字节)
int nBytesPerLineSrc=((cxSrc*lpbmifh->biBitCount+31)&~31)/8; // // 目标图象信息 //
lpbmif=(LPBITMAPINFO)hDIB;
lpbmifh=(LPBITMAPINFOHEADER)lpbmif;
nColors=lpbmifh->biClrUsed ? lpbmifh->biClrUsed : 1<<lpbmifh->biBitCount;
if ( nColors >256 )
nColors=0;
lpvBuf=(LPVOID)((LPBYTE)lpbmif->bmiColors+nColors*sizeof(RGBQUAD));
int cx=lpbmifh->biWidth;
int cy=lpbmifh->biHeight;
int nBytesPerLine=((cx*lpbmifh->biBitCount+31)&~31)/8;LPBYTE lpbPnt=NULL;
LPBYTE lpbPntSrc=NULL;
// // 通过alpha值合并两张图象的像素值 //
// 这里假设是24位真彩色图象,其他深度的图象处理方法可以以次类推
for ( int y=(cy<cySrc ? cy : cySrc); y>0 ;y-- )
{
lpbPnt=(LPBYTE)lpvBuf+nBytesPerLine*(y-1);
lpbPntSrc=(LPBYTE)lpvBufSrc+nBytesPerLineSrc*(y-1);
for ( int x=0; x<(cx<cxSrc ? cx : cxSrc); x++ )
{
for ( int i=0 ;i<3 ;i++ )
*lpbPnt++=(*lpbPnt*(255-alpha)+*(lpbPntSrc++)*alpha)/255;
}
}
return TRUE;
}
newPixeValG= (pixel1ValG*(255-Alpha)+pixel2ValG*Alpha)/255; // Alpha取值范围从0到255
newPixeValB= (pixel1ValB*(255-Alpha)+pixel2ValB*Alpha)/255; // Alpha取值范围从0到255 从上面的算式可以看出,只要修改Alpha的值,就可以改变合成后的图象中用来合成的两张图片各自所占的比值,改变合成后的显示效果。利用这个方法,我们就可以很轻易的制作出生动的淡入淡出效果和图片间的平滑过度特效。下面给出一个制作合成图的具体源码:BOOL CompoundDIB(HANDLE hDIB,HANDLE hDIBSrc,int alpha)
{
LPVOID lpvBuf=NULL; // 目标图象数据指针
LPVOID lpvBufSrc=NULL; // 源图数据指针// // 源图象信息 //
LPBITMAPINFO lpbmif=(LPBITMAPINFO)hDIBSrc;
LPBITMAPINFOHEADER lpbmifh=(LPBITMAPINFOHEADER)lpbmif;
// 计算图象数据偏移量
UINT nColors=lpbmifh->biClrUsed ? lpbmifh->biClrUsed : 1<<lpbmifh->biBitCount;
if ( nColors >256 )
nColors=0; // 如果颜色数大于256色,则没有调色板
lpvBufSrc=(LPVOID)((LPBYTE)lpbmif->bmiColors+nColors*sizeof(RGBQUAD));
int cxSrc=lpbmifh->biWidth; // 源图象宽度
int cySrc=lpbmifh->biHeight; // 源图象高度
// 计算图象每行的字节数(图象位数 x 图象宽度,如果不能被2整除则在每行后面添加一个0字节)
int nBytesPerLineSrc=((cxSrc*lpbmifh->biBitCount+31)&~31)/8; // // 目标图象信息 //
lpbmif=(LPBITMAPINFO)hDIB;
lpbmifh=(LPBITMAPINFOHEADER)lpbmif;
nColors=lpbmifh->biClrUsed ? lpbmifh->biClrUsed : 1<<lpbmifh->biBitCount;
if ( nColors >256 )
nColors=0;
lpvBuf=(LPVOID)((LPBYTE)lpbmif->bmiColors+nColors*sizeof(RGBQUAD));
int cx=lpbmifh->biWidth;
int cy=lpbmifh->biHeight;
int nBytesPerLine=((cx*lpbmifh->biBitCount+31)&~31)/8;LPBYTE lpbPnt=NULL;
LPBYTE lpbPntSrc=NULL;
// // 通过alpha值合并两张图象的像素值 //
// 这里假设是24位真彩色图象,其他深度的图象处理方法可以以次类推
for ( int y=(cy<cySrc ? cy : cySrc); y>0 ;y-- )
{
lpbPnt=(LPBYTE)lpvBuf+nBytesPerLine*(y-1);
lpbPntSrc=(LPBYTE)lpvBufSrc+nBytesPerLineSrc*(y-1);
for ( int x=0; x<(cx<cxSrc ? cx : cxSrc); x++ )
{
for ( int i=0 ;i<3 ;i++ )
*lpbPnt++=(*lpbPnt*(255-alpha)+*(lpbPntSrc++)*alpha)/255;
}
}
return TRUE;
}
成都电子科技大学
朱宁
在许多游戏和屏幕保护程序中,我们都可以发现位图的淡入淡出和渐隐(一幅图象渐渐的消失于另一幅图象中)
的应用。如何实现这些效果呢?
在windows(GDI)环境下,实现位图的淡入淡出和渐隐的方法有三种:1.调色板动画;2.模式画刷;3.动画法。
其中,第一种方法速度很快,但只能用于256色的图形,而且不易实现渐隐效果。第二种方法实现比较简单,但是
主观效果不及其余两种。第三种方法的效果很好,但速度要稍慢一些。由于现在已经很难得到质量较高的256色
图片,加之目前几乎所有的显卡均支持高彩和真彩模式,所以不推荐采用第一种方法。下面介绍后两种方法在
Visual C++编程环境下的实现。
一:模式画刷法:
CDC类的BitBlt(...),MaskBlt(...),以及WIN32API ::StretchDIBits(...)函数均支持三元ROP(Raster Operation)
操作,即由源,模式画刷(pattern brush)和目的区域原有的图形经一定的逻辑运算而形成最终的输出图形。所以,
通过改变模式画刷的图案,辅以一定的ROP操作,就可以形成一些特殊的效果。
首先,要准备若干个8*8的单色位图,作为模式画刷的模板。单色位图中应只含有黑白两种颜色的像素,每一个位
图中两种像素的比例和形状将决定显示的效果,通常我们由一个全黑的位图开始,逐渐增加白色像素的比例,最
后一幅位图全部由白色象素组成。
这些位图制作好以后,将它们Import入工程,命名为IDB_PATTERN1、IDB_PATTERN2 ... ...
调用CBitmap::LoadBitmap(...)函数将其选入对应的CBitmap对象,然后调用CBrush::CreatePatternBrush(...)
制作模式画刷。
有了合适的模式画刷以后,还需要设定我们所需的ROP码,对于淡入操作,要求将源位图与模式画刷的反依次相与。
对于淡出操作,要求将当前显示区域的位图与模式画刷依次相与。对于渐隐,我们需要把原位图与模式画刷相与
后,把这个结果和当前显示区域的位图和画刷的镜象(原画刷的非)相与的结果相或。依次改变画刷,就可以得到
渐隐的效果。这些操作的ROP码,MFC中并没有对应的预定义宏,但我们可以通过计算得到它,在Visual C++ 5.0
的在线文档"Ternary Raster Operations"中,详细介绍了计算方法。最终我们得到淡入、淡出操作的ROP码分别为
000C0324、0x00A000C9。渐隐操作的ROP码是0x00AC0744。为了形成完整的动画效果,我们需要设置一个定时器来
自动的执行这一系列的操作。
下面用一个简单的例子说明模式画刷法的实现:
1:建立一个基于对话框的项目,命名为PatternDemo.
2:删除对话框上的"Todo:..."注释,并增加一个按纽,命名为"DEMO"
3:为DEMO按纽加入对应的事件句柄OnDEMO(...).
4:在CPatternDemo中加入私有成员变量如下:
CDC *pdc;
CDC memDC;
CBitmap bmp;
CBrush brush[8];
UINT counter;
UINT mode;
UINT onrun;
5:用VC自带的位图编辑器,按上文要求编辑8个8*8像素的单色位图,命名为IDB_PATTERN1...IDB_PATTERN8。
6:Import两个100*100像素的真彩bmp图片,命名为IDB_BMPSOURCE1和IDB_BMPSOURCE2。
7:使用ClassWizard为CPatternDemoDlg加入WM_CREATE的消息响应函数OnCreate(...),并在其中添加如下代码:
...
for(int i=0;i<8;i++)
{
bmp.LoadBitmap(IDB_PATTERN1+i);
brush[i].CreatePatternBrush(&bmp);
bmp.DeleteObject();
}
...
8:在CPatternDemoDlg::OnDEMO(...)函数中添加如下代码:
...
if(!onrun)
{
pdc=GetDC();
pdc->SetBkColor(RGB(0,0,0));
pdc->SetTextColor(RGB(255,255,255));
pdc->FillSolidRect(0,0,100,100,RGB(0,0,0));
memDC.CreateCompatibleDC(pdc);
bmp.LoadBitmap(IDB_BMPSOURCE1);
memDC.SelectObject(&bmp);
bmp.DeleteObject();
mode=1;
counter=0;
SetTimer(1,200,NULL);
onrun=1;
}
...
9:使用ClassWizard为CPatternDemoDlg加入WM_TIMER的消息响应函数OnTimer(...),并在其中添加如下代码:
...
if(mode==1)
{
if(counter>7)
{
mode=2;
counter=0;
return;
}
pdc->SelectObject(&brush[counter]);
pdc->BitBlt(0,0,100,100,&memDC,0,0,0x000C0324);
counter++;
}
if(mode==2)
{
if(counter>7)
{
mode=3;
counter=0;
return;
}
if(counter==0)
{
bmp.LoadBitmap(IDB_BMPSOURCE2);
memDC.SelectObject(&bmp);
bmp.DeleteObject();
}
pdc->SelectObject(&brush[counter]);
pdc->BitBlt(0,0,100,100,&memDC,0,0,0x00AC0744);
counter++;
}
if(mode==3)
{
if(counter>7)
{
memDC.DeleteDC();
bmp.DeleteObject();
KillTimer(1);
onrun=0;
return;
}
pdc->SelectObject(&brush[counter]);
pdc->BitBlt(0,0,100,100,NULL,0,0,0x00A000C9);
counter++;
}
...
9:在CPatternDemoDlg::CpatternDemoDlg()中加入:
...
onrun=0;
...
10: 使用ClassWizard为CPatternDemoDlg加入WM_DESTORY的消息响应函数OnDestory(...),并在其中添加如下代码:
...
memDC.DeleteDC();
KillTimer(1);
...
编译运行该项目,可以看到第一幅图象从背景中渐渐的浮现出来,随后,又渐渐地隐入第二幅图象之中,接着,
第二幅图象又慢慢地消失于背景中。
动画法:
这种方法是利用直接操作位图的数据来实现的,可以实现像素颜色的平滑变化,视觉效果可以做的很
好,因此,这种方法在屏保中的应用非常多。
首先,我们必须了解bmp图形的结构。一个bmp图形由两个部分组成,即文件头和数据区,文件头存放
bmp图形的大小、格式等信息,数据区存放bmp图形各个像素的颜色信息。对于24位真彩色的bmp来说,文件头的大
小为54个字节,前14个字节对应VC中定义的BITMAPFILEINFO结构,后40个字节对应BITMAPINFOHEADER结构。我们
把bmp数据区的数据读出,经过一定的运算,再利用WIN32API::StretchDIBits(...)函数直接输出到显示DC上,就
可以实现一些特技效果。
下面让我们分步去实现一个全屏幕的演示程序:
1:生成一个基于对话框的项目,命名为F1:
2:删除F1Dlg.h、F1Dlg.h和F1.cpp中与其相关的所有代码。
3:在项目中添加一个基类为generic Cwnd的新类,命名为CW.
4:为CW类添加如下私有成员变量:
//////////////////////
UINT y_offset;
UINT x_offset;
UINT stage;
BYTE* p3;
BYTE * p2;
BYTE * p1;
BITMAPINFOHEADER header;
HGLOBAL hlb1;
HGLOBAL hlb2;
HGLOBAL hlb3;
UINT start;
UINT counter;
////////////////////////
并在W.h的顶部加入宏定义 #define BMP_SIZE 192000
5:为CW添加Create(...)虚函数,WM_CREATE,WM_TIMER,WM_PAINT,WM_DESTORY,WM_LBUTTONDOWN的消息句柄,接受缺
省的函数名称。
6:删除CW::Create(...)中的原有代码,用以下代码替换:
/////////////////////////
LPCTSTR m_lpszCN;
m_lpszCN = AfxRegisterWndClass(CS_BYTEALIGNCLIENT,
::LoadCursor(AfxGetResourceHandle(),
MAKEINTRESOURCE(IDC_NULLCORSOR)));
return CWnd::CreateEx(WS_EX_TOPMOST,m_lpszCN, lpszWindowName, dwStyle, rect, pParentWnd, nID, pContext);
//////////////////////////
7: 删除CF1App:: InitInstance()中#endif以后的所有代码,用以下代码代替:
///////////////////////////
int cx=GetSystemMetrics(SM_CXSCREEN);
int cy=GetSystemMetrics(SM_CYSCREEN);
CRect rectDefault(0,0,cx,cy);
m_pMainWnd=new CW();
m_pMainWnd->Create(NULL, _T("Hello World!"), WS_VISIBLE|WS_POPUP, rectDefault,NULL,NULL);
return TRUE;
///////////////////////////
8:在CW::OnCreate(...)函数中加入如下代码:
/////////////////////////////
counter=0;
int cx=GetSystemMetrics(SM_CXSCREEN);
int cy=GetSystemMetrics(SM_CYSCREEN);
x_offset=(cx-640)/2;
y_offset=(cy-400)/2;
/////////////////////////////
9: 在CW::OnDestroy()函数中加入如下代码:
////////////////////////////////
KillTimer(1);
GlobalFree(hlb1);
GlobalFree(hlb2);
GlobalFree(hlb3);
/////////////////////////////
10: 在CW:: OnLButtonDown(...)函数中加入如下代码:
//////////////////////////////
SendMessage(WM_CLOSE);
//////////////////////////////
11: 在CW:: OnLButtonDown(...)函数中加入如下代码:
/////////////////////////////
CPaintDC dc(this);
dc.FillSolidRect(0,0,800,600,RGB(0,0,0));
dc.SetTextColor(RGB(200,0,0));
if(!start) return;
CFile f1,f2;
f1.Open("bmp1.bmp",CFile::modeRead);
f2.Open("bmp2.bmp",CFile::modeRead);
f1.Seek(14,CFile::begin);
f1.Read(&header,40);
f2.Seek(54,CFile::begin);
hlb1=GlobalAlloc(GMEM_MOVEABLE,BMP_SIZE);
p1=(BYTE*)GlobalLock(hlb1);
p1=(BYTE*)malloc(BMP_SIZE);
f1.ReadHuge(p1,BMP_SIZE);
GlobalUnlock(hlb1);
hlb2=GlobalAlloc(GMEM_MOVEABLE,BMP_SIZE);
p2=(BYTE*)GlobalLock(hlb2);
f2.ReadHuge(p2,BMP_SIZE);
GlobalUnlock(hlb2);
hlb3=GlobalAlloc(GMEM_MOVEABLE,BMP_SIZE);
p3=(BYTE*)GlobalLock(hlb3);
GlobalUnlock(hlb3);
f1.Close();
f2.Close();
stage=1;
SetTimer(1,100,NULL);
start=0;
///////////////////////////
12: 在CW:: OnTimer(...)函数中加入如下代码:
///////////////////////////
if(stage==1)
{
if(counter++>63)
{
stage=2;
counter=0;
return;
}
for(int i=0;i<BMP_SIZE;i++)
p3[i]=counter*p1[i]/64;
::StretchDIBits(GetDC()->m_hDC,x_offset,y_offset,640,400,0,0,320,200,p3,
((BITMAPINFO*)(&header)),NULL,SRCCOPY);
}
if(stage==2)
{
if(counter++>63)
{
stage=3;
counter=0;
return;
}
for(int i=0;i<BMP_SIZE;i++)
p3[i]=(64-counter)*p1[i]/64+counter*p2[i]/64;
::StretchDIBits(GetDC()->m_hDC,x_offset,y_offset,640,400,0,0,320,200,p3,
((BITMAPINFO*)(&header)),NULL,SRCCOPY);
}
if(stage==3)
{
if(counter++>63)
{
KillTimer(1);
SendMessage(WM_CLOSE);
return;
}
for(int i=0;i<BMP_SIZE;i++)
p3[i]=(64-counter)*p2[i]/64;
::StretchDIBits(GetDC()->m_hDC,x_offset,y_offset,640,400,0,0,320,200,p3,
((BITMAPINFO*)(&header)),NULL,SRCCOPY);
}
//////////////////////////////////
13:最后,将两个分辨率为320*200的24bitBMP拷入工程所在的目录中,分别命名为1.bmp和2.bmp. 编译运行程序,
可以看到在黑色的背景中,第一幅图象(1.bmp)由暗渐渐变亮,当它完全出现后,第二幅图象(2.bmp)从第一幅图象
中慢慢的浮现出来。当第二幅图象完全取代第一幅后,它的亮度又逐渐减小,最终消失在黑色的背景中。与模式画
刷法不同,这些过渡非常平滑。
怎么初始化这些参数
m_bf.BlendOp =AC_SRC_OVER;
m_bf.BlendFlags =0;
m_bf.SourceConstantAlpha =250;
m_bf.AlphaFormat =0;
char filename[255];
::GetModuleFileName (AfxGetApp()->m_hInstance ,filename,255);
CString lpszPathName=filename;
lpszPathName=lpszPathName.Left (lpszPathName.ReverseFind ('\\'));
lpszPathName=lpszPathName.Left (lpszPathName.ReverseFind ('\\'));
CString bkfile=lpszPathName;
bkfile+="\\black.bmp";
lpszPathName+="\\coco.bmp";
bkfile="black.bmp";
lpszPathName="coco.bmp";
HBITMAP hbm = (HBITMAP)::LoadImage(NULL,
lpszPathName,
IMAGE_BITMAP,
0,
0,
LR_LOADFROMFILE);
if (cross.Attach (hbm))
{ /*if (!cross.LoadBitmap (IDB_BLACK))
{
AfxMessageBox("load bitmap eror");
}*/
cross.GetBitmap (&bmp);
}
//lantern.LoadBitmap (IDB_COCO);
hbm = (HBITMAP)::LoadImage(NULL,
bkfile,
IMAGE_BITMAP,
0,
0,
LR_LOADFROMFILE);
if (lantern.Attach (hbm))
{
lantern.GetBitmap (&bmp);
}
dc=pwnd->GetDC ();
bmpWidth=bmp.bmWidth ;
bmpHeight=bmp.bmHeight ;
dc->SetBkMode (TRANSPARENT);
dcForCross.CreateCompatibleDC (dc);
dcForLantern.CreateCompatibleDC (dc);
dcForCross.SelectObject (&cross);
dcForLantern.SelectObject (&lantern);
if (bShowLantern)
{
AlphaBlend(dc->GetSafeHdc (),0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
}
else
{
AlphaBlend(dc->GetSafeHdc (),0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
}
[email protected]