|
楼主 |
发表于 2009-9-25 13:54:06
|
显示全部楼层
回复: 在vc环境中,如何将一附图嵌入到另一幅图中
实现一个简单的精灵动画,可以有以下几种方法来实现:
1.用SetPixel函数直接在屏幕上逐点画,这是最笨也是效率最低的方法,我们就不要考虑这种方法了。
2.BitBlt函数:需要为精灵图片制作一张黑白掩膜图片,然后用掩膜图片分别对背景和精灵图片进行处理,最后把处理过的精灵图片拷贝到背景图上。前后要使用三个BitBlt函数,如果为了防止屏幕闪烁,需要使用内存DC,这样就要使用五个BitBlt函数,如果图片比较大的话,处理速度是比较慢的。
3.最快的方法:直接写屏。在游戏编程中,一般都有大量的精灵,用Windows的GDI函数根本无法实现。一个很好的方法就是使用直接写屏技术,通过直接操作显存来加快显示速度,这在DOS时代很轻易的实现,由于Windows程序不能直接访问硬件,需要借助外挂环境来实现,比如Microsoft DirectX就被广泛用于游戏编程中,具体做法可以参阅DirectX编程方面的书籍,这里将不做介绍。
4.直接修改数据缓冲区。这种方法比较简单,其实现原理和直接写屏类似,速度也还可以。另外,通过修改数据缓冲区,你还可以实现一些其他的特殊效果,本文将重点对这种方法进行讲解。
BitBlt函数方法:
GDI的BitBlt函数的功能是将图形数据块从一个位置搬移到另一个位置,源和目标位图可以在同一个设备文本对象,也可以在不同的设备文本对象,函数原型如下:
BitBlt(HDC hDC,int x,int y,int cx,int cy,HDC hDCSrc,int xSrc,int ySrc,DWORD dwRop);
参数dwRop为光栅操作码,决定位图的显示方式,这里介绍三个下面画透明位图需要用到的的光栅操作码:
光栅操作码:MERGEPAINT
效果:源的反向"或上"目标(即:dest=(NOT src) OR dest)
说明:白色或上任何颜色都等于白色;黑色或上任何颜色颜色都不变
光栅操作码:NOTSRCERASE
效果:源的反向"与上"目标的反向(即:dest=(NOT src) AND (NOT dest))
说明: 黑色与上任何颜色都等于黑色;白色与上任何颜色颜色都不变
光栅操作码:SRCINVERT
效果:源与目标"异或"起来(即:dest=src XOR dest)
说明:黑色与任何颜色异或都等于原来颜色;白色与任何颜色异或都等于原来颜色的反色
例子:先准备一张精灵图片、一张精灵的掩膜图和一张背景图(见下图)
1.用精灵掩膜图处理精灵图片(BitBlt函数用MERGEPAINT光栅操作码):
dcFairy.BitBlt(0,0,bm.bmWidth,bm.bmHeight,&dcMask,0,0,);
处理过的精灵图片:
2.用精灵掩膜图处理背景(BitBlt函数用NOTSRCERASE光栅操作码)
pDC->BitBlt(x,y,bm.bmWidth,bm.bmHeight,&dcMask,0,0,NOTSRCERASE);
处理过的背景图:
3.把处理过的精灵图片贴到背景上(BitBlt函数用SRCINVERT光栅操作码)
pDC->BitBlt(x,y,bm.bmWidth,bm.bmHeight,&dcFairy,0,0,);
合成后的图形:
完整代码如下:
void DrawFairy(CDC *pDC, int x, int y) // pDC为窗口DC指针
{
CDC dcFairy;
CDC dcMask;
dcFairy.CreateCompatibleDC(pDC);
dcMask.CreateCompatibleDC(pDC);
CBitmap *pMask=dcMask.SelectObject(&m_bmMask); // m_bmMask为精灵掩膜图
CBitmap *pFairy=dcFairy.SelectObject(&m_bmFairy); // m_bmFairy为精灵图片
// 得到精灵图片的大小
BITMAP bm;
m_bmFairy.GetObject(sizeof(bm),&bm);
// 处理精灵图片
dcFairy.BitBlt(0,0,bm.bmWidth,bm.bmHeight,&dcMask,0,0,MERGEPAINT);
// 处理背景图片(用来贴精灵图片的那一部分)
pDC->BitBlt(x,y,bm.bmWidth,bm.bmHeight,&dcMask,0,0,NOTSRCERASE);
// 将处理过的精灵图片与背景经过处理的部分"异或"起来
pDC->BitBlt(x,y,bm.bmWidth,bm.bmHeight,&dcFairy,0,0,SRCINVERT);
// Release
dcMask.SelectObject(pMask);
dcFairy.SelectObject(pFairy);
}
上面是用BitBlt函数的实现方法,我们也可以用直接操作位图数据缓冲区的方法:
直接操作位图数据缓冲区:
这种方法也很简单,首先创建精灵图片和背景图片的设备无关位图对象(DIB),然后读取精灵图片各个像素的颜色值,如果颜色值等于我们设定的透明颜色(Mask Color),就把该点的颜色换为背景图上相对位置的点的颜色值,然后将经过处理的精灵图片拷贝到背景上就行了。不过需要要注意的是位图的颜色深度,不同的颜色深度决定了数据缓冲区中一个像素值的长度,需要自己写一些代码来判断这些情况,下面以一个256色的位图来做一个例子:
完整的例子代码:
void CComposeDoc:rawFairy(HDC hDC, int left, int top, int mask)
{
LPBITMAPINFO lpbmif;
LPBITMAPINFOHEADER lpbmifh;
if ( m_hDIBFairy == NULL || m_hDIBBack == NULL ) // 分别是精灵图片的DIB对象和背景图片的DIB对象
return;
// // 得到精灵图片信息 //
lpbmifh=(LPBITMAPINFOHEADER)m_hDIBFairy;
lpbmif=(LPBITMAPINFO)m_hDIBFairy;
// 这里假设精灵图片的颜色深度为8位(256色)
ASSERT( lpbmifh->biBitCount==8 );
int cx=lpbmifh->biWidth; // 长度
int cy=lpbmifh->biHeight; // 宽度
int nBytesPerLineFairy=((lpbmifh->biWidth*lpbmifh->biBitCount+31)&~31)/8; // 每行字节数
UINT nColors=lpbmifh->biClrUsed ? lpbmifh->biClrUsed :
1<<lpbmifh->biBitCount; // 颜色数
LPVOID lpvBufFairy=lpbmif->bmiColors+nColors; // 精灵图片数据指针
// // 得到背景图片信息 //
lpbmif=(LPBITMAPINFO)m_hDIBBack;
lpbmifh=(LPBITMAPINFOHEADER)m_hDIBBack;
// 同样假设背景图片的颜色深度是8位(256色)
ASSERT( lpbmifh->biBitCount == 8 );
int cxBack=lpbmifh->biWidth; // 宽度
int cyBack=lpbmifh->biHeight; // 高度
int nBytesPerLineBack=((cxBack*lpbmifh->biBitCount+31)&~31)/8;// 每行字节数
nColors=lpbmifh->biClrUsed ? lpbmifh->biClrUsed :
1<<lpbmifh->biBitCount;
LPVOID lpvBufBack=lpbmif->bmiColors+nColors;// 背景图片数据指针
// // 创建精灵图片的临时DIB对象 //
int nSize=GlobalSize(m_hDIBFairy);
LPVOID lpvBufTemp=GlobalAlloc(0,nSize);
if ( lpvBufTemp == NULL )
return ;
memcpy(lpvBufTemp,m_hDIBFairy,nSize);
// 由于这里假设图片的颜色深度数8位的,用BYTE指针来表示一个像素
LPBYTE lpbBufFairy=NULL;
LPBYTE lpbBufBack=NULL;
for ( int y=cy; y>0; y-- )
{
// 读取的精灵图片数据指针
lpbBufFairy=(LPBYTE)lpvBufFairy+(y-1)*nBytesPerLineFairy;
// 相对位置的背景图片数据指针
lpbBufBack=(LPBYTE)lpvBufBack+(cyBack-top-cy+y-1)*nBytesPerLineBack+left;
for ( int x=0; x<cx; x++ )
{
// 如果当前像素等于我们设定的透明颜色索引值,修改当前像素索引值
if ( *lpbBufFairy == mask )
*lpbBufFairy = * lpbBufBack;
lpbBufFairy++;
lpbBufBack++;
}
}
// 画精灵图片到屏幕上
SetDIBitsToDevice(hDC,left,top,cx,cy,0,0,0,cy,lpvBufFairy,
(LPBITMAPINFO)m_hDIBFairy,DIB_RGB_COLORS);
// 回复原来的精灵图片数据
memcpy(m_hDIBFairy,lpvBufTemp,nSize);
GlobalFree(lpvBufTemp);
}
用上面的两种方法都可以输出一个透明位图到屏幕上,只要修改在屏幕上的显示位置,就可以轻易的制作出一个精灵动画,不过用这两种方法有一个缺点,就是显示的精灵有一个明显的轮廓,特别是前景和背景颜色反差很大时。如何避免这种情况呢?就是下面要介绍的利用Alpha通道输出位图的方法。 |
|