03绘图3动画与图标.docx
- 文档编号:23148324
- 上传时间:2023-05-08
- 格式:DOCX
- 页数:40
- 大小:1.14MB
03绘图3动画与图标.docx
《03绘图3动画与图标.docx》由会员分享,可在线阅读,更多相关《03绘图3动画与图标.docx(40页珍藏版)》请在冰豆网上搜索。
03绘图3动画与图标
3.9动画
动画是利用人的视觉滞留原理(25ms~400ms)动态生成系列相关画面以产生运动视觉的技术。
所谓位图动画就是先制作好一系列表示连续画面的位图,然后按一定的时间间隔一幅接一幅地显示这些位图,就可以产生动画效果。
因为制作位图需要美术与动画知识,我们这里不讲,而只介绍如何显示位图以产生动画效果。
里面需要用到计时器(Timer)操作。
注意,不要使用OnDraw来实现动画,应该直接在计时器响应函数OnTimer中绘图。
在视图窗口每次被重画时,系统都会先用背景色(白色)清除窗口(在对话框中用灰色清除)。
可以为视图类添加WM_ERASEBKGRD消息的响应函数OnEraseBkgnd,并返回TRUE,让系统重画时,不清除窗口内容。
例如:
BOOLC*View:
:
OnEraseBkgnd(CDC*pDC){
returnTRUE;
//returnCView:
:
OnEraseBkgnd(pDC);
}
1.固定位图动画
下面我们介绍,利用一系列的位图资源,在同一个屏幕位置,连续显示位图,以达到动画的效果的具体方法。
为了讲解位图动画的编程细节,我们可以在绘图程序中添加一个如下图所示的位图动画对话框:
并添加对应的对话框类CDukeDlg。
当然还必须添加相应的“位图动画”菜单项(ID_ANI)和(为视图类添加)菜单响应函数,并在该函数中创建对话框类的对象,打开对话框来运行动画:
voidCMFCDrawView:
:
OnAni(){
CDukeDlgdlg;
dlg.DoModal();
}
1)准备位图
我将原来用于Java动画的系列公爵(Duke)GIF文件用Windows的画图软件转化成BMP文件T1.BMP~T10.BMP(见下图),存放在项目的Duke子目录中。
(该位图资源已经打包成Duke.rar文件后,放到了系里的网站上)
2)加入位图资源
用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
3)装入与删除位图
在对话框类的定义中添加位图指针数组和BITMAP结构变量:
CBitmap*m_pBmp[10];
BITMAPbs;
为CDukeDlg添加(重写型)消息响应函数OnInitDialog,在该函数中(也可以在构造函数中)创建位图对象并装入位图资源,然后获取位图结构(其中的位图宽和高用于BitBlt函数):
BOOLCDukeDlg:
:
OnInitDialog(){
CDialog:
:
OnInitDialog();
......
for(inti=0;i<10;i++){
m_pBmp[i]=newCBitmap;
m_pBmp[i]->LoadBitmap(IDB_BITMAP1+i);
}
m_pBmp[0]->GetBitmap(&bs);
returnTRUE;//returnTRUEunlessyousetthefocustoacontrol
}
其中,m_pBmp[i]->LoadBitmap(IDB_BITMAP1+i);中的IDB_BITMAP1+i利用了位图资源ID的连续性。
为了避免内存泄漏,需要为对话框类添加析构函数,并在该函数中删除位图对象:
CMTestDlg:
:
~CMTestDlg(){
for(inti=0;i<10;i++)deletem_pBmp[i];
}
4)设置位图控件与启动/停止按钮
在对话框界面的应用程序中添加图片控件:
打开对话框资源,在控件工具箱中选图片控件(PictureControl)工具,在对话框的适当位置添加图片控件,设置其“ID”属性值为“IDC_ANI”,修改“Type”属性为(在其下拉式列表框中选中)“Bitmap”,再在“Image”属性的下拉式列表框中选中“IDB_BITMAP1”位图资源,则该位图绘显示在图片控件中。
为了控制动画的播放,需要添加一个“启动/停止动画”按钮,可设置其ID为“IDC_ANI_STARTSTOP”。
5)启动/停止动画(设置/删除计时器)
为该按钮IDC_ANI_STARTSTOP添加单击消息响应函数:
voidCDukeDlg:
:
OnBnClickedAniStartstop(){
if(m_bStarted){
KillTimer
(1);
m_bStarted=FALSE;
SetDlgItemText(IDC_ANI_STARTSTOP,L"开始动画");
}
else{
m_bStarted=TRUE;
m_nCurFrame=0;
m_nTimesPerSecond=GetDlgItemInt(IDC_N);
if(m_nTimesPerSecond<=0)m_nTimesPerSecond=1;
elseif(m_nTimesPerSecond>100)m_nTimesPerSecond=100;
SetDlgItemInt(IDC_N,m_nTimesPerSecond);
SetTimer(1,(UINT)(1000.0/m_nTimesPerSecond+0.5),NULL);
SetDlgItemText(IDC_ANI_STARTSTOP,L"停止动画");
}
}
其中,
●m_bStarted为布尔型类变量,用于判断动画是否已经开始播放,在构造函数中初始化为FALSE
●m_nCurFrame为整数型类变量,用于记录当前所要显示的位图序号,初始化为0
●m_nTimesPerSecond也为整数型类变量,用于记录当前的每秒帧数(范围可设为1~100)
●调用SetDlgItemText函数,是为了动态修改按钮上的文本
●SetTimer用于设置计时器,它是CWnd的成员函数(所以也可在其派生的视图类和对话框类中使用),其函数原型为:
UINTSetTimer(UINTnIDEvent,UINTnElapse,
void(CALLBACKEXPORT*lpfnTimer)(HWND,UINT,UINT,DWORD));
⏹nIDEvent为此计时器的编号。
因为一个应用程序可以设置多个计时器,为了在响应时区分它们,必须各有一个编号。
简单程序的计时器一般只有一个,所以取nIDEvent=1即可
⏹nElapse为间隔时间,单位为毫秒(1/1000秒)。
如5~30帧/秒对应于200~33毫秒
⏹lpfnTimer为应用程序提供的处理WM_TIMER消息的回调函数,一般为NULL,这时WM_TIMER消息由CWnd派生类的对应消息响应函数来处理
●KillTimer用于删除计时器,它也是CWnd的成员函数,其函数原型为:
BOOLKillTimer(intnIDEvent);
在设置了计时器后,系统会按指定的时间间隔发送WM_TIMER消息给应用程序,应用程序可在消息响应函数中作需要的处理,在我们的程序中是依次显示位图。
6)绘制动画(响应计时器消息)
为CDukeDlg的WM_TIMER消息添加响应函数:
voidCDukeDlg:
:
OnTimer(UINTnIDEvent){
CDC*pDC=GetDlgItem(IDC_ANI)->GetDC();
CDCdc;
dc.CreateCompatibleDC(pDC);
dc.SelectObject(m_Bmp[m_nCurFrame]);
pDC->BitBlt(0,0,bs.bmWidth,bs.bmHeight,&dc,0,0,SRCCOPY);
m_nCurFrame++;
m_nCurFrame%=10;
CDialog:
:
OnTimer(nIDEvent);
}
其中,
●CDC*pDC=GetDlgItem(IDC_ANI)->GetDC();获得图片控件的DC
●dc.SelectObject(m_Bmp[m_nCurFrame]);选入当前序号的位图
●m_nCurFrame++;设置下一个要显示的位图序号
●m_nCurFrame%=10;循环显示,可防止序号超过位图数组的范围
其余语句参见前面的位图显示部分。
7)滑块控件的使用
为了使用户能够利用鼠标快速修改每秒帧数的值,我们在对话框中添加了一个滑块控件(SliderControl)(可将其ID值设为“IDC_SLIDER_N”)。
对应的MFC类为CSliderCtrl,它是直接从CWnd派生的类:
CObject→CCmdTarget→CWnd→CSliderCtrl。
可以在对话框类中定义一个CSliderCtrl的指针变量:
CSliderCtrl*pSlider;
然后在对话框类的初始化函数中获得滑块控件对象的指针,并设置其取值范围为1~100,再设置其滑块的当前位置:
BOOLCDukeDlg:
:
OnInitDialog(){
CDialog:
:
OnInitDialog();
//TODO:
在此添加额外的初始化
pSlider=(CSliderCtrl*)GetDlgItem(IDC_SLIDER_N);
pSlider->SetRange(1,100);
pSlider->SetPos(m_nTimesPerSecond);
SetDlgItemInt(IDC_N,m_nTimesPerSecond);
……
}
其中,SetDlgItemInt(IDC_N,m_nTimesPerSecond);语句,是为编辑控件设置初值m_nTimesPerSecond(可取为10)。
为了能在用户移动滑块时,动态修改编辑控件中的值,需要为对话框类添加水平滚动的消息响应函数OnHScroll:
voidCDukeDlg:
:
OnHScroll(UINTnSBCode,UINTnPos,CScrollBar*pScrollBar){
SetDlgItemInt(IDC_N,pSlider->GetPos());
CDialog:
:
OnHScroll(nSBCode,nPos,pScrollBar);
}
同样,为了在用户修改编辑控件中的值时,动态改变滑块的位置,还需要为对话框类添加编辑控件的EN_CHANGE消息响应函数:
voidCDukeDlg:
:
OnEnChangeN(){
pSlider->SetPos(GetDlgItemInt(IDC_N));
}
8)CImageList类
上面的位图动画,也可以使用MFC的CImageList类:
CObject→CImageList
(1)成员函数
CImageList类的常用成员函数有:
●构造函数:
CImageList();
●创建函数:
BOOLCreate(intcx,intcy,UINTnFlags,intnInitial,intnGrow);
BOOLCreate(UINTnBitmapID,intcx,intnGrow,COLORREFcrMask);
BOOLCreate(LPCTSTRlpszBitmapID,intcx,intnGrow,COLORREFcrMask);
BOOLCreate(CImageList&imagelist1,intnImage1,CImageList&imagelist2,intnImage2,intdx,intdy);
BOOLCreate(CImageList*pImageList);
●添加函数:
intAdd(CBitmap*pbmImage,CBitmap*pbmMask);
intAdd(CBitmap*pbmImage,COLORREFcrMask);
intAdd(HICONhIcon);
●绘制函数:
BOOLDraw(CDC*pDC,intnImage,POINTpt,UINTnStyle);
BOOLDrawEx(CDC*pDC,intnImage,POINTpt,SIZEsz,COLORREFclrBk,COLORREFclrFg,UINTnStyle);
(2)例子
//类变量
intm_nCurFrame;
CImageListm_mlImgList;
//初始化
m_nCurFrame=0;
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();
//绘图
CDC*pDC=GetDlgItem(IDC_ANI)->GetDC();
mlImgList.Draw(pDC,m_nCurFrame,CPoint(0,0),ILD_NORMAL);
m_nCurFrame++;m_nCurFrame%=10;
2.图形动画
前面在利用鼠标进行交互绘图时,我们就已经实现的简单的图形动画——动态画直线、矩形或椭圆等,实现用户对绘图坐标和大小的选择。
具体做法是,用灰色笔在同一个位置异或画两次一样的图形——第一次画图,第二次擦除。
快速不断地在不同的地方画擦,就达到了动画的效果。
如果要画的不是简单的线状图形,而是复杂的面状图,则异或画图方法不再有效。
因为异或会大大改变原图形的色彩,这是用户所不能容忍的。
可以使用的方法是,擦除(或保存)要绘图的区域,然后再绘制新图形(并恢复原区域的图形)。
具体的实现方法有两种——直接绘图和缓冲绘图。
1)直接绘图
利用直接绘图方法,来产生动画的原理很简单,但是会存在讨厌的闪烁现象。
(1)原理
直接绘图产生动画的原理很简单,就是不断地擦除(要绘图的区域)和绘制(新图形)。
可以使用MFC的CDC类的画填充矩形的函数(使用白色刷):
voidFillRect(LPCRECTlpRect,CBrush*pBrush);
来擦除指定矩形区域。
例如:
pDC->FillRect(&rect,newCBrush(RGB(255,255,255)));
然后,再在该矩形区域内绘制新图形。
例如:
pDC->SelectObject(&pen);//选入画边框的笔
pDC->SelectObject(&brush);//选入画填充色的刷
pDC->Ellipse(&rect);//绘制填充椭圆
(2)例子
下面是一个在白色背景上动态画伸缩填充椭圆的例子,需要创建一个基于SDI的MFC应用程序。
主要代码片段如下:
//在视图类中定义若干类变量
boolshrink;//用于判断伸缩
intr,R,w,h,W,H,xc,yc;
CPenpen,whitePen;
CBrushbrush,whiteBrush;
//在视图类的构造函数中,设置初值、构造笔和刷
shrink=true;
COLORREFgreenCol=RGB(0,150,0),whiteCol=RGB(255,255,255);
pen.CreatePen(0,0,greenCol);//实心单像素宽的绿色笔
whitePen.CreatePen(0,0,whiteCol);//实心单像素宽的白色笔
brush.CreateSolidBrush(greenCol);//实心绿色刷
whiteBrush.CreateSolidBrush(whiteCol);//实心白色刷
//在视图类的初始化函数OnInitialUpdate中计算初值,启动计时器
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,100,NULL);//可以设置不同的时间间隔,或者让用户用对话框来设置
//在计时器的消息响应函数OnTimer中,擦除并绘制椭圆,调整半径
CDC*pDC=GetDC();
//擦除
if(shrink){//对膨胀不需要擦除
pDC->FillRect(newCRect(xc-w,yc-h,xc+w,yc+h),&whiteBrush);
/*pDC->SelectObject(&whitePen);//等价擦除
pDC->SelectObject(&whiteBrush);
pDC->Ellipse(xc-w,yc-h,xc+w,yc+h);*/
}
//调整半径
if(shrink){
w--;h--;r--;
if(r==0)shrink=false;
}else{
w++;h++;r++;
if(r==R)shrink=true;
}
//绘制填充椭圆
pDC->SelectObject(&pen);
pDC->SelectObject(&brush);
pDC->Ellipse(xc-w,yc-h,xc+w,yc+h);
ReleaseDC(pDC);
运行结果如:
伸缩填充椭圆
运行该程序后会发现,存在明显的闪烁现象,这主要是由收缩时的擦除操作所造成的。
解决办法是,采用内存DC进行缓冲绘图。
2)缓冲绘图
在前面资源位图动画的绘制过程中,我们已经采用了缓冲方法来显示位图:
CDC*pDC=GetDC();
CDCdc;//内存DC
dc.CreateCompatibleDC(pDC);
dc.SelectObject(m_Bmp[m_nCurFrame]);
pDC->BitBlt(0,0,bs.bmWidth,bs.bmHeight,&dc,0,0,SRCCOPY);//显示位图
其中,起主要作用的是内存DC和CDC类的位块传送函数:
BOOLBitBlt(intx,inty,intnWidth,intnHeight,CDC*pSrcDC,
intxSrc,intySrc,DWORDdwRop);
而且在该位图动画中并没有闪烁现象。
(1)原理
下面我们将这种方法加以扩展,不仅使其可用于已有位图的绘制,还可以用于普通图形的动态绘制。
这需要先创建一个与当前视图DC兼容的空位图对象,并将其选入内存DC中,然后对该内存DC进行各种图形绘制,最后再用同样的BitBlt函数将绘图结果传送到屏幕上。
具体步骤为:
●获取当前视图DC——采用视图类CView基类CWnd的成员函数:
CDC*GetDC();
●创建与当前视图DC兼容的位图对象——分两步进行,先用CBitmap类的缺省构造函数:
CBitmap();
构造空位图对象,再利用成员函数:
BOOLCreateCompatibleBitmap(CDC*pDC,intnWidth,intnHeight);
创建指定宽高(一般为当前客户区大小)的与当前DC兼容的位图对象;
●创建与当前视图DC兼容的内存DC——也分两步进行,也是先用CDC类的缺省构造函数:
CDC();
构造空DC对象,再利用成员函数:
BOOLCreateCompatibleDC(CDC*pDC);
创建与当前DC兼容的DC对象;
●选位图对象入内存DC——利用CDC类的成员函数:
CBitmap*SelectObject(CBitmap*pBitmap);
将所创建的空白位图对象选入内存DC中;
●内存DC绘图——用该内存DC,调用各种CDC的绘图成员函数,在内存DC中的位图上进行绘图。
包括绘制白色客户区矩形,进行白色背景设置(缺省为黑色背景);
●绘制屏幕——利用CDC类的位块传送函数:
BOOLBitBlt(intx,inty,intnWidth,intnHeight,CDC*pSrcDC,
intxSrc,intySrc,DWORDdwRop);
将内存DC中,已经被绘制好图形的位图对象,传送到屏幕上。
例如:
CDCmemDC;//内存DC
memDC.CreateCompatibleDC(pDC);//创建与当前视图DC兼容的DC
CRectrect;//矩形对象,用于表示客户区
GetClientRect(&rect);//获取客户区矩形
intw=rect.Width(),h=rect.Height();//定义表示客户区宽高的变量
CBitmapbmp;//位图对象,用于创建可传送的兼容DC
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 03 绘图 动画 图标