




已阅读5页,还剩17页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第10章 GDI编程3-动画动画是利用人的视觉滞留缺陷 (25ms400ms)和心理认可来动态生成系列相关画面以产生运动视觉的技术。位图动画是将预先制作好的一系列表示连续画面的位图,按一定的时间间隔一幅接一幅地连续显示,从而产生动画效果。因为绘制动画所需的图形,以及拍摄和处理图片,需要美术、摄影、数字图像处理、动画设计等知识,我们这里不讲。本书只介绍如何显示已有的位图(序列)以产生动画效果,以及如何动态绘制不同的简单图形以产生二维图形动画等。用GDI编程实现动画,一般需要用到计时器(Timer)操作,通常在计时器响应函数OnTimer中(而不要使用OnDraw)绘图来实现动画。10.1 固定位图动画本节介绍利用一系列的位图资源,在同一个屏幕位置,接连显示位图序列,以达到动画的效果的具体方法。为此,可在交互绘图程序中添加一个如图10-1所示的位图动画对话框,并添加对应的对话框类CDukeDlg。也可以创建一个基于对话框的独立的MFC应用程序。图10-1 位图动画对话框资源当然还需添加相应的“位图动画”菜单项(ID_DUKE)和(为视图类添加)对应的菜单响应函数,并在该函数中创建对话框类的对象,打开对话框来运行动画:#include DukeDlg.hvoid CDrawView:OnDuke() CDukeDlg dlg;dlg.DoModal();10.1.1 准备位图、加入位图资源系列公爵(Duke)BMP文件T1.BMP T10.BMP(见图10-2),来自Java吉祥物的GIF动画文件,可存放在项目的res子目录的Duke子目录中(该位图资源已经打包成Duke.rar文件后,放到了系里的网站和我的个人网页上)。 T1.BMP T2.BMP T3.BMP T4.BMP T5.BMP T6.BMP T7.BMP T8.BMP T9.BMP T10.BMP图10-2 Duke位图文件用VC的资源编辑器依次加入位图文件:在左边的项目工作区中选“资源视图”页,展开项目的资源列表,在“Bitmap”表项(若无此项,则可直接在项目资源项)上单击鼠标右键,在弹出的浮动菜单中选“添加资源”菜单项,在打开的“添加资源”对话框中,选中左边“资源类型”栏中的“Bitmap”表项,单击右边的“导入”按钮,在弹出的“导入”文件对话框中,定位Duke目录,选中所有Ti.BMP后按“打开”钮,则会自动加入ID为IDB_BITMAPi的位图资源。为了以后循环编程的方便,必须保证是从T1.BMP到T10.BMP顺序依次加入。为了确认,可打开头文件Resource.h查看,若其中的常量IDB_BITMAPi的定义数值不连续,可作一些手工修改使其连续,例如:#define IDB_BITMAP1 131#define IDB_BITMAP2 132#define IDB_BITMAP3 133#define IDB_BITMAP4 134#define IDB_BITMAP5 135#define IDB_BITMAP6 136#define IDB_BITMAP7 137#define IDB_BITMAP8 138#define IDB_BITMAP9 139#define IDB_BITMAP10 14010.1.2 添加控件、创建对话框类为对话框资源添加图片控件:打开对话框资源,在控件工具箱中选图片控件(Picture Control)工具,在对话框的适当位置添加图片控件,设置其“ID”属性值为“IDC_ANI”,修改“Type”属性为(在其下拉式列表框中选中)“Bitmap”,再在“Image”属性的下拉式列表框中选中“IDB_BITMAP1”位图资源,则该位图绘显示在图片控件中。为了控制动画的播放,需要添加一个即可表示开始动画又可表示停止动画的按钮(初始标题为“开始动画”),可设置其ID为“IDC_ANI_START_STOP”。为了让用户选择动画的速度,可以添加静态文本提示框“每秒帧数:”和文本编辑框(IDC_N),在后面的10.1.6小节中还会添加滑块控件(Slider Control,IDC_SLIDER_N)。创建该对话框资源所对应的对话框类CDukeDlg。10.1.3 添加类变量、装入与删除位图在对话框类的定义(头文件)中添加若干类变量:CBitmap *m_pBmp10; / 位图指针数组BITMAP m_bs; / 位图结构变量bool m_bStarted; / 判别动画是否启动(初始化为false)int m_nCurFrame, / 当前帧号(初值为0) m_nFramesPerSecond; / 每秒帧数(初值为10)为CDukeDlg类添加(重写型)消息响应函数OnInitDialog,在该函数中(也可以在构造函数中)创建位图对象并装入位图资源,然后获取位图结构(其中的位图宽和高用于BitBlt函数),并初始化其他类变量,最后设置编辑控件的初值(粗体部分为新加的):BOOL CDukeDlg:OnInitDialog()CDialog:OnInitDialog();/ TODO: 在此添加额外的初始化for (int i = 0; i LoadBitmap(IDB_BITMAP1 + i);m_pBmp0-GetBitmap(&m_bs); /获取位图结构m_bStarted = false; / 设置已开始动画为假m_nCurFrame = 0; / 设置初始的当前帧为0m_nFramesPerSecond = 10; / 设置初始动画速度为每秒10帧SetDlgItemInt(IDC_N, m_nFramesPerSecond); / 设置编辑框初值return TRUE; / return TRUE unless you set the focus to a control/ 异常: OCX 属性页应返回 FALSE其中,语句m_pBmpi-LoadBitmap(IDB_BITMAP1 + i);中的IDB_BITMAP1 + i用到了Duke位图资源ID的连续性。为了避免内存泄漏,还需要为对话框类添加析构函数,并在该函数中删除位图对象:CDukeDlg:CDukeDlg()for (int i = 0; i 10; i+) delete m_pBmpi;10.1.4 启动/停止动画(设置/删除计时器)为按钮IDC_ANI_START_STOP添加单击消息响应函数:void CDukeDlg: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 100) / 最大帧速值为100m_nFramesPerSecond = 100;/ 用调整后帧速值重新设置编辑框的内容SetDlgItemInt(IDC_N, m_nFramesPerSecond);/ 利用帧速值来设置计时器SetTimer(1, (UINT)(1000.0/m_nFramesPerSecond + 0.5), NULL);/ 设置按钮文本为“停止动画”SetDlgItemText(IDC_ANI_START_STOP, L停止动画);其中:l m_bStarted为布尔型类变量,用于判断动画是否已经开始播放。在构造函数中初始化为假,在用户按了开始/停止动画按钮后取反。l m_nCurFrame为整数型类变量,用于记录当前所要显示的位图序号,初始化为0。l m_nFramesPerSecond也为整数型类变量,用于记录当前的每秒帧数(帧速值,范围可设为1100)。l SetDlgItemText函数,用于动态修改按钮上的文本。l SetTimer用于设置计时器,它是CWnd的成员函数(所以也可在其派生的视图类和对话框类中使用),其函数原型为:UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );n nIDEvent为此计时器的编号。因为一个应用程序可以设置多个计时器,为了在响应时区分它们,必须各有一个编号。简单程序的计时器一般只有一个,所以取nIDEvent = 1即可。n nElapse为间隔时间,单位为毫秒(1/1000秒)。可用表达式(UINT) (1000.0 / m_nFramesPerSecond + 0.5),从每秒帧数计算出间隔时间的毫秒数。n lpfnTimer为应用程序提供的处理WM_TIMER消息的回调函数,一般取为NULL,这时WM_TIMER消息由CWnd派生类的对应消息响应函数来处理。l KillTimer用于删除计时器,它也是CWnd的成员函数,其函数原型为:BOOL KillTimer( int nIDEvent );在设置了计时器后,系统会按指定的时间间隔发送WM_TIMER消息给应用程序窗口,程序员可在计时器消息响应函数OnTimer中作需要的处理,在本程序中是显示当前位图。10.1.5 绘制动画(响应计时器消息)为CDukeDlg类的WM_TIMER消息添加响应函数:void CDukeDlg:OnTimer(UINT nIDEvent) / TODO: 在此添加消息处理程序代码和/或调用默认值CDC *pDC = GetDlgItem(IDC_ANI)-GetDC(); / 获取图片框DCCDC dc; / 定义内存DCdc.CreateCompatibleDC(pDC); / 创建兼容DCdc.SelectObject(m_pBmpm_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-3 Duke动画10.1.6 滑块控件的使用为了使用户能够利用鼠标快速修改每秒帧数的值,我们在对话框中添加了一个滑块控件(Slider Control)(可将其ID值设为“IDC_SLIDER_N”)。对应的MFC类为CSliderCtrl,它是直接从CWnd派生的类:CObjectCCmdTargetCWndCSliderCtrl。可以在对话框类中定义一个CSliderCtrl的类指针变量:CSliderCtrl *m_pSlider;然后在对话框类的初始化函数中获得滑块控件对象的指针,并设置其取值范围为1100,再设置其滑块的当前位置(注意:粗体代码必需位于SetDlgItemInt之前):BOOL CDukeDlg: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:void CDukeDlg:OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) / TODO: 在此添加消息处理程序代码和/或调用默认值SetDlgItemInt(IDC_N, m_pSlider-GetPos();CDialog:OnHScroll(nSBCode, nPos, pScrollBar);同样,为了在用户修改编辑控件中的值时,动态改变滑块的位置,还需要为对话框类添加编辑控件的EN_CHANGE消息响应函数:void CDukeDlg:OnEnChangeN() / TODO: 在此添加控件通知处理程序代码m_pSlider-SetPos(GetDlgItemInt(IDC_N);注意,如果滑块初始化的代码位于SetDlgItemInt之后,则会造成此语句中的m_pSlider指针无效。10.1.7 CImageList类上面的位图动画,也可以使用MFC的图像列表类CImageList来实现,代码会更简单一些。CImageList类是CObject的直接派生类:CObject CImageList1成员函数CImageList类的常用成员函数有:l 构造函数:CImageList( );l 创建函数:BOOL Create( int cx, int cy, UINT nFlags, int nInitial, int nGrow );l 添加函数:int Add( CBitmap* pbmImage, COLORREF crMask );l 绘制函数:BOOL Draw( CDC* pDC, int nImage, POINT pt, UINT nStyle );2例子可在动画对话框类中添加如下代码:/ 类变量(头文件)bool m_bStarted;int m_nCurFrame, m_nFramesPerSecond;CSliderCtrl *m_pSlider;CImageList imgList;/ 初始化(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); BITMAP bs;CBitmap bmp;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 (int i = 1; i 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是使用位图数组实现固定位图动画的主要过程框图。获取位图结构GetBitmap装入位图资源LoadBitmap创建位图对象CBitmap获取DCGetDC显示当前位图BitBlt获取控件窗口对象GetDlgItem响应计时器消息OnTimer添加位图资源Ti.BMP定义位图结构BITMAP设置计时器SetTimer定义位图对象指针数组CBitmap * 更新当前位图序号定义内存DCCDC创建兼容DCCreateCompatibleDC选入位图对象SelectObject图10-4 使用位图数组实现固定位图动画的主要步骤2使用图像列表图10-5是使用CImageList类实现固定位图动画的主要过程框图。获取位图结构GetBitmap装入位图资源LoadBitmap定义位图对象CBitmap创建图像列表Create加入位图对象Add获取DCGetDC绘制当前位图Draw获取控件窗口对象GetDlgItem响应计时器消息OnTimer添加位图资源Ti.BMP定义位图结构BITMAP定义图像列表对象CImageList设置计时器SetTimer更新当前位图序号图10-5 使用CImageList类实现固定位图动画的主要步骤10.2 图形动画在前面(参见8.5.3小节)利用鼠标进行交互绘图时,我们就已经实现了简单的图形动画动态画直线、矩形或椭圆等,用户可通过鼠标对绘图位置坐标和图形大小进行交互式选择。具体做法是,用灰色点线笔在同一个位置异或画两次一样的图形第一次画图,第二次擦除。快速不断地在不同的地方画擦,就达到了动画的效果。即图形动画的原理就是边擦边画。如果要画的不再是简单的线状图形,而是复杂的面状图,则异或画图方法就不再有效。因为异或会大大改变窗口中原有图形的色彩,这是用户所不能容忍的。可用的解决方法是,擦除(或保存)要绘图的区域,然后再绘制新图形(并恢复原区域的图形)。具体的实现方法有两种直接绘图和缓冲绘图。10.2.1 直接绘图利用直接绘图方法,来产生动画的原理很简单,但是会存在讨厌的闪烁现象。1原理通过不断地擦除(要绘图的区域)和绘制(新图形)动态图形而产生动画效果。可以使用CDC类的画填充矩形的函数(使用白色刷):void FillRect(LPCRECT lpRect, CBrush* pBrush);来擦除指定矩形区域。例如:pDC-FillRect(&rect, new CBrush(RGB(255, 255, 255);然后,再在该矩形区域内绘制新图形。例如:pDC-SelectObject(&pen); / 选入画边框的笔pDC-SelectObject(&brush); / 选入画填充色的刷pDC-Ellipse(&rect); / 绘制填充椭圆2例子下面是一个在白色背景上动态画伸缩填充椭圆的例子,需要创建一个传统单文档MFC应用程序。主要代码片段如下:1)在视图类中定义若干类变量:bool shrink; / 用于判断伸缩int r, w, h, / 当前椭圆的短轴半径和宽高 R, W, H, / 最大椭圆的短轴半径和宽高 xc, yc; / 椭圆的中心坐标CPen pen; / 绘制椭圆边框的笔(与刷同色)CBrush brush, whiteBrush; / 绘制椭圆内部的刷和删除原椭圆的白刷2)在视图类的构造函数中,设置初值、构造笔和刷:shrink = true; / 初始为缩小COLORREF greenCol = RGB(0, 150, 0), /定义绿色whiteCol = RGB(255, 255, 255); /定义白色pen.CreatePen(0, 0, greenCol ); / 实心单像素宽的绿色笔brush.CreateSolidBrush(greenCol ); / 实心绿色刷whiteBrush.CreateSolidBrush(whiteCol); / 实心白色刷3)在某个菜单项的事件处理函数中计算并设置初值、启动计时器:CRect rect; 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) / 对膨胀不需要擦除CRect rect(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();CDC dc;dc.CreateCompatibleDC(pDC);dc.SelectObject(m_pBmpm_nCurFrame);pDC-BitBlt(0, 0, bs.bmWidth, bs.bmHeight, &dc, 0, 0, SRCCOPY); 其中,起主要作用的是内存DC和CDC类的位块传送函数BitBlt,而且在该位图动画中并没有出现闪烁现象。1原理下面我们将这种方法加以扩展,不仅使其可用于已有位图的绘制,还可以用于普通图形的动态绘制。这需要先创建一个与当前视图DC兼容的空位图对象,并将其作为画布选入内存DC中,然后对该内存DC进行各种图形绘制,最后再用同样的BitBlt函数将绘图结果传送到屏幕上。具体步骤为:l 获取当前视图DC采用视图类CView基类CWnd的成员函数:CDC* GetDC( );l 创建与当前视图DC兼容的位图对象分两步进行,先用CBitmap类的缺省构造函数:CBitmap( ); 构造空位图对象,再利用成员函数:BOOL CreateCompatibleBitmap(CDC* pDC, int nWidth, int nHeight);创建指定宽高(一般为当前客户区大小)的与当前DC兼容的位图对象。l 创建与当前视图DC兼容的内存DC也分两步进行,也是先用CDC类的缺省构造函数:CDC( ); 构造空DC对象,再利用成员函数:BOOL CreateCompatibleDC(CDC* pDC);创建与当前DC兼容的DC对象。l 选位图对象入内存DC利用CDC类的成员函数:CBitmap* SelectObject(CBitmap* pBitmap);将所创建的空白位图对象选入内存DC中。l 内存DC绘图用该内存DC,调用各种CDC的绘图成员函数,在内存DC中的位图上进行绘图。包括绘制白色客户区矩形,进行白色背景设置(缺省为黑色背景)。l 绘制屏幕利用CDC类的位块传送函数:BOOL BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC,int xSrc, int ySrc, DWORD dwRop );将内存DC中,已经被绘制好图形的位图对象,传送到屏幕上。其中,与前面显示位图资源的方法最大的不同是,需要先创建一个与当前视图DC兼容的空位图对象,并将其作为画布选入内存DC中,然后才能对该内存DC进行各种图形绘制。注意,在没有选入位图对象的内存DC中直接绘图是无效的。2例子1)简单的例子:CDC memDC; / 内存DCmemDC.CreateCompatibleDC(pDC); / 创建与当前视图DC兼容的DCCRect rect; / 矩形对象,用于表示客户区GetClientRect(&rect); / 获取客户区矩形CBitmap bmp; / 位图对象,作为画布,用于创建可传送的兼容DC/ 创建大小与客户区一致并且与视图DC兼容的位图对象bmp.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height();/ 将位图选入内存DC,使其可以进行各种绘图操作,并能够传递到屏幕CBitmap *pOldBmp = memDC.SelectObject(&bmp);/ 用白色填充背景(缺省为黑色)CBrush brush(RGB(255, 255, 255);memDC.FillRect(&rect, &brush);/ 绘制各种图形memDC.Rectangle(10, 10, 200, 150);/ / 将内存DC中的图形传送到屏幕(在视图客户区绘图)pDC-BitBlt(0, 0, w, h, &memDC, 0, 0, SRCCOPY); 2)伸缩填充椭圆例现在仍然以伸缩填充椭圆为例,说明缓冲绘图产生动画的方法。程序的结构同1)中的例,下面主要介绍不同之处(可以在原程序的基础上进行修改)。(1)在视图类中增加若干类变量定义:CDC memDC; / 空内存DC对象CBitmap bmp, *pOldBmp; / 空位图对象和老位图对象指针bool created; / 是否创建了内存DC和位图对象(2)在视图类的构造函数中,设置初值:created = false; / 初始为未创建(3)在某个菜单项的事件处理函数(也可以在OnSize信息响应函数)中,对内存DC和位图对象进行初始化:CDC *pDC = GetDC(); if (!created) memDC.CreateCompatibleDC(pDC);/ created为真时,需先删除原位图对象,才能创建新位图对象if (created) memDC.SelectObject(pOldBmp);bmp.DeleteObject();bmp.CreateCompatibleBitmap(pDC, W, H); / W和H可能已经变化pOldBmp = memDC.SelectObject(&bmp);memDC.FillRect(&rect, &whiteBrush);created = true; shrink = true;xc = W / 2; yc = H /2;SetTimer(1, 10, NULL); / 可以设置不同的时间间隔(4)在计时器的消息响应函数OnTimer中,擦除并绘制椭圆,调整半径:void CAniView:OnTimer(UINT_PTR nIDEvent)/ TODO: 在此添加消息处理程序代码和/或调用默认值CDC *pDC = GetDC();/ 擦除原椭圆(用白色填充客户区)if (shrink) / 对膨胀不需要擦除/ 擦除原椭圆(用白色填充客户区)CRect rect;GetClientRect(&rect); / 获取当前客户区矩形CBrush brush(RGB(255, 255, 255);memDC.FillRect(&rect, &brush);/ 调整半径/ 绘制新椭圆memDC.SelectObject(&pen);memDC.SelectObject(&brush);memDC.Ellipse(xc - w, yc - h, xc + w, yc + h);/ 将内存DC中的图形传送到屏幕(在视图客户区绘图)pDC-BitBlt(0, 0, W, H, &memDC, 0, 0, SRCCOPY);ReleaseDC(pDC);CView:OnTimer(nIDEvent);(5)可以添加一个停止动画的菜单项(ID_KT)及其事件处理函数:void CAniView:OnKt()/ TODO: 在此添加命令处理程序代码KillTimer(1); / 删除计时器运行结果类似图10-6的,但是已经没有闪烁了。3过程框图图10-7是缓冲绘制图形动画的主要过程框图(粗体为主要步骤):获取客户区矩形GetClientRect创建兼容空位图CreateCompatibleBitmap定义位图对象CBitmap获取当前DCGetDC显示当前图形BitBlt创建兼容内存DCCreateCompatibleDC定义矩形结构RECT或CRect定义内存DC对象CDC修改图形参数响应计时器消息OnTimer设置计时器SetTimer选入位图对象SelectObject绘制白色背景FillRect绘制当前图形Ellipse等擦除原图FillRect释放DCReleaseDC图10-7 缓冲绘制图形动画的主要步骤10.3 移动位图动画前面的固定(资源)位图动画,是在屏幕的同一位置,连续显示多帧不同的图像,产生动画效果。现在我们要讨论的是,同一图像在屏幕的不同位置显示,产生移动动画的效果。这里被移动的图像,可以是资源位图或用户装入的图像、也可以是从屏幕的指定矩形区域中获取的截图、还可以是程序在内存DC中绘制图形所生成的图像。下面介绍在背景图上移动图像块以产生动画效果的具体方法。我们先讨论原理,再画出过程图,最后给出一个移动足球的具体例子。10.3.1 原理与图形动画一样,移动位图动画原理也是边擦边画。不过,为了不破坏原有的背景,即不能采用异或画图,也不能用白色覆盖来擦除。为了实现图像块在背景图上移动,非常重要的一点是,必须预先保存将会被移动图像所覆盖的背景图上的对应矩形区域,并在图像移走后再恢复该区域的图形,参见图10-8。这一功能可由含图像的内存DC和位块传送函数来完成。另外,为了避免会产生严重的闪烁现象,必须采用缓冲绘图方法。保存背景图块(供擦除时用)显示移动图块(画图)恢复原背景图块(擦除)更新图块位置(循环形成动画)图10-8 移动位图动画的基本原理还有一点需要考虑的是,被移动的图形一般不是矩形,直接绘制会产生难看的背景遮挡,例如,在9.3.3的随机绘制足球和单击定位图像块例子中(参见图9-23和图9-24),就存在难看的足球四角上的白色遮挡现象。为了将矩形块中的背景部分去掉,可以将矩形中的背景区,用一种图形中没有的颜色来统一着色,并将该色指定为透明色,再利用CDC类或CImage类的透明位块传送成员函数TransparentBlt来进行绘制操作。函数原型为:CDC类:BOOL TransparentBlt(int xDest, int yDest, int nDestWidth, int nDestHeight, CDC* pSrcDC, int xSrc, int ySrc, int nSrcWidth, int nSrcHeight, UINT clrTransparent);CImage类:BOOL TransparentBlt(HDC hDestDC, int xDest, int yDest, int nDestWidth, int nDestHeight, UINT crTransparent = CLR_INVALID) const throw( );透明位块传送函数在传送的同时,可以进行缩放。其最后一个输入参数最关键clrTransparent为指定的透明颜色(UINT类型与COLORREF所对应的DWORD类型是等价的,都是无符号的4字节整数)。例如(参见图10-9):COLORREF transpCol = RGB(255, 0, 0); / 设置透明色memDC.TransparentBlt(x0, y0, w, h, &dc, 0, 0, w, h, transpCol); 白色背景红色背景图10-9 足球图片10.3.2 过程框图图9-10是利用CBitmap类实现移动位图主要过程的逻辑框图。绘制传送恢复背景传送绘制图块传送绘制 底图传送 保存背景块传送 绘制背景选入 画布与当前DC兼容的客户区内存DC对象CDC memDC与当前DC兼容、大小同客户区的位图对象装入了图片文件的图像对象选入 画布窗口客户区与当前DC兼容的背景块内存DC对象CDC memDC与当前DC兼容、大小同移动图块的位图对象选入 图块与当前DC兼容的图块内存DC对象CDC memDC装入了位图资源的位图对象装足球图块对应客户区的内存DC图10-10 移动位图的主要步骤10.3.3 例子下面是一个单击和拖动鼠标来在一背景图上移动足球的例子:1创建项目、添加资源创建一个传统的单文档MFC应用程序,加入红色背景的足球资源(IDB_FOOTBALL_RED)。2添加类变量在视图类中定义若干类变量:/ #include atlimage.hint W, H, w, h, x0, y0; / W 和H为客户区的宽高、w和h为图像块的 / 宽高、x0和y0为图像块上次位置的坐标COLORREF transpCol; / 透明色CDC memDC, dc, dc0; / memDC:客户区、dc:图像块、dc0:背景块CBitmap memBmp, bmp, bmp0, *pOldBmp; / memBmp用于客户区、/ bmp用于图像块、bmp0用于背景块、pOldBmp用于选出位图CImage imgbk; / 用于底图3初始化在初始化函数OnInitialUpdate中装入图像、计算参数、创建图像和内存DC对象:/ 装入图像-transpCol = RGB(255, 0, 0); / 设置透明色/ 装入res目录中的底图(在IDE中运行时,当前目录为项目所在目录)imgbk.Load(Lrestulips.tif); / 如果直接在Release或Debug目录中运行,则需要修改文件路径为:if(imgbk.IsNull() imgbk.Load(L.restulips.tif);/ 如果图片文件位于可执行程序所在目录,则可修改文件路径为: if(imgbk.IsNull() imgbk.Load(Ltulips.tif);bmp.LoadBitmap(IDB_FOOTBALL_RED); / 装入图像块资源(红色背景足球)/ 创建图像块对应的内存DC,并选入图像块-CDC *pDC = GetDC(); / 获取视图DCdc.CreateCompatibleDC(pDC);pOldBmp = dc.SelectObject(&bmp);/ 获取图像块的宽高-BITMAP bm;bmp.GetBitmap(&bm);w = bm.bmWidth;h = bm.bmHeight;/ 创建背景块位图和内存DC,选入图像-bmp0.CreateCompatibleBitmap(pDC, w, h);dc0.CreateCompatibleDC(pDC);pOldBmp = dc0.SelectObject(&bmp0);/ 获取客户区大小-CRect rect;GetClientRect(&rect);W = rect.Width(); H = rect.Height();/ 创建与客户区对应的图像和内存DC对象,选入图像,绘制白色背景-/ 创建与视图DC兼容并与客户区大小一致的位图对象memBmp.CreateCompatibleBitmap(pDC, W, H);memDC.CreateCompatibleDC(pDC); / 创建与视图DC兼容的内存DC对象pOldBmp = memDC.SelectObject(&memBmp); / 选位图对象入内存DC/ 填充白背景色(缺省为黑色)/memDC.FillRect(&rect, new CBrush(RGB(255, 255, 255);/ 计算位于客户区中央的图像块的左上角坐标-x0 = W / 2 - w / 2;y0 = H / 2 - h / 2;4绘制底图和初始图块在OnDraw函数中,先绘制底图,再在客户区中央绘制首个图像块:RECT rect; GetClientRect(&rect); / 获取当前客户区矩形if(!imgbk.IsNull()/ 绘制底图imgbk.Draw(pDC-m_hDC, rect); / 利用位块传送将背景块复制到内存DCdc0.BitBlt(0, 0, w, h, pDC, x0, y0, SRCCOPY);/ 将客户区复制到与客户区对应的内存DCmemDC.BitBlt(0, 0, W, H, pDC, 0, 0, SRCCOPY);/ 绘制图像块到与客户区对应的内存DCmemDC.TransparentBlt(x0, y0, w, h, &dc, 0, 0, w, h, transpCol);/ 将与客户区对应的内存DC中的图像,传送到屏幕上pDC-BitBlt(0, 0, W, H, &memDC, 0,
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 地铁站保洁服务协调措施
- 2025版石膏基吸音材料销售合同范本
- 2025年餐饮行业特色小吃租赁管理服务合同
- 2025版预制装配式建筑瓦工劳务合作合同
- 2025年导购员客户关系管理与客户满意度提升合同
- 2025年度绿色环保办公家具采购合同
- 饲料行业知识培训课件
- 松树出售砍伐合同协议书
- 检测外包服务协议合同书
- 2025年合同履行延期协议书
- 安徽省皖江名校2024-2025学年高一上学期12月联考英语试题(含答案无听力原文及音频)
- 汽车维修业务接待
- 洒水降尘合同范例
- 吊装作业安全会议
- 脑健康中心建设指南(2024年版)
- 2024-2025学年八年级上册历史期末复习选择题(解题指导+专项练习)原卷版
- 高考小说阅读导练:蒙太奇、意识流、冰山理论专项(理论指导+强化训练+参考答案)
- 慢性化脓性中耳炎护理查房
- 园林局城市绿化养护手册
- 法社会学教程(第三版)教学
- 人工智能对会计信息披露的挑战与机遇
评论
0/150
提交评论