windows编程技术10GDI编程3动画.docx
- 文档编号:4439538
- 上传时间:2022-12-01
- 格式:DOCX
- 页数:24
- 大小:526.06KB
windows编程技术10GDI编程3动画.docx
《windows编程技术10GDI编程3动画.docx》由会员分享,可在线阅读,更多相关《windows编程技术10GDI编程3动画.docx(24页珍藏版)》请在冰豆网上搜索。
windows编程技术10GDI编程3动画
第10章GDI编程3-动画
动画是利用人的视觉滞留缺陷(25ms~400ms)和心理认可来动态生成系列相关画面以产生运动视觉的技术。
位图动画是将预先制作好的一系列表示连续画面的位图,按一定的时间间隔一幅接一幅地连续显示,从而产生动画效果。
因为绘制动画所需的图形,以及拍摄和处理图片,需要美术、摄影、数字图像处理、动画设计等知识,我们这里不讲。
本书只介绍如何显示已有的位图(序列)以产生动画效果,以及如何动态绘制不同的简单图形以产生二维图形动画等。
用GDI编程实现动画,一般需要用到计时器(Timer)操作,通常在计时器响应函数OnTimer中(而不要使用OnDraw)绘图来实现动画。
10.1固定位图动画
本节介绍利用一系列的位图资源,在同一个屏幕位置,接连显示位图序列,以达到动画的效果的具体方法。
为此,可在交互绘图程序中添加一个如图10-1所示的位图动画对话框,并添加对应的对话框类CDukeDlg。
也可以创建一个基于对话框的独立的MFC应用程序。
图10-1位图动画对话框资源
当然还需添加相应的“位图动画”菜单项(ID_DUKE)和(为视图类添加)对应的菜单响应函数,并在该函数中创建对话框类的对象,打开对话框来运行动画:
#include"DukeDlg.h"
voidCDrawView:
:
OnDuke(){
CDukeDlgdlg;
dlg.DoModal();
}
10.1.1准备位图、加入位图资源
系列公爵(Duke)BMP文件T1.BMP~T10.BMP(见图10-2),来自Java吉祥物的GIF动画文件,可存放在项目的res子目录的Duke子目录中(该位图资源已经打包成Duke.rar文件后,放到了系里的网站和我的个人网页上)。
T1.BMPT2.BMPT3.BMPT4.BMPT5.BMP
T6.BMPT7.BMPT8.BMPT9.BMPT10.BMP
图10-2Duke位图文件
用VC的资源编辑器依次加入位图文件:
在左边的项目工作区中选“资源视图”页,展开项目的资源列表,在“Bitmap”表项(若无此项,则可直接在项目资源项)上单击鼠标右键,在弹出的浮动菜单中选“添加资源”菜单项,在打开的“添加资源”对话框中,选中左边“资源类型”栏中的“Bitmap”表项,单击右边的“导入”按钮,在弹出的“导入”文件对话框中,定位Duke目录,选中所有Ti.BMP后按“打开”钮,则会自动加入ID为IDB_BITMAPi的位图资源。
为了以后循环编程的方便,必须保证是从T1.BMP到T10.BMP顺序依次加入。
为了确认,可打开头文件Resource.h查看,若其中的常量IDB_BITMAPi的定义数值不连续,可作一些手工修改使其连续,例如:
#defineIDB_BITMAP1131
#defineIDB_BITMAP2132
#defineIDB_BITMAP3133
#defineIDB_BITMAP4134
#defineIDB_BITMAP5135
#defineIDB_BITMAP6136
#defineIDB_BITMAP7137
#defineIDB_BITMAP8138
#defineIDB_BITMAP9139
#defineIDB_BITMAP10140
10.1.2添加控件、创建对话框类
为对话框资源添加图片控件:
打开对话框资源,在控件工具箱中选图片控件(PictureControl)工具,在对话框的适当位置添加图片控件,设置其“ID”属性值为“IDC_ANI”,修改“Type”属性为(在其下拉式列表框中选中)“Bitmap”,再在“Image”属性的下拉式列表框中选中“IDB_BITMAP1”位图资源,则该位图绘显示在图片控件中。
为了控制动画的播放,需要添加一个即可表示开始动画又可表示停止动画的按钮(初始标题为“开始动画”),可设置其ID为“IDC_ANI_START_STOP”。
为了让用户选择动画的速度,可以添加静态文本提示框“每秒帧数:
”和文本编辑框(IDC_N),在后面的10.1.6小节中还会添加滑块控件(SliderControl,IDC_SLIDER_N)。
创建该对话框资源所对应的对话框类CDukeDlg。
10.1.3添加类变量、装入与删除位图
在对话框类的定义(头文件)中添加若干类变量:
CBitmap*m_pBmp[10];//位图指针数组
BITMAPm_bs;//位图结构变量
boolm_bStarted;//判别动画是否启动(初始化为false)
intm_nCurFrame,//当前帧号(初值为0)
m_nFramesPerSecond;//每秒帧数(初值为10)
为CDukeDlg类添加(重写型)消息响应函数OnInitDialog,在该函数中(也可以在构造函数中)创建位图对象并装入位图资源,然后获取位图结构(其中的位图宽和高用于BitBlt函数),并初始化其他类变量,最后设置编辑控件的初值(粗体部分为新加的):
BOOLCDukeDlg:
:
OnInitDialog()
{
CDialog:
:
OnInitDialog();
//TODO:
在此添加额外的初始化
for(inti=0;i<10;i++){//装入位图资源
m_pBmp[i]=newCBitmap;
m_pBmp[i]->LoadBitmap(IDB_BITMAP1+i);
}
m_pBmp[0]->GetBitmap(&m_bs);//获取位图结构
m_bStarted=false;//设置已开始动画为假
m_nCurFrame=0;//设置初始的当前帧为0
m_nFramesPerSecond=10;//设置初始动画速度为每秒10帧
SetDlgItemInt(IDC_N,m_nFramesPerSecond);//设置编辑框初值
returnTRUE;//returnTRUEunlessyousetthefocustoacontrol
//异常:
OCX属性页应返回FALSE
}
其中,语句m_pBmp[i]->LoadBitmap(IDB_BITMAP1+i);中的IDB_BITMAP1+i用到了Duke位图资源ID的连续性。
为了避免内存泄漏,还需要为对话框类添加析构函数,并在该函数中删除位图对象:
CDukeDlg:
:
~CDukeDlg()
{
for(inti=0;i<10;i++)deletem_pBmp[i];
}
10.1.4启动/停止动画(设置/删除计时器)
为按钮IDC_ANI_START_STOP添加单击消息响应函数:
voidCDukeDlg:
:
OnBnClickedAniStartstop(){
//TODO:
在此添加控件通知处理程序代码
if(m_bStarted){//已开始动画
m_bStarted=false;//设置已开始动画为假
KillTimer
(1);//取消计时器
//设置按钮文本为“开始动画”
SetDlgItemText(IDC_ANI_START_STOP,L"开始动画");
}
else{//未开始动画
m_bStarted=true;//设置已开始动画为真
m_nCurFrame=0;////设置当前帧为0
//获取编辑框中的帧速值
m_nFramesPerSecond=GetDlgItemInt(IDC_N);
if(m_nFramesPerSecond<=0)//限制最小帧速值为1
m_nFramesPerSecond=1;
elseif(m_nFramesPerSecond>100)//最大帧速值为100
m_nFramesPerSecond=100;
//用调整后帧速值重新设置编辑框的内容
SetDlgItemInt(IDC_N,m_nFramesPerSecond);
//利用帧速值来设置计时器
SetTimer(1,(UINT)(1000.0/m_nFramesPerSecond+0.5),NULL);
//设置按钮文本为“停止动画”
SetDlgItemText(IDC_ANI_START_STOP,L"停止动画");
}
}
其中:
●m_bStarted为布尔型类变量,用于判断动画是否已经开始播放。
在构造函数中初始化为假,在用户按了开始/停止动画按钮后取反。
●m_nCurFrame为整数型类变量,用于记录当前所要显示的位图序号,初始化为0。
●m_nFramesPerSecond也为整数型类变量,用于记录当前的每秒帧数(帧速值,范围可设为1~100)。
●SetDlgItemText函数,用于动态修改按钮上的文本。
●SetTimer用于设置计时器,它是CWnd的成员函数(所以也可在其派生的视图类和对话框类中使用),其函数原型为:
UINTSetTimer(UINTnIDEvent,UINTnElapse,
void(CALLBACKEXPORT*lpfnTimer)(HWND,UINT,UINT,DWORD));
⏹nIDEvent为此计时器的编号。
因为一个应用程序可以设置多个计时器,为了在响应时区分它们,必须各有一个编号。
简单程序的计时器一般只有一个,所以取nIDEvent=1即可。
⏹nElapse为间隔时间,单位为毫秒(1/1000秒)。
可用表达式(UINT)(1000.0/m_nFramesPerSecond+0.5),从每秒帧数计算出间隔时间的毫秒数。
⏹lpfnTimer为应用程序提供的处理WM_TIMER消息的回调函数,一般取为NULL,这时WM_TIMER消息由CWnd派生类的对应消息响应函数来处理。
●KillTimer用于删除计时器,它也是CWnd的成员函数,其函数原型为:
BOOLKillTimer(intnIDEvent);
在设置了计时器后,系统会按指定的时间间隔发送WM_TIMER消息给应用程序窗口,程序员可在计时器消息响应函数OnTimer中作需要的处理,在本程序中是显示当前位图。
10.1.5绘制动画(响应计时器消息)
为CDukeDlg类的WM_TIMER消息添加响应函数:
voidCDukeDlg:
:
OnTimer(UINTnIDEvent){
//TODO:
在此添加消息处理程序代码和/或调用默认值
CDC*pDC=GetDlgItem(IDC_ANI)->GetDC();//获取图片框DC
CDCdc;//定义内存DC
dc.CreateCompatibleDC(pDC);//创建兼容DC
dc.SelectObject(m_pBmp[m_nCurFrame]);//选入当前帧位图
pDC->BitBlt(0,0,m_bs.bmWidth,m_bs.bmHeight,&dc,
0,0,SRCCOPY);//显示当前帧位图
m_nCurFrame++;//当前帧加一(设置下一次要显示的位图序号)
m_nCurFrame%=10;//当前帧余10(避免超出位图数组,实现循环)
CDialog:
:
OnTimer(nIDEvent);
}
结果如图10-3所示。
图10-3Duke动画
10.1.6滑块控件的使用
为了使用户能够利用鼠标快速修改每秒帧数的值,我们在对话框中添加了一个滑块控件(SliderControl)(可将其ID值设为“IDC_SLIDER_N”)。
对应的MFC类为CSliderCtrl,它是直接从CWnd派生的类:
CObject→CCmdTarget→CWnd→CSliderCtrl。
可以在对话框类中定义一个CSliderCtrl的类指针变量:
CSliderCtrl*m_pSlider;
然后在对话框类的初始化函数中获得滑块控件对象的指针,并设置其取值范围为1~100,再设置其滑块的当前位置(注意:
粗体代码必需位于SetDlgItemInt之前):
BOOLCDukeDlg:
:
OnInitDialog(){
CDialog:
:
OnInitDialog();
//TODO:
在此添加额外的初始化
……
m_pSlider=(CSliderCtrl*)GetDlgItem(IDC_SLIDER_N);
m_pSlider->SetRange(1,100);
m_pSlider->SetPos(m_nFramesPerSecond);
SetDlgItemInt(IDC_N,m_nFramesPerSecond);
……
}
为了能在用户移动滑块时,动态修改编辑控件中的值,需要为对话框类添加水平滚动消息(WM_HSCROLL)的响应函数OnHScroll:
voidCDukeDlg:
:
OnHScroll(UINTnSBCode,UINTnPos,
CScrollBar*pScrollBar){
//TODO:
在此添加消息处理程序代码和/或调用默认值
SetDlgItemInt(IDC_N,m_pSlider->GetPos());
CDialog:
:
OnHScroll(nSBCode,nPos,pScrollBar);
}
同样,为了在用户修改编辑控件中的值时,动态改变滑块的位置,还需要为对话框类添加编辑控件的EN_CHANGE消息响应函数:
voidCDukeDlg:
:
OnEnChangeN(){
……
//TODO:
在此添加控件通知处理程序代码
m_pSlider->SetPos(GetDlgItemInt(IDC_N));
}
注意,如果滑块初始化的代码位于SetDlgItemInt之后,则会造成此语句中的m_pSlider指针无效。
10.1.7CImageList类
上面的位图动画,也可以使用MFC的图像列表类CImageList来实现,代码会更简单一些。
CImageList类是CObject的直接派生类:
CObject→CImageList
1.成员函数
CImageList类的常用成员函数有:
●构造函数:
CImageList();
●创建函数:
BOOLCreate(intcx,intcy,UINTnFlags,intnInitial,intnGrow);
●添加函数:
intAdd(CBitmap*pbmImage,COLORREFcrMask);
●绘制函数:
BOOLDraw(CDC*pDC,intnImage,POINTpt,UINTnStyle);
2.例子
可在动画对话框类中添加如下代码:
//类变量(头文件)
boolm_bStarted;
intm_nCurFrame,m_nFramesPerSecond;
CSliderCtrl*m_pSlider;
CImageListimgList;
//初始化(OnInitDialog函数)
m_bStarted=false;
m_nCurFrame=0;
m_nFramesPerSecond=10;//设置初始动画速度为每秒10帧
m_pSlider=(CSliderCtrl*)GetDlgItem(IDC_SLIDER_N);
m_pSlider->SetRange(1,100);
m_pSlider->SetPos(m_nFramesPerSecond);
SetDlgItemInt(IDC_N,m_nFramesPerSecond);
BITMAPbs;
CBitmapbmp;
bmp.LoadBitmap(IDB_BITMAP1);
bmp.GetBitmap(&bs);
imgList.Create(bs.bmWidth,bs.bmHeight,ILC_COLOR8,10,0);
imgList.Add(&bmp,RGB(0,0,0));
for(inti=1;i<10;i++){
bmp.DeleteObject();
bmp.LoadBitmap(IDB_BITMAP1+i);
imgList.Add(&bmp,RGB(0,0,0));
}
bmp.DeleteObject();
//绘图(OnTimer函数)
CDC*pDC=GetDlgItem(IDC_ANI)->GetDC();
imgList.Draw(pDC,m_nCurFrame,CPoint(0,0),ILD_NORMAL);
m_nCurFrame++;m_nCurFrame%=10;
//OnBnClickedAniStartStop、OnHScroll和OnEnChangeN函数同前
10.1.8过程框图
下面分别给出使用位图数组和CImageList类来实现固定位图动画的主要过程框图。
1.使用位图数组
图10-4是使用位图数组实现固定位图动画的主要过程框图。
图10-4使用位图数组实现固定位图动画的主要步骤
2.使用图像列表
图10-5是使用CImageList类实现固定位图动画的主要过程框图。
图10-5使用CImageList类实现固定位图动画的主要步骤
10.2图形动画
在前面(参见8.5.3小节)利用鼠标进行交互绘图时,我们就已经实现了简单的图形动画——动态画直线、矩形或椭圆等,用户可通过鼠标对绘图位置坐标和图形大小进行交互式选择。
具体做法是,用灰色点线笔在同一个位置异或画两次一样的图形——第一次画图,第二次擦除。
快速不断地在不同的地方画擦,就达到了动画的效果。
即图形动画的原理就是边擦边画。
如果要画的不再是简单的线状图形,而是复杂的面状图,则异或画图方法就不再有效。
因为异或会大大改变窗口中原有图形的色彩,这是用户所不能容忍的。
可用的解决方法是,擦除(或保存)要绘图的区域,然后再绘制新图形(并恢复原区域的图形)。
具体的实现方法有两种——直接绘图和缓冲绘图。
10.2.1直接绘图
利用直接绘图方法,来产生动画的原理很简单,但是会存在讨厌的闪烁现象。
1.原理
通过不断地擦除(要绘图的区域)和绘制(新图形)动态图形而产生动画效果。
可以使用CDC类的画填充矩形的函数(使用白色刷):
voidFillRect(LPCRECTlpRect,CBrush*pBrush);
来擦除指定矩形区域。
例如:
pDC->FillRect(&rect,newCBrush(RGB(255,255,255)));
然后,再在该矩形区域内绘制新图形。
例如:
pDC->SelectObject(&pen);//选入画边框的笔
pDC->SelectObject(&brush);//选入画填充色的刷
pDC->Ellipse(&rect);//绘制填充椭圆
2.例子
下面是一个在白色背景上动态画伸缩填充椭圆的例子,需要创建一个传统单文档MFC应用程序。
主要代码片段如下:
1)在视图类中定义若干类变量:
boolshrink;//用于判断伸缩
intr,w,h,//当前椭圆的短轴半径和宽高
R,W,H,//最大椭圆的短轴半径和宽高
xc,yc;//椭圆的中心坐标
CPenpen;//绘制椭圆边框的笔(与刷同色)
CBrushbrush,whiteBrush;//绘制椭圆内部的刷和删除原椭圆的白刷
2)在视图类的构造函数中,设置初值、构造笔和刷:
shrink=true;//初始为缩小
COLORREFgreenCol=RGB(0,150,0),//定义绿色
whiteCol=RGB(255,255,255);//定义白色
pen.CreatePen(0,0,greenCol);//实心单像素宽的绿色笔
brush.CreateSolidBrush(greenCol);//实心绿色刷
whiteBrush.CreateSolidBrush(whiteCol);//实心白色刷
3)在某个菜单项的事件处理函数中计算并设置初值、启动计时器:
CRectrect;GetClientRect(&rect);//获取当前客户区矩形
W=rect.Width();H=rect.Height();
r=R=min(W,H)/2;//初始为最大椭圆
w=W/2;h=H/2;
xc=W/2;yc=H/2;
SetTimer(1,10,NULL);//可设置不同的时间间隔,或者让用户来设置
4)在计时器的消息响应函数OnTimer中,擦除并绘制椭圆,调整半径:
CDC*pDC=GetDC();
//擦除
if(shrink){//对膨胀不需要擦除
CRectrect(xc-w,yc-h,xc+w,yc+h);
pDC->FillRect(rect,&whiteBrush);
}
//调整半径
if(shrink){
w--;h--;r--;//缩小1像素
if(r==0)shrink=false;//转换成放大
}else{
w++;h++;r++;//放大1像素
if(r==R)shrink=true;//转换成缩小
}
//绘制填充椭圆
pDC->SelectObject(&pen);
pDC->SelectObject(&brush);
pDC->Ellipse(xc-w,yc-h,xc+w,yc+h);
ReleaseDC(pDC);
运行结果如图10-6所示。
图10-6伸缩填充椭圆
运行该程序后会发现,存在明显的闪烁现象,这主要是由收缩时的擦除操作所造成的。
解决办法是,采用内存DC进行缓冲绘图。
10.2.2缓冲绘图
在前面资源位图动画的绘制过程中,我们已经采用了缓冲(buffering)方法来显示位图:
CDC*pDC=GetDC();
CDCdc;
dc.CreateCompatibleDC(pDC);
dc.SelectObject(m_pBmp[m_nCurFrame]);
pDC->BitBlt(0,0,bs.bmWidth,bs.bmHeight,&d
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- windows 编程 技术 10 GDI 动画
![提示](https://static.bdocx.com/images/bang_tan.gif)