您的位置:寻梦网首页编程乐园C/C++编程Visual C++教程
一、走进Visual C++ 二、MFC程序结构分析 三、深入MFC类库 四、VC程序调试 五、Visual C++与多媒体
Visual C++教程 五 Visual C++与多媒体
  • 1 对声音的处理
  • 1.1媒体控制接口
  • 1.2波形混音器
  • 2 多媒体文件I/O
  • 3 多媒体图形图像技术
  • 4 图像合成
  • 5 FLC动画
  • 6 热点
  • 五 Visual C++与多媒体

    对于一般的应用程序来说,Visual C++ 可以说是包罗万象,然而令人遗憾的是,几乎没有听说过Visual C++ 对多媒体提供过什么支持,甚至有人说Visual C++不适合多媒体编程。若是我们完全使用Visual C++的类库而不想点花招的话,恐怕连最一个简单的RPG游戏都编不出来。对于一个需要大量动画、声音的多媒体应用程序来说,Visual C++ 最多提供了一个外壳,而编制一个优秀的声音、动画引擎的任务,就落到了程序员的身上。

    在以后各章节中,将陆续介绍大家如何开发这个引擎。

    1 对声音的处理

    1.1媒体控制接口

    MCI(Media Control Interface)媒体控制接口是MircroSoft提供的一组多媒体设备和文件的标准接口,它的好处是可以方便地控制绝大多数多媒体设备包括音频、视频、影碟、录像等多媒体设备,而不需要知道它们的内部工作状况。但是古人云:成也萧何,败也萧何。MCI虽然看上去高大全,但对于一些高级应用来说,它是远远不够的。好比Visual C++虽然看上去无所不能,却需要程序员自己开发多媒体引擎一样。对于MCI指令集,我们将只作简单介绍,重点放在后面的波形文件混音器上。

    MCI的控制方式:

    一般说来,程序员使用两个函数就可以与MCI打交道了:

    MCIERROR mciSendCommand(MCIDEVICEID wDeviceID, UINT uMsg,

    DWORD dwFlags, DWORD dwParam );

    命令字符串方式,用接近于日常生活用语的方式发送控制命令,适用于高级编程如VB、TOOLBOOK等。

    MCIERROR mciSendString(LPCTSTR lpszCommand,LPTSTR lpszReturnStr

    ing, UINT cchReturn, HANDLE hwndCallback

    );

    命令消息方式,用专业语法发送控制消息,适用于VC等语言编程,此方式直接与MCI设备打交道。

    对于mciSendCommand,第一个参数指定了设备标识,这个标识会在程序员打开MCI设备时由系统提供。第二个参数指定将如何控制设备,详细请查阅后面“MCI指令”一栏。第三个参数为访问标识,第四个参数一般是一个数据结构,标识程序在访问MCI时要的一些信息。有关详细资料,请查阅本光盘配套书。

    对于mciSendString,第一个参数为一串控制字符串,返回信息由系统填入第二个参数,第三个参数指明返回信息的最大长度,若对MCI装置设定了"notify"标志则需要在第四个参数填上返回窗口句柄。

    举例:

    mciSendCommand(DeviceID,MCI_CLOSE,NULL,NULL);//关闭一个MCI设备;

    mciSendString("open aaa.avi",0,0,0);//打开文件"aaa.avi";

    MCI的设备类型:

    MCI的设备类型有:

    设备描述 描述字符串 说明
    MCI_ALL_DEVICE_ID   所有设备
    MCI_DEVTYPE_ANIMATION Animation 动画设备
    MCI_DEVTYPE_CD_AUDIO Cdaudio CD音频
    MCI_DEVTYPE_DAT Dat 数字音频
    MCI_DEVTYPE_DIGITAL_VIDEO Digitalvideo 数字视频
    MCI_DEVTYPE_OTHER Other 未定义设备
    MCI_DEVTYPE_OVERLAY Overlay 重叠视频
    MCI_DEVTYPE_SCANNER Scanner 扫描仪
    MCI_DEVTYPE_SEQUENCER SequencerMIDI 序列器
    MCI_DEVTYPE_VCR Vcr 合式录像机
    MCI_DEVTYPE_VIDEODISC Videodisc 激光视盘
    MCI_DEVTYPE_WAVEFORM_AUDIO waveaudioWave 音频

    对于未在上面定义的MCI设备,用户可查看system.ini文件中[mci]部分,例如:

    [mci]
    cdaudio=mcicda.drv
    sequencer=mciseq.drv
    waveaudio=mciwave.drv
    avivideo=mciavi.drv
    videodisc=mcipionr.drv
    vcr=mcivisca.drv
    ActiveMovie=mciqtz.drv
    QTWVideo=mciqtw.drv
    MPEGVideo=C:\PROGRA~1\XING\XINGMP~1\xmdrv95.dll

    其中最后两句分别指明了Apple的QuickTime设备,设备名为"QTWVidio"、MPEG影像设备,设备名为"MPEGVideo"。

    在MCI编程中,既可以将设备描述当设备名,也可以将描述字符串当设备名,一个极端偷懒的办法是程序员不要在程序中指定设备名,Windows将自动根据文件扩展名识别设备类型。

    举个例子来说,打开一个多媒体文件有以下三种方式:

    [1]:自动识别:打开一个"WAV"文件

    MCI_OPEN_PARMS mciOpen;

    mciOpen.lpstrDeviceType=0;

    mciOpen.lpstrElementName="aaa.wav";

    mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_ELEMENT,

    (DWORD)&mciOpen);

    [2]:指定设备描述:打开CD播放器

    MCI_OPEN_PARMSmciOpen;

    mciOpen.lpstrDeviceType=(LPSTR)MCI_DEVTYPE_CD_AUDIO ;

    mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID,

    (DWORD)&mciOpen);

    [3]:指定描述字符串:打开一个AVI文件

    MCI_OPEN_PARMSmciOpen;

    mciOpen.lpstrDeviceType="avivideo";

    mciOpen.lpstrElementName="aaa.avi";

    mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,

    (DWORD)&mciOpen);

    注意三种打开方式中,函数第三个参数的区别,后面会讲到这种区别。

    MCI指令

    MCI使用如下指令:

    MCI_BREAK 设置中断键,缺省是”CTRL+BREAK"
    MCI_CAPTURE 抓取当前帧并存入指定文件,仅用于数字视频
    MCI_CLOSE 关闭设备
    MCI_CONFIGURE 弹出配置对话框,仅用于数字视频
    MCI_COPY 拷贝数据至剪贴板
    MCI_CUE 延时播放或录音
    MCI_CUT 删除数据
    MCI_DELETE 删除数据
    MCI_ESCAPE 仅用于激光视频
    MCI_FREEZE 将显示定格
    MCI_GETDEVCAPS 获取设备信息
    MCI_INDEX 当前屏幕显示与否,仅用于VCR设备
    MCI_INFO 获取字符串信息
    MCI_LIST 获取输入设备数量,支持数字视频和VCR设备
    MCI_LOAD 装入一个文件
    MCI_MARK 取消或做一个记号,与MCI_SEEK配套
    MCI_MARK 取消或做一个记号,与MCI_SEEK配套
    MCI_MONITOR 为数字视频指定报告设备
    MCI_OPEN 打开设备
    MCI_PASTE 粘帖数据
    MCI_PAUSE 暂停当前动作
    MCI_PLAY 播放
    MCI_PUT 设置源、目的和边框矩形
    MCI_QUALITY 定义设备缺省质量
    MCI_RECORD 开始录制
    MCI_RESERVE 分配硬盘空间
    MCI_RESTORE 拷贝一个bmp文件至帧缓冲
    MCI_RESUME 使一个暂停设备重新启动
    MCI_SAVE 保存数据
    MCI_SEEK 更改媒体位置
    MCI_SET 设置设备信息
    MCI_SETAUDIO 设置音量
    MCI_SETTIMECODE 启用或取消VCR设备的时间码
    MCI_SETTUNER 设置VCR设备频道
    MCI_SETVIDEO 设置video参数
    MCI_SIGNAL 在工作区上设置指定空间
    MCI_STATUS 获取设备信息
    MCI_STEP 使播放设备跳帧
    MCI_STOP 停止播放
    MCI_SYSINFO 返回MCI设备信息
    MCI_UNDO 取消操作
    MCI_UNFREEZE 使使用MCI_UNFREEZE的视频缓冲区恢复运动
    MCI_UPDATE 更新显示区域
    MCI_WHERE 获取设备裁减矩形
    MCI_WINDOW 指定图形设备窗口和窗口特性

    其中比较常用的指令有MCI_OPEN、MCI_CLOSE、MCI_PLAY、MCI_STOP、MCI_PAUSE、MCI_STATUS等等。

    实例分析

    在Visual C++ 5.0菜单上点取new,单击projects,选取MFC AppWizard(exe),创建一个新的名为"mcitest"的工程文件,OK确定。

    注意在应用程序类型中选择"Dialog based",然后Finish完成。这是一个基于对话框的应用程序,为了完成MCI测试的任务,我们要更改一下对话框资源。点取"Resource View",在"Dialog"下选取"IDD_MCITEST_DIALOG"对话框,依次添加Button如图所示。

    完成对话框的修改。右键单击mcitest files、选取Add Files To Project,加入配套光盘中提供的"commci.cpp"和"commci.h"文件。打开ClassWizard,在Class Name下选择CMcitestDlg,加入所有的按键消息处理函数。

    在"cmcitestDlg"类中,分别用"COMMCI"定义Wav、Midi、Avi三个成员变量,在按钮响应过程中分别写上相应处理函数open( )、play( )、close( )、pause( )、stop( )。

    在"projoct"菜单下单击setting,弹出设置对话框,在"link"下"object/library modules"下加入"winmm.lib",编译并运行程序:

    图5.1

    源程序介绍

    // commci.h: interface for the commci class.
    //
    //////////////////////////
    
    #if !defined(AFX_COMMCI_H__90CEFD04_CC96_11D1_94F8_0000B431BBA1__INCLUDED_)
    #define AFX_COMMCI_H__90CEFD04_CC96_11D1_94F8_0000B431BBA1__INCLUDED_
    
    #if _MSC_VER >= 1000
    #pragma once
    #endif // _MSC_VER >= 1000
    
    //#include <windows.h>
    #include <mmsystem.h>
    
    class COMMCI
    {
    private:
    HWNDhOwer;//窗口的拥有者
    MCI_OPEN_PARMS mciOpen;
    
    public:
    COMMCI();
    ~COMMCI()        {Close();  }
    MCIERROROpen(LPCSTR DeviceType,LPCSTR filename);
    
    
    //通过描述字符串打开设备
    
    
    MCIERROROpen(int DeviceType,LPCSTR filename); //通过设备类型打开设备
    MCIERROROpen(LPCSTR filename);//自动检测设备
    voidPlay(HWND hWnd);//播放MCI,hWnd为回调窗口句柄
    voidClose(void);//关闭设备
    voidStop(void);//停止设备
    void    Pause(void);//暂停设备
    DWORDQuery();//检测设备
    };
    
    //////////////////////////////
    //  COMMCI.CPP  中使用到的结构
    //////////////////////////////
    //typedef struct tagMCI_OPEN_PARMS {
    //DWORD       dwCallback;
    //  MCIDEVICEID wDeviceID;
    //  WORD        wReserved0;
    //  LPCSTR      lpstrDeviceType;
    //  LPCSTR      lpstrElementName;
    //  LPCSTR      lpstrAlias;
    //} MCI_OPEN_PARMS, FAR *LPMCI_OPEN_PARMS;
    
    //typedef struct tagMCI_PLAY_PARMS {
    //  DWORD   dwCallback;
    //  DWORD   dwFrom;
    //  DWORD   dwTo;
    //} MCI_PLAY_PARMS, *PMCI_PLAY_PARMS, FAR *LPMCI_PLAY_PARMS;
    
    //typedef struct tagMCI_STATUS_PARMS {
    //  DWORD   dwCallback;
    //  DWORD   dwReturn;
    //DWORD   dwItem;
    //  DWORD   dwTrack;
    //} MCI_STATUS_PARMS, *PMCI_STATUS_PARMS,
                    FAR * LPMCI_STATUS_PARMS;
    
    
    
    //////////////////////////
    //mci 初始化方式
    //////////////////////////
    //COMMCI.Open("waveaudio",filename);   wave ; *.wav ,
    //COMMCI.Open("sequencer",filename);   midi ; *.mid , *.rmi
    //COMMCI.Open("reelmagic",filename);   vcd  ; *.mpg ,
    //COMMCI.Open("avivideo",filename);    avi  ; *.avi ,
    
    
    //////////////////////////
    //   mci 状态返回值
    //////////////////////////
    //case MCI_MODE_NOT_READY:
    //case MCI_MODE_STOP:
    //case MCI_MODE_PLAY:
    //case MCI_MODE_RECORD:
    //case MCI_MODE_SEEK:
    //case MCI_MODE_PAUSE:
    //case MCI_MODE_OPEN:
    
    #endif // !defined(AFX_COMMCI_H__90CEFD04_CC96_11D1_94F8_0000B431BBA1__INCLUDED_)
    
    
    // commci.cpp: implementation of the commci class.
    //
    //////////////////////////////////////
    
    #include "stdafx.h"
    //#include "mcitest.h"
    #include "commci.h"
    
    #ifdef _DEBUG
    #undef THIS_FILE
    static char THIS_FILE[]=__FILE__;
    #define new DEBUG_NEW
    #endif
    
    //////////////////////////////////////
    // Construction/Destruction
    //////////////////////////////////////
    
    COMMCI::COMMCI()
    {
    memset(this,0,sizeof(COMMCI));
    }
    
    MCIERROR COMMCI::Open(LPCSTR DeviceType,LPCSTR filename)
    {
    //如果有打开的设备就关闭
    if (mciOpen.wDeviceID) Close();
    //初始化MCI_OPEN_PARMS结构
    mciOpen.lpstrDeviceType=DeviceType;
    mciOpen.lpstrElementName=filename;
    
    
    //除了打开设备设备代码为0,下面的任何mciSendCommand语句都要指定设
    //备代码。
    
    
    if ( mciSendCommand(NULL,MCI_OPEN,
    MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,
                                          (DWORD)&mciOpen))
    return FALSE;
    return TRUE;
    }
    
    MCIERROR COMMCI::Open(LPCSTR filename)
    {
    if (mciOpen.wDeviceID) Close();
    mciOpen.lpstrElementName=filename;
    if ( mciSendCommand(NULL,MCI_OPEN,
    /*MCI_OPEN_TYPE |*/ MCI_OPEN_ELEMENT,
                                               (DWORD)&mciOpen))
    return FALSE;
    return TRUE;
    }
    
    MCIERROR COMMCI::Open(int DeviceType,LPCSTR filename)
    {
    if (mciOpen.wDeviceID) Close();
    mciOpen.lpstrDeviceType=(LPCSTR)DeviceType;
    mciOpen.lpstrElementName=filename;
    return  mciSendCommand(NULL,MCI_OPEN,
    MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID ,(DWORD)&mciOpen);
    }
    
    void COMMCI::Play(HWND hWnd)
    {
    MCI_PLAY_PARMSmciPlay;
    hOwer=hWnd;//回调窗口句柄
    //MCI_PLAY_PARMS结构只需要设定回调窗口句柄
    mciPlay.dwCallback=(WORD)hOwer;
    mciSendCommand(mciOpen.wDeviceID,MCI_PLAY,MCI_NOTIFY,
                                                  (DWORD)&mciPlay);
    }
    
    void COMMCI::Close(void)
    {
    if (mciOpen.wDeviceID)
    mciSendCommand(mciOpen.wDeviceID,MCI_CLOSE,NULL,NULL);
    memset(this,0,sizeof(COMMCI));
    }
    
    void COMMCI::Stop(void)
    {
    if (mciOpen.wDeviceID)
    mciSendCommand(mciOpen.wDeviceID,MCI_STOP,NULL,NULL);
    }
    
    void COMMCI::Pause(void)
    {
    if (mciOpen.wDeviceID)
    mciSendCommand(mciOpen.wDeviceID,MCI_PAUSE,NULL,NULL);
    }
    
    
    DWORD COMMCI::Query()
    {
    MCI_STATUS_PARMSmciStatus;
    mciStatus.dwItem=MCI_STATUS_MODE;
    mciSendCommand(mciOpen.wDeviceID,MCI_STATUS,
                            MCI_STATUS_ITEM,(LPARAM)&mciStatus);
    return mciStatus.dwReturn;
    };
    

    对于类COMMCI定义了如下几个成员函数:

    • 一个构造函数和一个析构函数。
    • 一个打开函数,其参数分别是要打开设备的类型和文件名。
    • 播放函数,其参数是回调函数句柄。
    • 关闭函数。
    • 停止函数。
    • 暂停函数。
    • 状态检测函数。

    在一个MCI的处理过程中,必须使用以下流程:

    • 打开一个MCI设备。
    • 然后Open播放MCI设备,其间可以暂停和停止播放,
    • 最后关闭MCI设备。

    在以上任何步骤中,都可以用状态检测函数检测工作状态。

    下面我们看一下MCI的实现过程:

    1. OPEN MCI

    首先,我们 初始化一个MCI_OPEN_PARMS的结构,其中要用到两个值。

    其中mciOpen.lpstrDeviceType指定了要打开的设备类型,这些设备类型可从前面的“MCI设备类型”选取。可以是标识或描述字符串,例如语句mciOpen.LpstrDeviceType=MCI_DEVTYPEVCR与语句mciopen.LpstrDeviceType="Vcr"是等价的。若不指定类型则计算机将根据文件名自动识别设备,接下来mciOpen.LpstrElimmentName指定了要打开的文件名,最后调用MciSendComand指定计算机将在结构的wDeviceID中填入打开的设备代码;以后应用程序将根据此设备代码访问MCI设备。

    这里谈一下三种打开方式的区别:

    [1]:自动识别:打开一个"WAV"文件

    MCI_OPEN_PARMS mciOpen;

    mciOpen.lpstrDeviceType=0;

    mciOpen.lpstrElementName="aaa.wav";

    mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_ELEMENT,

    (DWORD)&mciOpen);

    [2]:指定设备描述:打开CD播放器

    MCI_OPEN_PARMSmciOpen;

    mciOpen.lpstrDeviceType=(LPSTR)MCI_DEVTYPE_CD_AUDIO ;

    mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID,

    (DWORD)&mciOpen);

    [3]:指定描述字符串:打开一个AVI文件

    MCI_OPEN_PARMSmciOpen;

    mciOpen.lpstrDeviceType="avivideo";

    mciOpen.lpstrElementName="aaa.avi";

    mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_TYPE

    MCI_OPEN_ELEMENT,

    (DWORD)&mciOpen);

    请注意mciSendCommand函数第三个参数的区别:

    MCI_OPEN_TYPE:表示要使用MCI_OPEN_PARMS结构中的LpstrDiviceType参数,这可区分指定设备打开方式和自动识别方式之间的区别。在自动方式中,不需使用LpstrDeviceType参数。因此,也不需指定MCI_OPEN_TYPE。

    MCI_OPEN_ELEMENT:表示LpstrDeviceType参数中的是设备表述字符串。

    MCI_OPEN_TYPE_ID:表示LpstrDeviceType参数中的是设备描述。

    2 PlayMci:

    在play函数中,需要一个返回窗口句柄,以便应用程序在播放结束后向此窗口发送一个消息,告诉窗口已经播放结束。我们首先初始化一个MCI_PLAY_PARMS的数据结构:将其中dwCallback参数赋与窗口句柄。然后调用mciSendCommend,当然发送的指令是MCI_PLAY,告诉系统开始播放,另外第三个参数指定MCI_NOTIFY,告诉系统播放完后要通知自己。

    QueryMci:

    要想检测MCI播放状态,就要发送指MCI_STATUS,并标志MCI_STATUS_ITEM,返回值在结构MCI_STATUS_PARMS的dwReturn上。

    关于MCI的进一步详细情况,本章节将不再讲述,有关MCI中用到的命令详解、数据结构,请大家自己查阅本书配套图书。

    1.2波形混音器

    假如你是一位游戏爱好者,不知道有没有注意过不同的游戏音乐,音效实现上的区别。比较早的游戏一般采用midi加上单个wav文件的方式,例如《天使帝国》,早期的“三国志”等等,这些游戏一般以midi作背景音乐。遇到人物走路、打架或天灾人祸时用一些波形文件衬托一下气氛,只是Midi文件在早期声卡上表现实在单薄,以一个发烧友的眼光来看实在不堪入耳。后来采用CD+单个wav的声音方式,音质有了明显改善,例如《仙剑奇侠传》。但由于wav在同一时刻只能播放一个,毕竟单薄。很难想象两人打架只有一人发出“嘿嘿”的声音。因此多波形混音成了游戏编程主流,大家看“红色警报”中坦克的轰鸣声,人物的哭叫声,飞机的咆啸声,电塔的滋滋声与出色的背景音乐溶合一起,令人心旷神怡情不自禁。想要编出这样的程序,就要掌握波形混音的原理。

    什么是Wav文件:

    Wav文件直接反映了一个声音在每个时刻的大小值,比如说以下一段波形:

    我们按每人0.1秒取一点,得到的wav文件数值就是0,1,1,-1,0,1。因此,假如我们能把许多Wav文件的数据直接相加,你听到的就是所有的声音,这就是混音器的原理。

    下面我们分析一下Wav文件结构:

    我们可以打开一个Wav文件直接看其二进制码:

        C:\user\wave\22.wav
    00000000 5249 4646 9CB6 1E00 5741 5645 666D 7420
    00000010 1000 0000 0100 0200 2256 0000 44AC 0000
    00000020 0200 0800 6461 7461 78B6 1E00 7F7F 7F7F
    00000030.7F7F 7F7F 7F7F 7F7F 7F7F 7F7F 7F7F 7F7F
    00000040.7F7F 7F7F 7F7F 7F7F 7F7F 7F7F 7F7F 7F7F
    00000050.7F7F 7F7F 7F7F 7F7F 7F7F 7F7F 7F7F 7F7F
    00000060.7F7F 7F7F 7F7F 7F7F 7F7F 7F7F 7F7F 7F7F
    00000070.7F7F 7F7F 7F7F 7F7F 7F7F 7F7F 7F7F 7F7F
    00000080.7F7F 7F7F 7F7F 7F7F 7F7F 7F7F 7F7F 7F7F
    00000090.7F7F 7F7F 7F7F 7F7F 7F7F 7F7F 7F7F 7F7F
    000000A0.7F7F 7F7F 7F7F 7F7F 7F7F 7F7F 7F7F 7F7F
    

    大家可以看到wav文件存储格式如下:

    "RIFF"
    ××××    文件大小
    "WAVE"
    "fmt"
    ××××    PCMWAWFORMAT——数据结构大小
    ××××
    ……      数据结构“PCMWAVEFORMAT”
    ××××
    data
    ××××    数据大小
    ∶       数据

    首先是字符串“RIFF”,表示此文件遵循一种标准格式名为“资源互换文件格式”(Resource Intercharge Format)。后面紧跟四个字节指明文件大小。其次是字符串“WAVE”和“fmt”,后面紧跟一个名为“PCMWAVEFORMAT”的结构,最后是字符串“data”,紧跟数据大小及所有数据。PIFF文件为一种树状结构,基本构成单位是“块”,图5.2中是wav文件中“块”关系图。



    图5.2

    如图所示,wav文件结构为两层,由父块“RIFF”和两个子块“fmt”、“data”组成。“fmt”块包含wav文件格式信息,“data”块包含数据信息。

    2 多媒体文件I/O

    多媒体文件I/O与普通文件I/O相似,但支持多媒体“RIFF”格式,并提供了缓冲和非缓冲文件I/O。

    所有的多媒体文件I/O函数名前缀为mmio,消息名前缀为MMIO。

    低级波形音频函数:

    低级音频服务允许用户直接与音频设备驱动程序打交道,直接控制音频设备如波形,Midi的播放与记录,低级音频函数是一个设备无关接口。

    低级音频函数前缀均为wave,按输入函数、输出函数区分为WaveIn××××和WaveOut××××。

    波形音频的重放过程。

    首先,我们要调用多媒体文件I/O函数mmIO××××( ),并根据多媒体文件I/O生成在wave重放中需要的结构和数据,并将这些结构和数据用waveOut××××( )函数重放。 用户可以根据加密的需要将wave文件篡改,去掉文件头和“fmt”块,只保留数据块。 或者将其压缩,只要重放时能在内存中还原出数据文件。并记得文件音频格式和大小,就能重放音频。

    常用mmio函数及实现过程简介:

    mmioOpen( )    打开一个RIFF文件

    mmioDescend( )  进入块

    mmioRead( );   该取RIFF文件

    mmioAscend ( );  跳出块

    mmioClose( );   关闭PIFF文件

    对于块来说,进入块和跳出块是配对的。

    读取WAV文件的读取过程:

    mmioOpen( )   打开文件

       ↓

    mmioDescend ("WAVE") 进入"fmt"块

       ↓

    mmioRead( )     读取WAVE文件格式信息

       ↓

    mmioAscend ( )    跳出"fmt"块

       ↓

    mmioDescend ("data") 进入"data"块

       ↓

    mmioRead( )     读取WAVE数据信息

       ↓

    mmioClose( )     关闭文件。

    输出WAV文件的过程:

    WaveOutOpen ()打开一个输出设备

       ↓

    WaveOutPrepareHeader()准备WAVE数据头。

       ↓

    WaveOutWrite() 将数据写入设备并开始播放

       ↓

    WaveOutReset() 停止播放并重置管理器

       ↓

    WaveOutClose()并闭播放设备

       ↓

    WaveOutUnpareHeader()清理用WaveOutPrepareHeader准备的Wave。

    实例:

    这个实例实现的功能是首先打开背景音乐,然后每五秒钟加入一段配音。背景音乐放完后将停止播放,大家可以听一下背景+配音+配音+……+配音产生的实际效果。为了实现这个功能,我们封装了一个类。大家可以在光盘上找到这两个文件“wavemix..h”和“wavemix.cpp” .

    首先,我们看一下“wavmix.h”,这里定义了一个称为“mwave”的类,

    其中成员函数有:构造、析构函数、open(打开文件)、play(播放文件)、Add(往缓冲中加混音文件)、Stop(停止播出)、close(关闭输出设备,类重新初始化)。

    现在我们照前面方法建一个基于对话框的程序,在“File View”中加入上述两个文件。打开“WavemixDlg.h”,在前面加上“#include "wavemix.h"”,

    在类class cWavemixDlg中加入私有数据“MWAVE mWave”,在类“cwavemixDlg”中加入成员OnlnitDialog( )和OnTime(),,

    在此函数中加入一个定时器,打开并播放背景音乐。在定时器的向应过程OnTime()中加入函数mwave.Add("2.waw".)使之每五秒钟向缓冲加入一个配音。编译并运行之。

    源程序分析

    // wavemix.h : main header file for the WAVEMIX application
    //
    #if !defined(AFX_WAVEMIX_H__54F4AF66_CE37_11D1_94F8_0000B431BBA1__INCLUDED_)
    #define AFX_WAVEMIX_H__54F4AF66_CE37_11D1_94F8_0000B431BBA1__INCLUDED_
    
    #if _MSC_VER >= 1000
    #pragma once
    #endif // _MSC_VER >= 1000
    
    #ifndef __AFXWIN_H__
    #error include 'stdafx.h' before including this file for PCH
    #endif
    
    #include "resource.h"// main symbols
    /////////////////////////////////////////////
    #include "windows.h"
    #include "mmsystem.h"
    #ifdef WIN32
    #define WAVDATABYTE
    #else
    #define WAVDATABYTE _huge
    #define WAVEFORMATEX PCMWAVEFORMAT
    #endif
    
    #ifdef WIN32
    #define WAV16DATAWORD
    #else
    #define WAV16DATAWORD _huge
    #endif
    
    class MWAVE
    {
    private:
    BOOL OpenFlage;
    DWORDDataSize;
    HGLOBALhData;
    WAVDATA*lpData;
    
    PCMWAVEFORMAT pFormat;
    WAVEHDRWaveHead;
    HWAVEOUT hWaveOut;
    
    public:
    MWAVE(){memset(this,0,sizeof(MWAVE));};
    ~MWAVE(){Close();};
    int Open(char*);//打开一个WAV文件
    int Play(HWND);//播放一个WAV文件
    int Add(char*);//往正在播放的WAV设备中添加WAV                                       文件
    int Stop();//停止播放
    int Close();//关闭设备
    };
    
    #endif // !defined(AFX_WAVEMIX_H__54F4AF66_CE37_11D1_94F8_0000B431BBA1__INCLUDED_)
    
    // wavemix.cpp:Defines the class behaviors for the application.
    //
    
    #include "stdafx.h"
    #include "wavemix.h"
    #include "wavemixDlg.h"
    
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = __FILE__;
    #endif
    
    ///////////////////////////////////////
    // wavemix Class
    //////////////////////////////////////
    
    int MWAVE::Open(char* name)
    {
    HMMIO hMmio;
    MMCKINFO pinfo;
    MMCKINFO cinfo;
    
    if(hMmio)Close();
    
    //打开WAV文件,返回一个HMMIO句柄
    hMmio=mmioOpen(name,NULL,MMIO_READ);
    if(!hMmio)return FALSE;
    OpenFlage=1;
    
    //查找父块"wave";
    pinfo.fccType=mmioFOURCC('W','A','V','E');
    if(mmioDescend(hMmio,&pinfo,NULL,MMIO_FINDRIFF))goto FALSE_END;
    
    //查找子块"fmt"  parent"riff";
    cinfo.ckid=mmioFOURCC('f','m','t',' ');
    if(mmioDescend(hMmio,&cinfo,&pinfo,MMIO_FINDCHUNK))
                 goto FALSE_END;
    
    mmioRead(hMmio,(LPSTR)&pFormat,sizeof(PCMWAVEFORMAT));//cinfo.cksize);
    if(pFormat.wf.wFormatTag!=WAVE_FORMAT_PCM)
                 goto FALSE_END;
    
    //跳入块"FMT"
    mmioAscend(hMmio,&cinfo,0);
    
    //查找数据块
    cinfo.ckid=mmioFOURCC('d','a','t','a');
    if(mmioDescend(hMmio,&cinfo,&pinfo,MMIO_FINDCHUNK))
                 goto FALSE_END;
    DataSize=cinfo.cksize;
    
    //读取数据
    hData=GlobalAlloc(GMEM_MOVEABLE
                             | GMEM_SHARE,DataSize);
    lpData=(WAVDATA*)GlobalLock(hData);
    if( !hData || !lpData ) goto FALSE_END;
    if(mmioRead(hMmio,(HPSTR)lpData,DataSize)
                                            !=(LRESULT)DataSize)
                 goto FALSE_END;
    
    //close and return
    mmioClose(hMmio,MMIO_FHOPEN);
    return TRUE;
    
    FALSE_END:
    if(hMmio)mmioClose(hMmio,MMIO_FHOPEN);
    if(lpData)LocalUnlock(hData);
    if(hData)GlobalFree(hData);
    memset(this,0,sizeof(MWAVE));
    return 0;
    }
    
    int MWAVE::Play(HWND hP)
    {
    if(!OpenFlage)return FALSE;
    
    //检测系统播放功能
    if(waveOutOpen(NULL,WAVE_MAPPER,
                           (WAVEFORMATEX*)&pFormat,NULL,
                            NULL,WAVE_FORMAT_QUERY))
                return Close();
    
    if(waveOutOpen(&hWaveOut,WAVE_MAPPER,
                      ( WAVEFORMATEX*)&pFormat,(DWORD)hP,
                                     0,CALLBACK_WINDOW))
                return Close();
    
    WaveHead.lpData=(LPSTR)lpData;
    WaveHead.dwBufferLength=DataSize;
    WaveHead.dwFlags=0L;
    WaveHead.dwLoops=0L;
    
    //往WAV设备中添加数据
    
    if(waveOutPrepareHeader(hWaveOut,&WaveHead,
             sizeof(WAVEHDR)))
    
               return Close();
    
    if(waveOutWrite(hWaveOut,&WaveHead,sizeof(WAVEHDR)))
               return Close();
    
    return TRUE;
    }
    
    //#define min(a, b)  (((a) < (b)) ? (a) : (b))
    
    int MWAVE::Add(char* name)
    {
    register int x;
    if(!OpenFlage)return Open(name);
    
    MWAVE wav;
    if(!wav.Open(name))return FALSE;
    
    MMTIME time;
    //获得WAV文件当前播放位置
    
    time.wType=TIME_BYTES;
    
    if(waveOutGetPosition(hWaveOut,&time,sizeof(MMTIME)))
    time.u.cb=0;
    DWORD start=((time.u.cb>>1)<<1);
    DWORD end=min(DataSize_start,wav.DataSize);
    
    register WAVDATA* lpd=lpData+start;
    for(register DWORD i=0;i<end;i++)
    {
    
    
    //将两组WAV文件数据相加,并检测数据大小是否合法,如果//数据大小越界,则分别取最大值和最小值
    x=(((*(lpd+i))+(*(wav.lpData+i))))_128;
    
    
    if(x<0)x=0;
      if(x>255)x=255;
    *(lpd+i)=(BYTE)(x);
    }
    
    return TRUE;
    }
    
    int MWAVE::Stop()
    {return !waveOutReset(hWaveOut);}
    
    int MWAVE::Close()
    {
    if(hWaveOut)
    {
    waveOutReset(hWaveOut);
    waveOutClose(hWaveOut);
    waveOutUnprepareHeader(hWaveOut,&WaveHead,
                                                 sizeof(WAVEHDR));
    }
    if(lpData)LocalUnlock(hData);
    if(hData)GlobalFree(hData);
    memset(this,0,sizeof(MWAVE));
    return 0;
    }
    

    3多媒体图形图像技术

    现在我们讲述windows多媒体中最重要,最核心的技术──图形技术。对于Windows的图形图像技术来说,包括基本的GDI绘制对像如点、线、矩形、图像和位图文件,引而广之所有的动画文件都要利用到windows图像图形技术编程。

    本章节我们主要讲述Bmp文件实现过程、调色板应用,及一些Bmp图像合成技术,例如:透空技术、Bmp动画技术等。

    Bmp文件结构

    Bmp文件由以下几个部分组成:文件头、位图信息头、调色板、数据区。下面这张图显示了Bmp文件结构:

    Windows位图显示的必要条件:

    我们分析一下一个Windows API函数:

    int SetDIBitsToDevice(hdc,uXDest,uYDest,uWidth,uHeight,uXSrc,uYSre,

    uStartScan,cScanLines,lpvBits,lpbmi,fuColorUse)

    请查看这个函数的第十、十一个参数lpvBits,lpbmi。其中lpvBits指明了指向内存中BMP数据的地址,lpbmi指向一个BITMAPINFO的数据结构,只要有了这两个参数,一个BMP位图就被确定了。大家可以看到绝大多数的Windows图形图像浏览软件所能分析的文件格式,例如jpg、gif、pcx、tif等等,都是先在内存中建一个数据缓冲区,再根据图形图像格式建立一个BITMAPINFO的数据结构,再利用SetDIBitsToDevice函数写到屏幕上。我们下面几个实例,也基本上采用这个方式。

    Windows的调色板

    Windows的显示设备可以显示出成千上万种颜色,但是,在同一个屏幕上能同时显示的颜色数并不是成千上万种,我们把显示设备分为单色、十六色、二百五十六色、增强色、真彩色,或者是按照显示位区分成1bit,4bit,8bit,16bit,24bit。由于Windows的理论显示数和实际显示数并不相符,因此需要一个方案来解决这个问题,这就要用到调色板这个概念。为何要使用调色板我们在此并不作详细讨论,只是有一点要弄明白,只有十六色和二百五十六色位图才需要调色板。

    下面是使用调色板的方法

    首先要读入一个位图文件,再判断这个位图文件的颜色数,假如这个文件是十六色或是二百五六色,那么这个文件中必会有一个调色板信息,读取这个调色板信息,将这个调色板信息转为一个LOGPALETTE的结构,根据这个结构生成逻辑调色板,然后每次在要显示位图前,使用SelectPalette函数将逻辑调色板选入设备描述表hDC,再使用RealizePalette函数实现这个调色板,当显示完成后再使用SelectPalette函数,将旧的调色板选择回来,一个调色板调用过程就完成了。

    构造windows图像处理类库

    我们假设这个类库包含以下功能:

    1. 处理调色板;
      1. .生成逻辑调色板;
      2. 实现调色板;
    2. 处理BMP文件
      1. 读取BMP图像;
      2. 显示图像;
      3. 实现以上“处理调色板”的功能;
    3. 处理FLC动画
      1. 读取FLC动画文件;
      2. 播放参数设置;
      3. 显示图像;
      4. 实现以上“处理调色板”的功能;

    通过以上假设我们看到要设计的类库有一些公共特点:

    1.都要处理调色板;

    2.除了“1”以外,“2”和“3”均包含了windows图像显示的必要条件:结构

    BITMAPINFO和一块bitmap数据区。

    为此我们首先构造一个处理调色板的类,这个类的实现上述“处理调色板”的功能。为什么要单独处理调色板而不让它附属于“处理BMP文件”或是“处理FLC动画”,其原因是:在大规模多媒体编程中,往往有几十或几百个BMP图像或动画,却只需要一两个调色板,过度泛滥的调色板往往造成版面切换过程中的闪烁,并浪费内存空间,因此要单独处理调色板。

    其次我们构造一个处理DIB图像的类:在这个类里,核心为两个参数(结构BITMAPINFO和一块bitmap数据区)和一个调用API函数SetDIBitsToDevice()的显示函数Show()。这个类继承于调色板类,因为处理图像文件必须处理调色板。并成为其他图像文件的基类,因为要处理图像文件必须要有这两个结构和函数。

    有了这两个基类后,我们再在这两个基类的基础上构造其他类。

    // MyBmp.h: interface for the MyBmp class.
    //
    //////////////////////////////////////
    
    #if !defined(AFX_MYBMP_H__34151075_C57B_11D1_94F8_0000B431BBA1__INCLUDED_)
    #define AFX_MYBMP_H__34151075_C57B_11D1_94F8_0000B431BBA1__INCLUDED_
    
    #if _MSC_VER >= 1000
    #pragma once
    #endif // _MSC_VER >= 1000
    
    #define FALSERETURN {_lclose(hFile);return 0;}
    #define IMAGECOLORS(x,y)(1<<((x)*(y)))
    //单行BMP数据的字节数
    #define DWORD_WBYTES(x)((((x)+31UL)>>5)<<2)
    
    #define min(a, b)  (((a) < (b)) ? (a) : (b))
    
    #ifdef WIN32
    #define BMPDATABYTE
    #else
    #define BMPDATABYTE _huge
    #endif
    // #include "test.cpp"
    
    #define PALVERSION      0x0300
    #define DIB_STORAGEWIDTH(x) ((x + 3) & ~3)
    #define WIDTHBYTES(i)((i+31)/32*4)
    #define MAXPALETTE256  /* max. # supported palette entries */
    #define MAXLOADFLCNUMBER255
    
    class PALETTE
    {
    private:
    HPALETTE             hOldPal;
    protected:
    HPALETTE hPal;
    
    public:
    PALETTE(){memset(this,0,sizeof(PALETTE));}
    ~PALETTE(){if(hPal)DeleteObject(hPal);};
    HPALETTE CreatePal(BITMAPINFO*);
    HPALETTE CreatePal(LPLOGPALETTE Pal);
    HPALETTE GetPal(){return hPal;}
    UINT SetPal(HDC);
        void ResetPal(HDC);
    
    };
    
    
    class MYDIB : public PALETTE
    {
    
    protected:
    HGLOBALhData;
    struct
    {
    BITMAPINFOHEADERb;
    RGBQUADr[256];
    }p;
    
    public:
    LPBITMAPINFO lpInfo;
    LPBITMAPINFOHEADER lpInfoHead;
    
    MYDIB();
    ~MYDIB();
    int Show(HDC,int,int,int,int,int,int,int,int,DWORD);
    inline int Show(HDC hDC,int x1,int y1,int x2,int y2,
                                      int x3,int y3,int x4,int y4)
    {return Show(hDC,x1,y1,x2,y2,x3,y3,x4,y4,SRCCOPY);}
    inline int Show(HDC hDC,int x,int y)
    {return Show(hDC,x,y,0,0,0,0,0,0,SRCCOPY);}
    inline int Show(HDC hDC)
    {return Show(hDC,0,0,0,0,0,0,0,0,SRCCOPY);}
    
    Show(MYDIB*,int,int,int,int,int,int,BYTE,BYTE,BYTE,
                                                   BYTE,BYTE,BYTE);
    Show(MYDIB*,int,int,int,int,int,int,register
                                               BYTE,register BYTE);
    
    };
    
    class BMP : public MYDIB
    {
    
    public:
    int Open(LPCSTR,int);
    int inline Open(LPCSTR name){return Open(name,0);}
    };
    
    #endif // !defined(AFX_MYBMP_H__34151075_C57B_11D1_94F8_0000B431BBA1__INCLUDED_)
    
    // MyBmp.cpp: implementation of the MyBmp class.
    //
    //////////////////////////////////////
    
    #include "stdafx.h"
    #include "MyBmp.h"
    
    #ifdef _DEBUG
    #undef THIS_FILE
    static char THIS_FILE[]=__FILE__;
    #define new DEBUG_NEW
    #endif
    
    //////////////////////////////////////
    // Construction/Destruction
    //////////////////////////////////////
    
    //////////////////////////////////////
    //class PALETTE
    //////////////////////////////////////
    
    UINT PALETTE::SetPal(HDC hDC)
    {
    if(hPal)
    {
    SelectPalette(hDC,hPal,0);
    return RealizePalette(hDC);
    }
    else return FALSE;
    }
    
    void PALETTE::ResetPal(HDC hDC)
    {
    if(hOldPal)
    {
    SelectPalette(hDC,hOldPal,0);
                hOldPal=0;
    }
       }
    
    HPALETTE PALETTE::CreatePal(LPLOGPALETTE Pal)
    {
    if(hPal)DeleteObject(hPal);
    if(Pal_>palNumEntries<=256)hPal=CreatePalette(Pal);
    return hPal;
    }
    
    //////////////////////////////////////
    //class DIBPALETTE
    //////////////////////////////////////
    
    HPALETTE PALETTE::CreatePal(BITMAPINFO* info)
    {
    
    struct
    {
    WORD         palVersion;
    WORD         palNumEntries;
    PALETTEENTRY palPalEntry[256];
    } p;
    LPLOGPALETTE Pal=(LPLOGPALETTE)&p;
    
    Pal_>palVersion=0x300;
    Pal_>palNumEntries=
    /*min*/((WORD)IMAGECOLORS(info_>bmiHeader.biBitCount,1));//,info_>bmiHeader.biClrUsed);
    
    for(int i=0;i<Pal_>palNumEntries;i++)
    {
    Pal_>palPalEntry[i].peRed=info_>bmiColors[i].rgbRed;
    Pal_>palPalEntry[i].peGreen=
                            info_>bmiColors[i].rgbGreen;
    Pal_>palPalEntry[i].peBlue=info_>bmiColors[i].rgbBlue;
    Pal_>palPalEntry[i].peFlags=PC_NOCOLLAPSE;
    }
    
    return PALETTE::CreatePal(Pal);
    }
    
    //////////////////////////////////////
    // MYDIB Class
    //////////////////////////////////////
    
    MYDIB::MYDIB()
    {
    memset(this,0,sizeof(BMP));
    lpInfo=(LPBITMAPINFO)&p;
    lpInfoHead=(LPBITMAPINFOHEADER)&p;
    }
    
    MYDIB::~MYDIB()
    {
    if(hData)GlobalFree(hData);
    }
    
    int MYDIB::Show(HDC hDC,int x1,int y1,int x2,int y2,int x3,
                                 int y3,int x4,int y4,DWORD Rop)
    {
    if(x2<=0)x2=(int)lpInfoHead_>biWidth+x2;
    if(y2<=0)y2=(int)lpInfoHead_>biHeight+y2;
    if(x4<=0)x4=(int)lpInfoHead_>biWidth+x4;
    if(y4<=0)y4=(int)lpInfoHead_>biHeight+y4;
    //if(w_hp)SetPalette(hDC);
    BMPDATA* Data=(BMPDATA*)GlobalLock(hData);
    //int i=StretchDIBits(hDC,x1,y1,x2,y2,x3,y3,x4,y4,
                                     Data,Info,DIB_RGB_COLORS,Rop);
    int i=StretchDIBits(hDC,x1,y1,x2,y2,x3,
            (int)lpInfoHead_>biHeight_y3_y4,x4,y4,Data,lpInfo,
                                              DIB_RGB_COLORS,Rop);
    GlobalUnlock(hData);
    return i;
    }
    
    int MYDIB::Show(MYDIB* dib,int x1,int y1,int x2,int y2,int x3,int y3,
    BYTE r1,BYTE g1,BYTE b1,BYTE r2,BYTE g2,BYTE b2)
    {
    register DWORD c1=(((DWORD)r1)<<16)+(((DWORD)g1)<<8)+b1;
    register DWORD c2=(((DWORD)r2)<<16)+(((DWORD)g2)<<8)+b2;
    register DWORD c;
    register DWORD *rq=(DWORD*)p.r;
    if(!dib_>hData)
    {
    memcpy(dib,this,sizeof(MYDIB));
    dib_>lpInfo=(LPBITMAPINFO)&(dib_>p);
    dib_>lpInfoHead=(LPBITMAPINFOHEADER)&(dib_>p);
    dib_>lpInfoHead_>biWidth=x2+x1;
    dib_>lpInfoHead_>biHeight=y2+y1;
    DWORD Size=dib_>lpInfoHead_>biWidth
                     *dib_>lpInfoHead_>biHeight
                     *IMAGECOLORS(dib_>lpInfoHead_>biBitCount,1)/8;
    dib_>hData=GlobalAlloc(GMEM_FIXED,Size);
    }
    
    x2=min(x2,(int)dib_>lpInfoHead_>biWidth _x1);
    y2=min(y2,(int)dib_>lpInfoHead_>biHeight_y1);
    
    BMPDATA *Data1=(BMPDATA*)GlobalLock(hData);
    BMPDATA *Data2=(BMPDATA*)GlobalLock(dib_>hData);
    DWORD w1=DWORD_WBYTES(lpInfoHead_>biWidth
                           *lpInfoHead_>biBitCount);
    DWORD w2=DWORD_WBYTES(dib_>lpInfoHead_>biWidth
                           *dib_>lpInfoHead_>biBitCount);
    Data1+=(w1*(lpInfoHead_>biHeight_y3_y2)
                              +x3*lpInfoHead_>biBitCount/8);
    Data2+=(w2*(dib_>lpInfoHead_>biHeight_y1_y2)
                           +x1*dib_>lpInfoHead_>biBitCount/8);
    for(int j=0;j<y2;j++)
    {
    for(register int i=0;i<x2;i++)
    {
    c=*(rq+*(Data1+i));
    if((c<c1)||(c>c2)||((WORD)c<(WORD)c1)
                          ||((WORD)c>(WORD)c2)
                          ||((BYTE)c<(BYTE)c1)
                    ||((BYTE)c>(BYTE)c2))*(Data2+i)=*(Data1+i);
    }
    Data1+=w1;
    Data2+=w2;
    }
    
    GlobalUnlock(hData);
    GlobalUnlock(dib_>hData);
    return TRUE;
    }
    
    int MYDIB::Show(MYDIB* dib,int x1,int y1,int x2,int y2,int x3,
                           int y3,register BYTE x,register BYTE y)
    {
    register BMPDATA d;
    
    if(!dib_>hData)
    {
    memcpy(dib,this,sizeof(MYDIB));
    dib_>lpInfo=(LPBITMAPINFO)&(dib_>p);
    dib_>lpInfoHead=(LPBITMAPINFOHEADER)&(dib_>p);
    dib_>lpInfoHead_>biWidth=x2+x1;
    dib_>lpInfoHead_>biHeight=y2+y1;
    DWORD Size=(dib_>lpInfoHead_>biWidth+1)
                      *dib_>lpInfoHead_>biHeight
                      *dib_>lpInfoHead_>biBitCount/8;
                    //IMAGECOLORS(dib_>lpInfoHead_>biBitCount,1)/8;
    dib_>hData=GlobalAlloc(GMEM_FIXED,Size);
    }
    
    x2=min(x2,(int)dib_>lpInfoHead_>biWidth _x1);
    y2=min(y2,(int)dib_>lpInfoHead_>biHeight_y1);
    BMPDATA *Data1=(BMPDATA*)GlobalLock(hData);
    BMPDATA *Data2=(BMPDATA*)GlobalLock(dib_>hData);
    DWORD w1=DWORD_WBYTES(lpInfoHead_>biWidth
                                  *lpInfoHead_>biBitCount);
    DWORD w2=DWORD_WBYTES(dib_>lpInfoHead_>biWidth
                                  *dib_>lpInfoHead_>biBitCount);
    Data1+=(w1*(lpInfoHead_>biHeight_y3_y2)
                  +x3*lpInfoHead_>biBitCount/8);
    Data2+=(w2*(dib_>lpInfoHead_>biHeight_y1_y2)
                  +x1*dib_>lpInfoHead_>biBitCount/8);
    
    for(int j=0;j<y2;j++)
    {
    for(register int i=0;i<x2;i++)
    {
    d=*(Data1+i);
    if((d<x)||(d>y))*(Data2+i)=d;
    }
    Data1+=w1;
    Data2+=w2;
    }
    
    GlobalUnlock(hData);
    GlobalUnlock(dib_>hData);
    return TRUE;
    }
    
    //////////////////////////////////////
    //class bmp
    //////////////////////////////////////
    
    int BMP::Open(LPCSTR File,int sty)  //sty:   0:enable hpal  1:disnnable hpal
    {
    BITMAPFILEHEADER FileHead;
    int i;//,j,k;
    HFILEhFile;
    DWORDuBytes;
    DWORDSize;
    
    hFile=_lopen(File,OF_READ);
    if(hFile==HFILE_ERROR)return 0;
    
    //读取文件头
    
    i=_lread(hFile,&FileHead,sizeof(BITMAPFILEHEADER));
    if(i==HFILE_ERROR) FALSERETURN;//goto BMP_FALSE_END;
    //Type=FileHead.bfType;
    
    //读取信息头
    i=sizeof(BITMAPINFOHEADER)+sizeof(RGBQUAD)*256;
    _lread(hFile,lpInfoHead,i);
    
    //建立调色板
    
    if(!sty)CreatePal(lpInfo);
    
    uBytes=_llseek(hFile,0,2);
    if ((FileHead.bfSize)>uBytes) FALSERETURN;//goto BMP_FALSE_END;
    
    if(hData)GlobalFree(hData);
    Size=uBytes_FileHead.bfOffBits;
    hData=GlobalAlloc(GMEM_FIXED,Size);
    BMPDATA * Data=(BMPDATA*)GlobalLock(hData);
    
    //读取数据
    _llseek(hFile,FileHead.bfOffBits,0);
    
    for(;_lread(hFile,Data,RBLOCK)==RBLOCK;Data+=RBLOCK);
    GlobalUnlock(hData);
    _lclose(hFile);
    return TRUE;
    }
    

    在MyBmp.h文件中为了构造BMP框架,我们定义了三个类:class PALETTE

    class DIBPALETTE 、class MYDIB 。其中PALETTE是基类。DIBPALETTE继承了类PALETTE,而类MYDIB又继承了类DIBPALETTE。

    • class PALETTE:

    在此类中,我们定义了两个成员变量hPal和hOldPal、七个成员函数。这七个成员函数功能如下:其中两个分别是构造函数和析构函数,两个函数CreatePal

    (BITMAPINFO*)、CreatePal(LPLOGPALETTE )根据指定参数完成构造调色板的作用,一个函数SetPal(HDC)实现调色板,一个函数ResetPal(HDC)恢复调色板,一个函数GetPal()获得调色板。

    • class MYDIB:

    在此类中,我们定义了两个核心成员变量p和hData,其中p是一个自定义结构,它包含一个BMPINFO头信息和一个调色板,hData是指向内存中一块数据的句柄,另外两个参数lpInfo和lpInfoHead实际上是指向结构p的指针。四个成员函数Show()的内核是API函数SetDIBitsToDevice()。它们的功能是根据结构p和句柄hData把图像显示到屏幕上。

    • class BMP:

    在此类中,我们只定义了两个成员函数Open,它们的功能是打开一个BMP文件,并将文件内容填入其基类的参数中。前面我们提到过在多媒体编程中需要用公共调色板,但有时也需要用私有调色板,因此在Open函数中第二个参数指定了这个区别,若参数为0则构造自己的hPal,否则自己的hPal无效。

    实例分析

    在这个实例中,我们将调入一个BMP文件,并把它显示到屏幕上,程序过程如下:

    建立一个对话框属性的应用程序,然后是加入光盘中提供的源文件“mybmp.h”和“mybmp.cpp”,在文件“showbmpdlg.h”头加上“#include "mybmp.h"”,在类“CShowbmpDlg”定义中加入成员变量“BMP bmp”,在类“CShowbmpDlg”的成员函数OnInitDialog()中用“bmp.Open("1.bmp")”读取文件“1.bmp”,在OnPaint()中加入以下调色板实现函数和显示函数

    bmp.SetPal(dc.m_hDC)

    bmp.Show(dc.m_hDC)

    bmp.ResetPal(dc.m_hDC)

    注意SetPal()和ResetPal()要配对使用,SetPal()用在Show()前,ResetPal()用在Show()后。编译并运行之。

    4图像合成

    假如要实现一个动画例如一只老鼠从屏幕左边往右边跑过去,一般的书上是这么介绍的:首先做一个老鼠的画片,再画一张黑白老鼠掩模图片。首先用掩模图处理屏幕,再用掩模处理老鼠图片,最后把处理过的老鼠贴到屏幕上,前后要处理三个BitBlt函数。而且这样处理过程会使屏幕出现明显闪烁。要想制止闪烁还要先做一个兼容DC,先把屏幕图片拷贝至兼容DC,再在兼容DC上处理完老鼠后在再拷贝回屏幕。前后要用到五个BitBlt函数。图片比较小还好,若是图片很大,那速度简直就是“去年今日此电脑,人面老鼠相映红,人面不知何处去,老鼠还在慢慢爬”。是否有其他的解决方法呢?

    实现透明位图的方式

    1. 最愚蠢的办法:直接在屏幕上逐点用SetPixel函数画图。
    2. 一般的方法:用BitBlt函数作至少三个运算。
    3. 最快的方法:直接写屏。
    4. 性价比最高的办法:直接写数据缓冲区。

    四种方式的讨论:

    1. 对于初搞图像处理的程序员来说,似乎逐点画过去的办法是第一选择,然而事实证明这是世界上速度最慢的方法,用它实现的最理想的动画效果是“去年一滴相思泪,今日方流到嘴边”。
    2. BitBlt:前面已介绍过,此处不再介绍。
    3. 直接写屏:Dos下这种方式用得比较多,在windows环境下编程,windows3.1环境下只能加挂WinG才能实现,在win95或NT下可用函数CreateDIBSection()或是使用MicroSoftDirectX函数实现。这种方式我们将出专著介绍。此处不作讨论。
    4. 写数据缓冲区:这个方法对环境要求较小,不需外挂软件,兼容于3.1和95、NT,速度也还可以,它的原理与直接写屏相似,而且可以方便地移植到直接写屏方式中去。我们将在此介绍此方法。

    读写数据缓冲区:

    大家可能还记得在前面介绍的class MYDIB,里面有两个参数,一个是bmp信息头,一个是bmp数据区,大家是否能想象得到假如修改了bmp数据区的数据,再显示图像会有什么结果?这块数据区,就是我们要使用的数据缓冲区。

    透明位图

    要想实现透明位图,首先要有两张图片,一张作为源位图,一张作为目的位图,程序员要把源位图贴到目的位图上,并且要指明什么颜色要屏蔽掉,为此,我们在class MYDIB上增加了一个函数Show(MYDIB* dib,int x1,int y1,int x2,int y2,int x3,int y3,BYTE r1, BYTE g1,BYTE b1,BYTE r2,BYTE g2,BYTE b2),这个函数的用法有点类似于BitBlt函数,它的意思为:把己方缓冲区内的数据拷贝到类dib的缓冲区中去,其中从RGB(r1,g1,b1)至RGB(r2,g2,b2)的颜色为透明色,x1、y1、x2、y2、x3、y3为目标坐标、拷贝范围、源坐标,其意义与BitBlt相同。在Show函数的实现过程中,我们首先算出要改变的源数据、目标数据地址,然后分析要拷贝的数据颜色是否属于屏蔽色,假如是屏蔽色,则不拷贝数据,否则拷贝。

    另外一种透明位图方式

    透明色固然是一种比较简单的实现方式,但是有的时候也需要另外一种实现方式,这就是直接指定颜色索引方式,我们可以指定在调色板中某某号至某某号为透明色。因此,在class MYDIB中再增加一个函数Show(MYDIB* dib,int x1,int y1,int x2,int y2,int x3,int y3,register BYTE x,register BYTE y),这个函数的原理与前一种方式差不多,只是比前一种方式少了四个参数,由以颜色指定透明色改成以颜色索引指定透明色。

    透明位图的刷新速度

    到底更改数据缓冲区方式的速度快,还是BitBlt速度快?要是BitBlt速度快的话,以前的一番心血岂非成了滚滚长江东逝水,为此我们要用实例分析一下,建立一个名为Tp的基于对话框的程序,加入源程序mybmp.cpp和mybmp.h,在tpdlg.h文件头中加入#include "mybmp.h",在类CTPDlg中加入两个成员变量bmp1和bmp2。在窗口初始化时设置定时器,打开文件“1.bmp”、“2.bmp”,在定时器消息响应过程中完成拷贝和刷新过程,编译并运行程序。我们可以看到一个“AllTime”参数,它显示刷新256张位图需要大约20_21秒左右。现在注释掉定时器消息响应过程中的透底函数bmp1.Show((MYDIB*)&bmp2,0,0,640,480,0,0, 0,0,0,i,i,i),再看刷新256张位图大约需要15_16秒,这是单纯使用函数StretchDIBits所需的时间。可见此处一个透明位图完成时间相当于一点四个BitBlt时间,比照BitBlt方式的三个BitBlt时间(差效果)、五个BitBlt时间(好效果)要好得多。当然,这与直接写屏比又差得多了。

    现在再将透底函数换成bmp1.Show((MYDIB*)&bmp2,0,0,640,480,0,0, 0,i),我们不由惊喜地看到现在刷新256张位图的时间为16_17秒,几乎可以认为,缓冲区读写时间已经

    可以忽略不计。

    图5.3

    实例分析

    在这个实例中,我们要实现一个动画,背景是一位绝代佳人,前面有一只狗牵着它的宠物跑来跑去。素材需要五张图片,其中背景一张,动画四张。我们分析一下它的实现方式:

    在类CMovieDlg中,我们首先用语句BMP bmp[5]定义了五张图片,然后用语句MYDIB temp定义了一个临时图片。在对话框初始化过程函数中分别读入五张位图,设定定时器为一百毫秒,在定时器响应函数中操作过程如下:首先将背景写入临时图片,再将小狗透去白色写入临时图片,最后将临时图片写上屏幕。

    图5.4

    5 FLC动画

    FLC和FLI动画同属于AutoDesk公司的产品,它们采用帧与帧之间求差及单帧RLE编码的方式,其特点是易于解码和编码。当然,它们没有为音频提供便利限制了它的应用范围。在FLC和FLI之间,FLI由于大小仅局限于320x200,调色板仅64色,最多4000帧长度,使它已差不多退出了历史舞台。因此,我们在此不再讲述FLI,只介绍FLC动画原理及实现方式。

    FLC文件的结构

    FLC文件的结构图示如下:

    FLC文件头数据结构如下:

    帧头数据结构:

    块头数据结构:

    块的类型及解释:

    FLC动画播放源程序简介

    我们分析一下FLC动画源程序:打开文件flcw.h,可以看见文件中定义了FLC文件头、帧头、块头及块类型的宏。另外我们可以发现类FLCW是从类MYDIB中派生来的,这是因为不管FLC动画本身如何复杂,只要是往屏幕上画图,最后都是要在内存中转换成位图形式。FLC动画实现过程中,在Open阶段就生成了一个大小与FLC动画相同的内存位图,以后每次读取下一帧,就把数据写入内存位图,再将内存位图贴到屏幕上。

    // FLCW.h: interface for the FLCW class.
    //
    //////////////////////////////////////
    
    #if !defined(AFX_FLCW_H__2A3B58A3_C964_11D1_94F8_0000B431BBA1__INCLUDED_)
    #define AFX_FLCW_H__2A3B58A3_C964_11D1_94F8_0000B431BBA1__INCLUDED_
    
    #if _MSC_VER >= 1000
    #pragma once
    #endif // _MSC_VER >= 1000
    
    #ifdef__cplusplus
    extern "C" {
    #endif
    
    /*#ifndefGlobalPtrHandle
    #define     GlobalPtrHandle(lp)         \
    ((HGLOBAL)LOWORD(GlobalHandle(FP_SEG(lp))))
    #endif
    
    #defineIsOverSegAlians(lp,off)\
                  (((DWORD)FP_OFF(lp)+(WORD)off)>0XFFFFL?1:0)
    
    #include "mybmp.h"
    
    
    //#define PALVERSION      0x300
    #define DIB_STORAGEWIDTH(x) ((x + 3) & ~3)
    #define WIDTHBYTES(i)((i+31)/32*4)
    #define MAXPALETTE256  /* max. # supported palette entries */
    
    #pragma pack(2)
    
    
    /* Flic Header */
    typedef struct{
    longsize;/* Size of flic including this header. */
    WORDtype;/* Either FLI_TYPE or FLC_TYPE below. */
    WORDframes;/* Number of frames in flic. */
    WORDwidth;/* Flic width in pixels. */
    WORDheight;/* Flic height in pixels. */
    WORDdepth;/* Bits per pixel.  (Always 8 now.) */
    WORDflags;/* FLI_FINISHED | FLI_LOOPED ideally. */
    long speed;/* Delay between frames. */
    shortreserved1;/* Set to zero. */
    DWORDcreated;/* Date of flic creation. (FLC only.) */
    DWORDcreator;/* Serial # of flic creator.(FLC only.)*/
    DWORDupdated;/* Date of flic update. (FLC only.) */
    DWORDupdater;/* Serial # of flic updater.(FLC only.)*/
    WORDaspect_dx;/* Width of square rectangle. (FLC only.) */
    WORDaspect_dy;/* Height of square rectangle. (FLC only.) */
    char reserved2[38];/* Set to zero. */
    long oframe1;/* Offset to frame 1. (FLC only.) */
    long oframe2;/* Offset to frame 2. (FLC only.) */
    char reserved3[40];/* Set to zero. */
    } FLICHEAD;
    
    typedef FLICHEAD  *LPFLICHEAD ;
    /* Values for FlicHead.type */
    #define FLI_TYPE 0xAF11u/* 320x200 .FLI type ID */
    #define FLC_TYPE 0xAF12u/* Variable rez .FLC type ID */
    /* Values for FlicHead.flags */
    #define FLI_FINISHED 0x0001
    #define FLI_LOOPED 0x0002
    
    /* Optional Prefix Header */
    typedef struct{
    long size;/* Size of prefix including header. */
    WORDtype;/* Always PREFIX_TYPE. */
    short chunks;/* Number of subchunks in prefix. */
    char reserved[8];/* Always 0. */
    }PrefixHead;
    
    /* Value for PrefixHead.type */
    #define PREFIX_TYPE  0xF100u
    
    /* Frame Header */
    typedef struct{
    long size;/* Size of frame including header. */
    WORD type;/* Always FRAME_TYPE */
    short chunks;/* Number of chunks in frame. */
    char reserved[8];/* Always 0. */
    } FRAMEHEAD;
    
    typedef FRAMEHEAD  *LPFRAMEHEAD ;
    
    /* Value for FrameHead.type */
    #define FRAME_TYPE 0xF1FAu
    
    /* Chunk Header */
    typedef struct{
    LONG size;/* Size of chunk including header. */
    WORD type;/* Value from ChunkTypes below. */
    }CHUNKHEAD;
    
    typedef CHUNKHEAD  *LPCHUNKHEAD ;
    #pragma pack(4)
    
    enum ChunkTypes
    {
    COLOR_256 = 4,/* 256 level color pallette info. (FLC only.) */
    DELTA_FLC = 7,/* Word_oriented delta compression. (FLC only.) */
    COLOR_64 = 11,/* 64 level color pallette info. */
    DELTA_FLI = 12,/* Byte_oriented delta compression. */
    BLACK = 13,/* whole frame is color 0 */
    BYTE_RUN = 15,/* Byte run_length compression. */
    LITERAL = 16,/* Uncompressed pixels. */
    PSTAMP = 18,/* "Postage stamp" chunk. (FLC only.) */
    };
    
    typedef struct {
    BYTEr ;
    BYTEg ;
    BYTEb ;
    }COLOR ;
    typedef COLOR  *LPCOLOR ;
    
    typedef struct {
    WORDx ;
    WORDy ;
    WORD width ;
    WORDheight ;
    }WRYRECT ;
    
    
    //define load method
    #define FLC_NONE        0
    #defineFLC_LOADINMEM1
    //#define FLC_SKIPFIRST   2
    #define FLC_AUTO4
    
    #defineFLC_LOOPS0
    
    //defineerror message
    #define FLC_ERR_OPEN_1
    #defineFLC_ERR_HEADER_2
    #defineFLC_ERR_FRAME_3
    #defineFLC_ERR_CHUNK_4
    #defineFLC_ERR_COLOR_5
    
    #defineFLC_SUCCESS0
    
    //defineflc play status
    #defineFLC_STOPPED0
    #defineFLC_PLAYING1
    #defineFLC_PAUSED2
    #defineFLC_NOTREADY3
    
    typedef union tagWPT{
    short * w ;
    BYTE * ub ;
    char *b ;
    }WPT ;
    
    //if fps=NULL,this function is GetFlcSpeed
    // return is frame per second nulli is error;
    
    #ifdef __cplusplus
    }
    #endif
    
    #defineSUCCESSFUL0
    #defineFLCERR_READHEAD_1
    #defineFLCERR_NOTFLC_2
    #defineFLCERR_NOMEMORY_3
    #defineFLCERR_READFILE_4
    #defineFLCERR_READFRAME_5
    #defineFLCERR_READCHUNK_6
    #defineSKIPFRAME_7
    
    class FLCW : public MYDIB
    {
    private:
    PALETTE* pal;//0:havn't palette  !0:have palette
    WORD wm_Notify ;
    WORD width ;
    WORD height ;
    long size ;
    WORD frames ;
    DWORDspeed;
    WORD loops ;
    WORD curr_frame ;
    WORD curr_loop ;
    shortflags ;
    longoframe2 ;
    WORD StartFrame;
    DWORDdwNextTime;
    HFILEhFile ;
    HWNDhPlayWnd ;
    shortfStatus ;
    WORDxOrg ;
    WORDyOrg ;
    WRYRECTrcChange ;
    
    void DecodeColor(LPSTR Body) ;
    void DecodeBRun(LPSTR Body) ;
    void DecodeCopy(LPSTR Body) ;
    void DecodeBlack() ;
    void DecodeDeltaFli(LPSTR Body) ;
    void DecodeDeltaFlc(LPSTR Body) ;
    public:
    //FLCW(){memset(this,0,sizeof(FLCW));}
    FLCW()
    {
    lpInfoHead_>biSize = sizeof(BITMAPINFOHEADER) ;
    lpInfoHead_>biPlanes=1 ;
    lpInfoHead_>biBitCount=8 ;
    lpInfoHead_>biCompression=BI_RGB;
    }
    
    ~FLCW(){CloseFlc();}
    
    //BOOL SDIFlcMessageLoop(HWND hMainWnd,HACCEL hAccel,
                                                   MSG* msg);
    //BOOL MDIFlcMessageLoop(HWND hFrameWnd,HWND hClientWnd,
                                         HACCEL hAccel,MSG* msg);
    
    int Open(LPCSTR ,PALETTE* );
    BOOL CloseFlc() ;
    
    void PlayFlc(HWND hwnd,WORD xOrg,WORD yOrg,
                                               WORD wm_notify) ;
    void StopFlc() ;
    void PauseFlc() ;
    short QueryFlc() ;
    int Loop(WORD wLoops);
    float Speed(float fps);
    
    WORDGetTotalFrame();
    WORDGetCurFrame();
    
    BOOL ShowFlc(HDC hdc) ;
    
    BOOL FlcIdle() ;
    
    short ReadFrame() ;
    
    void PlayFlcAFrame();
    
    };
    
    #endif // !defined(AFX_FLCW_H__2A3B58A3_C964_11D1_94F8_0000B431BBA1__INCLUDED_)
    
    // FLCW.cpp: implementation of the FLCW class.
    //
    //////////////////////////////////////
    
    #include "stdafx.h"
    
    #include "flcw.h"
    
    #ifdef _DEBUG
    #undef THIS_FILE
    static char THIS_FILE[]=__FILE__;
    #define new DEBUG_NEW
    #endif
    
    //#include "test.cpp"
    //////////////////////////////////////
    // Construction/Destruction
    //////////////////////////////////////
    
    //modefy in 1998.4.4.10:50  TJ
    
    //////////////////////////////////////
    // Class FLCW
    //////////////////////////////////////
    /*
    void FLCW::DecodeColor(LPSTR Body)
    {
    shortstart=0 ;
    BYTE *cbuf=(BYTE *)(Body+2) ;
    shortops =*(short *)Body ;
    WORDcount ;
    shortnumber=0 ;
    shorti ;
    
    while(__ops >= 0)
    {
    start += *cbuf++ ;
    if((count = *cbuf++)==0)count = 256 ;
    number += count ;
    for(i = start;i < (start+count);i ++)
    {
    lpInfo_>bmiColors[i].rgbRed = *cbuf++ ;
    lpInfo_>bmiColors[i].rgbGreen = *cbuf++ ;
    lpInfo_>bmiColors[i].rgbBlue = *cbuf++ ;
    lpInfo_>bmiColors[i].rgbReserved = 0;
    }
    start+=count ;
    }
    lpInfo_>bmiHeader.biBitCount=8;
    lpInfoHead_>biClrUsed = number ;
    if(pal)CreatePal(lpInfo);
    
    }
    */
    void FLCW::DecodeColor(LPSTR Body)
    {
    int point=0 ;
    BYTE *cbuf=(BYTE *)(Body+2) ;
    shortops =*(short *)Body ;
    WORDcount ;
    inti;
    
    while(__ops >= 0)
    {
    point += *cbuf++ ;
    if((count = *cbuf++)==0)count = 256 ;
    i=point+count;
    for(;point < i;point ++)
    {
    lpInfo_>bmiColors[point].rgbRed = *cbuf++ ;
    lpInfo_>bmiColors[point].rgbGreen = *cbuf++ ;
    lpInfo_>bmiColors[point].rgbBlue = *cbuf++ ;
    lpInfo_>bmiColors[point].rgbReserved = 0/*PC_NOCOLLAPSE*/ ;
    }
    }
    lpInfo_>bmiHeader.biBitCount=8;
    lpInfoHead_>biClrUsed = point ;
    if(!pal)CreatePal(lpInfo);
    }
    
    void FLCW::DecodeBRun(LPSTR Body)
    {
    short x,y=0 ;
    WORDbytewidth ;
    charpsize ;
    LPSTRlpDib ;
    LPSTRlpTemp ;
        LPSTRt ;
    LPSTRt1 ;
    
    bytewidth = DIB_STORAGEWIDTH(width) ;
    
    lpDib = (LPSTR)GlobalLock(hData) ;
    if(!lpDib)return  ;
    
    lpTemp= lpDib;//(LPSTR)MoveToDibData(lpDib);
    t1=Body ;
    //t1++ ;
    t=lpTemp+(unsigned long)(height_1)*(unsigned long)bytewidth ;
    while(y<height){
    x=0 ;
    t1++ ;
    psize = 0 ;
    while(x<width){
    psize = *t1++ ;
    if(psize>=0){
    //            shortj ;
    memset(t,*t1++,psize) ;
    t += psize ;
    
    }
    else{
    psize = _psize ;
    memcpy(t,t1,psize);
    t+=psize ;
    t1+=psize ;
    }
    x+=psize ;
    }
    y++ ;
    t=lpTemp+(unsigned long)(height_y_1)*
    (unsigned long)bytewidth ;
    }
    GlobalUnlock(hData) ;
    rcChange.x = 0 ;
    rcChange.y = 0 ;
    rcChange.width = width ;
    rcChange.height=height ;
    
    }
    
    void FLCW::DecodeDeltaFlc(LPSTR Body)
    {
    shortlp_count,i ;
    shortopcount ;
    shortmin_x,min_y,max_x,max_y ;
    WORDbytewidth ;
    LPSTRtemp ;
    LPSTRt,t1 ;
    LPSTRlpDib ;
    WPTwpt ;
         shortj ;
    
    shortpsize ;
         signedcurr_x,curr_y ;
    
    min_x = 900 ;
    min_y = 900;
    max_x = 0;
    max_y = 0 ;
    
    curr_x=0 ;
    curr_y=height_1 ;
    
    bytewidth = WIDTHBYTES(width*8) ;
    lpDib = (LPSTR)GlobalLock(hData) ;
         if(!lpDib)return ;
    temp=lpDib;
    temp = temp+(DWORD)(height_1)*bytewidth ;
    t = temp ;
         i = 0 ;
    
    wpt.ub =(BYTE *)Body ;
    lp_count = *wpt.w++ ;
    while(i<=lp_count){
    if((opcount=*wpt.w++)>=0){
    i++ ;
    for(j = 0; j<opcount;j++){
    curr_x += *wpt.ub++ ;
    psize = *wpt.b ;
    t = temp+curr_x ;
    if(curr_x!=*(wpt.ub_1)||*wpt.ub!=1
                                ||*t!=*(wpt.ub+1)
                                ||*(t+1)!=*(wpt.ub+2)){
                    if(min_x>curr_x)min_x = curr_x ;
    }
                    wpt.b++ ;
    if((psize+=psize)>=0){
    memcpy(t,wpt.ub,psize);
    curr_x+=psize ;
    t += psize ;
    wpt.ub+=psize ;
    if (max_x<curr_x)max_x=curr_x ;
    }
    else{
                         BYTEj ;
    psize = _psize ;
                           curr_x+=psize ;
    psize=psize>>1 ;
    for(j=0;j<psize ;j++)
    {
    *(short *)t=*wpt.w ;
    t++;
                            t++ ;
                        }
    wpt.w++ ;
    if (max_x<curr_x)max_x=curr_x ;
    }
    }
    if(i==lp_count)break ;
    curr_x=0 ;
    curr_y__ ;
    if((WORD)min_y>(height_curr_y_1))
    min_y = height_curr_y_2 ;
    temp = temp_(DWORD)bytewidth ;
    continue ;
    }
    if(((WORD)opcount)&0x4000){//skip lines
    opcount = _opcount ;
    curr_y _= opcount ;
    temp = temp_(DWORD)opcount*(DWORD)bytewidth ;
    if((WORD)min_y>(height_curr_y_1))
    min_y = height_curr_y_2 ;
    curr_x = 0 ;
                continue ;
    }
    t = temp+(DWORD)bytewidth_1L ;
    t1 = (LPSTR)(wpt.w_1) ;
    *t=*t1 ;
    max_x=width ;
            continue ;
    }
    GlobalUnlock(hData);
    max_y = height_curr_y_1 ;
    if(min_y==900)min_y = 0 ;
    rcChange.x = min_x ;
    rcChange.y=min_y ;
    rcChange.width = max_x_min_x ;
    rcChange.height=max_y_min_y+1 ;
    return ;
    }
    
    void FLCW::DecodeDeltaFli(LPSTR Body)
    {
    short *w ;
    BYTE *cpt ;
    shortlines ;
    BYTEopcount ;
    charpsize ;
    LPSTRlpDib ;
    LPSTRt,temp ;
    WORDmin_x,min_y,max_x,max_y ;
    WORDcurr_x,curr_y ;
    shorti ;
    shortbytewidth ;
    
    bytewidth=WIDTHBYTES(width*8) ;
    
    min_x = 900 ;
    min_y = 900 ;
    max_x = 0;
    max_y = 0 ;
    
    w = (short *)Body ;
    cpt = (BYTE *)(w+2);
    lpDib=(LPSTR)GlobalLock(hData) ;
    if(!lpDib)return ;
    
    curr_y = *w++ ;
    lines = *w ;
    temp=lpDib+(DWORD)(height_1_curr_y)*bytewidth ;
    
    curr_y = height_curr_y_1 ;
    if((unsigned)min_y>(height_curr_y_1))
    min_y=height_curr_y_2  ;
    if((unsigned)max_y<(height_curr_y_1))
    max_y=height_curr_y_1 ;
    for(i=0;i<lines;i++){
    curr_x = 0 ;
    opcount = *cpt++ ;
    while(opcount>0){
    curr_x+=*cpt++ ;
    if((WORD)min_x>curr_x)min_x = curr_x ;
    if((WORD)max_x<curr_x)max_x = curr_x ;
    //t = lpDib+(Ulong)curr_y*(Ulong)bytewidth+curr_x ;
    t=temp+curr_x ;
    psize = *cpt++ ;
    if(psize<0){
    psize = _psize ;
    memset(t,*cpt++,psize);
    curr_x += psize ;
    t+=psize ;
    }
    else{
    memcpy(t,cpt,psize) ;
    t+=psize ;
    cpt+=psize ;
    curr_x+=psize ;
                }
    if(min_x>curr_x)min_x=curr_x ;
    if(max_x<curr_x)max_x = curr_x ;
                opcount__ ;
    }
    curr_y__ ;
            temp_=bytewidth ;
    if(min_y>(height_curr_y_1))min_y = height_curr_y_2;
    if(max_y<(height_curr_y_1)) max_y=height_curr_y_2 ;
        }
    GlobalUnlock(hData) ;
    rcChange.x = min_x ;
    rcChange.y=min_y ;
    rcChange.width=max_x_min_x ;
    rcChange.height=max_y_min_y+1 ;
        return ;
    }
    
    void FLCW::DecodeBlack()
    {
    DWORD s=GlobalSize(hData);
    LPSTR p=(LPSTR)GlobalLock(hData) ;
    memset(p,0,s);
    GlobalUnlock(hData);
    
    rcChange.x = 0 ;
    rcChange.y = 0 ;
    rcChange.width = width ;
        rcChange.height = height ;
    }
    
    void FLCW::DecodeCopy( LPSTR Body)
    {
    shortbytewidth ;
    LPSTRlpDib ;
    LPSTRt ;
    WORDi ;
    
    lpDib = (LPSTR)GlobalLock(hData) ;
    if(!lpDib)return ;
    bytewidth = WIDTHBYTES(width*8) ;
    
    t=lpDib;
    t=t+(DWORD)(height_1)*bytewidth ;
    for(i=0;i<height;i++){
    memcpy(t,Body,bytewidth) ;
    t_=bytewidth ;
    Body+=bytewidth ;
    }
    GlobalUnlock(hData) ;
    rcChange.x = 0 ;
    rcChange.y = 0 ;
    rcChange.width = width ;
    rcChange.height = height ;
    return;
    }
    
    int FLCW::Open(LPCSTR lpszFile,PALETTE* p)
    {
    ///////////////////////////////
    //func:
    //1:read FlicHead;
    ///////////////////////////////
    
    FLICHEADflicHead ;
    longoframe1;
    long        dibSize;
    
    if(hFile)_lclose(hFile);
    hFile = _lopen(lpszFile,OF_READ) ;
    if(hFile==HFILE_ERROR)return 0 ;
    
    if(_hread(hFile,(LPSTR)&flicHead,sizeof(FLICHEAD))!=sizeof(FLICHEAD)){
    _lclose(hFile);
    returnFALSE;//FLCERR_READHEAD ;
    }
    if(flicHead.type!=FLC_TYPE){
    _lclose(hFile);
    return FALSE;//FLCERR_NOTFLC ;
    }
    
    width=flicHead.width ;
    height=flicHead.height;
    size = flicHead.size_flicHead.oframe2 ;
    frames=flicHead.frames ;
    speed=flicHead.speed ;
    
    loops = 1 ;
    curr_frame = 0 ;
    curr_loop = 0 ;
    pal=p;
    
    //flags = nFlag ;
    oframe1 = flicHead.oframe1 ;
    oframe2=flicHead.oframe2;
    fStatus = FLC_NOTREADY ;
    
    dibSize=(LONG)(WIDTHBYTES(width*8)*(LONG)height);
    if(hData)GlobalFree(hData);
    hData=GlobalAlloc(GHND,dibSize) ;
    if(!hData)return FLCERR_NOMEMORY ;
    lpInfoHead_>biWidth=width ;
    lpInfoHead_>biHeight=height;
    lpInfoHead_>biSizeImage=dibSize ;
    
    _llseek(hFile,oframe1,0);
    return TRUE ;
    }
    
    BOOL FLCW::CloseFlc()
    {
    _lclose(hFile) ;
    
    returnTRUE ;
    }
    
    void FLCW::PlayFlcAFrame()
    {
    HDChdc ;
    
    curr_frame++;
    dwNextTime=speed+GetTickCount();
    
    if(ReadFrame()==SUCCESSFUL)
    {
    hdc = GetDC(hPlayWnd) ;
    if(pal)pal_>SetPal(hdc);
    else SetPal(hdc);
    Show(hdc,xOrg+rcChange.x,yOrg+rcChange.y,
                          rcChange.width,rcChange.height,
          rcChange.x,rcChange.y,rcChange.width,
                                                rcChange.height);
    if(pal)pal_>ResetPal(hdc);
    else  ResetPal(hdc);
    ReleaseDC(hPlayWnd,hdc) ;
    }
    
    if(curr_frame>frames)
    {
    curr_frame=1;
    _llseek(hFile,oframe2,0);
    curr_loop++;
    }
    
    return ;
    }
    
    short FLCW::ReadFrame()
    {
    HGLOBALhBody ;
    LPSTRlpBody ;
    //LPSTRlpBuf ;
    WORDnChunkNumber ;
    WORDi ;
    CHUNKHEADChunkHead ;
    WORDtype ;
    //longli ;
    longsize ;
    
    FRAMEHEADFrameHead ;
    if(_hread(hFile,&FrameHead,sizeof(FRAMEHEAD))
                  !=sizeof(FRAMEHEAD))return FLCERR_READFRAME ;
    if(FrameHead.type!=FRAME_TYPE)
             return FLCERR_READFRAME ;
    
    nChunkNumber=FrameHead.chunks ;
    
    for(i=0;i<nChunkNumber;i++){
    {
    if(_hread(hFile,&ChunkHead,sizeof(CHUNKHEAD))
                                            !=sizeof(CHUNKHEAD)){
    return FLCERR_READFRAME ;
    }
    type=ChunkHead.type ;
    size = ChunkHead.size_sizeof(CHUNKHEAD) ;
    #ifdef _MN_CDROM
    dwReadSize+=size;
    #endif
    }
    if(type==LITERAL)
    size+=2 ;
    if(size){
    LPSTRlpTemp ;
    hBody=GlobalAlloc(GMEM_MOVEABLE,size);
    lpBody=(LPSTR)GlobalLock(hBody) ;
    lpTemp = lpBody ;
    {
    if(_hread(hFile,(LPSTR)lpBody,size)!=size){
    GlobalUnlock(hBody) ;
    GlobalFree(hBody) ;
    return FLCERR_READCHUNK ;
    }
    }
    }
    switch(type){
    case COLOR_256 ://4,FLI_COLOR_256
    DecodeColor(lpBody) ;
    if(nChunkNumber==1)
                        memset(&rcChange,0,sizeof(RECT));
    break ;
    case DELTA_FLC ://7,FLI_WORLD_LC
    DecodeDeltaFlc(lpBody) ;
    break ;
    case DELTA_FLI ://12,FLI_LC
    DecodeDeltaFli(lpBody) ;
    break ;
    case BLACK ://13,FLI_BALCK
    DecodeBlack() ;
    break ;
    case BYTE_RUN ://15,FLI_BRUN
    DecodeBRun(lpBody) ;
    break ;
    case LITERAL ://16,FLI_COPY
    DecodeCopy(lpBody) ;
    break;
    case PSTAMP ://18,FLI_PREVIEW
                break ;
    }
    GlobalUnlock(hBody) ;
    GlobalFree(hBody) ;
    }
    if(nChunkNumber==0)
    {
    rcChange.x=0;
    rcChange.y=0;
    rcChange.width=0;
    rcChange.height=0 ;
    }
    
    return SUCCESSFUL ;
    }
    
    
    void FLCW::PlayFlc(HWND hwnd,WORD xorg,WORD yorg,WORD wm_notify)
    {
    wm_Notify=wm_notify ;
    xOrg=xorg ;
    yOrg=yorg ;
    hPlayWnd = hwnd ;
    
    if(fStatus!=FLC_PLAYING)
    {
    fStatus=FLC_PLAYING;
    StartFrame=curr_frame;
    dwNextTime=GetTickCount();
    }
    return ;
    }
    
    void FLCW::StopFlc()
    {
    curr_frame=1;
    _llseek(hFile,oframe2,0);
    fStatus=FLC_STOPPED ;
    PostMessage(hPlayWnd,wm_Notify,
    FLC_STOPPED,MAKELONG(curr_frame,0));
    }
    
    void FLCW::PauseFlc()
    {
    
    if(fStatus!=FLC_PAUSED&&fStatus!=FLC_NOTREADY){
    fStatus=FLC_PAUSED ;
    PostMessage(hPlayWnd,wm_Notify,
    FLC_PAUSED,MAKELONG(curr_frame,0));
    }
    return ;
    }
    
    BOOL FLCW::ShowFlc(HDC hdc)
    {
    
    if(hPal==0)return FALSE ;
    if(hData==0)return FALSE ;
    SetPal(hdc);
    Show(hdc,xOrg,yOrg);
    ResetPal(hdc);
    return TRUE ;
    }
    
    
    short FLCW::QueryFlc()
    {
    return fStatus ;
    }
    
    
    WORD FLCW::GetTotalFrame()
    {
    return frames;
    }
    
    intFLCW::Loop(WORD wLoops)
    {
    int Lastloop=loops;
    if(wLoops>=0)loops=wLoops;
    return Lastloop;
    }
    
    WORD FLCW::GetCurFrame()
    {
    return curr_frame;
    }
    
    float FLCW::Speed(float fps)
    {
    float oldfps=  (float)(1000000.0/speed);
    if(fps>0)speed=(DWORD)(1000000.0/fps);
    return oldfps;
    }
    
    BOOL FLCW::FlcIdle()
    {
    //if(GetCapture())return TRUE;
    
    if(fStatus==FLC_PLAYING)
    {
    if(GetTickCount()>=dwNextTime)PlayFlcAFrame();
    if(curr_loop>=loops)
    {
    curr_loop=0;
    fStatus=FLC_STOPPED;
    PostMessage(hPlayWnd,wm_Notify,fStatus,
                                                 curr_frame);
    }
    }
    return FALSE;
    }
    

    程序员调用FLC流程

    1. 用Open(LPCSTR lpszFile,int id)函数打开FLC文件,其参数lpszFile指要打开的文件名,参数id意义与类BMP中Open函数相同,值为零表示使用FLC文件中内建的调色板,否则忽略这个调色板,当在屏幕上播放多个动画或既有动画又有图片时要用到这个参数。程序首先打开指定文件,读入文件头,根据文件头信息申请内存建立内存位图,初始化部分参数,并将文件指针指向文件起始数据区。
    2. PlayFlc(HWND hwnd,WORD xorg,WORD yorg,WORD wm_notify)开始播放动画。在类FLCW内部,用参数fStatus决定动画播放与否,只要这个参数值为FLC_PLAYING,就播放动画,我们还可以用这个参数检验当前播放状态,函数QueryFlc()就是返回fStatus值。参数xorg和yorg指定播放位置,假如在播放过程中想移动位置,可以用这个函数重新指定。参数wm_notify指定当动画播放完成后将发送什么消息给父窗口。
    3. FlcIdle()函数是使用整个FLC类的关键。由于FLC播放是一个过程,不是一转眼就能完成的事,我们当然不能把整个程序的控制权交给它,让它播放完后再交回来,只好每隔一段时间关照它一下,让它检查一下现在的状态,假如时间到了,可以播放下一帧了,就放下一帧后返回,否则直接返回。因此在程序中需要有一个定时器,不停地调用FlcIdle。在FlcIdle函数中,核心又是if(GetTickCount()>=dwNextTime)PlayFlcAFrame()函数,这个函数的作用是检测是否到了播放下一帧的时间,然后调用PlayFlcAFrame函数播放下一帧。

    FLC播放单帧图片机理

    1. PlayFlcAFrame(),PlayFlcAFrame()的作用是逐帧播放FLC,它首先调用ReadFrame()函数,如读取成功则使用MYDIB类函数Show将内存位图拷贝至屏幕。
    2. ReadFrame(),ReadFrame()函数是整个FLC类的核心所在,它的作用是读取文件数据,再将数据解码至内存中。其操作过程如下:读取帧头,获取当前帧内块数,循环读取块头、块数据,根据块类型解码数据至内存位图中。块类型FLI_COLOR_256、FLI_WORLD_LC、FLI_LC、FLI_BALCK、FLI_BRUN、FLI_COPY分别对应于函数DecodeColor()、DecodeDeltaFlc()、DecodeDeltaFli()、DecodeBlack()、DecodeBRun()、DecodeCopy()。

    实例分析

    在这个实例中,我们要在一个窗口中同时播放三个FLC动画,注意这三个动画的调色板是不同的,为了协调这三个动画的颜色,干脆这三个调色板一个都不要,使用一个photoshop内置公用调色板,这个调色板在二百五十六种颜色范围内,采用每种颜色兼顾一点的办法,在实际应用中,要使用抖动算法才能模拟出与原来颜色乱真的效果,在此自己算太累,系统又不提供算法,况且这个算法与我们的主题关系不大,因此你看到的颜色会有些失真,除非系统设置大于二百五十六色。

    建立一个基于对话框的应用程序playflc,在类CPlayflcDlg内定义三个FLCW类flc,外加一个BMP类bmp,bmp类的作用是取得一个调色板,我们已事先用photoshop作了一个长宽分别为1比1的图片,当然它包含了公用调色板的信息。在对话框初始化过程函数OnInitDialog()中,我们设定一个定时器,并分别读入文件1.flc、2.flc、3.flc、1.bmp,注意读FLC时第二个参数为(PALETTE*)&bmp,这就是告诉程序使用bmp的调色板。我们打算当用户按下ok键时开始播放动画,因此增加一个按键响应函数OnOK() ,添加三个PlayFlc函数,在定时器响应过程函数中填入三个FlcIdle函数。运行程序。

    图5.5

    热点

    所谓热点,是指在指定窗口上设定一些区域,当用户在此区域上有所动作时,会发生一些事情,这些区域就是热点,热点的作用类似于在窗口上开了若干按钮窗口,当然它的功能要比button强大得多,最重要的是热点可以是不规则的,而button必须是矩形。

    热点的原理

    热点涉及到极复杂的坐标系统,比如一个四边形,需要四个坐标确定它的形状,六边形就需要六个坐标确定形状,若是再复杂一些,比如需在世界地图上区分中国和其它国家,那就不是几个几十个坐标就能解决问题了。假如一个中大型的多媒体程序,涉及到比较多的热点,就算一个热点由四个坐标设定,二十个热点就需要80个坐标。若是这些热点都由手工设定,那么以后的麻烦事就层出不穷。因为二个图像吻合得不好,就算有一个点的误差,人的肉眼也是看得出来的。有没有比较简单的办法可以减少程序员处理坐标的工作量,甚至可以不理会热点坐标?还好Windows提供了处理区域的函数,这就是RGN。

    RGN的处理过程如下:程序员向系统提供一串坐标值,系统返回一个HRGN句柄;以后程序员就可以使用这个句柄管理应用程序。比如说,程序员可以在指定区域范围内画图,而不影响到区域外面的部位。

    热点制作工具

    我们首先介绍一下热点制作工具,Hospot4.exe这个程序的作用是由用户自定义热点,再将热点写入指定文件。

    打开Hospot4.exe文件,读入文件“1.bmp”,然后在菜单上选择“定义”就可以开始定义热点了。读入的bmp文件一般是程序中要用到的背景文件,在上面按键应该已经画好,所以一般只要照着画好的按键轮廓描,当描完后点取左键鼠标,在文件菜单上选择存盘,输入定义的热点名既可。然后又可以定义下一个热点。照此顺序做完所有热点退出,查看上多了一个文件*.dat,这个dat文件就是热点配置文件。

    热点类Class Hot

    #ifndef HOT_H
    #defineHOT_H
    
    #include <windows.h>
    
    #define MAX_POINTS50
    #define MAX_HOT20
    
    typedef struct {
    HRGNhRGN;                  //handle of hot region
    char image[14];             //image name
    char target[32];            //hotspot name
    BOOLactive;        //tag the hotspot enable or disable
    } POLYHOT;
    
    class HOT
    {
    private:
    HRGNhOldRgn;
    
    public:
    intHotNumber;
    POLYHOT PHot[MAX_HOT];
    
    public:
    HOT(){ memset(this,0,sizeof(HOT)); }
    ~HOT(){ DeleteAll(); }
    voidDeleteAll();
    intOpen(LPCTSTR filename);
    intCurInHot(POINT pt );
    intEnableHot(int id ,int statue);
                     //enable or disnable one hotspot
    
    HRGNSetRgn(HDC hdc,int id);
    voidResetRgn(HDC hdc);
    };
    
    #endif
    
    #include "stdafx.h"
    
    #include "hotspot.h"
    
    int HOT::Open(LPCTSTR filename)
    {
    HFILE  fh;
    char poly_header[5];
    
    struct{
    char image[128];              //image name
    char target[128];             //hotspot name
    int num;                     //number of hotspots
    POINTpoints[MAX_POINTS];      //point array for every hotspot
    } hsr;
    
    if(HotNumber)DeleteAll();
    
    fh = _lopen(filename,OF_READ);
    _lread(fh,poly_header,4);
    poly_header[4] = '\0';
    if (strcmp(poly_header,"POLY")){_lclose(fh);return FALSE; }
    
    for ( ; ; HotNumber++)
    {
    if(_lread(fh,&hsr,sizeof(hsr))!=sizeof(hsr))break;
    
    PHot[HotNumber].hRGN
                 =CreatePolygonRgn(hsr.points,hsr.num, ALTERNATE );
    strcpy(PHot[HotNumber].image, hsr.image);
    strcpy(PHot[HotNumber].target, hsr.target);
    PHot[HotNumber].active = 1;
    }
    _lclose(fh);
    
    return HotNumber;
    }
    
    intHOT::CurInHot(POINT pt )
    {
    for ( int i = 0 ; i < HotNumber;  i ++ )
    if(PtInRegion(PHot[i].hRGN,pt.x,pt.y)&&PHot[i].active)           return i;
    return _1;
    }
    
    void HOT::DeleteAll()
    {
    for(int i=0;i<HotNumber;i++) if(PHot[i].hRGN)DeleteObject( PHot[i].hRGN );
    memset(this,0,sizeof(HOT));
    }
    
    //if statue<0 return Current active
    //else set active=statue and return last active
    int HOT::EnableHot(int id ,int statue)
    {
    if ( id >= HotNumber || id < 0 )return _1;
    int i=PHot[id].active;
    if (statue<0) return i;
    PHot[id].active = statue ;
    return i;
    }
    
    HRGN HOT::SetRgn(HDC hdc,int id)
    {
    if ( id >= HotNumber || id < 0 ) return FALSE;
    
    if(hdc,PHot[id].hRGN)hOldRgn=
                     (HRGN)SelectObject(hdc,PHot[id].hRGN);
      return hOldRgn;
    }
    
    void HOT::ResetRgn(HDC hdc)
    {
    if(hOldRgn)SelectObject(hdc,hOldRgn);
    hOldRgn=0;
    }
    

    热点类实现将dat文件转化为RGN,并且对RGN进行操作功能。

    热点类由以下成员函数组成:

    open(LPCTSTR filename)

    打开名为 filename的热点文件,并将热点坐标转成HRGN。

    CurInHot(point pt)

    检查点point是否属于热点区域内,假如是则返回属于哪一个热点。

    EnableHot (int id, int statue)

    把第id个热点设置成使用或不使用状态,假如Statue是负数,则返回第id个热点的状态。

    DeletAll( )

    删除所有热点。

    SetRgn( )

    设置HDC上可使用区域,便将以后对HDC的操作只限制在指定区域范围内,而对范围外的区域无效。

    ResetRgn( )返回设置RGN以前的状态。

    在程序中使用类HOT

    现在我们要编一个安装程序,假设这个安装程序有如下功能:有四个按键,分别是"install"、"uninstall"、"Run"、"Exit",其中“Run”键为禁止状态,当鼠标移到任何一个按键上,除禁止状态外,按钮都要变成加亮状态,鼠标形状变成手状,当鼠标在按钮上按下时,按钮形状变成按下状态,鼠标形状变成按下的手状。为了编好这个程序,要美工做出四张图片,第一张为正常状态,第二张为四个按钮全为加亮状态,第三张为四个按钮全部按下状态,第四张为四个按钮全部禁止状态。我们如下构造应用程序:

    建立一个基于对话框的应用程序HOT,在类CHotDLg pt增加七个成员变量,其中一个热点类HOT hot,四个bmp 类BMP bmp[4],分别表示四张位图,二个鼠标HCURSOR hcu[2]。在对话框初始化函数中,分别读入四个bmp位图,一个热点资源文件“1.dat”,两个鼠标资源。注意四个位图文件只有一个有调色板,并且要用SetClass Long函数将窗口类属性设成没有缺省鼠标,否则不能用SetCusor函数设置当前鼠标。然后用Classwizard添加三个成员函数OnMouseMove、OnLButtonDown、OnLButtonUp。处理OnMouseMove函数过程如下:检查鼠标是否被按下。检查鼠标移到了哪个热点内,根据热点及鼠标状态确定当前应该用哪个鼠标。检查当前热点是否刚才热点,如果两个相同,表示状态没有发生改变,那么就没有必要进行进一步的处理,函数就可以返回了。若发生了改变,则要区分进行进一步的处理,函数就可以返回了,若发生了改变,则要区分两种状态:一种是从外面进入热点,那么要重画当前热点,假如鼠标移出热点,那么要重画刚才热点。处理鼠标键按下过程如下:检查当前鼠标在哪个热点内,并重画这个热点,再将鼠标形状设成手指按下状。处理鼠标键放开过程如下:检查当前鼠标在哪个热点,并重画这个热点,并将鼠标设回手指状。编译并运行程序。

    图5.6