软件开发技术基础 顾 刚 计算机教学实验中心
第6章 多媒体编程技术 讲课4学时 实验2学时
多媒体定义 (Multimedia)就是文字、图形、图像、声音、动画、视频等多种媒体信息的集合
多媒体技术 指利用计算机技术将各种媒体以数字化的方式集成在一起,并能够对它们进行加工处理的技术。
多媒体编程技术 指在WINDOWS环境下,编写程序对多媒体信息进行播放和处理的技术。
数字声音编程 1.声音的特性 2.声音信号的数字化 3.数字音频的处理
1.声音的特性 声音:大脑对声波的感知就是声音(Sound) 幅度(Amplitude):指声音的大小、强弱程度 声波:当物体在空气中振动时,便会产生连续的波 声音:大脑对声波的感知就是声音(Sound) 幅度(Amplitude):指声音的大小、强弱程度 频率(Frequency):指信号每秒钟变化的次数
1.声音的特性 声波可以看成在时间上和幅度上都是连续变化的模拟信号 幅度a 时间t
1.声音的特性 带宽 声音信号的频率范围称为带宽 高保真声音的频率范围为10~20000Hz,它的带宽约为20kHz 高于20kHz的信号称为超音频信号(Supersonic),或称超声波(Ultrasonic)信号 频率范围为20Hz~20kHz的信号称为音频信号 在多媒体技术中,处理的声音信号主要是音频信号,它包括音乐、话音、风声、雨声、鸟叫声、机器声等.
2.声音信号数字化 采样 量化 编码 模拟信号:指在时间和幅度上都连续的信号 声 音 的 模 拟 信 号 声 音 的 数 字 信 号 数字信号:把时间和幅度都用离散的数字表示的信号
采样(sampling) 采样 在某些特定的时刻对这种模拟信号进行 测量叫做采样(Sampling) 得到的信号称为离散时间信号 如何采样使声音信号不失真?
量化(measuring) 量化 将信号幅度取值的数目加以限定,由有限个 数值来表示信号。或者对幅值加以进似的过程 量化 将信号幅度取值的数目加以限定,由有限个 数值来表示信号。或者对幅值加以进似的过程 得到的信号就称为离散幅度信号
举例说明 例:假设输入电压的范围是0.0~0.7V,而它的取值仅限定在0,0.l,0.2,…,0.7V共8个值。如果采样得到的幅度值是0.123V,则近似取值为0.1V,如果采样得到的幅度值是0.271V,它的取值就近似为0.3V。
数字信号 声音信息的数字化实际上就是采样和量化 模拟信号 数字信号
采样频率 采样频率:指单位时间内的采样次数 ◆根据奈奎斯特理论(Nyqust theory):如果采样频率不低于信号最高频率的两倍,就能把以数字表达的声音还原成原来的声音 ◆对于话音信号,最高频率为3400Hz,采样频率为8000Hz,能以数字声音还原原来的声音 ◆对于一般音频信号,最高频率为20kHz,采样频率为40kHz以上时,就能无失真地还原出原来的声音 ◆采样频率越高,得到的数据占用的存储空间越大
采样精度 采样精度:指存放采样点幅值的二进制位数 ◆采样精度(Sampling precision),也叫样本位数或位深度,用每个声音样本的位数(bit或b)表示 ◆位数越少,声音的质量越低,需要的存储空间越少 ◆采样频率和采样精度是数字声音质量的两项重要指标 ◆声道数是数字声音的第三项指标
声音的采样和量化
数据率(b/s)=采样频率(Hz)×样本精度(bit)×声道数 ◆数据率指还原1秒钟的声音所需传输的数据位数,也是数字化1秒钟的声音的所需传输的数据位数。未经压缩的数字声音的数据率为 数据率(b/s)=采样频率(Hz)×样本精度(bit)×声道数 ◆由于播放设备、传输线路和网络传输速度的限制,需要对声音数据进行压缩。压缩后的数据率变小,但质量也会降低
8(bit)×8kHz×2(声道数)×60(秒)/8(bit/Byte)=960kB 计算声音文件的数据量 1分钟的双声道声音文件的数据量为: 8(bit)×8kHz×2(声道数)×60(秒)/8(bit/Byte)=960kB
采样的3个常用频率分别: 11.025kHz对应AM广播声音 模拟声音信号的波形成 采样得到的离散时间信号 再量化得到的数字信号 模拟声音信号的波形成 采样得到的离散时间信号 再量化得到的数字信号 采样的3个常用频率分别: 11.025kHz对应AM广播声音 22.05kHz对应FM广播声音 44.1kHz对应CD高保真声音
不同质量的声音的性能指标 质量 采样频率 (kHz) 样本精度 (b/s) 声道 数据率 (kb/s) 频率范围 (Hz) 电话 8 单声道 64.0 200~3 400 AM 11.025 88.2 50~ 7 000 FM 22.050 16 立体声 705.6 20~15 000 CD 44.1 1411.2 20~20 000 DAT 48 1536.0
编码(coding) 将采集到的物理量转换为在计算机中表示的代码的过程称为编码 ◆数据编码举例: 设量化值为20、30、40、......、170(间隔10),有16种不同的数据,相当于16种符号,在计算机中可以用4位二进制数表示,如用0000表示20,0001表示30,0010表示40,......,1111表示170 ◆编码方法:自然码编码、哈夫曼编码、算术编码 ◆非压缩编码、压缩编码
声音文件的存储格式 ◆文件格式是数据在存储器中的存放形式。相同的数据,可以有不同的存放形式,所以也就有多种文件格式。 ◆WAV是Windows采用的波形声音文件存储格式,主要用于自然声的保存与回放,其特点是声音层次丰富,还原性好,表现力强。如果使用足够高的采样频率和采样精度,可以获得极好的音质,但文件的数据量比较大。该格式的文件可以被几乎所有的多媒体软件使用,易于编辑。
文件格式 ◆MP3是MPEG(Moving Picture Expert Group运动图像专家组)第三层压缩文件格式,提供8kb/s到128kb/s的数据率和高达96:1的压缩比,有较好的音质。主要应用于因特网上的声音传输。常见的MP3音乐就使用了该压缩标准,是MP3播放器的主要格式 ◆RM、RA是RealNetworks公司制定的声音文件格式,有较高的压缩比,可以采用流媒体的方式在网络上实时播放,主要使用RealNetworks公司的播放器播放
使用MCI播放音频 Windows MCI(Media Control Interface)是Windows提供的控制多媒体设备的高层、通用的命令接口。它提供一组与设备无关的函数和命令,可有效地控制多媒体设备。 Windows采用两种MCI接口与MCI设备通信: 一是使用命令消息接口函数,直接控制MCI设备; 二是使用命令字符串接口函数,基于文本接口或命令脚本来控制MCI设备。
命令字符串接口函数说明 MCIERROR mciSendString(LPCTSTR lpszCommand, LPTSTR lpszReturnString, UINT cchReturn, HANDLE hwndCallback) 各参数含义如下: ◈lpszCommand 指向MCI命令字符串 //打开01.wave波形文件命令字符串 open c:\\music\\01.wave //播放mysound设备,从位置1000到2000命令字符串 play mysound from 1000 to 2000 notify ◈ lpszReturnString指向接受返回信息的缓冲区 ◈ cchReturn缓冲区大小 ◈ hwndCallback回调窗口句柄
使用命令字符串接口函数播放声音文件 编译连接运行,注意程序永久运行。 #include <windows.h> #include <iostream> #include <mmsystem.h> using namespace std; #pragma comment(lib, "winmm") void main() { char szReturn[256]; MCIERROR errorCode=mciSendString("play d:\\周冰倩_真的好想你.mp3", szReturn,sizeof(szReturn),NULL);\\"play d:\\邓丽君_甜蜜蜜_wav.wav" int i = 0; for(;;) cout<<"Now playing\t"<<i++<<"\tsecond\r"; Sleep(1000); } 编译连接运行,注意程序永久运行。
MCI命令消息接口函数说明 MCIERROR mciSendCommand(MCIDEVICEID IDDevice, UINT uMsg, DWORD fdwCommand, DWORD_PTR dwParam); 参数含义如下: IDDevice 接受命令消息的MCI设备的ID号。该参数不和MCI_OPEN命令消息一起使用。 uMsg 命令消息。 fdwCommand 命令消息的标志位。 dwParam 指向包含命令消息参数结构的指针。 书270页给出了MCI_OPEN和MCI_PLAY的结构体定义
使用命令消息接口函数播放wav文件 #include <windows.h> #include <iostream> #include <mmsystem.h> #pragma comment(lib, "winmm") using namespace std; int main() { MCI_OPEN_PARMS openParms; openParms.lpstrDeviceType=“waveaudio”; \\只能播放WAV格式声音文件 openParms.lpstrElementName="d:\\邓丽君_甜蜜蜜_wav.wav"; MCIERROR errCode=mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_ELEMENT | MCI_OPEN_TYPE,(DWORD)(LPMCI_OPEN_PARMS) &openParms); if(!errCode) { MCI_PLAY_PARMS playParms; MCIERROR errCode2=mciSendCommand(openParms.wDeviceID, MCI_PLAY,NULL,(DWORD)(LPMCI_OPEN_PARMS) &playParms); } else return 1; //出错返回 int i = 0; for(;;) { cout<<"Now playing\t"<<i++<<"\tsecond\r"; Sleep(1000); return 0;
数字声音文件数据处理说明 通过修改WAV格式文件可以实现音频的音效处理 淡入、淡出、滤波、删除等
矢量图和位图 矢量图主要用于工程图、白描图、卡通漫画等,这些图形可以分解为单个的线条、文字、圆、矩形、多边形等单个的图形元素。再用一个代数式来表达每个被分解出来的元素。 位图图像,亦称为点阵图像或绘制图像,是由称作像素的单个点组成的。这些点可以进行不同的排列和染色以构成图样。当放大位图时,可以看见赖以构成整个图像的无数单个方块。
数字图像编程 1.人类视觉对图像的感知 2.图像的数字化和数字图像 3.图像的颜色模型 4.数字图像文件格式 5.数字图像处理
人类视觉对图像的感知 图像是二维平面上的模拟信号 自然界多姿多彩的景物通过人们的视觉 器官在大脑中留下的印象,这就是图像 空间坐标位置和景物的明暗程度 均是连续变化的,称为连续图像 图像是二维平面上的模拟信号
2.图像的数字化和数字图像 将连续的模拟图像转换为离散的数字图像 图像数字化的步骤与声音数字化步骤相似 采样 量化 编码 整量 将真实图像转换成计算机能表示的形式
采样和整量的含义 图像的采样(Image Sampling): ◆将空间连续坐标(x,y)离散化,称为图像的采样 ◆即将平面区域的分割成若干较小的区域,以便测量小区域的值 图像的整量(Density Measuring) ◆将幅值f(x,y)离散化, 称为图像的整量,或灰度级整量 ◆即将测量的图像色彩浓淡的连续值用离散值进似表示 图像的数字化 ◆对图像进行采样和灰度级的整量称为图像的数字化。
数字化图示 空间上和灰度级上都离散的图像称为数字图像 图像 采样 量化 数字图像
采样和整量的方法 如果对连续图像在x、y方向上均取N个等间隔来采样,就将图像划分成N×N个小方块图像,当N很大时,小方块就可以近似看成小圆点;若对每一点的灰度(或色彩)值也用等间隔整数值来表示,即进行整量(量化),那么就可以得到一个N×N的数组(即N × N个数据值)。 数组中的每一个数据都是数字图像的一个元素,称为图像元素,简称像素(Pixel)或者像元。
数字图像的性能指标 图像分辨率 扫描分辨率 像素深度 显示分辨率
图像分辨率 图像采样的点数(或像元数),称为图像分辨率 用 “行数×列数”表示 如数码像机常用的图像分辨率为640×480、1024×786、1128×764等。 对相同尺幅的图像,如果组成该图的像素数目越多,则说明图像的分辨率越高,看起来就越逼真。相反,图像显得越粗糙。 图像分辨率越高,图像文件占用的存储空间越大。
扫描分辨率(Scanning resolution) 用每英寸多少像素点(dots per inch,DPI)表示。 如果用300DPI来扫描一幅8“×10”的图像,就得到一幅2400×3000个像素的数字图像。 扫描分辨率越高,像素就越多,获得的图像越细腻。 扫描分辨率是采样时,单位尺寸内采样的点数,而图像分辨率是组成数字图像的像素数。
像素深度(Pixel Depth) 数字图像中表示每个像素的颜色使用的二进制位数称为像素深度或位深度。 像素深度值越大,图像能表示颜色数越多,色彩越丰富逼真,占用的存储空间越大。 常见的像素深度有1位、4位、8位和24位 分别用来表示: 黑白图像、 16色或16级灰度图像 256色或256级灰度图像 真彩色(224—16 777 216种颜色)图像
图像的显示分辨率 数字图像的视觉效果与图像输出设备有关,图像在屏幕上的显示尺幅称为图像的显示分辨率 分辨率低的图像可以以高的分辨率显示,分辨率高的图像也可以以低的分辨率显示,但只要不是以图像的正常分辨率显示图像,都会引起图像的失真。 使用图像时应按需要设置图像的分辨率和像素深度
图像文件的大小估算 图像的分辨率和像素位深度决定了图像文件的大小 例如:一幅640×480的图像,位深度是24位(真彩色图像),则文件大小为: 24(bit)/8(bit/Byte)×640(列)×480(行) =921600B(Bytes)约900kB
图像按颜色分类 图像按颜色又分为 灰度图像的颜色只有黑白和浓淡之分 只有黑白两种颜色的称为单色图像 灰度图像还有16级、256级灰度 灰度图像(Gray image) 彩色图像(Color image) 灰度图像的颜色只有黑白和浓淡之分 只有黑白两种颜色的称为单色图像 灰度图像还有16级、256级灰度 彩色图像有红、绿、蓝等丰富的色彩,有16色、256色和24位真彩色之分
黑白图像
16色图像
256色图像
24位真彩色图像
不同位深度的图像对比
图像的颜色模型 颜色:指视觉器官对各种不同波长的光波的感觉 光波是一种具有一定频率范围的电磁波,人们能用眼睛感受到的波长在380~780 nm的范围内 波长不同,感受到的颜色就不同 国际照明委员会(CIE )定义,用颜色的三个特性来区分颜色,它们是:色调,饱和度和明度
色调与饱和度 色调指颜色的外观,用于区别颜色的名称或颜色的种类。 饱和度是指颜色的纯洁性,它可用来区别颜色明暗的程度 是视觉系统对一个区域呈现的颜色的感觉 取决于可见光谱中的光波的频率 饱和度是指颜色的纯洁性,它可用来区别颜色明暗的程度 当一种颜色渗入其他光成分愈多时,就说颜色愈不饱和。 完全饱和的颜色是指没有渗入白光所呈现的颜色
明度是视觉系统对可见物体辐射或者发光明亮度的感知属性 明度的主观感觉值目前还无法用物理设备来测量 国际照明委员会用亮度来代替明度,亮度就像光的强度
取色框
RGB相加混色模型 理论上,任何一种颜色都可用红(Red)、绿(Green)、蓝(Blue)三种基本颜色按不同的比例混合得到,称为相加混色。 在计算机中,将红、绿、蓝三种颜色分别按光强度(深浅)的不同分为256个级别,0级实际上是黑色,255级是纯色(红、绿或蓝),分别用8位二进制数表示,每个像素占24位。
图像文件格式 图像文件格式是图像数据在文件中的存放形式,不同的软硬件厂商可能定义不同的文件格式 (1)位图文件(Bitmap-File,BMP) bmp格式是Windows采用的图像文件存储格式,在Windows环境下运行的所有图像处理软件都支持这种格式 是一种不压缩的格式,因此占有较大的存储空间,但图像质量较高,没有数据损失
(2)GIF格式 GIF(Graphics Interchange Format)是CompuServe公司开发的图像文件存储格式 压缩效率、占用的存储空间很小 支持透明图像属性和动画图像属性 但表示的颜色数量有限,适合存储颜色较少的卡通图像、徽标等手绘图像
背景透明 背景不透明
(3)JPEG JPEG(Joint Photographic Experts Group)负责制定静态的数字图像数据压缩编码标准 相应的文件存储格式为jpg格式 可以选择压缩质量 适合存储色彩丰富的照片
矢量图和位图 矢量图主要用于工程图、白描图、卡通漫画等,这些图形可以分解为单个的线条、文字、圆、矩形、多边形等单个的图形元素。再用一个代数式来表达每个被分解出来的元素。 位图图像,亦称为点阵图像或绘制图像,是由称作像素的单个点组成的。这些点可以进行不同的排列和染色以构成图样。当放大位图时,可以看见赖以构成整个图像的无数单个方块。
在MFC应用程序中绘图 在MFC应用程序中,所有的绘制调用均通过相应的设备环境对象实现。在MFC类库中,用CDC类封装设备环境对象,并提供了在窗口的客户程序区域上画图的方法。 所有图形的输出都可以使用CDC类提供方法来进行。如对颜色进行操作、坐标映射和转换,对多边形和区域进行操作,绘制各种形状,绘制文本,处理字体等。
MFC中的绘图工具对象 MFC定义了若干种对应于Windows绘图工具的图形对象,包括画笔、画刷、字体、位图等。这些Windows绘图工具封装在MFC图形对象类中,并且都是由CGdiObject基类派生而来。这些图形对象类分别是:CPen、CBrush、CFont、CBitmap 等。
画笔 画笔是用来画线的工具,是CPen类的对象 ◈声明画笔对象,并创建宽度为3的红色实线笔 CPen penRed(PS_SOLID, 3, RGB(255, 0, 0)); ◈使用新的画笔,保存原来的画笔以便恢复 CPen *pOldPen=pDC->SelectObject(&penRed); ◈画完线后,恢复原来的画笔 pDC->SelectObject(pOldPen); ◈保存了原来的画笔。保存并恢复原来画笔的原因是,每个图形设备接口对象要占用一个HDC句柄,而可用的句柄数量是有限的,在使用完后要及时释放。
画线 画线工作需经两步完成:首先确定线的起始端位置,这可通过调用成员函数MoveTo完成,其原型为: CPoint MoveTo ( int x, int y ); MoveTo将绘图位置移至指定坐标处,并返回移动前的绘图位置。确定了线的起点后,即可使用成员函数LineTo画线: BOOL LineTo ( int x, int y );
绘制矩形、椭圆 绘制矩形的成员函数为: 绘制椭圆成员函数的原型为: BOOL Rectangle(int x1,int y1,int x2, int y2 ); 绘制椭圆成员函数的原型为: BOOL Ellipse(int x1, int y1, int x2, int y2 ); 其参数为需要绘制的矩形的左上角坐标(x1, y1)和右下角坐标(x2, y2)。
【例6-4】在窗口中随机的画了一些直线和其它几何形状。 ◈思路: 建立MFC框架程序,在OnDraw中写出相应的绘图代码即可。 ◈步骤: 1)新建一个单文档MFC程序,项目名称为6_4,其余设置不变。 2)在视图类的OnDraw中键入绘图代码,见278页程序 3)在视图类的开始出饱含以下头文件。 //包含的头文件 #include <stdlib.h> //支持Rand函数 #include <time.h> 4)编译并运行程序
srand( (unsigned)time( NULL ) ); //初始化随机数发生器 CRect clientRect; //得到客户区大小 GetClientRect(&clientRect); int maxX = clientRect.right; int maxY = clientRect.bottom; for( int i =0 ;i < 100; i++) //随机画100条线 { int r = rand() % 256; //产生随机的颜色 int g = rand() % 256; int b = rand() % 256; CPen newPen; //创建一个新画笔 newPen.CreatePen(PS_SOLID, 1, RGB(r, g, b)); CPen* oldPen = pDC->SelectObject(&newPen); //选新画笔保存旧画笔 int x0 = rand() % maxX; //画线 int y0 = rand() % maxY; pDC->MoveTo(x0, y0); int x1 = rand() % maxX; int y1 = rand() % maxY; pDC->LineTo(x1, y1); newPen.DeleteObject(); pDC->SelectObject(oldPen); } CBrush newBrush(RGB(255,0,0)); //画圆 CBrush* oldBrush = pDC->SelectObject(&newBrush); pDC->Ellipse(20, 20, 40, 40); pDC->Ellipse(100, 100, 200, 140); pDC->SelectObject(oldBrush);
显示位图 ◈载入位图资源: BOOL LoadBitmap( LPCTSTR ResourceName ); ◈读位图信息: int GetBitmap( BITMAP* pBitMap ); ◈首先应建立一合适的内存设备环境: CDC MemDC; MemDC. CreateCompatibleDC(NULL); ◈并将位图选入该设备环境: MemDC.SelectObject(&m_Bitmap);
显示位图 用CDC类的BitBlt( )成员函数从内存设备环境中将位图复制到指定设备(如窗口或打印机)。BitBlt( )函数的原型为: BOOL BitBlt ( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop ); 其中参数x,y为目标区左上角坐标,nWidth和nHeight分别为目标区的宽度和高度(逻辑坐标),pSrcDC为内存设备指针,xSrc和ySrc为原图中欲显示块左上角坐标,dwRop为复制方式,常用值为SRCCOPY,即按原样复制。
【例6-5】显示一张位图文件(BMP) 准备一张256色BMP照片或图像(神6航天员照片) 新建单文档MFC程序,项目名称6_5 切换到资源视图,在菜单中选择”Insert”->”Resource” 在弹出的对话框中选择Bitmap,然后单击【Import】按钮,导入一幅BMP位图。位图的ID号使用IDB_BITMAP1 修改视图类的定义(头文件)开始处,加入3个变量定义: // 定义CBitmap类对象以及位图的高度和宽度变量 CBitmap m_Bitmap; int m_nHeight; int m_nWidth;
【例6-5】显示一张位图文件(BMP) 修改视图类的构造函数 // 载入位图资源,读位图信息 m_Bitmap.LoadBitmap(IDB_BITMAP1); BITMAP BM; m_Bitmap.GetBitmap(&BM); m_nWidth = BM.bmWidth; m_nHeight = BM.bmHeight; 修改视图类的OnDraw函数,显示位图 // 显示位图 CDC MemDC; MemDC.CreateCompatibleDC(NULL); MemDC.SelectObject(&m_Bitmap); pDC->BitBlt(0,0,m_nWidth,m_nHeight,&MemDC,0,0,SRCCOPY); 编译连接运行
BMP图像文件处理 可以通过读入BMP图像文件数据,对图像进行一系列的处理,如旋转、缩小、放大等
位图动画 视频信息(电视、电影、动画片等) 实际上指时间上连续的图像 编写程序让简单位图图像运动起来 例如:下面图像
编程步骤 ◈建立单文档MFC程序框架,工程名称move_car ◈在资源视图中,进入菜单编辑,增加两个菜单选项 “显示小车”,其ID分别为ID_SHOW_CAR “运动小车”,其ID分别为ID_MOVE_CAR ◈在文档类中增加如下成员变量 CRect m_rectCarBody; CRect m_rectWheel[2]; ◈修改文档类的构造函数 m_rectCarBody=CRect(0,0,0,0); m_rectWheel[0]=CRect(0,0,0,0); m_rectWheel[1]=CRect(0,0,0,0);
◈修改视图类的OnDraw()函数 CBrush brushNew,*pbrushOld; CBrush brushNew1; CPen penNew,*ppenOld; brushNew.CreateSolidBrush(RGB(255,0,0)); penNew.CreatePen(PS_SOLID,2,RGB(0,255,0)); pbrushOld=pDC->SelectObject(&brushNew); ppenOld=pDC->SelectObject(&penNew); pDC->Rectangle(pDoc->m_rectCarBody); pDC->SelectObject(pbrushOld); brushNew1.CreateSolidBrush(RGB(255,255,0)); pbrushOld=pDC->SelectObject(&brushNew1); pDC->Ellipse(pDoc->m_rectWheel[0]); pDC->Ellipse(pDoc->m_rectWheel[1]); pDC->SelectObject(ppenOld);
◈用类向导在视图类中建立菜单ID_SHOW_CAR命令消息映射及修改相应的函数OnShowCar() CMove_carDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); pDoc->m_rectCarBody=CRect(0,100,90,130); pDoc->m_rectWheel[0]=CRect(16,130,35,150); pDoc->m_rectWheel[1]=CRect(61,130,80,150); Invalidate();//pDoc->m_rectCarBody,FALSE); ◈用类向导在视图类中建立菜单ID_MOVE_CAR命令消息映射及修改相应的函数OnMoveCar() CView::SetTimer(1,100,NULL);
◈用类向导ClassWizard在视图类生成定时器消息、消息映射、消息处理函数 CJg_test81Doc* pDoc = GetDocument(); ASSERT_VALID(pDoc); CRect rectClient; GetClientRect(&rectClient); Invalidate(); if(pDoc->m_rectCarBody.left<rectClient.right) { pDoc->m_rectCarBody.left+=5; pDoc->m_rectCarBody.right+=5; pDoc->m_rectWheel[0].left+=5; pDoc->m_rectWheel[0].right+=5; pDoc->m_rectWheel[1].left+=5; pDoc->m_rectWheel[1].right+=5; } else { pDoc->m_rectCarBody.left=0; pDoc->m_rectCarBody.right=90; pDoc->m_rectWheel[0].left=16; pDoc->m_rectWheel[0].right=35; pDoc->m_rectWheel[1].left=61; pDoc->m_rectWheel[1].right=80; Invalidate(); CView::OnTimer(nIDEvent); ◈编译连接运行