-- VC多媒体编程本文来自http://study.feloo.com/
|
||
-- 作者:admin -- 发布时间:2006-8-11 16:05:00 -- Visual C++中基于多文档视窗模型的重叠图象拼接技术摘要 图象拼接是在全景视频系统、地理信息系统等应用中经常遇到的一个问题,本文基于网格匹配的方法对边界部分有重叠的图象提出了一种行之有效的对准算法,并通过平滑因子对图象实现了无缝拼接。并应用文档视窗模型实现了该算法,并完成了位图文件的显示、存储等操作,具有一定的普遍意义。 关键词: 图象拼接,算法,重叠图象,文档视窗,位图文件,图象显示 文章正文 一、 多文档视窗模型概述 MFC的AppWizard可以生成三种类型的应用程序:基于对话框的应用、单文档应用(SDI)和多文档应用(MDI)。三种应用中,以多文档应用(MDI)最为复杂,其功能也最强大。当我们用AppWizard生成一个多文档应用时,系统由CMultiDocTemplate自动生成了一个从Cdocument类继承的文档类,一个从Cview类继承的视窗类,一个从CMDIChildWnd类继承的框架类。当我们每次建立一个新的文档时,程序根据文档模板生成一个新实例,这些我们均可不用关心AppWizard已经自动生成了代码。但如果我们要在程序中使用多个不同的文档类时,则需自己建立文档模板并控制文档实例的建立。假设我们要向一基于多文档的工程MDI中增加一Test的文档。 具体步骤如下: 1、用Clazard建立一个框架类CTestFrame基类选CMDIChildWnd。 2、用Clazard建立一个文档类CTestDoc基类选CDocument。 3、用Clazard建立一个文档类CTestView基类选CView。 4、将三个类的头文件加入应用类CMDIApp中。 5、创建新文档模板,在CMDIApp::InitInstance()函数中加入如下代码 CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate( IDR_TESTTYPE, RUNTIME_CLASS(CTestDoc), RUNTIME_CLASS(CTestFrame), RUNTIME_CLASS(CTestView)); AddDocTemplate(pDocTemplate); 6、定义一菜单项ID号为ID_NEWTEST,利用Clazard将其处理函数加入应用类(或主框架类),在其处理函数CMDIApp::OnNewtest()函数中加入如下代码 POSITION curTemplatePos = GetFirstDocTemplatePosition(); while(curTemplatePos != NULL) { //取下一个文档模板指针 CDocTemplate* curTemplate =GetNextDocTemplate(curTemplatePos); CString str; curTemplate->GetDocString(str, CDocTemplate::docName); //取文档名称 if(str == _T("Test")) //判断当前文档文档是否Test类 { curTemplate->OpenDocumentFile(NULL); //创建新的文档实例 return; } } 这样我们就建立了一个新的文档类。注意在5中创建文档模板时我们用到了一文档类型资源IDR_TESTTYPE,该资源ID在资源文件中定义如下(未包括图标和菜单的定义): STRINGTABLE PRELOAD DISCARDABLE BEGIN ………. IDR_TESTTYPE //nTest//nTest//n//n//nMDI.Document//nTest Document END 文档类型标识包括七个子串,包括窗口标题、文档名称、文件扩展名等。 在6中curTemplate->GetDocString(str, CDocTemplate::docName);取的就是第二个子串,文档名称。 文档建立之后我们就可以对其进行操作了。当然文档类和视窗类,文档类和主窗口类,以及不同文档类之间进行通信也是较为复杂的,并非几句话就能说清楚,如不熟悉文档视窗的读者请参看其它有关资料。 二、 重叠图象拼接技术 1.算法思想 在实现全景视频(Panoramic Video)系统、地理信息系统(GIS)及其它一些应用的过程中,我们通常会碰到这样的一个问题,就是要把几幅小的图象拼接成一幅大的图象。为了能让计算机自动对准图象我们要求待拼接的图象边界有部分重叠,计算机正是利用这些信息进行匹配对准。匹配算法的总体思想是既要保证对准的精度,又要保证运算量不至过大。这里算法利用了图象的自身特性,既在一般图象中,相邻的象素点的灰度值相差不大。因此,可在第二幅图象的边界取一个网格,然后将网格在第一幅图象上移动,计算所有网格点的两幅图象对应象素点的RGB值的差的平方和。记录最小的值的网格位置,即认为是最佳匹配位置。(如图1)为了减小运算量,我们将匹配分为两个步骤,第一步是粗略匹配,在该阶段网格每次水平或垂直移动一个网格间距。在完成粗略匹配之后,我们在当前最佳匹配点处进行精确匹配,在该阶段以当前最佳匹配点为中心,网格向上下、左右各移动一个小步长。初始步长为粗略拼接时移动步长的一半,即为半个网格间距。不断的与当前最小平方和进行比较,如果比当前值优,就替换当前最佳匹配点。循环进行这个过程每次步长减半,直到水平步长和垂直步长均为0为止。 2.算法描述 procedure ImageMatching { 输入FirstImage; 输入SecondImage; //获得两幅图象的大小 Height1=GetImageHeight(FirstImage); Height2=GetImageHeight(SecondImage); Width1=GetImageWidth(FirstImage); Width2=GetImageWidth(SecondImage); // 从第二幅图象取网格匹配模板 SecondImageGrid = GetSecondImageGrid(SecondImage); // 粗略匹配,网格在第一幅图象中先从左向右移动,再从下到上移动,每次移动一个网格间距,Step_Width 或Step_Height,当网格移出重叠区域后结束 y=Heitht1-GridHeight; MinValue = MaxInteger; While ( y<Height1-OverlapNumber) //当网格移出重叠部分后结束 { x=Grid_Width/2; //当网格位于第一幅图象的最左边时,A点的横坐标。 While ( x<(Width1-Grid_Width/2) ) { FirstImageGrid=GetImgaeGrid(FirstImgaeGrid, x, y); differ=CaculateDiff(FirstImgaeGrid, SecondImageGrid); //计算象素值差的平 //方和 if (differ<MinValue) { BestMatch_x=x; BestMatch_y=y; MinValue = differ; } x= x+Step_width; } y=y-Step_Height; } //精确匹配 Step_Width= Step_Width/2; Step_Height= Step_Height/2; While ( Step_Height>0 & Step_Width>0) //当水平步长和垂直步长均减为零时结束 { if(Step_Height==0) //当仅有垂直步长减为零时,将其置为1 Step_Height=1; If(Step_Width==0) //当仅有水平步长减为零时,将其置为1 Step_Width=1; temp_x = BestMatch_x; temp_y = BestMatch_y; for ( i= -1; i<1; i++) for( j= -1; j<1; j++) { if ((i=0&j!=0)|(i!=0&j=0)) { FirstImageGrid=GetImgaeGrid(FirstImgaeGrid, temp_x+i*Step_Width, temp_y +j*Step_Height); differ=CaculateDiff(FirstImgaeGrid, SecondImageGrid); if (differ<MinValue) { BestMatch_x=x; BestMatch_y=y; MinValue = differ; } } } Step_Height = Step_Height /2; Step_Width = Step_Width/2; } } 三、 基于多文挡视窗模型的重叠图象拼接技术 程序在Visual C++实现过程中有如下一些技术问题需要注意。 1、 位图文件的读取和显示 位图文件是一种最简单的图象文件,屏幕图象的每一点对应位图文件的几位数据。现有的标准有1位、4位、8位、24位。24位位图不含颜色表,每个象素用3个字节表示,依次表示RGB空间里的蓝、绿、红的灰度值。每种位图文件都由两部分组成,一部分是文件头和位图信息头,另一部分是图象的位数组。因此要想显示一个位图文件首先要声明一个CFile类实例将文件读入内存,然后根据文件头和位图信息头获得图象的相关信息和位数组的起始地址。调用SetDIBitsToDevice()函数即可把图象显示在屏幕上。 2、 位图文件中任意象素点颜色值的获取 要实现图象的处理,访问任意象素点的象素值是必需的操作。在访问位图文件时有两点需要注意,一是图象位数组的存储是按从下到上进行的。也就是说,图象的最底行的数据存在位数组的最开始位置。另一个特点是,图象的每行象素所占的空间是双字的整数倍,不足的用零填充。每行象素的实际存储大小可由以下公式加以计算。 WidthBytes=(((biWidth*biBitCount)+31)&~31)>>3 (1) 假设位数组的起始指针为lpStartBits屏幕坐标(x,y)在的象素值的指针可用下式计算。 lpBits=lpStartBits + (WidthBytes*(Height-y-1) + x*biBitCount); (2) 其中WidthBytes为(1)式计算的值,Height为图象的高度。 3、 不同文档类之间的数据交换的实现 不同文档类之间的数据交换我们可以通过应用程序类或主窗口类作为媒介进行。在文档类或视窗类可通过AfxGetApp()或AfxGetMainWnd()获得应用类和主窗口类的指针,在应用类和主窗口类则可以通过获得文档模板来获得文档类的指针来访问文档类的数据。这样我们可以通过应用类或主窗口类的成员变量进行数据交换了。 4、 图象的平滑连接 当找到最佳匹配点后,随后的工作将是把两幅图象合成一幅图象。对于重叠部分,我们如果只是简单的取第一幅图象或第二幅图象的数据,会造成图象的模糊和明显的边界,这是不能容忍的。即使取两幅图象的平均值,效果也不能令人满意。为了能使拼接区域平滑,保证图象质量,我们采用了渐入渐出的方法,即在重叠部分由第一幅图象慢慢过渡到第二幅图象,很自然我们可以想到设一渐变因子为0<d<1,对应的前后两幅图象为image1、image2,结果为image3,则image3=d*image1+(1-d)*imge2其中d的值由1渐变到0,它与该点距重叠边界的距离有关。 四、 多文挡视窗模型的重叠图象拼接程序框架 1. 程序构成 程序除应用类、主窗口类以外,还包括CFristImageDoc, CSecondImageDoc, CThirdImageDoc类用来保存第一幅、第二副以及拼接后图象的数据。还有与其相连的文档类和框架类。 2. 程序流程 程序的主要工作在应用类中完成,首先打开第一幅图象,图象的显示等操作由CFirstImageDoc 类和与其相关的视窗类及框架类完成,并将图象的位数组指针和图象大小传给应用类成员变量。再打开第二幅图象同样完成显示等操作,也将位数组指针和图象大小传给应用类成员变量。在应用类中完成图象的匹配对准工作,最后实现图象的合成。将合成后的图象传给CThirdImageDoc类进行显示当用户对拼接结果基本满意后,可以选择平滑连接将两幅图象平滑的连接起来。用户可将最后的结果保存成bmp文件。 五、 总结 图象拼接中,图象对准是前提和关键,程序基于网格匹配的方法实现了图象对准,应用了交互技术让用户可以对网格点的多少,网格间距大小,均可调整,还允许用户输入重叠范围使拼接过程有的放矢,缺省是较小图象高度的1/3。该程序实现了对位图图象文件的各种操作,具有普遍意义,还引入了不同文档类型的多文档视窗模型,并在不同文档类间实现了数据交换,具有一定的实用价值。为了使程序简单易用,只实现了对24位位图的拼接,也未提供垂直方向的拼接,只提供了水平方向的拼接。如要对不符合条件的图象进行拼接我们只需在Windows提供的画笔中将图象转化一下即可
|
||
-- 作者:admin -- 发布时间:2006-8-11 16:06:00 -- 在VC下显示JPEG、GIF格式图像的一种简便方法 一、 引言 JPEG图像压缩标准随然是一种有损图像压缩标准,但由于人眼视觉的不敏感,经压缩后的画质基本没有发生变化,很快便以较高的压缩率得到了广泛的认可。GIF格式虽然仅支持256色但它对于颜色较少的图像有着很高的压缩率,甚至超过JPEG标准,也得到了广泛的认同。但作为众多程序员的一个重要的开发工具--Microsoft Visual C++ 6.0的MFC库却仅对没有经过任何压缩的BMP位图文件有着良好的支持,可以读取、显示、存储甚至在内存中创建一块内存位图。由于BMP格式的图像没有经过任何的压缩,不论是作为程序的外部文件,还是作为程序的内部资源都要占据大量的空间,尤其是后者会大大增加可执行文件的长度。可以看出,如果能用经过压缩、具有较好的压缩率的JPEG或GIF格式的图像来取代BMP文件在VC中的应用,无疑还是很有吸引力的。 二、 设计思路 虽然有一些操作、处理JPEG、GIF等其他格式图像的Active X控件,但总的来说使用起来并不太方便,笔者经过实验摸索,总结出了一种借助于COM接口的OLE方法来实现上述功能的一种简便方法,现介绍如下以飨广大读者: 下面我们要使用IPicture 的COM接口,有必要对该图像接口做些了解: 该接口主要管理图像对象及其属性,图像对象为位图、图标和图元等提供一种与语言无关的抽象。和标准的字体对象一样,系统也提供了对图像对象的标准实现。其主要的接口是IPicture和IPictureDisp,后者是由IDispatch接口派生以便通过自动化对图像的属性进行访问。图像对象也支持外部接口IPropertyNotifySink,以便用户能在图像属性发生改变时作出决定。图像对象也支持IPersistStream接口,所以它能从一个IStream接口的实例对象保存、装载自己,而IStream接口也支持对流对象的数据读写。 我们可以用函数OleLoadPicture从包含有图像数据的流中装载图像。该函数简化了基于流的图像对象的创建过程,可以创建一个新的图像对象并且用流中的内容对它进行初始化。 其函数原型为: STDAPI OleLoadPicture( IStream * pStream, //指向包含有图像数据的流的指针LONG lSize, //从流中读取的字节数BOOL fRunmode, //图像属性对应的初值REFIID riid, //涉及到的接口标识,描述要返回的接口指针的类型VOID ppvObj // 在rrid中用到的接口指针变量的地址); 三、 具体的实现 在显示图像之前,首先要获取到图像文件的存放路径,这里采用标准的文件打开对话框来选取图像文件,文件名存放在CString型的变量m_sPath中: CFileDialog dlg(TRUE,"jpg","*.jpg", OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT, "JPEG文件(*.jpg)|*.jpg|GIF文件(*.gif)|*.gif||",NULL); if(dlg.DoModal()==IDOK) { m_sPath=dlg.GetPathName(); Invalidate(); } 为简单计,图形显示的代码直接在视类中的OnDraw中编写,首先打开文件并判断文件的可用性,并把文件内容放到流接口IStream的对象pStm中: IStream *pStm; CFileStatus fstatus; CFile file; LONG cb; …… if (file.Open(m_Path,CFile::modeRead)&&file.GetStatus(m_Path,fstatus)&& ((cb = fstatus.m_size) != -1)) { HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, cb); LPVOID pvData = NULL; if (hGlobal != NULL) { if ((pvData = GlobalLock(hGlobal)) != NULL) { file.ReadHuge(pvData, cb); GlobalUnlock(hGlobal); CreateStreamOnHGlobal(hGlobal, TRUE, &pStm); } } } 然后,就直接调用OleLoadPicture函数从流中装载图像: IPicture *pPic; …… OleLoadPicture(pStm,fstatus.m_size,TRUE,IID_IPicture,(LPVOID*)&pPic)); 由于该函数有时会导致失败,所以应当用SUCCEEDED宏来做一些适当的保护工作,只有在数据装载成功的前提下才能继续下面的图像显示工作: if(SUCCEEDED(OleLoadPicture(pStm,fstatus.m_size,TRUE,IID_IPicture,(LPVOID*)&pPic))) { OLE_XSIZE_HIMETRIC hmWidth; OLE_YSIZE_HIMETRIC hmHeight; pPic->get_Width(&hmWidth); pPic->get_Height(&hmHeight); double fX,fY; …… fX = (double)pDC->GetDeviceCaps(HORZRES)*(double)hmWidth/((double)pDC->GetDeviceCaps(HORZSIZE)*100.0); fY = (double)pDC->GetDeviceCaps(VERTRES)*(double)hmHeight/((double)pDC->GetDeviceCaps(VERTSIZE)*100.0); if(FAILED(pPic->Render(*pDC,0,0,(DWORD)fX,(DWORD)fY,0,hmHeight,hmWidth,-hmHeight,NULL))) AfxMessageBox("渲染图像失败!"); pPic->Release(); } else AfxMessageBox("从流中装载图像失败!"); 其中,显示工作主要是由IPicture接口对象的Render函数来完成的,该函数主要用来将图片的指定部分画到指定的设备环境的指定位置。原型如下: HRESULT Render( HDC hdc, //渲染图像用的设备环境句柄 long x, //在hdc上的水平坐标 long y, //在hdc上的垂直坐标 long cx, //图像宽度 long cy, //图像高度 OLE_XPOS_HIMETRIC xSrc, //在源图像上的水平偏移 OLE_YPOS_HIMETRIC ySrc, //在源图像上的垂直偏移 OLE_XSIZE_HIMETRIC cxSrc, //在源图像上水平拷贝的数量 OLE_YSIZE_HIMETRIC cySrc, //在源图像上垂直拷贝的数量 LPCRECT prcWBounds //指向目标图元设备环境句柄的指针); 小结:到此为止,通过上述代码已经能够在程序的客户区内显示JPEG、GIF等标准的图像了,但对于有多帧图片(即有动画)的GIF格式的图像,目前还只能显示第一帧,如要完整的显示GIF 动画的全过程,还需要外部Active X控件的支持。
|
||
-- 作者:admin -- 发布时间:2006-8-11 16:07:00 -- Visual C++实现Flash动画播放
|
||
-- 作者:admin -- 发布时间:2006-8-11 16:08:00 -- 用RealPlayer控件制作的播放器
//多谢杜修杏 老师指点 /////////////////////////////////// this->DestroyWindow(); } void CSunapplerealplayerDlg::OnFullscreen() { m_player->DoPause(); m_player->SetFullScreen(); m_player->DoPlay(); } void CSunapplerealplayerDlg::OnMp3down() { ShellExecute(NULL,_T("open"),"http://sunapple.51.net",NULL,NULL,TRUE); }
void CSunapplerealplayerDlg::OnPause() { m_player->DoPause(); } void CSunapplerealplayerDlg::OnStop() { m_player->DoStop(); KillTimer(0); }
void CSunapplerealplayerDlg::OnRepeat() { m_player->SetLoop(true); if(isRepeat){ isRepeat=FALSE; SetDlgItemText(IDC_REPEAT,"循环"); } else { isRepeat=TRUE; SetDlgItemText(IDC_REPEAT,"正常"); } }
void CSunapplerealplayerDlg::OnLower() { // TOD Add your control notification handler code here short volume=m_player->GetVolume(); m_player->DoPause(); m_player->SetVolume(volume-100); m_player->DoPlay(); }
void CSunapplerealplayerDlg::OnUpper() { // TOD Add your control notification handler code here short volume=m_player->GetVolume(); m_player->DoPause(); m_player->SetVolume(volume+100); m_player->DoPlay(); }
void CSunapplerealplayerDlg::OnFloat() { // TOD Add your command handler code here ShellExecute(NULL,_T("open"),"http://sunapple.51.net",NULL,NULL,TRUE); }
void CSunapplerealplayerDlg::OnPetroleum() { // TOD Add your command handler code here ShellExecute(NULL,_T("open"),"http://www.hdpu.edu.cn",NULL,NULL,TRUE); }
五、映射WM_CTLCOLOR消息,用于控制文本显示的颜色 HBRUSH CSunapplerealplayerDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); if(nCtlColor==CTLCOLOR_STATIC) if(pWnd.GetDlgCtrlID()==IDC_VOLUME) { pDC.SetTextColor(RGB(165,182,222)); pDC.SetBkMode(TRANSPARENT); return (HBRUSH) m_brush.GetSafeHandle(); } if(pWnd.GetDlgCtrlID()==IDC_STATIC1||pWnd.GetDlgCtrlID()==IDC_STATIC2 ||pWnd.GetDlgCtrlID()==IDC_SOURCE||pWnd.GetDlgCtrlID()==IDC_COPYRIGHT) { pDC.SetTextColor(RGB(0,0,255)); pDC.SetBkMode(TRANSPARENT); return (HBRUSH) m_brush.GetSafeHandle(); } if(pWnd.GetDlgCtrlID()==IDC_STATIC||pWnd.GetDlgCtrlID()==IDC_INFO) { pDC.SetTextColor(RGB(255,0,0)); pDC.SetBkMode(TRANSPARENT); return (HBRUSH) m_brush.GetSafeHandle(); } // TOD Return a different brush if the default is not desired return hbr; } 六、在APP类里的initInstance()里添加下面函数改变对话框背景。 SetDialogBkColor(RGB(206,227,99)); 七、为了美观我们的外形可引入CButtonXP类,将个按钮类型设置为CButtonXP 好了,应该大功告成了!调试一下吧! 本程序在调试过程中要多谢杜修杏老师的指点
|
||
-- 作者:admin -- 发布时间:2006-8-11 16:09:00 -- Visual C++编程控制鼠标鼠标是现在计算机的标准配置,很多软件都有控制鼠标的功能,比如,有的保密软件可以使鼠标移动限制在一定范围以内,有的可以模拟鼠标的点击,有的可以使鼠标自己移动。要实现以上的功能,必须使用Windows的API函数。 我们以下面的程序例子,来说明如何控制鼠标。我们使用Visual C++6.0来写这个程序。打开Visual C++6.0,使用MFC AppWizard新建1个基于对话框的工程,工程名为Mouse,在对话框上加上2个button控件,一个标题为"控制鼠标移动范围",另外1个的标题是"释放鼠标"在MFC Clazard中添加两个当我们使用鼠标单击这两个控件时响应的函数,标题为"控制鼠标移动范围"的控件的响应函数代码为: //首先使用GetWindowRect获得这个程序窗口的范围 CRect rect this->GetWindowRect&rect //然后使用ClipCursor函数把鼠标控制在这个范围以内,这个函数的功能就是控制鼠标的范围。 ClipCursor&rect 标题为"释放鼠标"的控件的响应函数代码为: ClipCursorNULL 这行代码非常简单,就是使鼠标可以自由移动,不受限制。 把以上程序编译好了以后运行,点下"控制鼠标移动范围"按钮,鼠标就只能在这个窗口的范围以内活动,离不开这个窗口了,点下"释放鼠标"按钮,鼠标就恢复正常了。 知道了如何控制鼠标范围,该讲讲如何移动鼠标了。移动鼠标非常简单,只需要一个API函数SetCursorPos,这个函数有2个参数,第1个参数是屏幕的x坐标,第2个参数是屏幕的y坐标,它可以把鼠标移动到指定的坐标上去。 模拟鼠标的点击功能也非常简单,比如模拟点鼠标右键,可以使用下面两行代码: mouse_eventMOUSEEVENTF_RIGHTDOWN0000 mouse_eventMOUSEEVENTF_RIGHTUP0000 mouse_even t函数的功能就是模拟鼠标点击,第1行代码是模拟按下鼠标右键,第2行代码是模拟鼠标右键弹起,这两行代码就模拟了1次点击鼠标右键的操作,如果想模拟点鼠标左键的操作,只要以上两行代码中的MOUSEEVENTF_RIGHTDOWN和MOUSEEVENTF_RIGHTUP参数换成MOUSEEVENTF_LEFTDOWN和MOUSEEVENTF_LEFTUP就可以了。 鼠标的模拟操作讲完了。以上的程序在Windows98下,使用Visual V++6.0编译成功,调试正常。 本期知识点:控制鼠标的API函数。
|
||
-- 作者:admin -- 发布时间:2006-8-11 16:11:00 -- 用VC实现图象渐显和渐隐摘 要 图象的渐显/渐隐被广泛运用与图象处理和多媒提娱乐软件。本文基于windows的调色板动画和时间码技术设计了通用的图象渐显和渐隐算法,并实现了其visual c++程序编码。 关键词 渐显、渐隐、调色板、调色板动画、时间码 图象的渐显/渐隐是十分重要的图象效果,广泛运用于图象处理和多媒提娱乐软件。渐显/渐隐算法设计的最大困难是速度控制,包括定时和快速改变图象中各象素的颜色。如采用普通的全图扫描算法,则速度较慢,很难真正体现渐显/渐隐效果。 利用windows(3.x.95/98/nt)操作系统特殊的调色板管理和时间码定时机制能设计出有效的图象渐显/渐隐算法。windows提供一种被称为调色板动画(palette animation)的颜色处理技术,它通过快速改变颜色调色板中所选取的表项中的颜色能模拟颜色的变化。设置时间码,定时调用该技术使图象颜色渐变就能实现图象的渐显和渐隐。 一、调色板动画 在visual c++中实现调色板动画依赖于mfc类库提供的cpalette类和cdc类中的若干成员函数,其基本步骤如下: 调用cpalette::createpalette(lplogpalette lplogpalette)函数创建逻辑调色板,注意将参数lplogpalette所指向的各颜色表项结构的peflags域设置为pc_reserved,以防止其它窗口同该调色板匹配颜色。; 调用cdc::selectpalette和cdc::realizepalette函数选择和实现所创建的逻辑调色板; 调用cpalette::animatepalette函数改变颜色,实现调色板动画; 动画完成后应恢复系统调色板。 cpalette::animatepalette是其中最关键的函数,其原型如下: void animatepalette( uint nstartindex, // 起始的表项号 uint nnumentries, // 变化的表项数 lppaletteentry lppalettecolors ); // 逻辑调色板表项指针 lppalettecolors为指向paletteentry结构的指针,其中存储着逻辑调色板将要更新的颜色信息。paletteentry结构定义如下: typedef struct tagpaletteentry { // pe byte pered; byte pegreen; byte peblue; byte peflags; } paletteentry; pered、pegreen、peblue分别表示逻辑调色板项的r、g、b颜色分量值。peflags 应被置为pc_reserved 。 nstartindex为lppalettecolors中将变化的起始表项号,nnumentries 为lppalettecolors中将变化的表项数。 二、时间码定时 cwnd::settimer函数可设置一个系统时间码,并指定每经过一定的时间间隔使windows系统发送一个wm_timer消息到窗口的消息队列中。窗口在每当接收到相应的wm_timer消息时做一定的处理,便实现了定时处理。 通常应在窗口的消息循环中接受和处理wm_timer消息,这样将很难编制通用的定时操作。通用的定时操作应将定时处理封装在一个函数中,而不与其它的代码纠缠在一起。笔者实现这一技术的技巧是,在循环操作中截获窗口消息,如消息为指定的时间码消息,则进行定时处理;否则分发消息给窗口消息处理机制。如果定时操作已结束,则修改循环标志,退出循环。具体的代码如下: ……………………………… // 设置时间码,pwnd为处理定时操作的窗口对象指针 pwnd->settimer(0x100, utimeout, null); // 屏蔽鼠标操作,使定时操作不受影响 pwnd->setcapture(); // 开始定时操作 bool bdone = false; msg msg; while (! bdone) { if (::peekmessage(&msg, null, 0, 0, pm_remove)) { if (msg.message == wm_timer && msg. wparam == 0x100) { ………………….. 定时操作代码 ………………….. // 如定时操作完成,则设置循环标志,结束操作 if (定时操作完成) bdone = true; } ::translatemessage(&msg); ::dispatchmessage(&msg); } } // 释放鼠标 ::releasecapture(); // 删除时间码 pwnd->killtimer(0x100); ………………………….. 函数peekmessage截获窗口消息,translatemessage和dispatchmessage函数解释和分发除指定时间码消息之外的所有消息,以避免丢失消息。 三、渐显 渐显就是将显示颜色由黑色(rgb(0, 0, 0))逐渐变化为图象各象素的颜色的过程。开始时调用cpalette::getpaletteentries函数保存图象调色板的各逻辑表项信息,然后调用cpalette::setpaletteentries函数将逻辑调色板中各逻辑表项的pered、pegreen、peblue置为0,定时调用cpalette::animatepalette,每次将各逻辑表项的pered、pegreen、peblue值增加一个变化量,直到它们分别等于图象逻辑调色板中各逻辑表项的pered、pegreen、peblue值。 下面的函数fadein通过对调色板颜色表项中的各颜色分量值先设为0,然后进行递增,直到所有颜色值都恢复成原调色板中颜色值来实现渐显。 // 图象渐显效果 // 参数: // pwnd – 显示图象的窗口 // ppal – 调色板指针 // ndeta – 各颜色分量的减小量 // utimeout – 时间的变化量 void fadein(cwnd *pwnd, cpalette *ppal, int ndeta, uint utimeout) { // 保留原来的调色板颜色表项 int ntotalcolors = ppal->getentrycount(); paletteentry palettecolors0[256]; ppal->getpaletteentries(0, ntotalcolors, palettecolors0); // 先将调色板表项中各颜色分量置为0 paletteentry palettecolors1[256]; for (int i=0; i<ntotalcolors; ++i) { palettecolors1[i].pered = 0; palettecolors1[i].pegreen = 0; palettecolors1[i].peblue = 0; palettecolors1[i].peflags = pc_reserved; } ppal->setpaletteentries(0, ntotalcolors, palettecolors1); ppal->animatepalette(0, ntotalcolors, palettecolors1); // 设置时间码 pwnd->settimer(0x100, utimeout, null); // 开始渐显 pwnd->setcapture(); bool bdone = false; msg msg; while (! bdone) { if (::peekmessage(&msg, null, 0, 0, pm_remove)) { if (msg.message == wm_timer && msg.wparam == 0x100) { cclientdc dc(pwnd); cpalette *poldpal = dc.selectpalette(ppal, false); dc.realizepalette(); // 递增各颜色分量 paletteentry palettecolors[256]; ppal->getpaletteentries(0, ntotalcolors, palettecolors); bool bredzero=false; bool bgreenzero=false; bool bbluezero=false; for (int i=0; i<ntotalcolors; ++i) { if (palettecolors[i].pered + ndeta < palettecolors0[i].pered) { palettecolors[i].pered += ndeta; bredzero = false; } else if (palettecolors[i].pered + 1 < palettecolors0[i].pered) { palettecolors[i].pered++; bredzero = false; } Else bredzero = true; if (palettecolors[i].pegreen + ndeta < palettecolors0[i].pegreen) { palettecolors[i].pegreen += ndeta; bgreenzero = false; } else if (palettecolors[i].pegreen + 1 < palettecolors0[i].pegreen) { palettecolors[i].pegreen++; bgreenzero = false; } Else bgreenzero = true; if (palettecolors[i].peblue + ndeta < palettecolors0[i].peblue) { palettecolors[i].peblue += ndeta; bbluezero = false; } else if (palettecolors[i].peblue +1 < palettecolors0[i].peblue) { palettecolors[i].peblue++; bbluezero = false; } else bbluezero = true; } // 直到恢复原始值结束 bdone = bredzero && bgreenzero && bbluezero; // 使系统改变调色板 ppal->animatepalette(0, ntotalcolors, palettecolors); } ::translatemessage(&msg); ::dispatchmessage(&msg); } } ::releasecapture(); pwnd->killtimer(0x100); // 恢复原始调色板 ppal->setpaletteentries(0, ntotalcolors, palettecolors0); ppal->animatepalette(0, ntotalcolors, palettecolors0); } 四、渐隐 渐隐就是将显示颜色由图象各象素的颜色逐渐变化为黑色(rgb(0, 0, 0))的过程,即定时调用cpalette::animatepalette,每次将各逻辑表项的pered、pegreen、peblue值减小一个变化量,直到它们都为0。 下面的函数fadeout通过对调色板颜色表项中的各颜色分量值进行递减,直到所有颜色值都变成0(即黑色)来实现渐隐。 // 图象渐隐效果 // 参数: // pwnd – 显示图象的窗口 // ppal – 调色板指针 // ndeta – 各颜色分量的减小量 // utimeout – 时间的变化量 void fadeout(cwnd *pwnd, cpalette *ppal, int ndeta, uint utimeout) { // 保留原来的调色板颜色表项 int ntotalcolors = ppal->getentrycount(); paletteentry palettecolors0[256]; ppal->getpaletteentries(0, ntotalcolors, palettecolors0); // 设置时间码 pwnd->settimer(0x100, utimeout, null); // 开始渐隐 pwnd->setcapture(); bool bdone = false; msg msg; while (! bdone) { if (::peekmessage(&msg, null, 0, 0, pm_remove)) { if (msg.message == wm_timer && msg.wparam == 0x100) { cclientdc dc(pwnd); cpalette *poldpal = dc.selectpalette(ppal, false); dc.realizepalette(); paletteentry palettecolors[256]; ppal->getpaletteentries(0, ntotalcolors, palettecolors); bool bredzero=false; bool bgreenzero=false; bool bbluezero=false; // 递减颜色分量 for (int i=0; i<ntotalcolors; ++i) { if (palettecolors[i].pered > ndeta) { palettecolors[i].pered -= ndeta; bredzero = false; } else if (palettecolors[i].pered > 1) { palettecolors[i].pered--; bredzero = false; } else bredzero = true; if (palettecolors[i].pegreen > ndeta) { palettecolors[i].pegreen -= ndeta; bgreenzero = false; } else if (palettecolors[i].pegreen > 1) { palettecolors[i].pegreen--; bgreenzero = false; } else bgreenzero = true; if (palettecolors[i].peblue > ndeta) { palettecolors[i].peblue -= ndeta; bbluezero = false; } else if (palettecolors[i].peblue > 1) { palettecolors[i].peblue--; bbluezero = false; } else bbluezero = true; } // 如所有颜色分量都为0,则结束渐隐 bdone = bredzero && bgreenzero && bbluezero; // 使系统改变调色板 ppal->animatepalette(0, ntotalcolors, palettecolors); } ::translatemessage(&msg); ::dispatchmessage(&msg); } } ::releasecapture(); pwnd->killtimer(0x100); // 恢复原始调色板 ppal->setpaletteentries(0, ntotalcolors, palettecolors0); ppal->animatepalette(0, ntotalcolors, palettecolors0);
|
||
n 作者:admin -- 发布时间:2006-8-11 16:11:00 n -- 用VC进行屏幕截取编程 n ---- 屏幕截取是令人比较感兴趣的事情.虽然现在有不少应用程序如HYPERSNAP等可以用来截取你所喜欢的屏幕画面,但是如果能把这个功能加到自己的程序中,就更能利用它强大的作用. n ---- 下面用VC来逐步介绍在Windows95下的实现过程.首先我们要确定屏幕截取的区域,用LPRECT结构来定义.可以截取一个窗口,或整个屏幕.以下代码把选定的屏幕区域拷贝到位图中. n HBITMAP CopyScreenToBitmap(LPRECT lpRect) n //lpRect 代表选定区域 { HDC hScrDC, hMemDC; n // 屏幕和内存设备描述表 HBITMAP hBitmap, hOldBitmap; n // 位图句柄 int nX, nY, nX2, nY2; n // 选定区域坐标 int nWidth, nHeight; n // 位图宽度和高度 int xScrn, yScrn; n // 屏幕分辨率 n // 确保选定区域不为空矩形 n if (IsRectEmpty(lpRect)) return NULL; n //为屏幕创建设备描述表 n hScrDC = CreateDC("DISPLAY", NULL, NULL, NULL); n //为屏幕设备描述表创建兼容的内存设备描述表 n hMemDC = CreateCompatibleDC(hScrDC); n // 获得选定区域坐标 n nX = lpRect- >left; n nY = lpRect- >top; n nX2 = lpRect- >right; n nY2 = lpRect- >bottom; n // 获得屏幕分辨率 n xScrn = GetDeviceCaps(hScrDC, HORZRES); n yScrn = GetDeviceCaps(hScrDC, VERTRES); n //确保选定区域是可见的 n if (nX 〈0) nX = 0; n if (nY 〈 0) n nY = 0; n if (nX2 > xScrn) n nX2 = xScrn; n if (nY2 > yScrn) n nY2 = yScrn; n nWidth = nX2 - nX; n nHeight = nY2 - nY; n // 创建一个与屏幕设备描述表兼容的位图 n hBitmap = CreateCompatibleBitmap (hScrDC, nWidth, nHeight); n // 把新位图选到内存设备描述表中 n hOldBitmap = SelectObject(hMemDC, hBitmap); n // 把屏幕设备描述表拷贝到内存设备描述表中 n BitBlt(hMemDC, 0, 0, nWidth, nHeight, hScrDC, nX, nY, SRCCOPY); n //得到屏幕位图的句柄 n hBitmap = SelectObject(hMemDC, hOldBitmap); n //清除 DeleteDC(hScrDC); DeleteDC(hMemDC); n // 返回位图句柄 n return hBitmap; } n 得到屏幕位图句柄以后,我们 可以把屏幕内容粘贴到剪贴板上. n if (OpenClipboard(hWnd)) n //hWnd为程序窗口句柄 n { n //清空剪贴板 n EmptyClipboard(); n //把屏幕内容粘贴到剪贴板上, hBitmap 为刚才的屏幕位图句柄 n SetClipboardData(CF_BITMAP, hBitmap); n //关闭剪贴板 n CloseClipboard(); } n 我们也可以把屏幕内容以位图格式存到磁盘文件上. n int SaveBitmapToFile(HBITMAP hBitmap , LPSTR lpFileName) n //hBitmap 为刚才的屏幕位图句柄 n { n //lpFileName 为位图文件名 n HDC hDC; n //设备描述表 n int iBits; n //当前显示分辨率下每个像素所占字节数 n WORD wBitCount; n //位图中每个像素所占字节数 n //定义调色板大小, 位图中像素字节大小 , 位图文件大小 , 写入文件字节数 n DWORD dwPaletteSize=0, dwBmBitsSize, dwDIBSize, dwWritten; n BITMAP Bitmap; n //位图属性结构 n BITMAPFILEHEADER bmfHdr; n //位图文件头结构 n BITMAPINFOHEADER bi; n //位图信息头结构 n LPBITMAPINFOHEADER lpbi; n //指向位图信息头结构 n HANDLE fh, hDib, hPal,hOldPal=NULL; n //定义文件,分配内存句柄,调色板句柄 //计算位图文件每个像素所占字节数 n hDC = CreateDC("DISPLAY",NULL,NULL,NULL); n iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES); n DeleteDC(hDC); n if (iBits 〈 = 1) wBitCount = 1; n else if (iBits 〈 = 4) n wBitCount = 4; n else if (iBits 〈 = 8) wBitCount = 8; n else if (iBits 〈 = 24) wBitCount = 24; n //计算调色板大小 n if (wBitCount 〈 = 8) n dwPaletteSize = (1 〈 〈 wBitCount) * sizeof(RGBQUAD); n //设置位图信息头结构 n GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&Bitmap); n bi.biSize = sizeof(BITMAPINFOHEADER); n bi.biWidth = Bitmap.bmWidth; n bi.biHeight = Bitmap.bmHeight; n bi.biPlanes = 1; n bi.biBitCount = wBitCount; n bi.biCompression = BI_RGB; n bi.biSizeImage = 0; n bi.biXPelsPerMeter = 0; n bi.biYPelsPerMeter = 0; n bi.biClrUsed = 0; n bi.biClrImportant = 0; n dwBmBitsSize = ((Bitmap.bmWidth * wBitCount+31)/32)* 4 *Bitmap.bmHeight ; n //为位图内容分配内存 n hDib = GlobalAlloc(GHND,dwBmBitsSize+ dwPaletteSize+sizeof(BITMAPINFOHEADER)); n lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib); n *lpbi = bi; n // 处理调色板 n hPal = GetStockObject(DEFAULT_PALETTE); n if (hPal) { n hDC = GetDC(NULL); n hOldPal = SelectPalette(hDC, hPal, FALSE); RealizePalette(hDC); n } n // 获取该调色板下新的像素值 n GetDIBits(hDC, hBitmap, 0, (UINT) Bitmap.bmHeight, (LPSTR)lpbi + sizeof(BITMAPINFOHEADER) +dwPaletteSize, (BITMAPINFOHEADER *) lpbi, DIB_RGB_COLORS); n //恢复调色板 n if (hOldPal) { n SelectPalette(hDC, hOldPal, TRUE); n RealizePalette(hDC); n ReleaseDC(NULL, hDC); } n //创建位图文件 n fh = CreateFile(lpFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_ FLAG_SEQUENTIAL_SCAN, NULL); n if (fh == INVALID_HANDLE_VALUE) n return FALSE; n // 设置位图文件头 n bmfHdr.bfType = 0x4D42; n // "BM" n dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize; n bmfHdr.bfSize = dwDIBSize; n bmfHdr.bfReserved1 = 0; n bmfHdr.bfReserved2 = 0; n bmfHdr.bfOffBits = (DWORD)sizeof (BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwPaletteSize; n // 写入位图文件头 n WriteFile(fh, (LPSTR)&bmfHdr, sizeof (BITMAPFILEHEADER), &dwWritten, NULL); n // 写入位图文件其余内容 n WriteFile(fh, (LPSTR)lpbi, dwDIBSize, &dwWritten, NULL); n //清除 n GlobalUnlock(hDib); n GlobalFree(hDib); n CloseHandle(fh);
|
||
-- 作者:admin -- 发布时间:2006-8-11 16:12:00 -- 用VC制作图片屏幕保护程序 VC++可谓神通广大,如果学到家了,或者就掌握了那么一点MFC,你也会感到它的方便快捷,当然最重要的是功能强大。不是吗,从最基本的应用程序.EXE到动态连接库DLL,再由风靡网上的ActiveX控件到Internet Server API,当然,还有数据库应用程序……瞧,我都用它来做屏幕保护程序了。一般的屏幕保护程序都是以SCR作为扩展名,并且要放在c://windows 目录或 c://windows//system 目录下,由Windows 98内部程序调用(Windows NT 是在 c://windows//system32 目录下)。怎么调用?不用说了,这谁不知道。 好了,我们来作一个简单的。选择MFC AppWizard(exe),Project Name 为MyScreensaver,[NEXT],对话框,再后面随你了。打开菜单Project、Settings,在Debug页、Executable for debug session项,以及Link页中Output file name项改为c://windows//MyScreensaver.scr,这样,你可以调试完后,直接在VC中运行(Ctrl+F5),便可看到结果。当然,这样做的唯一缺点是你必须手动清除Windows 目录下的垃圾文件(当然是在看到满意结果后;还有,你可借助SafeClean 这个小东东来帮你清除,除非你的硬盘大的让你感到无所谓……快快快回来,看我跑到那里去了)。接下来用Class Wizard生成CMyWnd类,其基类为CWnd(在Base Class 中为generic CWnd)。这个类是我们所要重点研究的。创建满屏窗口、计时器,隐藏鼠标,展示图片,响应键盘、鼠标等等,这家伙全包了。至于MyScreensaverDlg.h与MyScreensaverDlg.cpp文件我们暂时不管。打开MyScreensaver.cpp,修改InitInstance()函数:
BOOL CMyScreensaverApp::InitInstance() { AfxEnableControlContainer(); #ifdef _AFXDLL Enable3dControls(); // Call this when using MFC in a shared DLL #else Enable3dControlsStatic(); // Call this when linking to MFC statically #endif CMyWnd* pWnd = new CMyWnd; pWnd->Create(); m_pMainWnd = pWnd; return TRUE; } 当然,再这之前得先 #include “MyWnd.h" 。后面要做的都在MyWnd.h 与 MyWnd.cpp 两文件中了。 下面给出CMyWnd 的说明: class CMyWnd : public CWnd { public: CMyWnd(); static LPCSTR lpszClassName; //注册类名 public: BOOL Create(); public: // Clazard generated virtual function overrides // {{AFX_VIRTUAL(CMyWnd) protected: virtual void PostNcDestroy(); // }}AFX_VIRTUAL public: virtual ~CMyWnd(); protected: CPoint m_prePoint; //检测鼠标移动 void DrawBitmap(CDC& dc, int nIndexBit); // {{AFX_MSG(CMyWnd) afx_msg void OnPaint(); afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnMButtonDown(UINT nFlags, CPoint point); afx_msg void OnMouseMove(UINT nFlags, CPoint point); afx_msg void OnRButtonDown(UINT nFlags, CPoint point); afx_msg void OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); afx_msg void OnDestroy(); afx_msg void OnTimer(UINT nIDEvent); afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized); afx_msg void OnActivateApp(BOOL bActive, HTASK hTask); // }}AFX_MSG DECLARE_MESSAGE_MAP() }; MyWnd.cpp 文件: …… CMyWnd::CMyWnd() { m_prePoint=CPoint(-1, -1); } LPCSTR CMyWnd::lpszClassName=NULL; BOOL CMyWnd::Create() { if(lpszClassName==NULL) { lpszClassName=AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW, ::LoadCursor(AfxGetResourceHandle(),MAKEINTRESOURCE(IDC_NOCURSOR))); //注册类;IDC_NOCURSOR为新建光标的ID,这个光标没有任何图案 } CRect rect(0, 0, ::GetSystemMetrics(SM_CXSCREEN), ::GetSystemMetrics(SM_CYSCREEN)); CreateEx(WS_EX_TOPMOST, lpszClassName, _T(“”), WS_VISIBLE|WS_POPUP, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, GetSafeHwnd(), NULL, NULL); //创建一个全屏窗口 SetTimer(ID_TIMER, 500, NULL); //计时器,ID_TIMER别忘了定义 return TRUE; } 为了防止同时运行两个相同的程序,下面两个函数是必需的: void CMyWnd::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) { CWnd::OnActivate(nState,pWndOther,bMinimized); if (nState==WA_INACTIVE) PostMessage(WM_CLOSE); }
void CMyWnd::OnActivateApp(BOOL bActive, HTASK hTask) { CWnd::OnActivateApp(bActive, hTask); if (!bActive) //is being deactivated PostMessage(WM_CLOSE); } OnPaint()函数将全屏窗口置为黑色:
void CMyWnd::OnPaint() { CPaintDC dc(this); CBrush brush(RGB(0,0,0)); CRect rect; GetClientRect(rect); dc.FillRect(&rect, &brush); }
由计数器调用DrawBitmap()函数,切换图片;注意,下面两个函数中的IDB_BITMAP1, dc.BitBlt(0,0,800,600……以及if(nIndexBit>=5)中的有关数据依据你的bmp图片个数、尺寸、位置不同而不同,我是选择了5张800x600的bmp图片。注意,ID值是连续的,IDB_BITMAP1最小。
void CMyWnd::DrawBitmap(CDC &dc, int nIndexBit) { CDC dcmem; dcmem.CreateCompatibleDC(&dc); CBitmap m_Bitmap; m_Bitmap.LoadBitmap(IDB_BITMAP1+nIndexBit); dcmem.SelectObject(m_Bitmap); dc.BitBlt(0,0,800,600,&dcmem,0,0,SRCCOPY); }
void CMyWnd::OnTimer(UINT nIDEvent) { CClientDC dc(this); static nIndexBit=0; if(nIndexBit>=5) nIndexBit=0; DrawBitmap(dc, nIndexBit++); CWnd::OnTimer(nIDEvent); }
响应键盘、鼠标是屏幕保护程序不可缺少的,在OnKeyDown()、 OnLButtonDown()、 OnMButtonDown()、OnRButtonDown()、OnSysKeyDown()函数中都加入: PostMessage(WM_CLOSE); OnMouseMove()函数比较特殊,它应加的代码为:
if(m_prePoint == CPoint(-1,-1)) m_prePoint = point; else if(m_prePoint!=point) PostMessage(WM_CLOSE);
快要完工了。在OnDestroy()函数中删掉计时器:KillTimer(ID_TIMER); 还有啦,在CMyWnd::PostNcDestroy() 中加入: delete this; 哎呀,腰酸背疼,眼球发涩,手背奇麻(不会吧)!不过,相信你一定会迫不及待地按下Ctrl+F5, 看着一幅幅图片在你面前轮番展示,啊,自己的屏幕保护程序!赶快赶快,换上自制的屏保,感觉就是不一样:图片任你挑,时间间隔任你改,鼠标?键盘?我想响应谁就响应谁……哎呀,谁扔的纸团:(。 其实,上面的程序还有很多可以改进的地方,比如图片总是单一地显示;bmp 文件太大,导致生成的屏幕保护程序也很大,远没有jpg合算;没有密码,没有可直接控制的界面。由于InitInstance()函数的简单处理(直接调用CMyWnd类),你会发现当你在桌面上右击,选择“属性”、“屏幕保护程序”页、“屏幕保护程序”下拉菜单、选中MyScreensaver时,MyScreensaver就直接预览了(或是直接运行了);假设你确定MyScreensaver作为你的屏幕保护程序,等你第二次进入“屏幕保护程序”页时,就直接预览。Why? 回头看看InitInstance()函数就明白了。为了让它更听话地工作,可修改InitInstance()函数: LPTSTR lpszArgv = __argv[1]; if (lpszArgv[0] ==‘/’) lpszArgv++; if (lstrcmpi(lpszArgv, _T(“s”))==0) { CMyWnd* pWnd=new CMyWnd; pWnd->Create(); m_pMainWnd=pWnd; return TRUE; } return FALSE; 不过现在你要是再在VC中运行这个程序,“该程序执行了非法操作,即将关闭。如果仍有问题,请与我联系(??)”将会伴随着一超重低音供你欣赏。(啊?)原因是我们加了一句return FALSE; 还有,别忘了还有一个CMyScreensaverDlg类没有用上,用它来与你的屏保直接对话再好不过了。例如,为了方便地确定时间间隔,选取图片,加上一个编辑框和几个按钮就可以了。重申一点,由于生成文件较大,占用的内存也多,如果不能运行,很可能是开的窗口太多了。这时你可以换较小的图片。有任何问题请来信:[email protected]。晚些时候我将在家门口 http://yxz.163.net 放置上述例子的整个项目,有一些新玩意和大家共同探讨。(全文完)
|
||
-- 作者:admin -- 发布时间:2006-8-11 16:13:00 -- 在VC中自建操作BMP位图文件类有编程经验的程序员都知道:要使应用程序的界面美观不可避免的要使用大量位图。现在流行的可视化编程工具对位图的使用提供了很好的支持,被称为三大可视化开发工具的VB、VC、Delphi通过封装位图对象对位图使用提供了很好的支持:VB提供了两个功能很强的对象:PictureBox及Image,通过使用它们,装载、显示位图变得非常容易。Delphi中也提供了一个位图对象:TImage,它的功能与用法与VB中的Image类似。在VC中通过使用设备相关类CDC与GDI对象类CBitmap来完成位图的操作。 然而在VC中使用CBitmap类必须将BMP位图装入资源中,然后通过类 CBitmap的成员函数使用它,在通过CDC类的成员函数操作它。这样做有两点缺陷:将位图装入资源导致可执行文件增大,不利于软件发行;只能使用资源中有限的位图,无法选取其它位图。而且BMP位图文件是以DIB(设备无关位图)方式保存,BMP位图装入资源后被转换为DDB(设备相关位图),类CBitmap就是对一系列DDB操作的API函数进行了封装,使用起来有一定的局限性,不如DIB可以独立于平台特性。 要弥补使用资源位图的两点不足,就必须直接使用BMP位图文件。VC的示例中提供了一种方法读取并显示BMP位图文件,但使用起来相当的麻烦。首先使用API函数GlobalAlloc分配内存并创建HDIB位图句柄,所有操作只能直接读写内存,然后通过StrechDIBits及SetDIBsToDevice函数来显示于屏幕上,操作起来费时费力。 因此笔者通过研究类CBitmap的封装与DIB结构,使用Win32中提供的新函数,建立了一个专用于操作BMP文件的类,而且完全仿照类CBitmap的实现:从类CGdiObject派生,新类的所有接口与类CBitmap 的部分接口完全相同。这样对于习惯使用CBitmap类接口用法的程序员来说两者的接口在使用上没有什么分别。 首先我们先简单介绍一下DIB的结构。DIB位图既可以存在于内存,也可以以文件形式保存在磁盘上(BMP文件)。所有DIB都包含两部分信息:位图信息(BITMAPINFO),包括位图信息头和颜色表;位图数据。对于内存中DIB的只要有上述两部分就行,而对于DIB文件则还要加上位图文件头。 其次,Win32中提供了一个新函数CreateDIBSection,通过它可以创建一个存储DIB位的内存区域,既可以执行相应的GDI操作,又可以直接通过指向DIB位区域的指针方位DIB位区域。这是一个非常有用的函数,通过它我们可以用DIB替代DDB。 在了解了相应的知识后,我们可以自己由类CGdiObject派生一个操作BMP文件的类:CBitmapFile。 在自己编写类时有两点值得注意: 在BitmapFile.h文件中定义类CBitmapFile,首先必须声明类CBitmapFile是从类CGdiObject中公有派生。然后在类中首先使用宏DECLARE_DYNAMIC(CBitmapFile)表明新类的最高父类是类CObject,是符合MFC的类库规范。紧接着宏DECLARE_DYNAMIC的是声明静态函数FromHandle,这两个声明必须放在类定义的最前面。 在BitmapFile.cpp文件中类的成员函数的实现前加上IMPLEMENT_DYNAMIC(CBitmapFile,CGdiObject);表明类CBitmapFile直接派生于类CGdiObject。 在类CBitmapFile的声明中有三个函数与类Cbitmap中的定义稍有不同: 在类CbitmapFile中LoadBitmap函数的参数是LPCTSTR型,保存的是BMP文件的文件名。 在类CbitmapFile中CreateBitmap函数的参数中少了参数nPlanes,在函数内部默认为1。 在类CbitmapFile中CreateBitmapIndirect函数的参数中多了参数lpBits,它指向指定位图DIB位的内存区域。 在成员函数中最重要的是函数CreateBitmapIndirect和函数LoadBitmap: 在函数CreateBitmapIndirect中使用函数CreateDIBSection创建了一个以兼容DC为基础的HBITMAP句柄,并用继承自类CGdiObject 的函数Attach把它与类CGdiObject的句柄m_hObject关联起来。然后将指定位图的DIB位图数据拷贝到由函数CreateDIBSection创建的DIB位的内存区域。 在函数LoadBitmap中首先从指定文件名的文件中读取以结构BITMAPFILEHEADER为大小的数据块,然后由文件头标志判断文件是否为BMP位图文件,然后由BITMAPFILEHEADER中bfSize保存的文件大小与文件的真实大小比较文件是否有损坏,再由BITMAPFILEHEADER中bfOffBits与BITMAPFILEHEADER结构大小相减计算出位图信息头和颜色表一共的大小,动态申请一块空间保存位图信息头和颜色表信息,再由BITMAPFILEHEADER中bfSize与bfOffBits相减计算出DIB位图数据的大小,动态申请一块空间保存DIB位图数据,最后调用成员函数CreateBitmapIndirect来创建DIB位图。 在应用程序的OnPaint()事件中绘制DIB位图的方法与使用类CBitmap时绘制位图的方法完全相同,但有一点要注意的是由于CDC类没有提供返回新类CBitmapFile指针类型的将DIB位图选入内存的SelectObject函数,所以在使用SelectObject时要将返回类型强制转换为CbitmapFile *类型。 至此,关于新类CBitmapFile编写中的一些要点和使用时一些要注意的问题就介绍这么多了。 附源文件 // // 文件描述:定义类CBitmapFile,此类是用于读取BMP文件,涉及读取、 // 建立及一系列常用的操作。 // 文件名: BitmapFile.h // 时间: 1999-2-11 // 作者: 贾暾 // #ifndef _CBITMAPFILE_H_ #define _CBITMAPFILE_H_ class CBitmapFile : public CGdiObject { DECLARE_DYNAMIC(CBitmapFile) public: static CBitmapFile* PASCAL FromHandle(HBITMAP hBitmap); // Constructors CBitmapFile(); BOOL LoadBitmap(LPCTSTR lpszFileName); BOOL CreateBitmap(int nWidth, int nHeight, UINT nBitCount, const void* lpBits); BOOL CreateBitmapIndirect(LPBITMAPINFO lpBitmapInfo, const void* lpBits); // Attributes operator HBITMAP() const; int GetBitmap(BITMAP* pBitMap); protected: // Attributes int GetColorNumber(WORD wBitCount); public: // Operations DWORD SetBitmapBits(DWORD dwCount, const void* lpBits); DWORD GetBitmapBits(DWORD dwCount, LPVOID lpBits); // Implementation public: virtual ~CBitmapFile(); }; #endif // // 文件描述:类CBitmapFile内成员函数的实现 // 文件名: BitmapFile.cpp // 时间: 1999-2-11 // 作者: 贾暾 // #include "BitmapFile.h" #include <memory.h> IMPLEMENT_DYNAMIC(CBitmapFile,CGdiObject); CBitmapFile* PASCAL CBitmapFile::FromHandle(HBITMAP hBitmap) { return (CBitmapFile*) CGdiObject::FromHandle(hBitmap); }
CBitmapFile::CBitmapFile() { }
BOOL CBitmapFile::LoadBitmap(LPCTSTR lpszFileName) { CFile file; if(!file.Open(lpszFileName,CFile::modeRead|CFile::shareDenyWrite)) { MessageBox(NULL,"BMP file open error!","warning",MB_OK); return FALSE; } BITMAPFILEHEADER bfhHeader; file.Read(&bfhHeader,sizeof(BITMAPFILEHEADER)); if(bfhHeader.bfType!=((WORD) (/'M/'<<8)|/'B/')) { MessageBox(NULL,"The file is not a BMP file!","warning",MB_OK); return FALSE; } if(bfhHeader.bfSize!=file.GetLength()) { MessageBox(NULL,"The BMP file header error!","warning",MB_OK); return FALSE; } UINT uBmpInfoLen=(UINT) bfhHeader.bfOffBits-sizeof(BITMAPFILEHEADER); LPBITMAPINFO lpBitmap=(LPBITMAPINFO) new BYTE[uBmpInfoLen]; file.Read((LPVOID) lpBitmap,uBmpInfoLen); if((* (LPDWORD)(lpBitmap))!=sizeof(BITMAPINFOHEADER)) { MessageBox(NULL,"The BMP is not Windows 3.0 format!","warning",MB_OK); return FALSE; } DWORD dwBitlen=bfhHeader.bfSize - bfhHeader.bfOffBits; LPVOID lpBits=new BYTE[dwBitlen]; file.ReadHuge(lpBits,dwBitlen); file.Close(); BOOL bSuccess=CreateBitmapIndirect(lpBitmap, lpBits); delete lpBitmap; delete lpBits; if(!bSuccess) return FALSE; return TRUE; }
BOOL CBitmapFile::CreateBitmap(int nWidth, int nHeight, UINT nBitCount, const void* lpSrcBits) { ASSERT(nBitCount==1||nBitCount==4||nBitCount==8 ||nBitCount==16||nBitCount==24||nBitCount==32); LPBITMAPINFO lpBitmap; lpBitmap=(BITMAPINFO*) new BYTE[sizeof(BITMAPINFOHEADER) + GetColorNumber(nBitCount) * sizeof(RGBQUAD)]; lpBitmap->bmiHeader.biSize=sizeof(BITMAPINFOHEADER); lpBitmap->bmiHeader.biWidth=nWidth; lpBitmap->bmiHeader.biHeight=nHeight; lpBitmap->bmiHeader.biBitCount=nBitCount; lpBitmap->bmiHeader.biPlanes=1; lpBitmap->bmiHeader.biCompression=BI_RGB; lpBitmap->bmiHeader.biSizeImage=0; lpBitmap->bmiHeader.biClrUsed=0; BOOL bSuccess=CreateBitmapIndirect(lpBitmap, lpSrcBits); delete lpBitmap; if(!bSuccess) return FALSE; return TRUE; } BOOL CBitmapFile::CreateBitmapIndirect(LPBITMAPINFO lpBitmapInfo, const void* lpSrcBits) { DeleteObject(); LPVOID lpBits; CDC *dc=new CDC; dc->CreateCompatibleDC(NULL); HBITMAP hBitmap=::CreateDIBSection(dc->m_hDC,lpBitmapInfo,DIB_RGB_COLORS, &lpBits,NULL,0); ASSERT(hBitmap!=NULL); delete dc; Attach(hBitmap); BITMAP bmp; GetBitmap(&bmp); DWORD dwCount=(DWORD) bmp.bmWidthBytes * bmp.bmHeight; if(SetBitmapBits(dwCount,lpSrcBits)!=dwCount) { MessageBox(NULL,"DIB build error!","warning",MB_OK); return FALSE; } return TRUE; } CBitmapFile::operator HBITMAP() const { return (HBITMAP)(this == NULL ? NULL : m_hObject); } int CBitmapFile::GetBitmap(BITMAP* pBitMap) { ASSERT(m_hObject != NULL); return ::GetObject(m_hObject, sizeof(BITMAP), pBitMap); }
int CBitmapFile::GetColorNumber(WORD wBitCount) { ASSERT(wBitCount==1||wBitCount==4||wBitCount==8 ||wBitCount==16||wBitCount==24||wBitCount==32); { case 1: return 2; case 4: return 16; case 8: return 256; default: return 0; } }
DWORD CBitmapFile::SetBitmapBits(DWORD dwCount, const void* lpBits) { if(lpBits!=NULL) { BITMAP bmp; GetBitmap(&bmp); memcpy(bmp.bmBits,lpBits,dwCount); return dwCount; } else return 0; }
DWORD CBitmapFile::GetBitmapBits(DWORD dwCount, LPVOID lpBits) { if(lpBits!=NULL) { BITMAP bmp; GetBitmap(&bmp); memcpy(lpBits,bmp.bmBits,dwCount); return dwCount; } else return 0; }
CBitmapFile::~CBitmapFile() { CGdiObject::DeleteObject();
|
||
-- 作者:admin -- 发布时间:2006-8-11 16:16:00 -- VC实现多格式图像的转换色彩鲜艳漂亮的高品质图像,一个个形象生动的Windows图标,高速运动、活灵活现的三维动画,这些无一不显示出程序设计者的艺术才华。在程序设计中,图像处理已经成为每个程序员的必修课。 在VC中编程显示一幅位图,下列步骤是不可缺少的: 装入位图、获得位图的大小信息、启用设备环境、位传输等,所需的程序代码一般比较冗长而且复杂。如果想将装入的位图另存为其他格式的图像文件,代码就更长了。这一切都是因为GDI本身的局限性造成的。 GDI+技术 随着Windows 2000的推出,上述情况有了极大的改观: 程序员不必了解每种图像格式的具体含义,照样可以写出多格式图像浏览或转换程序,这一切全都依赖于Windows 2000及后继版中所使用的GDI+技术。 和传统的GDI不同,GDI+中引入了对COM(组件对象模型)技术的支持,通过COM技术,GDI+简化了对图像文件的访问(打开、保存)。它是通过调用COM组件来实现的,GDI+扮演的只是指挥者,而非操作员。对于图像文件,GDI+所关心的不是图像文件的文件头信息,不论要打开的文件格式是什么类型,GDI+首先要做的是在注册表中查看该图像格式的编码(或解码)信息是否已经注册(HKEY_CLASSES_ROOT//MIME//Database//Content Type)。如果已经注册,就通过该编码信息调用COM组件,就这么简单。这种技术早就在微软的其他软件中使用了(如IE)。“体验”过Nimda病毒的朋友可能对“audio/wav”这段代码并不陌生,Nimda就是靠它来伪装自己的:让IE认为附件是WAV文件而自动打开可执行程序,这其实也是IE使用COM技术的一个突出表现。 配合GDI+的推出,微软也同时发布了相应的SDK,如果已经安装了最新的Microsoft PlatForm SDK或已经开始使用VS.NET,GDI+的SDK就已经在系统中了。如果没有的话,可以到http: //noner.top263.net/progtool上去下载GDI+的头文件和库文件。有了GDI+之后,只需简单地创建一个图形对象(Graphics object),然后直接调用该对象的方法(methods)进行绘图即可。图形对象是GDI+中的核心,正如DC之于GDI那样。图形对象和DC有许多相似的地方,在使用上遵循着相同的使用规则,但是两者在本质上已经有很大的区别。一个是基于句柄的GDI,一个是基于组件对象模型的GDI+。使用GDI+的SDK编程,必须按照下面的规范来进行:使用GDI+的命名空间(namespace Gdiplus),在使用GDI+函数时必须进行GDI+的初始化,使用完毕要销毁GDI+,这些规范在下面所列的程序中有详细的说明。 访问注册表编码信息 上面说到GDI+是通过在注册表中查看编码信息来访问图像文件的,在GDI+的SDK中,编码信息是存储在 ImageCodecInfo类中的,在这个类中,有编码的CLSID(COM组件的GUID标识码)、编码方式描述等。对于GDI,在注册表中访问编码信息通常使用以下两个函数来实现: 1. 查看系统中可用的图像编码信息(数量及大小) Status GetImageEncodersSize( UINT* numEncoders, //存储编码器数量的地址 UINT* size //存储编码信息所需内存大小 ); 2. 得到所有的编码信息 Status GetImageEncoders( UINT numEncoders, //可用编码器数量 UINT size, //存储编码器信息所需内存(由ImageCodecInfo类组成的数组的大小) ImageCodecInfo* encoders //编码器信息指针 ); 在GetImageEncoders函数中,参数numEncoders和size都是由GetImageEncodersSize返回的。下面的代码在注册表中查找具体格式图像的编码方式: int GetImageCLSID(const WCHAR* format, CLSID* pCLSID) { //得到格式为format的图像文件的编码值,访问该格式图像的COM组件的GUID值保存在pCLSID中 UINT num = 0; UINT size = 0; ImageCodecInfo* pImageCodecInfo = NULL; GetImageEncodersSize(&num, &size); if(size == 0) return FALSE; // 编码信息不可用 //分配内存 pImageCodecInfo = (ImageCodecInfo*)(malloc(size)); if(pImageCodecInfo == NULL) return FALSE; // 分配失败 //获得系统中可用的编码方式的所有信息 GetImageEncoders(num, size, pImageCodecInfo); //在可用编码信息中查找format格式是否被支持 for(UINT i = 0; i < num; ++i) { //MimeType:编码方式的具体描述 if( wcscmp(pImageCodecInfo[i] .MimeType, format) == 0 ) { *pCLSID = pImageCodecInfo[i] .Clsid; free(pImageCodecInfo); return TRUE; } } free(pImageCodecInfo); return FALSE; } 实现多格式的图像浏览和转换 有了前面的知识,实现多格式的图像的浏览与转换就不是什么难事了。 1. 在VC中创建一个SDI项目ImageShow,对GDI+声明和初始化及销毁进行代码编制,具体代码如下: #include “Gdiplus.h” using namespace Gdiplus; CImageShowView::CImageShowView() { //初始化GDI+ GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); } CImageShowView::~CImageShowView() { //销毁GDI+ ULONG_PTR gdiplusToken; GdiplusShutdown(gdiplusToken); } 2. 通过类向导(Class Wizard),重载“文件”菜单中的“打开”和“另存为”两项。为了编程的简单,本程序只将当前打开的图像文件直接存为BMP文件(实际上保存为其他格式的文件也很简单,只不过是对文件名进行分析而已)。另外,为了在打开和保存文件时进行文件名的传递,还应在CImageShowView类中加入一全局变量“CString strOpenFileName”。“打开”和“另存为”两菜单的对应代码如下: WCHAR* ToWChar(char * str) { //在GDI+中,有关字符的参数类型全部都是WCHAR类型 //该函数是将传统字符串进行转换 static WCHAR buffer[1024]; wcsset(buffer,0); MultiByteToWideChar(CP_ACP,0,str,strlen(str),buffer,1024); return buffer; }
void CImageShowView::OnFileOpen() { //本程序能够打开各类常见格式的图像文件 static char szFilter[]=“常见格式图像文件(*.*)|*.*|”; CFileDialog dlgChoseImage(1,NULL,NULL,NULL,szFilter); if(dlgChoseImage.DoModal()==IDOK) { strOpenFileName=dlgChoseImage .GetPathName(); //打开文件后立即在窗口中显示(重绘客户窗口) this->Invalidate(); } }
void CImageShowView::OnFileSaveAs() { if(strOpenFileName.IsEmpty()) { AfxMessageBox(“当前没有打开图像文件,不能进行保存!”); return; } //建立图形对象 Graphics graphics(GetDC()->m_hDC); //装入当前已经打开的图像文件 Image image(ToWChar(strOpenFileName.GetBuffer(strOpenFileName.GetLength()))); CString strFileSave; //将其他格式的图像全部另存为BMP文件 static char szFilter[]=“位图(*.BMP)|*.BMP|”; CFileDialog dlgChoseImage(0,“BMP”,NULL,NULL,szFilter); if(dlgChoseImage.DoModal()==IDOK) { strFileSave=dlgChoseImage.GetPathName(); CLSID clsid; if(GetImageCLSID(L“image/bmp”,&clsid)) { image.Save(ToWChar(strFileSave.GetBuffer(strFileSave.GetLength())), &clsid, NULL); //将保存后的图像进行显示 strOpenFileName=strFileSave; this->Invalidate(); } } } 2. 为了浏览图像转换前后的效果,还应该在窗口中分别绘制转换前后的图像,这需要在OnDraw函数中添加绘制代码,如下所示: void CImageShowView::OnDraw(CDC* pDC) { CImageShowDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); //如果没有选择显示图像文件,则不用重绘 if(strOpenFileName.IsEmpty()) return; //显示当前打开的图像文件的全名 this->GetParent()->SetWindowText(strOpenFileName); //建立图像对象 Graphics graphics(pDC->m_hDC); //装入图形文件 Image image(ToWChar(strOpenFileName.GetBuffer(strOpenFileName.GetLength()))); Point destPoints[3] = { Point(0, 0), Point(image.GetWidth(), 0), Point(0, image.GetHeight()) }; Point* pdestPoints = destPoints; //在指定区域pdestPoints显示图像 graphics.DrawImage(&image, pdestPoints, 3); } 在编译上面的程序之前,应该将Gdiplus.lib文件连编到项目中去,否则将会出现“LINK 2001”编译错误。上述程序在Visual Studio 6.0、Windows 2000/XP下调试通过,它能够显示或转换的图像格式有BMP、GIF、JPEG、Exif、PNG、TIFF、ICON、WMF、EMF等等。需要说明的是,本文只就GDI+编程的基本原理进行了阐述,事实上,GDI+的应用远不止此。 结束语 如果对本程序进行些改进,还可以编制出功能更加强大的图像处理程序。本文中所提到的程序,在笔者的主页“国税之家”(http://nationaltax.home.chinaren.com)的“个人世界”中可以下载到。有关GDI+编程的帮助信息,大家可以到微软的MSDN网站去查阅
|
||
-- 作者:admin -- 发布时间:2006-8-11 16:17:00 -- ado方式访问带密码数据库的方法主要代码: CString strSql; TCHAR FileName[MAX_PATH],errMsg[MAX_PATH]; ::CoInitialize(NULL); //初始化Com IADORecordBinding *picRs = NULL; _RecordsetPtr pRs("ADODB.Recordset"); _ConnectionPtr pConn("ADODB.Connection" ); //CFootballTeamRs rsFootballTeam; GetModuleFileName(NULL,FileName,MAX_PATH); (_tcsrchr(FileName,/'/////'))[1] = 0; lstrcat(FileName,_T("pass.mdb")); strSql = strSql + "Provider=Microsoft.Jet.OLEDB.4.0;" + "Data Source = " + FileName + ";Persist Security Info=False;Jet OLEDB:Database Password=cd;"; //pConn->ConnectionString = chDataSource; //pConn->Provider = _T("Microsoft.Jet.OLEDB.4.0"); CString sqlSentence = _T("select * from pass"); try { pConn->Open((_bstr_t)strSql, "", "", adModeUnknown); pRs->QueryInterface( __uuidof(IADORecordBinding), (LPVOID*)&picRs); pRs->Open( (_variant_t)sqlSentence, // 查询DemoTable表中所有字段 pConn.GetInterfacePtr(), // 获取库接库的IDispatch指针 adOpenDynamic, adLockOptimistic, adCmdText); } catch (_com_error &e) { sprintf(errMsg,_T("Code meaning = %s//n"), (char*) e.ErrorMessage()); AfxMessageBox(errMsg); pRs->Close(); pConn->Close(); ::CoUninitialize(); return; } try { while(!(pRs->EndOfFile)) { CString pass = (LPCTSTR)(_bstr_t)pRs->GetCollect("pass"); if(pass.IsEmpty()) return; SetDlgItemText(IDC_PASS,pass); pRs->MoveNext(); //break; } pRs->Close(); pConn->Close(); } catch (_com_error &e) { sprintf(errMsg,_T("Code meaning = %s//n"), (char*) e.ErrorMessage()); AfxMessageBox(errMsg); pRs->Close(); pConn->Close(); ::CoUninitialize(); return; } CoUninitialize();
|
||
-- 作者:admin -- 发布时间:2006-8-11 16:18:00 -- VC编程实现文本语音转换 内容简介 文本语音(Text-to-Speech,以下简称TTS),它的作用就是把通过TTS引擎把文本转化为语音输出。本文不是讲述如何建立自己的TTS引擎,而是简单介绍如何运用Microsoft Speech SDK 建立自己的文本语音转换应用程序。 Microsoft Speech SDK简介 Microsoft Speech SDK是微软提供的软件开发包,提供的Speech API (SAPI)主要包含两大方面: 1. API for Text-to-Speech 2. API for Speech Recognition 其中API for Text-to-Speech,就是微软TTS引擎的接口,通过它我们可以很容易地建立功能强大的文本语音程序,金山词霸的单词朗读功能就用到了这写API,而目前几乎所有的文本朗读工具都是用这个SDK开发的。至于API for Speech Recognition就是与TTS相对应的语音识别,语音技术是一种令人振奋的技术,但由于目前语音识别技术准确度和识别速度不太理想,还未达到广泛应用的要求。 Microsoft Speech SDK可以在微软的网站免费下载,目前的版本是5.1,为了支持中文,还要把附加的语言包(LangPack)一起下载。 为了在VC中使用这SDK,必需在工程中添加SDK的include和lib目录,为免每个工程都添加目录,最好的办法是在VC的 Option->Directoris立加上SDK的include和lib目录。 一个最简单的例子 先看一个入门的例子: #include <sapi.h> #pragma comment(lib,"ole32.lib") //CoInitialize CoCreateInstance需要调用ole32.dll #pragma comment(lib,"sapi.lib") //sapi.lib在SDK的lib目录,必需正确配置 int main(int argc, char* argv[]) { ISpVoice * pVoice = NULL; //COM初始化: if (FAILED(::CoInitialize(NULL))) return FALSE; //获取ISpVoice接口: HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice); if( SUCCEEDED( hr ) ) { hr = pVoice->Speak(L"Hello world", 0, NULL); pVoice->Release(); pVoice = NULL; } //千万不要忘记: ::CoUninitialize(); return TRUE; } 短短20几行代码就实现了文本语音转换,够神奇吧。SDK提供的SAPI是基于COM封装的,无论你是否熟悉COM,只要按部就班地用CoInitialize(), CoCreateInstance()获取IspVoice接口就够了,需要注意的是初始化COM后,程序结束前一定要用CoUninitialize()释放资源。 IspVoice接口主要函数 上述程序的流程是获取IspVoice接口,然后用ISpVoice::Speak()把文本输出为语音,可见,程序的核心就是IspVoice接口。除了Speak外IspVoice接口还有许多成员函数,具体用法请参考SDK的文档。下面择要说一下几个主要函数的用法: HRESULT Speak(const WCHAR *pwcs,DWORD dwFlags,ULONG *pulStreamNumber); 功能:就是speak了 参数: *pwcs 输入的文本字符串,必需为Unicode,如果是ansi字符串必需先转换为Unicode。 dwFlags 用来标志Speak的方式,其中SPF_IS_XML 表示输入文本含有XML标签,这个下文会讲到。 PulStreamNumber 输出,用来获取去当前文本输入的等候播放队列的位置,只有在异步模式才有用。 HRESULT Pause ( void ); HRESULT Resume ( void ); 功能:一看就知道了。 HRESULT SetRate(long RateAdjust ); HRESULT GetRate(long *pRateAdjust); 功能:设置/获取播放速度,范围:-10 to 10 HRESULT SetVolume(USHORT usVolume); HRESULT GetVolume(USHORT *pusVolume); 功能:设置/获取播放音量,范围:0 to 100 HRESULT SetSyncSpeakTimeout(ULONG msTimeout); HRESULT GetSyncSpeakTimeout(ULONG *pmsTimeout); 功能:设置/获取同步超时时间。由于在同步模式中,电泳Speak后程序就会进入阻塞状态等待Speak返回,为免程序长时间没相应,应该设置超时时间,msTimeout单位为毫秒。 HRESULT SetOutput(IUnknown *pUnkOutput,BOOL fAllowFormatChanges); 功能:设置输出,下文会讲到用SetOutput把Speak输出问WAV文件。 这些函数的返回类型都是HRESULT,如果成功则返回S_OK,错误有各自不同的错误码。 使用XML 个人认为这个TTS api功能最强大之处在于能够分析XML标签,通过XML标签设置音量、音调、延长、停顿,几乎可以使输出达到自然语音效果。前面已经提过,把Speak参数dwFlags设为SPF_IS_XML,TTS引擎就会分析XML文本,输入文本并不需要严格遵守W3C的标准,只要含有XML标签就行了,下面举个例子: …… pVoice->Speak(L"<VOICE REQUIRED=/'/'NAME=Microsoft Mary/'/'/>volume<VOLUME LEVEL=/'/'100/'/'>turn up</VOLUME>", SPF_IS_XML, NULL); …… <VOICE REQUIRED=/'/'NAME=Microsoft Mary/'/'/> 标签把声音设为Microsoft Mary,英文版SDK中一共含有3种声音,另外两种是Microsoft Sam和Microsoft Mike。 …… <VOLUME LEVEL=/'/'100/'/'> 把音量设为100,音量范围是0~100。 另外:标志音调(-10~10): <PITCH MIDDLE="10">text</PITCH> 注意:" 号在C/C++中前面要加 // ,否则会出错。 标志语速(-10~10): <RATE SPEED="-10">text</RATE> 逐个字母读: <SPELL>text</SPELL> 强调: <EMPH>text</EMPH> 停顿200毫秒(最长为65,536毫秒): <SILENCE MSEC="200" /> 控制发音: <PRON SYM = /'/'h eh - l ow 1/'/'/> 这个标签的功能比较强,重点讲一下:所有的语言发音都是由基本的音素组成,拿中文发音来说,拼音是组成发音的最基本的元素,只要知道汉字的拼音,即使不知道怎么写,我们可知道这个字怎么都,对于TTS引擎来说,它不一定认识所有字,但是你把拼音对应的符号(SYM)给它,它就一定能够读出来,而英语发音则可以用音标表示,/'/'h eh - l ow 1/'/'就是hello这个单词对应的语素。至于发音与符号SYM具体对应关系请看SDK文档中的Phoneme Table。 再另外,数字、日期、时间的读法也有一套规则,SDK中有详细的说明,这里不说了(懒得翻译了),下面随便抛个例子: <context ID = "date_ ymd">1999.12.21</context> 会读成 "December twenty first nineteen ninety nine" XML标签可以嵌套使用,但是一定要遵守XML标准。XML标签确实好用,效果也不错,但是……缺点:一个字―――"烦",如果给一大段文字加标签,简直痛不欲生。 把文本语音输出为WAV文件 #include <sapi.h> #include <sphelper.h> #pragma comment(lib,"ole32.lib") #pragma comment(lib,"sapi.lib") int main(int argc, char* argv[]) { ISpVoice * pVoice = NULL; if (FAILED(::CoInitialize(NULL))) return FALSE; HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice); if( SUCCEEDED( hr ) ) { CComPtr<ISpStream> cpWavStream; CComPtr<ISpStreamFormat> cpOldStream; CSpStreamFormat OriginalFmt; pVoice->GetOutputStream( &cpOldStream ); OriginalFmt.AssignFormat(cpOldStream); hr = SPBindToFile( L"D:////output.wav",SPFM_CREATE_ALWAYS, &cpWavStream,&OriginalFmt.FormatId(), OriginalFmt.WaveFormatExPtr() ); if( SUCCEEDED( hr ) ) { pVoice->SetOutput(cpWavStream,TRUE); WCHAR WTX[] = L"<VOICE REQUIRED=/'/'NAME=Microsoft Mary/'/'/>text to wave"; pVoice->Speak(WTX, SPF_IS_XML, NULL); pVoice->Release(); pVoice = NULL; } } ::CoUninitialize(); return TRUE; } SPBindToFile把文件绑定到输出流上,而SetOutput把输出设为绑定文件的流上。 最后 |