利用VC++实现WIN95/NT下位图淡入淡出的二种技巧
岳朝伟 张秋枫
对于图象的淡入淡出,相信只要玩过游戏的人一定不会感到陌生。它往往是作为一个游戏场景与另一个场景的切换过渡,给人一种轻巧、灵便的感觉,不象直接切换场景图象那样生硬,因此几乎所有的游戏都要包含这种功能。同时,若在自己的应用程序开头加入淡入淡出图象功能也能起到赏心悦目的作用。
在DOS下作图片的淡入淡出特技处理的原理是直接控制系统硬件设备的调色板,让调色板中每种颜色的值由0逐渐增加到给定图象的值,即可实现淡入,相反则实现淡出。因此,淡入淡出的特技在DOS游戏中遍地开花随处可见。
如今,由于操作系统的升级,特别是在WIN95/NT这种高度封装硬件设备的操作系统中,直接修改硬件就显得力不从心。但也并不是没有办法。以下就是本人在WIN95/NT环境下经过实践并调试通过的两种方法,与大家共享。为了便于二种方法的比较说明,我们将这2种方法处理成命令消息处理函数添加在主菜单的VIEW项中,具体过程下面将一一介绍。
以下调试环境为VC++5.0、WIN95/NT,同时为了调试简便和增加成功率,我们将一些错误处理滤去,因此选用图片时必须为BMP格式的256色位图文件。
一、用API函数SetDIBitsToDevice()实现淡入淡出
对于这种方法此刊在98年第2期刊登过湖北江龙的一篇文章,不过他是完全利用SDK编写的。本人根据他的原理,将这种方法通过VC++5.0移植到WIN95/NT环境下。因此关于利用SetDIBitsToDevice()来实现淡入淡出的基本原理请参阅本刊9年第2期江龙的文章,其中有详细的说明,在这里我就不用多说了。下面是利用VC++实现它的主要步骤:
1、首先启动VC++5.0,从File下选择New,选择Project下的MFVAPP Wizard(exe)创建一个`单文档工程,命名为SlowShow,接受Appwizard其他所有缺省值。
2、打开资源中的MENU框架,在View菜价单的空白出加入ID号为IDC_USE_SETDIBITSTODEVICE, Caption为UseSetDIBitsToDevice,然后用鼠表右键单击刚加入的UseSetDIBitsToDevice菜单项选ClassWizard,选择SlowShowView处理类使用缺省设置。
3、打开SlowShowView.cpp,在开头处加入#define STEP 150,再找到命令消息处理函数
OnUseSetDIBitsToDevice()加入代码如下:
void CSlowShowView::OnViewUseSetdibitstodevice()
{
// TODO: Add your command handler code here
CString filename;
CFileDialog dlg(TRUE,NULL,NULL,
OFN_OVERWRITEPROMPT,
"BMP(*.bmp)|*.bmp||",NULL);
if(dlg.DoModal()==IDOK)//显示文件对话框
filename = dlg.GetPathName();//取得文件名
file://以上为显示选择位图文件对话框。
CFile file(filename,CFile::modeRead|CFile::shareDenyNone); file://打开文件
DWORD bfOffBits;
DWORD bfSize;
WORD bfType;
file.Read(&bfType,sizeof(WORD));//取得文件类型标识符‘BM’
file.Read(&bfSize,sizeof(DWORD));//取得文件总长度
file.Seek(2*sizeof(WORD),CFile::current);//文件定位
file.Read(&bfOffBits,sizeof(DWORD));//读取图象数据开始处到文件开始处的偏移距离
LPBITMAPINFO lpbitmapinfo;
int size = bfOffBits- 14;//调色板信息及BITMAPINFO结构的总长度
lpbitmapinfo = (LPBITMAPINFO) new BYTE[size];//为BITMAPINFO结构分配空间
file.Seek(14,CFile::begin);
file.ReadHuge(lpbitmapinfo,size);//读取调色板信息及BITMAPINFO结构
LPVOID pbitsrc;
pbitsrc = (LPVOID) new char[bfSize-bfOffBits];//为图象数据分配空间
file.ReadHuge(pbitsrc,bfSize-bfOffBits);//读取图象数据
file.Close(); file://关闭文件
LPLOGPALETTE oldlpal;
oldlpal = (LPLOGPALETTE) new BYTE[sizeof(LOGPALETTE)+256*sizeof(PALETTEENTRY)];
file://对创建的逻辑调色板分配空间
oldlpal->palVersion = 0x300;//使调色板能够渐变
oldlpal->palNumEntries = 256;//使用八位的颜色数据
for(int i=0;i<256;i++)
{
oldlpal->palPalEntry[i].peRed =
lpbitmapinfo->bmiColors[i].rgbRed;
oldlpal->palPalEntry[i].peGreen =
lpbitmapinfo->bmiColors[i].rgbGreen;
oldlpal->palPalEntry[i].peBlue =
lpbitmapinfo->bmiColors[i].rgbBlue;
oldlpal->palPalEntry[i].peFlags = 0;
file://以上为将文件中的调色板信息保存在新创建的逻辑调色板oldlpal中
}
CDC* pdc ;
pdc = GetDC();
CGdiObject * pold = pdc->SelectStockObject(BLACK_BRUSH);
CRect arry;
GetClientRect(&arry);
pdc->FillRect(arry,NULL);
ValidateRect(&arry);//将窗口客户区变黑并使客户区无效
LPLOGPALETTE newlpal;
newlpal = (LPLOGPALETTE) new BYTE[sizeof(LOGPALETTE)+(256*sizeof(PALETTEENTRY))];
newlpal->palVersion = 0x300;
newlpal->palNumEntries = 256;
file://创建保存变化的调色板信息的新的逻辑调色板
for(int j=0;j<=1;j++)
{
for(int m=0;m<=STEP;m++)
{
if(j==0)//渐进
for(int i=0;i<256;i++)
{
newlpal->palPalEntry[i].peRed=
oldlpal->palPalEntry[i].peRed*m/STEP;
newlpal->palPalEntry[i].peGreen=
oldlpal->palPalEntry[i].peGreen*m/STEP;
newlpal->palPalEntry[i].peBlue=
oldlpal->palPalEntry[i].peBlue*m/STEP;
newlpal->palPalEntry[i].peFlags=0;
file://改变调信板信息并存入新的逻辑调色板中
}
else file://淡出
for(int i=0;i<256;i++)
{
newlpal->palPalEntry[i].peRed=
oldlpal->palPalEntry[i].peRed*(STEP-m)/STEP;
newlpal->palPalEntry[i].peGreen=
oldlpal->palPalEntry[i].peGreen*(STEP-m)/STEP;
newlpal->palPalEntry[i].peBlue=
oldlpal->palPalEntry[i].peBlue*(STEP-m)/STEP;
newlpal->palPalEntry[i].peFlags=0;//改变调信板信息并存入新的逻辑调色板中
}
CPalette* hpal= new CPalette;
CPalette* holdpal;
hpal->CreatePalette(newlpal);
holdpal = pdc->SelectPalette(hpal,FALSE);
pdc->RealizePalette();
file://创建调色板处理类,将其选入当前设备中并实现之
::SetDIBitsToDevice(pdc->m_hDC,
(arry.right-lpbitmapinfo->bmiHeader.biWidth)/2,
(arry.bottom-lpbitmapinfo->bmiHeader.biHeight)/2,//居中显示
lpbitmapinfo->bmiHeader.biWidth,
lpbitmapinfo->bmiHeader.biHeight,
0,0,0,lpbitmapinfo->bmiHeader.biHeight,
pbitsrc,lpbitmapinfo,DIB_RGB_COLORS);
file://利用API全局函数SetDIBitsToDevice()将图象数据显示在当前窗口
pdc->SelectPalette(holdpal,0);
delete hpal;//为了不影响系统调色板应将创建调色板处理类删除掉
}
}
delete pbitsrc;//释放图象数据所占内存
delete lpbitmapinfo;//释放BITMAPINFO结构所占内存
delete newlpal;//释放创建调色板所占内存
delete oldlpal;//释放创建调色板所占内存
pdc->SelectObject(pold);//恢复画刷
ReleaseDC(pdc);//释放DC
}
注:在WIN95/NT环境中,SetDIBitsToDevice()最后参数UNIT fuColorUse可以设置成DIB_RGB_COLORS,条件是WIN95/NT系统的颜色属性必须为256色,超过或低于均不能产生淡入淡出效果.可以通过在桌面上单击右键,选择属性栏改变系统颜色值.同时若你是在Windows NT这种以客户-----服务器结构构成的环境下使用SetDIBitsToDevice()显示图象,更是一个复杂的过程。Windows NT首先将DIB拷贝到服务器端创建一个DDB,然后才能将DDB中的数据输出到视屏内存,而且当DIB超过56K时,系统还要为其分配缓冲区,满满的显示,哎!这东西真是一个既费时又费内寸的家伙,而且表现还不怎么样。面对如此苛刻的要求,迫使我们不得不另寻它法。经过一番苦心挖掘,我们终于在VC++的多媒体部分找到了直到现在都让我们兴奋不已,而且要求不高、处理简单、效果绝对一流的DrawDib函数组。使用它的条件是你的MFC类库中必须包含有vfw.h和 vfw32.lib库文件。下面将是制作的具体步骤,你可得睁大眼睛仔细看清楚喔!
二、利用DrawDib函数组实现位图的淡入淡出
1、DrawDib函数组简介
MicroSoft针对与设备无关位图DIB,在其WIN32 SDK 的Multimedia中提供了一组绘制DIB位图的高性能函数组。它们支持8为、16位、24位、32位图象深度的DIB。它可以对图象进行拉伸和抖动的功能,还支持图象的解压、数据流以及更多的显示适配器。它只支持颜色信息表格式为DIB_RGB_COLORS格式的图象不支持DIB_PAL_COLORS和DIB_PAL_INDICES,只支持SRCCOPY光栅操作模式。
2、DrawDib函数组一般操作过程
2.1、 通过使用DrawDibOpen()函数初始化DrawDib函数组.它会得到一个非常重要的所有DrawDib函数所必需的专用的DrawDib设备场景句柄,因此它应是最开始的操作.
2.2、 通过DrawDibBegin()初始化专用的DrawDib设备场景,便于DrawDibDraw()显示,若不用DrawDibBegin初始化,也可以用DrawDibDraw()直接进行DrawDib设备场景初始化。
2.3、 通过DrawDibDraw()由各种方式显示图象,这些图象可以是单幅的,也可以是图象序列。
2.4、 通过DrawDibClose()释放DrawDib设备场景,因此它应是最后的DrawDib操作。
3、利用DrawDib实现调色板动画的原理
此原理主要是通过改变PALETTEENTRY队列中的RGB颜色值,并提供给一系列的DrawDib绘图函数使用,即动态的改变原图象调色板中每一个RGB的颜色值其中用到的关键参数lppe是一个包含新颜色值的LPPALETTEENTRY队列的结构指针,lpbi是一个LPBITMAPINFOHEADER的结构指针(此结构是包含在BITMAPINFO结构当中的,lpBits是图象数据的地址。具体实现的流程图如下:
hdc=GetDC(hwnd);
hdd=DrawDibOpen();
DrawDibBegin(hdd,……,DDF_ANIMATE);//标志DDF_ANIMATE表示允许改变调色板
创建一个LPPALETTEENTRY队列指针lppe,并分配空间;
for(i=0;i〈=255;i++)
{ lppe颜色队列`中的RGB值=图象文件中的RGB值*i/STEP;
DrawDibRealize(hdd,hdc,fBackgraund);
DrawDibChangePalette(hdd,iStart,iLen,lppe);
DrawDibDraw(hdd,hdc,……,lpbi,lpBits,…..,DDF_SAME_DRAW|DDF_SAME_HDC);
file://其中标志DDF_SAME_DRAW表示所显示图象的BITMAPINFOHEADER结构地址
file://和源矩形及目的矩形的尺寸均不改变,DDF_SAME_HDC表示DC句柄不变
file://及不使用位图序列,只使用单幅位图
释放分配的图象内存;
DrawDibClose(hdd);
ReleaseDC(hwnd,hdc);
}
其中用到的各函数参数分别表示为如下:
BOOL DrawDibBegin( HDRAWDIB hdd, file://由DrawDibOpen()得到的专用的DrawDib设备场景句柄
HDC hdc,// 当前设备句柄
int dxDest, file://目标矩形宽度
int dyDest, file://目标矩形高度
LPBITMAPINFOHEADER lpbi, file://指向需要显示的图象的BITMAPINFOHEADER结构指针
int dxSrc, file://需要显示的图象宽度
int dySrc, file://需要显示的图象高度
UINT wFlags file://初始化位图的标志,此处应设置为DDF_ANIMATE
);
UINT DrawDibRealize( HDRAWDIB hdd,//同上
HDC hdc, file://同上
BOOL fBackground file://此出为FALSE时则所实现调色板选入前景,反之若为TURE则选入背景
);
BOOL DrawDibChangePalette( HDRAWDIB hdd, file://同上
int iStart, // 所创建PALETTEENTRY颜色队列数目的开始值, 此出为0
int iLen, // 所创建PALETTEENTRY颜色队列数目的最大值, 此出为255
LPPALETTEENTRY lppe file://一个包含新颜色值的PALETTEENTRY队列的结构指针
);
BOOL DrawDibDraw( HDRAWDIB hdd, file://同上
HDC hdc, file://同上
int xDst, file://目标圆点X坐标
int yDst, file://目标圆点Y坐标
int dxDst, file://目标矩形宽度
int dyDst, file://目标矩形高度
LPBITMAPINFOHEADER lpbi,// 指向需要显示的图象的BITMAPINFOHEADER结构指针
LPVOID lpBits, file://是指向需要显示的图象数据的地址
int xSrc,//来源矩形圆点X坐标
int ySrc, file://来源矩形圆点Y坐标
int dxSrc,// 需要显示的图象宽度
int dySrc, file://需要显示的图象高度
UINT wFlags//操作显示位图的标志 ,此出应设位DDF_SAME_DRAW|DDF_SAME_HDC
);
以上只是使用到的DrawDib函数组的简介,若要得到更为详尽的说明请参阅MFC参考守则。
4、实现淡入淡出的具体步骤:
4.1、利用上文中的工程SlowShow,打开StdAfx.h加入代码如下:
#include <vfw.h>
#pragme comment (lib,”vfw32.lib”)
4.2、打开MENU对话框,在View菜价单的空白出加入ID号为IDC_USE_DRAWDIBDRAW, Caption为 UseDrawDibDraw,然后用鼠表右键单击刚加入的UseDrawDibDraw,菜单项选ClassWizard,选择SlowShowView 处理类使用缺省设置。
4.3、 打开SlowShowView.cpp文件,找到OnUseDrawDibDraw()命令消息处理函数加入代码如下:
void CSlowShowView::OnViewUseDrawdibdraw()
{
// TODO: Add your command handler code here
CString filename;
CFileDialog dlg(TRUE,NULL,NULL,
OFN_OVERWRITEPROMPT,
"BMP(*.bmp)|*.bmp||",NULL);
if(dlg.DoModal()==IDOK) file://显示文件对话框
filename = dlg.GetPathName();//取得文件名
file://以上为显示选择位图文件对话框。
CFile file(filename,CFile::modeRead|CFile::shareDenyNone);//打开文件
WORD bfType;
DWORD bfSize;
file.Read(&bfType,sizeof(WORD));//取得文件类型标识符‘BM’
file.Read(&bfSize,sizeof(DWORD));//取得文件总长度
DWORD bfOffBits;
file.Seek(2*sizeof(WORD),CFile::current);//文件定位
file.Read(&bfOffBits,sizeof(DWORD));//读取图象数据开始处到文件开始处的偏移距离
LPBITMAPINFO lpbitmapinfo;
int size = bfOffBits- 14;//调色板信息及BITMAPINFO结构的总长度
lpbitmapinfo = (LPBITMAPINFO) new BYTE[size];//为BITMAPINFO结构分配空间
file.Seek(14,CFile::begin);
file.ReadHuge(lpbitmapinfo,size);//读取调色板信息及BITMAPINFO结构
LPVOID pbitsrc;
pbitsrc = (LPVOID) new char[bfSize-bfOffBits];//为图象数据分配空间
file.ReadHuge(pbitsrc,bfSize-bfOffBits);//读取图象数据
file.Close(); file://关闭文件
LPLOGPALETTE oldlpal;
oldlpal = (LPLOGPALETTE) new BYTE[sizeof(LOGPALETTE)+256*sizeof(PALETTEENTRY)];
file://对创建的逻辑调色板分配空间
oldlpal->palVersion = 0x300;//使调色板能够渐变
oldlpal->palNumEntries = 256;//使用八位的颜色数据
for(int i=0;i<256;i++)
{
oldlpal->palPalEntry[i].peRed =
lpbitmapinfo->bmiColors[i].rgbRed;
oldlpal->palPalEntry[i].peGreen =
lpbitmapinfo->bmiColors[i].rgbGreen;
oldlpal->palPalEntry[i].peBlue =
lpbitmapinfo->bmiColors[i].rgbBlue;
oldlpal->palPalEntry[i].peFlags = 0;
file://以上为将文件中的调色板信息保存在新创建的逻辑调色板oldlpal中
}
CDC* pdc ;
pdc = GetDC();
CGdiObject * pold = pdc->SelectStockObject(BLACK_BRUSH);
CRect arry;
GetClientRect(&arry);
pdc->FillRect(arry,NULL);
ValidateRect(&arry);//将窗口客户区变黑并使客户区无效
HDRAWDIB hdd = ::DrawDibOpen();
file://初始化DrawDib函数组并得到此DrawDib函数组所必须的HDRAWDIB句柄
::DrawDibBegin(hdd,pdc->m_hDC,
lpbitmapinfo->bmiHeader.biWidth,
lpbitmapinfo->bmiHeader.biHeight,
&lpbitmapinfo->bmiHeader,
lpbitmapinfo->bmiHeader.biWidth,
lpbitmapinfo->bmiHeader.biHeight,
DDF_ANIMATE);
file://为DrawDibDraw()函数准备好现示图象所需信息
file://此处的标志位必须设为DDF_ANIMATE,它表示可以使DrawDib设备场景中的调色板的信息改变
LPPALETTEENTRY entry;
entry = (LPPALETTEENTRY) new BYTE[256*sizeof(PALETTEENTRY)];
file://创建一个可以改变的PALETTEENTRY结构指针
for(int j=0;j<=1;j++)
{
for(int m=0;m<=STEP;m++)
{
if(j==0)//淡入
for(int i=0;i<256;i++)
{
entry[i].peRed= oldlpal->palPalEntry[i].peRed*m/STEP;
entry[i].peGreen =oldlpal->palPalEntry[i].peGreen*m/STEP;
entry[i].peBlue = oldlpal->palPalEntry[i].peBlue*m/STEP;
entry[i].peFlags = 0;
file://改变调色板信息并存入新创建的PALETTEENTRY结构指针中
}
else file://淡出
for(int i=0;i<256;i++)
{
entry[i].peRed= oldlpal->palPalEntry[i].peRed*(STEP-m)/STEP;
entry[i].peGreen =oldlpal->palPalEntry[i].peGreen*(STEP-m)/STEP;
entry[i].peBlue = oldlpal->palPalEntry[i].peBlue*(STEP-m)/STEP;
entry[i].peFlags = 0;
file://改变调色板信息并存入新创建的PALETTEENTRY结构指针中
}
::DrawDibRealize(hdd,pdc->m_hDC,FALSE);
::DrawDibChangePalette(hdd,0,255,entry);
::DrawDibDraw(hdd,pdc->m_hDC,
(arry.right-lpbitmapinfo->bmiHeader.biWidth)/2,
(arry.bottom-lpbitmapinfo->bmiHeader.biHeight)/2,
file://居中显示
lpbitmapinfo->bmiHeader.biWidth,
lpbitmapinfo->bmiHeader.biHeight,
&lpbitmapinfo->bmiHeader,
pbitsrc,
0,0,
lpbitmapinfo->bmiHeader.biWidth,
lpbitmapinfo->bmiHeader.biHeight,
DDF_SAME_DRAW|DDF_SAME_HDC);
file://根据DrawDib设备场景中当前状态来显示图象数据
file://其中标志DDF_SAME_DRAW表示所显示图象的BITMAPINFOHEADER结构地址
file://和源矩形及目的矩形的尺寸均不改变,DDF_SAME_HDC表示DC句柄不变
file://及不使用位图序列,只使用单幅位图
}
}
::DrawDibClose(hdd);//清除DrawDib设备场景,释放DrawDib句柄
pdc->SelectObject(pold);//恢复原画刷
delete pbitsrc;//释放图象数据所占内存
delete lpbitmapinfo;//释放BITMAPINFO结构所占内存
delete oldlpal;//释放创建调色板所占内存
ReleaseDC(pdc);//释放DC
}
至此,两种方法介绍完毕,赶快编译运行瞧瞧。怎么样?是不是够专业水准。简直难以置信,DrawDib表现的太棒了,而且速度比SetDIBitsToDevice()明显快多了。对系统的要求也相当随意,应其有显示适配自动抖动功能,故无论系统颜色为多少位,它均能顺利完成淡入淡出的任务,而且效果并无一试太大的影响,不信赶快试一试。