-- VC多媒体编程 |
||||||||||
-- 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 { x=Grid_Width/2; //当网格位于第一幅图象的最左边时,A点的横坐标。 While ( x<(Width1-Grid_Width/2) ) { FirstImageGrid=GetImgaeGrid(FirstImgaeGrid, x, y); differ=CaculateDiff(FirstImgaeGrid, SecondImageGrid);//计算象素值差的平 //方和 if (differ { 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 { 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 四、 多文挡视窗模型的重叠图象拼接程序框架 1. 程序构成 程序除应用类、主窗口类以外,还包括CFristImageDoc, CSecondImageDoc, CThirdImageDoc类用来保存第一幅、第二副以及拼接后图象的数据。还有与其相连的文档类和框架类。 2. 程序流程 程序的主要工作在应用类中完成,首先打开第一幅图象,图象的显示等操作由CFirstImageDoc 类和与其相关的视窗类及框架类完成,并将图象的位数组指针和图象大小传给应用类成员变量。再打开第二幅图象同样完成显示等操作,也将位数组指针和图象大小传给应用类成员变量。在应用类中完成图象的匹配对准工作,最后实现图象的合成。将合成后的图象传给CThirdImageDoc类进行显示当用户对拼接结果基本满意后,可以选择平滑连接将两幅图象平滑的连接起来。用户可将最后的结果保存成bmp文件。 五、 总结 图象拼接中,图象对准是前提和关键,程序基于网格匹配的方法实现了图象对准,应用了交互技术让用户可以对网格点的多少,网格间距大小,均可调整,还允许用户输入重叠范围使拼接过程有的放矢,缺省是较小图象高度的1/3。该程序实现了对位图图象文件的各种操作,具有普遍意义,还引入了不同文档类型的多文档视窗模型,并在不同文档类间实现了数据交换,具有一定的实用价值。为了使程序简单易用,只实现了对24位位图的拼接,也未提供垂直方向的拼接,只提供了水平方向的拼接。如要对不符合条件的图象进行拼接我们只需在Windows提供的画笔中将图象转化一下即可 |
||||||||||
-- 在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控件的支持。 |
||||||||||
-- Visual C++实现Flash动画播放 |
||||||||||
-- 用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 好了,应该大功告成了!调试一下吧! 本程序在调试过程中要多谢杜修杏老师的指点 |
||||||||||
-- 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); 标题为"释放鼠标"的控件的响应函数代码为: ClipCursor(NULL); 这行代码非常简单,就是使鼠标可以自由移动,不受限制。 把以上程序编译好了以后运行,点下"控制鼠标移动范围"按钮,鼠标就只能在这个窗口的范围以内活动,离不开这个窗口了,点下"释放鼠标"按钮,鼠标就恢复正常了。 知道了如何控制鼠标范围,该讲讲如何移动鼠标了。移动鼠标非常简单,只需要一个API函数SetCursorPos,这个函数有2个参数,第1个参数是屏幕的x坐标,第2个参数是屏幕的y坐标,它可以把鼠标移动到指定的坐标上去。 模拟鼠标的点击功能也非常简单,比如模拟点鼠标右键,可以使用下面两行代码: mouse_event(MOUSEEVENTF_RIGHTDOWN,0,0,0,0); mouse_event(MOUSEEVENTF_RIGHTUP,0,0,0,0); mouse_event函数的功能就是模拟鼠标点击,第1行代码是模拟按下鼠标右键,第2行代码是模拟鼠标右键弹起,这两行代码就模拟了1次点击鼠标右键的操作,如果想模拟点鼠标左键的操作,只要以上两行代码中的MOUSEEVENTF_RIGHTDOWN和MOUSEEVENTF_RIGHTUP参数换成MOUSEEVENTF_LEFTDOWN和MOUSEEVENTF_LEFTUP就可以了。 鼠标的模拟操作讲完了。以上的程序在Windows98下,使用Visual V++6.0编译成功,调试正常。 本期知识点:控制鼠标的API函数。 |
||||||||||
-- 用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 { 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 { 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 { 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); |
||||||||||
-- 用VC进行屏幕截取编程 ---- 屏幕截取是令人比较感兴趣的事情.虽然现在有不少应用程序如HYPERSNAP等可以用来截取你所喜欢的屏幕画面,但是如果能把这个功能加到自己的程序中,就更能利用它强大的作用. ---- 下面用VC来逐步介绍在Windows95下的实现过程.首先我们要确定屏幕截取的区域,用LPRECT结构来定义.可以截取一个窗口,或整个屏幕.以下代码把选定的屏幕区域拷贝到位图中. HBITMAP CopyScreenToBitmap(LPRECT lpRect) //lpRect 代表选定区域 { HDC hScrDC, hMemDC; // 屏幕和内存设备描述表 HBITMAP hBitmap, hOldBitmap; // 位图句柄 int nX, nY, nX2, nY2; // 选定区域坐标 int nWidth, nHeight; // 位图宽度和高度 int xScrn, yScrn; // 屏幕分辨率 // 确保选定区域不为空矩形 if (IsRectEmpty(lpRect)) return NULL; //为屏幕创建设备描述表 hScrDC = CreateDC("DISPLAY", NULL, NULL, NULL); //为屏幕设备描述表创建兼容的内存设备描述表 hMemDC = CreateCompatibleDC(hScrDC); // 获得选定区域坐标 nX = lpRect- >left; nY = lpRect- >top; nX2 = lpRect- >right; nY2 = lpRect- >bottom; // 获得屏幕分辨率 xScrn = GetDeviceCaps(hScrDC, HORZRES); yScrn = GetDeviceCaps(hScrDC, VERTRES); //确保选定区域是可见的 if (nX 〈0) nX = 0; if (nY 〈 0) nY = 0; if (nX2 > xScrn) nX2 = xScrn; if (nY2 > yScrn) nY2 = yScrn; nWidth = nX2 - nX; nHeight = nY2 - nY; // 创建一个与屏幕设备描述表兼容的位图 hBitmap = CreateCompatibleBitmap (hScrDC, nWidth, nHeight); // 把新位图选到内存设备描述表中 hOldBitmap = SelectObject(hMemDC, hBitmap); // 把屏幕设备描述表拷贝到内存设备描述表中 BitBlt(hMemDC, 0, 0, nWidth, nHeight, hScrDC, nX, nY, SRCCOPY); //得到屏幕位图的句柄 hBitmap = SelectObject(hMemDC, hOldBitmap); //清除 DeleteDC(hScrDC); DeleteDC(hMemDC); // 返回位图句柄 return hBitmap; } 得到屏幕位图句柄以后,我们 可以把屏幕内容粘贴到剪贴板上. if (OpenClipboard(hWnd)) //hWnd为程序窗口句柄 { //清空剪贴板 EmptyClipboard(); //把屏幕内容粘贴到剪贴板上, hBitmap 为刚才的屏幕位图句柄 SetClipboardData(CF_BITMAP, hBitmap); //关闭剪贴板 CloseClipboard(); } 我们也可以把屏幕内容以位图格式存到磁盘文件上. int SaveBitmapToFile(HBITMAP hBitmap , LPSTR lpFileName) //hBitmap 为刚才的屏幕位图句柄 { //lpFileName 为位图文件名 HDC hDC; //设备描述表 int iBits; //当前显示分辨率下每个像素所占字节数 WORD wBitCount; //位图中每个像素所占字节数 //定义调色板大小, 位图中像素字节大小 , 位图文件大小 , 写入文件字节数 DWORD dwPaletteSize=0, dwBmBitsSize, dwDIBSize, dwWritten; BITMAP Bitmap; //位图属性结构 BITMAPFILEHEADER bmfHdr; //位图文件头结构 BITMAPINFOHEADER bi; //位图信息头结构 LPBITMAPINFOHEADER lpbi; //指向位图信息头结构 HANDLE fh, hDib, hPal,hOldPal=NULL; //定义文件,分配内存句柄,调色板句柄 //计算位图文件每个像素所占字节数 hDC = CreateDC("DISPLAY",NULL,NULL,NULL); iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES); DeleteDC(hDC); if (iBits 〈 = 1) wBitCount = 1; else if (iBits 〈 = 4) wBitCount = 4; else if (iBits 〈 = 8) wBitCount = 8; else if (iBits 〈 = 24) wBitCount = 24; //计算调色板大小 if (wBitCount 〈 = 8) dwPaletteSize = (1 〈 〈 wBitCount) * sizeof(RGBQUAD); //设置位图信息头结构 GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&Bitmap); bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = Bitmap.bmWidth; bi.biHeight = Bitmap.bmHeight; bi.biPlanes = 1; bi.biBitCount = wBitCount; bi.biCompression = BI_RGB; bi.biSizeImage = 0; bi.biXPelsPerMeter = 0; bi.biYPelsPerMeter = 0; bi.biClrUsed = 0; bi.biClrImportant = 0; dwBmBitsSize = ((Bitmap.bmWidth * wBitCount+31)/32)* 4 *Bitmap.bmHeight ; //为位图内容分配内存 hDib = GlobalAlloc(GHND,dwBmBitsSize+ dwPaletteSize+sizeof(BITMAPINFOHEADER)); lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib); *lpbi = bi; // 处理调色板 hPal = GetStockObject(DEFAULT_PALETTE); if (hPal) { hDC = GetDC(NULL); hOldPal = SelectPalette(hDC, hPal, FALSE); RealizePalette(hDC); } // 获取该调色板下新的像素值 GetDIBits(hDC, hBitmap, 0, (UINT) Bitmap.bmHeight, (LPSTR)lpbi + sizeof(BITMAPINFOHEADER) +dwPaletteSize, (BITMAPINFOHEADER *) lpbi, DIB_RGB_COLORS); //恢复调色板 if (hOldPal) { SelectPalette(hDC, hOldPal, TRUE); RealizePalette(hDC); ReleaseDC(NULL, hDC); } //创建位图文件 fh = CreateFile(lpFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_ FLAG_SEQUENTIAL_SCAN, NULL); if (fh == INVALID_HANDLE_VALUE) return FALSE; // 设置位图文件头 bmfHdr.bfType = 0x4D42; // "BM" dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize; bmfHdr.bfSize = dwDIBSize; bmfHdr.bfReserved1 = 0; bmfHdr.bfReserved2 = 0; bmfHdr.bfOffBits = (DWORD)sizeof (BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwPaletteSize; // 写入位图文件头 WriteFile(fh, (LPSTR)&bmfHdr, sizeof (BITMAPFILEHEADER), &dwWritten, NULL); // 写入位图文件其余内容 WriteFile(fh, (LPSTR)lpbi, dwDIBSize, &dwWritten, NULL); //清除 GlobalUnlock(hDib); GlobalFree(hDib); CloseHandle(fh); |
||||||||||
-- 用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类没有用上,用它来与你的屏保直接对话再好不过了。例如,为了方便地确定时间间隔,选取图片,加上一个编辑框和几个按钮就可以了。重申一点,由于生成文件较大,占用的内存也多,如果不能运行,很可能是开的窗口太多了。这时你可以换较小的图片。 |
||||||||||
-- 在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 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(); |
||||||||||
-- 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(); } } } 3.为了浏览图像转换前后的效果,还应该在窗口中分别绘制转换前后的图像,这需要在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+的应用远不止此。 结束语 如果对本程序进行些改进,还可以编制出功能更加强大的图像处理程序。 |
||||||||||
-- 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(); |
||||||||||
-- 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把输出设为绑定文件的流上。 最后 看完本文后,是不是觉得很简单,微软把强大的功能封装的太好了。其实SDK中另外一个API,SR(语音识别)更有趣,有兴趣不妨试试,你会有意外的收获的 |
||||||||||
-- VC++5.0下MIDI、WAV及CD的播放 加入音乐是增强应用程序功能的所有方法中最简单的一个。几乎每个计算机游戏或多 媒体程序都以某种MIDI或CD音乐为背景。音乐可以使用户心情愉快;在合适的场合播 放恰当的音乐能够使程序员和他的VC++程序焕发光彩。 第一部分 MIDI的播放 ---- 乐器数字化接口(MIDI)是由音乐界的一些大公司(包括生产电子音乐合成器的公司) 制订的一项协议,后来被计算机产业所采用并成为多媒体音乐文件的标准格式。MIDI文件 一般较小,对硬件设备的要求高。 ---- 一、 原理 ---- 虽然MicroSoft支持MIDI文件,然而Visual C++或MFC并没有创建任何组件来实现 这种支持,但是MicroSoft API提供了三种不同的方法来实现MIDI的播放: MCI(The Media Control Interface)。这是最基本的方法,本文将详细讨论这种方法。 流缓冲器。这种格式允许应用程序为MIDI数据分配缓冲器。在需要精确控制MIDI播放的时候,流缓冲器将很有用处。 低级MIDI设备。需要完全控制MIDI数据的应用程序可以使用这种方法。 ---- MCI可以通过mciSendCommand()和mciSendString()来完成,本文仅使用mciSendCommand()函数。 ---- 原型:DWORD mciSendCommand(UINT wDeviceID,UINT wMessage,DWORD dwParam1,DWORD dwParam2); 参数: wDeviceID:接受消息的设备ID wMessage:MCI命令消息 dwParam1:命令的标志位 dwParam2:所使用参数块的指针 ---- 返值:调用成功,返回零;否则,返回双字中的低字存放有错误信息。 二MIDI的播放控制 ---- 1. 打开设备 MCI_OPEN_PARMS OpenParms; OpenParms.lpstrDeviceType = (LPCSTR) MCI_DEVTYPE_SEQUENCER;//MIDI类型 OpenParms.lpstrElementName = (LPCSTR) Filename; OpenParms.wDeviceID = 0; mciSendCommand (NULL, MCI_OPEN, MCI_WAIT | MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID | MCI_OPEN_ELEMENT, (DWORD)(LPVOID) &OpenParms) ---- MCI设备ID指明打开了哪个设备,当发送了MCI_OPEN命令时,这个值在参数块中返回——应被保存备用。 ---- 2. 关闭设备 mciSendCommand (m_wDeviceID, MCI_CLOSE, NULL, NULL); ---- 3. 播放 MCI_PLAY_PARMS PlayParms; PlayParms.dwFrom = 0; // 指定从什么地方(时间)播放 mciSendCommand (m_wDeviceID, MCI_PLAY, MCI_FROM, (DWORD)(LPVOID) &PlayParms)); ---- 4. 暂停 MCI_PLAY_PARMS PlayParms; mciSendCommand (m_wDeviceID, MCI_PAUSE, 0, (DWORD)(LPVOID) &PlayParms); ---- 5. 停止 mciSendCommand (m_wDeviceID, MCI_STOP, NULL, NULL); ---- 6. 跳跃 * 跳转到任意地方 MCI_SEEK_PARMS SeekParms; SeekParms.dwTo = (nMinute * 60 + nSecond) * 1000; //跳转的目标时间,时间单位为毫秒 mciSendCommand (m_wDeviceID, MCI_SEEK, MCI_TO | MCI_WAIT,(DWORD)(LPVOID) &SeekParms); * 跳到文件头 mciSendCommand (m_wDeviceID, MCI_SEEK, MCI_SEEK_TO_START, NULL); * 跳到文件尾 mciSendCommand (m_wDeviceID, MCI_SEEK, MCI_SEEK_TO_END, NULL); ---- 7. 查询当前信息 MCI_STATUS_PARMS StatusParms; StatusParms.dwItem = MCI_SEQ_STATUS_DIVTYPE; mciSendCommand (m_wDeviceID, MCI_STATUS, MCI_WAIT | MCI_STATUS_ITEM, (DWORD)(LPVOID) &StatusParms); 返回信息存放于StatusParms.dwReturn中。 MCI_STATUS标志 MCI_STATUS_LENGTH 获得文件长度 MCI_STATUS_MODE 获得文件播放的当前状态 MCI_STATUS_POSITION 获得文件播放的当前位置 MCI_STATUS_TIME_FORMAT 获得当前的时间格式 MCI_SEQ_STATUS_DIVTYPE 判断文件是PPQN类型还是SMPTE类型 MCI_SEQ_STATUS_TEMPO 获得当前播放速度,PQRN类型, 此值为节拍/分,SMPTE类型,此值为祯/秒 ---- 8. 设置时间格式及播放速度 MCI_SET_PARMS SetParms; SetParms.dwTimeFormat = MCI_FORMAT_MILLISECONDS; //设置时间单位为毫秒 mciSendCommand (m_wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD)(LPVOID) &SetParms); MCI_SEQ_SET_TEMPO 设置播放速度, PQRN类型,此值为节拍/分, SMPTE类型,此值为祯/秒 第二部分 WAV文件的播放 ---- 一、原理 ---- MicroSoft API提供了三种不同的方法来实现WAV的播放: PlaySound()函数。它可以通过单行编码来播放Wave格式的声音。此函数有两个限制:必须将声音数据完整地载入物理内存;数据格式必须被所配置的某一音频驱动器支 持。根据经验,PlaySound()适用于100K以下的文件。 MCI(The Media Control Interface),与上一章播放MIDI文件相似,可以播放100K 以上的文件。 低级Wave音频设备。用这些设备可以运行完全控制Wave数据的应用文件。 ---- 二、 WAV文件播放控制 ---- 因为同样使用MCI,与上一章相同,只列出不同的部分。 ---- 1. 打开设备 ---- 将MIDI的MCI_DEVTYPE_SEQUENCER 改为"waveaudio" ---- 2. 录音 MCI_RECORD_PARMS RecordParms; mciSendCommand (m_wDeviceID, MCI_RECORD, NULL, (DWORD)(LPVOID) &RecordParms); ---- 3. 保存录音 MCI_SAVE_PARMS SaveParms; SaveParms.lpfilename = (LPCSTR) Filename; mciSendCommand (m_wDeviceID, MCI_SAVE, MCI_SAVE_FILE | MCI_WAIT, (DWORD)(LPVOID) &SaveParms); 第三部分 CD的播放 ---- CD的独特优势在于,它由作曲家设计,并由音乐厂家生产。不同的计算机播放MIDI 文件时,声音效果也不一样,但是CD的声音效果总是相同的。高品质的音频对计算机用 户产生的效果会使你感到吃惊。 我们依然采用MCI播放CD,大部分的播放控制与前两部分相同,只列出不同的部分 ---- 1. 开光驱门 mciSendCommand (m_wDeviceID, MCI_SET, MCI_SET_DOOR_OPEN, NULL); ---- 2. 关光驱门 mciSendCommand (m_wDeviceID, MCI_SET, MCI_SET_DOOR_CLOSED, NULL); ---- 3. 打开设备 将MIDI的MCI_DEVTYPE_SEQUENCER 改为MCI_DEVTYPE_CD_AUDIO ---- 4. 播放 ---- 指定播放起点必须经过MCI_MAKE_TMSF(Track,Minute,Second,Frame)转化 ---- 5. 查询当前信息 MCI_STATUS_CURRENT_TRACK 得到当前曲目 MCI_STATUS_LENGTH 得到CD或指定曲目长度 MCI_STATUS_MODE 得到驱动器的当前状态 MCI_STATUS_NUMBER_OF_TRACKS 得到CD曲目的数目 MCI_STATUS_POSITION 得到当前格式下的位置 MCI_STATUS_READY 检查设备是否就绪 MCI_STATUS_TIME_FORMAT 得到当前时间格式 MCI_STATUS_MEDIA_PRESENT 检查以确认CD是否在驱动器内 MCI_CDA_STATUS_TYPE_TRACK 检查已确认某曲目是否为音频曲目 ---- 注意: 使用MCI_STATUS_LENGTH参数查询CD 及曲目长度,返回值通过调用MCI_MSF_MINUTE(),MCI_MSF_SECOND()转换为分、秒。 MCI_STATUS_POSITION参数返回值调用MCI_TMSF_TRACK(), MCI_TMSF_MINUTE(), MCI_TMSF_SECOND(),MCI_TMSF_FRAME才能得到当前的位置的道、分、秒、帧。 ---- 6. 跳跃 ---- 跳转的目标必须经过MCI_MAKE_TMSF(Track,Minute,Second,Frame)转化最好将上述三种格式分开建类,或做成动态连接库。在 Project-- >Setting-- >Link-- >Object/library modules中加入winmm.lib,源程序中包含< mmsystem.h >。 ---- MCI调用简单,功能强大,可以满足日常多媒体编程的基本需要。但是,MCI一次只能播放一个文件,使用DirectSound技术可以实现八个以上WAV文件的同时播放 |
||||||||||
-- 用VC制作简单AVI文件播放器
|
||||||||||
-- 直方图变换图像处理技术 图像增强处理技术一直是图像处理领域一类非常重要的基本处理技术。通过采取适当的增强处理可以将原本模糊不清甚至根本无法分辨的原始图片处理成清楚、明晰的富含大量有用信息的可使用图像,因此此类图像处理技术在医学、遥感、微生物、刑侦以及军事等诸多领域得到广泛应用。本文从空间域的角度对图像的灰度直方图增强处理方法进行详细的介绍。 图像的灰度直方图处理技术 在空间域对图像进行增强处理的方式有许多种,如增强对比度和动态范围压缩等等,但这些处理方式都是针对原始图像的每一个像素直接对其灰度进行处理的,其处理过程主要是通过增强函数对像素的灰度级进行运算并将运算结果作为该像素的新灰度值来实现的。通过改变选用的增强函数的解析表达式就可以得到不同的处理效果,这类处理方法比较灵活方便,处理效果也不错,但对于某些灰度分布很密集或对比度很弱的图像,虽然也能起到一定的增强效果但并不明显。对于这种情况就可以采用本文提出的灰度直方图变换方法将原始图像密集的灰度分布变得比较疏散,从而拉大图像的对比度并在视觉上达到明显增强的效果,使一些原本不易观察到的细节能变得清晰可辨。 图像的灰度变换处理是通过改变原始图像各像素在各灰度级上的概率分布来实现的。通过对图像的灰度值进行统计可以得到一个一维离散的图像灰度统计直方图函数p(sk)=nk/n(这里k=0,1,2……L-1),该式表示在第k个灰度级上的像素个数nk占全部像素总数n的比例,p(sk)则给出了对sk出现概率的一个估计。因此该直方图函数实际是图像的各灰度级的分布情况的反映,换句话说也就是给出了该幅图像所有灰度值的整体描述。通过该函数可以清楚地了解到图像对应的动态范围情况,可以了解到图像灰度的主要集中范围。因此可以通过图像增强程序的干预来改变直方图的灰度分布状况,使灰度均匀地或是按预期目标分布于整个灰度范围空间,从而达到增强图像对比度的效果。这种方法是基于数理统计和概率论的,比直接在空间域对原始图像采取对比度增强效果要好得多。在实际应用中直方图的变换主要有均衡变换和规定变换两种,而后者又可根据灰度级映射规则的不同分单映射规则和组映射规则两种。 直方图均衡化处理 直方图均衡化处理的中心思想是把原始图像的灰度直方图从比较集中的某个灰度区间变成在全部灰度范围内的均匀分布。对图像空间域点的增强过程是通过增强函数t=EH(s)来完成的,t、s分别为目标图像和原始图像上的像素点(x,y),在进行均衡化处理时,增强函数EH需要满足两个条件:增强函数EH(s)在0≤s≤L-1的范围内是一个单调递增函数,这个条件保证了在增强处理时没有打乱原始图像的灰度排列次序; 另一个需要满足的条件是对于0≤s≤L-1应当有0≤EH(s)≤L-1,它保证了变换过程中灰度值的动态范围的一致性。同样的,对于反变换过程s=EH-1(t),在0≤t≤1时也必须满足上述两个条件。累计分布函数(cumulative distribution function,CDF)就是满足上述条件的一种,通过该函数可以完成s到t的均匀分布转换。此时的增强转换方程为: tk = EH(sk)=∑(ni/n)=∑ps(si),(k=0,1,2……L-1) 上述求和区间为0到k,根据该方程可以由原图像的各像素灰度值直接得到直方图均衡化后各像素的灰度值。在实际处理变换时,一般先对原始图像的灰度情况进行统计分析,并计算出原始直方图分布,然后根据计算出的累计直方图分布tk,按式tk=[(N-1)* tk+0.5]对其取整并得出源灰度sk到tk的灰度映射关系,其中N为灰度的级数。重复上述步骤,得到所有的源图像各灰度级到目标图像各灰度级的映射关系,再按照新的映射关系对源图像各点像素进行灰度转换,即可完成对源图的直方图均衡化。下面是按照上述算法实现的部分关键程序代码。 首先对原始图像的各像素点的灰度情况进行统计计算。对于24位BMP图像,图像阵列是从第54字节开始的,每像素按R、G、B的顺序占3个字节。 for (DWORD i=54; i ns_r[m_cpBuffer[i]]++; //ns_r[k]为k灰度级像素数,m_cpBuffer[i]为当前的灰度值 i++; ns_g[m_cpBuffer[i]]++; //ns_g为G分量的统计计数 i++; ns_b[m_cpBuffer[i]]++; //ns_b为B分量的统计计数 } for (i=0; i<256; i++) //计算R、G、B三分量的直方图分布 { ps_r[i]=ns_r[i]/((m_dwFileLen-54)/3.0f); //ps_r[i]为R分量中i灰度级出现的概率 ps_g[i]=ns_g[i]/((m_dwFileLen-54)/3.0f); //ps_b[i]为G分量中i灰度级出现的概率 ps_b[i]=ns_b[i]/((m_dwFileLen-54)/3.0f); //ps_b[i]为B分量中i灰度级出现的概率 } 然后计算R、G、B三分量各灰度级的累计直方图分布,并对其进行取整以得出源和目标图像灰度之间的映射关系: for (i=0; i<256; i++) { //计算累计直方图分布 temp_r[i]=temp_r[i-1]+ps_r[i]; temp_g[i]=temp_g[i-1]+ps_g[i]; temp_b[i]=temp_b[i-1]+ps_b[i]; //累计分布取整,ns_r[]、ns_g[]、ns_b[]保存有计算出来的灰度映射关系 ns_r[i]=(int)(255.0f*temp_r[i]+0.5f); ns_g[i]=(int)(255.0f*temp_g[i]+0.5f); ns_b[i]=(int)(255.0f*temp_b[i]+0.5f); } 最后按照计算出来的映射关系,把原图的原始灰度值映射到经过均衡化的新灰度级上。图1、图2分别是原始图像和用本程序得出的经过直方图均衡化处理后的目标图像,从实验结果可以看出原始图像太暗,根本看不清细节,而处理过的图像则比较清晰: for (i=54; i { m_cpBuffer[i]=ns_r[m_cpBuffer[i]]; //对R分量进行灰度映射(均衡化) i++; m_cpBuffer[i]=ns_g[m_cpBuffer[i]]; //对G分量进行灰度映射(均衡化) i++; m_cpBuffer[i]=ns_b[m_cpBuffer[i]]; //对B分量进行灰度映射(均衡化) } 单映射规则的直方图规定化处理 前面介绍的直方图均衡化处理方法从实验效果看还是很不错的,从实现算法上也可以看出其优点主要在于能自动增强整幅图像的对比度,但具体的增强效果也因此不易控制,只能得到全局均衡化处理的直方图。在科研和工程应用中往往要根据不同的要求得到特定形状的直方图分布以有选择的对某灰度范围进行局部的对比度增强,此时可以采用对直方图的规定化处理,通过选择合适的规定化函数取得期望的效果。对于灰度级数分别为M和N(满足M≥N)的原始图和规定图,一般仍先按均衡化对原始图进行处理: tk =EHs(si)=∑ps(si),(k=0,1,2……M-1) 然后规定需要的直方图,并计算出能使规定的直方图均衡化的变换: vl = EHu(uj)=∑pu(ui),(l=0,1,2……N-1) 最后将第一步得到的变换反转过去,即把原始直方图对应映射到规定的直方图,也就是把所有的ps(si)映射到pu(uj)中去。由于映射是在离散空间进行的,而且在取整时不可避免会引入误差,因此采取何种对应规则是一个很重要的问题。比较常用的一种方法是Gonzalez在1987年提出的单映射规则(single mapping law,SML),首先寻找能满足|∑ps(si)-∑pu(uj)| 最小的k和l(ps()和pu()的求和上限),然后把ps(si)映射到pu(uj)中去。 本文针对原始图像比较暗的特点,采取如下的规定直方图,以使高亮度像素能得到充分的加强: float a=1.0f/(32.0f*63.0f); //64个灰度级,a为步长 for (int i=0; i<64; i++) { nu[i]=i*4; pu[i]=a*i; } 接下来对原始图和规定直方图计算累计直方图,在此不再赘述,重点是根据SML规则把ps(si)映射到pu(uj)中去: for (i=0;i<256;i++) { …… for (int j=0; j<64; j++) { float now_value=0.0f; //计算R分量的两累计直方图的绝对差值,并找到满足最小的灰度级 if (ps_r[i]-pu[j]>=0.0f) now_value=ps_r[i]-pu[j]; else now_value=pu[j]-ps_r[i]; if (now_value { m_r=j; min_value_r=now_value; } …… //对G和B分量处理的代码与R分量类似,在此省略 …… } //建立灰度映射关系 ns_r[i]=nu[m_r]; ns_g[i]=nu[m_g]; ns_b[i]=nu[m_b]; } 在得到ps(si)到pu(uj)的映射关系后,按照该映射关系把原图的原始灰度值映射到经过均衡化的新灰度级上,以完成最后的处理。上图(图3)为实验得到的按单映射规则对直方图规定化后的效果,同直方图均衡化处理效果相比,可以看出高亮度部分得到了充分的增强。 组映射规则的直方图规定化处理 单映射规则虽然实现起来比较简单直观,但在实际处理时仍存在不可忽视的取整误差,因此在一定程度上还不能很好的实现规定直方图的意图。可以通过在规定化直方图时选取适当的对应规则来改善,一种比较好的对应规则是组映射规则(group mapping law,GML)。这种规则的约定如下: 存在一维离散整数函数I(a),(a=0,1,2……N-1),而且满足0≤I(0) ≤I(1) ≤……≤I(a) ≤……≤I(N-1) ≤M-1。寻找能使 |∑ps(si)-∑pu(uj)| 达到最小的I(a),其中ps(si)的求和区间为[0,I(a)],pu(uj)的求和区间仍为[0,a]。a=0时,将介于0和I(0)之间的ps(si)都映射到pu(u0)中;1≤a≤N-1时,将介于I(a-1)+1和I(a)之间的ps(si)都映射到pu(uj)中去。 由于同单映射规则相比只是对应规则做了变化,因此编码部分只需将对应规则部分的代码根据上面介绍的组映射规则做必要修改即可: for (i=0; i<64; i++) //对规定直方图的灰度级进行枚举 { …… for (int j=0; j<256; j++) //对原图的灰度级进行枚举 { float now_value=0.0f; //寻找对于R变量能满足差值最小的I(a),保存于A2_r if (ps_r[j]-pu[i]>=0.0f) now_value=ps_r[j]-pu[i]; else now_value=pu[i]-ps_r[j]; if (now_value { A2_r=j; min_value_r=now_value; } for (int k=A1_r; k<=A2_r; k++) //建立R分量的映射规则 ns_r[k]=nu[i]; A1_r=A2_r+1; //对于G、B分量的处理类似,在此省略 …… } 对原始图像应用本算法,实验得出的按组映射规则对原图做直方图规定化后的效果如图4所示。 该图同单映射规则处理图像相比虽无太大变化,但在直方图分布和图像细节上更能体现出规定直方图的意图,而且通过下面的分析也可以看出组映射规则的误差要小得多。 在ps(si)映射到pu(uj)时,采取SML规则的映射方法由于取整误差的影响可能产生的最大误差是pu(uj)/2,而采用GML规则的映射方法可能出现的误差为ps(si)/2,由于M≥N,所以一定有pu(uj)/2≥ps(si)/2成立,也就是说SML映射规则的期望误差一定不会小于GML映射规则的期望误差。而且从算法实现上也可以看出,SML映射规则是一种有偏的映射规则,某些范围的灰度级会被有偏地映射到接近开始计算的灰度级;而GML映射规则是统计无偏的,从根本上避免了上述问题的出现。通过分析可以看出GML映射规则总会比SML映射规则更能体现规定直方图的意图,而且通常产生的误差只有SML映射规则的十几分之一。 结论 本文从理论上讲述了直方图变换处理中常用的直方图均衡化、采取单映射和组映射规则的直方图规定化变换方法,通过程序算法实现了上述图像增强过程,并给出了通过三种算法实验得出的处理图像。实验表明,本文介绍的方法对于暗、弱信号的原始图像的目标识别和图像增强等有着良好的处理效果,尤其是通过组映射规则的直方图规定化变换方法结合设计良好的规定直方图,可以得到更佳的图像处理效果。本文给出的程序代码在Microsoft Visual C++ 6.0下编译通过 |
||||||||||
-- 在你的MFC应用程序中显示一个JPG文件
答:好问题!有时使用VB的程序员觉得这个很容易。只要往你的表中拖入一个图像控件,然后你就可以往下做了……然而C++程序员就不得不感到烦恼和头疼。那我们要做些什么呢,编写我们自己的JPG解压函数吗?
OleLoadPicture从信息流里加载图像,并创建一个你能够用来显示图像的新的IPicture对象。
IPicture包揽了所有的令人厌烦的用来推算图像是否是Windows位图,JPEG,或GIF文件的事, 它甚至还可以推算图像是否是图标和图元文件!自然,其中的细节是需要些技巧,所以我就写了一个将它们都包含其中的叫"ImgView"的演示程序。 Figure 2 ImgView ImgView是一个典型的MFC文档/视图结构的应用程序,它使用了一个我以前写的叫"CPicture"的类来封装IPicture。CPicture将一些麻烦的COM类型的参数映射为那些更容易被MFC程序员接受的类型。例如,CPicture可以让你直接从一个文件名加载图像,如CFile或CArchive,而不是处理信息流;而且CPicture::Render完成了所有的令人厌烦的而又是IPicture所需要的HIMETRIC坐标转换,这样,你就没必要去做这些了。CPicture甚至还有一个可以从你的资源数据中加载图像的加载函数,所以要显示一个资源图像,你所要做的就是像下面那样写:
什么能够使工作变得更加容易呢?CPicture::Render能获得一个你想在其中显示图像的矩形。IPicture可以适当地拉伸图像。如果你传递了一个空的矩形,CPicture就使用图像本来的尺寸,并不对其进行拉伸。对于图像本身,CPicture要寻找一个名为"IMAGE"的资源类型,所以你必须对你的RC文件进行如下的编写:
总的来说,CPicture相当没头脑。它有一个ATL CComQIPtr 巧妙的指向IPicture界面的指针,其中不同的加载函数通过调用OleLoadPicture来初始化该界面。CPicture提供一般的封装函数来调用里面的IPicture。CPicture仅封装了我编写ImgView所需要的IPicture成员函数;这么做是因为我是这样的一个懒惰的程序员。如果你还需要调用IPicture::get_Handle或一些其它的较少用的IPicture成员函数,很抱歉,你就只好自己为其添加封装了。至少,这代码是件琐碎的事情。
,并且CPictureDoc::Serialize调用CPicture::Load从MFC所建立的存档中读取图像。
仅仅是为了有趣,CPictureDoc::OnNewDocument从程序的资源数据中加载了一张漂亮的NASA图像。为了显示这图像,CPictureView::OnDraw调用了CPicture::Render。
GetImageRect是CPictureView的一个函数,它依靠当前ImgView缩放比例而返回一个适当的图像矩形。(ImgView可以通过25%,33%,50%,75%,100%或"自适应比例"这六种比例形式来显示图像)。GetImageRect调用CPicture::GetImageSize获得真实的图像尺寸,随后依据比例适当地缩放。 图 5 OnEraseBkgnd填充被剪切的图像
这里假设在你的对话框中有一个静态控件,它的ID是IDC_IMAGE,同时还有一个具有相同ID的IMAGE资源。我从我那很常用的CStaticLink中派生了CPictureCtrl,这样,如果你想的话就可以声明一个URL超链接了(或仅仅创建一个与控件和图像具有相同ID的字符串资源)。如果你声明了一个URL,在这图像上点击鼠标将启动你的浏览器并实现这个链接。令人惊奇的是,CPicture保存了一个CPicture对象,并通过重载WM_PAINT来调用CPicture::Render,而不是通过一般的静态控件。 |
||||||||||
|
||||||||||
-- 在屏幕上作图 建立一个透明的窗体:
开始:
|
||||||||||
-- 用VC设计自动循环MP3播放器 【实现内容】自动循环播放的MP3播放器 【应用】自动循环播放的MP3播放器主要用于企业文化的应用。在每天的休息时段,播放器自动循环播放指定的MP3音乐。本自动播放器主要根据本公司的情况编写,包括早上上班,中午吃饭和下午下班三个时间段各播放1小时左右的音乐。播放器启动后,只在托盘中有图标,配置好各个时间段的音乐目录后,不需要人工干预,音乐可以自动启动、循环播放和结束。现已在本公司使用。 【特点】本软件用到许多常用的技巧: 1. 目录选择对话框 2. 文件选择对话框中的多个文件类型过滤设定 3. 应用程序在任务栏上隐藏,而在托盘上显示图标 4. 进度条的制作 5. 定时器使用 6. 带有链接功能的按钮――点击按钮可以打开电子信箱 7. 椭圆型的彩色按钮,彩色文本 8. 彩色标题条,彩色标题文字。等等 【实现原理】主要使用定时器。程序一旦运行,定时器将每秒响应一次。直到手工退出程序。 【主要控件】 m_MusicList---ComboBox类型 音乐文件列表 【主要程序段及说明】 void CMp3PlayerDlg::OnOpen() //点击打开按钮,选择音乐文件。可以多选 { m_MusicList.ResetContent(); m_MusicAry.RemoveAll(); m_Music.Close(); //m_Music是CCOMMCI对象 Init(); m_Pause.SetWindowText("暂停"); // CString filter = "媒体文件(所有类型)|*.cda;*.mid;*.rmi;*.midi;*.asf;*.wm;*.wma; *.asx;*.wax;*.m3u;*.wvx;*.mpeg;*.mpg;*.m1v;*.mp3; *.avi;*.wmv;*.wav;*.snd;*.au;*.aif;*.aifc|"; filter = filter +"CD音乐曲目(*.cda)|*.cda|" + "MIDI文件(*.mid;*.rmi;*.midi)|*.mid;*.rmi;*.midi|" + "Windows Media文件(*.asf;*.wm;*.wma;*.wa?)|*.asf;*.wm;*.wma;|" + "媒体播放表(*.asx;*.wax;*.m3u;*.wvx)|*.asx;*.wax;*.m3u;*.wvx|" + "电影文件(*.mpeg;*.mpg;*.m1v;*.mp3)|*.mpeg;*.mpg;*.m1v;*.mp3|" + "视频文件(*.avi;*.wmv)|*.avi;*.wmv)|" + "音频文件(*.wav;*.snd;*.au;*.aif;*.aifc)|*.wav;*.snd;*.au;*.aif;*.aifc|" + "所有文件(*.*)|*.*|"; CFileDialog music(TRUE,NULL,NULL,OFN_HIDEREADONLY|OFN_ALLOWMULTISELECT,filter); if(music.DoModal() == IDOK) { POSITION pos = music.GetStartPosition(); while (pos != NULL) { CString str = music.GetNextPathName(pos); m_MusicAry.Add(str); int index = str.ReverseFind(/'/'); if(index == -1) m_MusicList.AddString(str); else { CString name = str.Mid(index + 1); m_MusicList.AddString(name); } } } } ============================ void CMp3PlayerDlg::Play(int index)//播放函数,index为曲目的序号 { m_Music.Close(); m_MusicList.SetCurSel(index); CString sPath = m_MusicAry.GetAt(index); m_Music.Open(sPath); m_Status.SetWindowText(sPath + _T(" 正在播放..."));//在状态条中显示提示信息 m_nLength = m_Music.GetLength();//获取音乐长度 m_nPos = 0; m_nTime = m_nLength/1000;//除以1000即为播放时间 // m_sMusicLen.Format("%d分%d秒",m_nTime/60,m_nTime%60);//显示播放时间 m_sMusicPos.Format("%d分%d秒",m_nPos/60,m_nPos%60);//显示当前播放位置 // UpdateData(false); m_MPBar.SetScrollRange(0,m_nTime);//设置音乐播放的进度条范围 // m_Volume.SetRange(0,1000);//设置音量调节范围 m_Volume.SetPos(m_nVolume); // m_Music.Play(this->m_hWnd);//开始播放 SetTimer(2,1000,NULL);//启动2号定时器 } |
||||||||||
-- Windows Media Player SDK 简介 摘要:了解如何更改 Windows Media Player 的外观和行为,如何将其嵌入基于 Web 或基于 Windows 的应用程序,以及如何使用插件对象扩展其功能。本文介绍了 Windows Media Player 软件开发工具包 (SDK) 并描述了这些功能的用法。 简介 Microsoft® Windows Media™ Player 为数字音频和视频提供了出色的播放效果,但您的业务可能需要显示更多信息,或者需要修改视频或音频内容的播放方式。使用 Windows Media Player 软件开发工具包 (SDK)(代号为“Corona”),您可以扩展独立 Player 的功能,并将播放功能嵌入到自己的应用程序中。本文对 SDK 进行了高级概述,涉及如下所述的三个主要功能;本文适用于决策者以及初次接触数字媒体编程的程序员。 可以将 Player 嵌入 Web 应用程序或基于 Microsoft Windows® 的应用程序中。Windows Media Player 具有模块化体系结构,使您可以只使用所需的部分。尤其是,用户界面与音频和视频内容的播放功能相互独立。您可以使用其播放功能,并可决定在应用程序中是使用 Player 的现有用户界面,还是创建自己的用户界面。 Windows Media Player 提供了外观功能,您可以使用该功能创建个性化的 Player 外观,也可以基于 Player 创建截然不同的功能。 还可以创建插件来扩展 Player 的主要功能,方法是向用户界面添加新的交互式控件,在 Player 呈现音频或视频数据前对其进行修改,然后在 Windows Media 文件中呈现非标准数据流。 本文包括以下主题: 创建 Player 应用程序。介绍如何在 Web 或基于 Windows 的应用程序中嵌入 Windows Media Player 功能。 创建外观。介绍外观功能:更改 Windows Media Player 的外观和行为。 创建插件。介绍修改 Windows Media Player 外观和行为的插件。其中包括呈现专有内容、修改音频或视频播放以及通过交互式控件提供全新的功能。 创建 Player 应用程序 Windows Media Player 包括用于呈现视频和音频的 Microsoft ActiveX® 控件。该控件可在任何运行 Windows Media Player 的计算机上获得。Windows Media Player 是一种独立的技术,此外,它还包括一个 ActiveX 控件形式的组件对象模型 (COM) 服务器(Player 与 ActiveX 控件之间的关系相当于 Microsoft Internet Explorer 与其所提供的 WebBrowser ActiveX 控件之间的关系)。 有两种方法可用于创建使用 Windows Media Player ActiveX 控件的应用程序。您可以在 Web 应用程序中使用该控件,也可以在基于 Windows 的应用程序中使用它。 要在 Web 应用程序中使用 Windows Media Player,应在页面的超文本标记语言 (HTML) 中包含一个 OBJECT 元素。并在 OBJECT 元素中包含嵌套的 PARAM 元素,以指定 Windows Media Player ActiveX 控件是否可见、包含哪些操作按钮以及该控件的其他属性。通过包含多个 OBJECT 元素,可在一个 Web 页面中包含多个控件。要完全控制嵌入的 Player,可以在页面的 HTML 中编写脚本代码。 要在基于 Windows 的应用程序中使用 Windows Media Player,可以包含一个对服务于该控件的动态链接库 (DLL) 的引用。例如,在 Microsoft Visual Basic® 中,使用 Components(组件)对话框设置一个对“Windows Media Player”(这是 Wmp.dll 文件中库的助记名称)的引用。 如何设置控件属性取决于所用的编程环境。例如,在 Visual Basic 中,使用自定义 Properties(属性)对话框在设计时设置属性。也可以通过编写代码设置或读取属性以及在运行时调用方法。 最终用户可在任何安装了 Windows Media Player 的基于 Windows 的计算机上运行该应用程序。他们可以通过已经熟悉(或由您创建)的用户界面收听音频或观看视频。 企业程序员可以简单地使用 Player 提供的播放功能,并将精力集中在应用程序的特定业务需要上。 创建外观 您可以使用 Windows Media Player 中的外观技术更改 Player 的外观,同时保持它的标准行为。Player 包含的外观便是该功能的体现。尽管这些外观使 Player 看起来迥然不同,但它们却都保留了 Play(播放)和 Pause(暂停)按钮、显示播放列表、返回到完整模式等基本功能。 您还可以使用外观技术创建一个外观和行为与标准 Player 相差很大的应用程序。您可以随意在外观中添加各种各样的控件,使其执行自定义操作。要定义自定义操作,可以编写 JScript® 代码;您无需精通 Microsoft Visual C++® 或 Visual Basic 即可将外观技术用作编程平台。 您可以将外观定义文件、图像文件和任何 Jscript 文件压缩到文件扩展名为 .wmz 的单个文件中。这是将外观提供给最终用户的标准方法。 您还可以创建称作边框的特殊外观,它显示在完整模式下的 Windows Media Player 的 Now Playing(正在播放)功能中。您可以将边框、播放列表文件和数字媒体文件压缩到文件扩展名为 .wmd 的单个可下载文件中。当最终用户单击 .wmd 文件的链接时,Windows Media Player 将对包含的所有文件进行解压缩,将边框应用到 Now Playing(正在播放)功能,然后开始播放在播放列表文件中指定的内容。 这种可下载文件功能尤其适用于向最终用户提供出色的娱乐或教育内容。它不要求最终用户进行任何安装,同时允许您进行广泛的自定义。 创建插件 Windows Media Player SDK 包含支持广泛扩展性的接口。要扩展 Player 的基本功能,可以编写借助于 DLL 文件的 COM 对象。 SDK 包含一个可创建示例插件项目的 Visual C++ 向导。该项目包含编译和注册插件所需的代码和一个示例实现。运行向导后,您便可以将编程工作的重点放在实现插件所需的特定代码上。 利用插件提供的扩展性,您可以将 Windows Media Player 用作提供和操作数字媒体内容的平台。插件有几种不同的类型,将在以下主题中进行讨论。 可视化效果插件 当 Player 处于完整模式或外观模式(取决于外观)时,可视化效果插件可在只播放音频内容时,向 Player 的 Now Playing(正在播放)功能添加有趣的动态图像。可视化效果的外观和动态运动基于正在播放的音乐并与之同步。 将可视化效果作为 COM 对象实现。Player 每秒多次为当前选定的可视化效果引发事件。该事件包含以下数据: TimedLevel 结构,其中包括当前音频的频率和音量信息 设备上下文句柄,用于指定绘图面 RECT 结构,定义绘图面的大小 在可视化效果对象中,实现处理此事件的 Render 方法。 可以使用 Windows 图形设备接口 (GDI) 功能、Microsoft Direct3D® 或 DirectDraw® 功能等技术对图形进行可视化编程。 在调用那些利用设备上下文的 GDI 函数时,Windows Media Player 插件向导生成的示例实现使用音频和音量数据(位于 TimedLevel 结构中)。可以修改或替换该实现,以生成所需的可视化效果。 该向导可以编写用于编译自行注册的 COM DLL 的全部代码。要看到可视化效果,只需编译项目,然后运行 Windows Media Player 并选择新的可视化效果即可。 用户界面插件 Windows Media Player 为最终用户提供了各种信息和功能。但是,您可能需要提供自定义交互或自定义数据。使用用户界面插件可完成此项工作。 Windows Media Player 的完整模式由许多区域组成,如 Now Playing(正在播放)功能和播放列表窗格。某些区域在默认状态下不可见,但最终用户可将其显示为可见。这些区域包括显示在 Now Playing(正在播放)功能底部的设置区域,以及显示在播放列表上方的元数据区域。 用户界面插件分五种类型。其中三种类型显示在 Player 的不同区域中。在每个区域中,每次只能启用一个插件。 显示插件。这些插件占据 Now Playing(正在播放)功能中的可视化效果显示区域。由于该区域通常很大,因此比较适合显示大量数据或复杂的交互控件集。 设置插件。这些插件位于 Now Playing(正在播放)功能中可视化效果显示区域的下面。该区域包含图形均衡器、视频设置和其他用于配置播放或 Windows Media Player 外观的控件。设置插件比较适合添加相似的自定义功能,并使最终用户能够配置 Player 的外观或行为。 元数据插件。这些插件位于播放列表上方的一个小区域。它们比较适合显示曲目、唱片集或播放列表的确切信息,同时也适用于简单控件或超链接。例如,Windows Media Player 包含一个元数据插件,它显示的封面图形是一个指向有关当前播放的唱片集和音乐家的更多信息的链接。 有两类用户界面插件在 Windows Media Player 窗口中不显示。 窗口插件。这些插件占用一个单独的窗口。它们比较适合显示信息或向最终用户提供从 Now Playing(正在播放)功能切换到其他功能或加载其他插件时始终存在的交互操作。 背景插件。这些插件没有图形界面(除非像对待任何插件那样,为它们提供属性页)。它们比较适合不要求最终用户进行输入的自动服务。 Windows Media Player 插件向导可以为每种类型的用户界面插件创建示例实现。您可以修改实现细节,以提供所需功能,编译项目,然后使用 Player 测试插件。该向导包括编译自行注册的 COM DLL 所需的所有代码,因而您可以在编码时将重点放在特殊需要上。 DSP 插件 数字信号处理 (DSP) 插件在播放过程中修改数字媒体流。使用 DSP 插件,可以将彩色视频更改为黑白视频,或使用反色使图像看起来像底片。还可在音频中添加颤音或回声效果。插件在处理数字媒体内容时将改变该内容的播放。DSP 插件与可视化效果插件不同,后者接收种子值形式的数字音频数据以生成视觉输出,但并不影响音频播放本身。 除 Windows Media Player SDK 之外,编写 DSP 插件还需要 Microsoft DirectX® SDK。这些插件有一个明显特征,即实现由 DirectX SDK 提供的 IMediaObject 接口。 当 DSP 插件安装在最终用户的计算机上并被激活时,Windows Media Player 在数据呈现之前将音频和视频数据传递给该插件。Player 分配输入缓冲区和输出缓冲区,并使插件能够对这两个缓冲区进行访问。必须实现 IMediaObject 接口的各种方法,以便从输入缓冲区读取数据,以适用于插件的任何方式对数据进行处理,然后将修改后的数据写入输出缓冲区。Windows Media Player 将呈现从输出缓冲区获取的数据。 可以运行 Windows Media Player 插件向导创建 DSP 插件的示例实现。该示例实现 IMediaObject 接口,并实现一个称为 DoProcessOutput 的实用程序函数。很多情况下,只需使用 DSP 插件的特有代码修改此实用程序,并利用向导提供的所有其他代码。 呈现插件 使用 Windows Media Format SDK,可以向 Windows Media 文件中添加任何数据流。此数据流的格式不受限制,包括 Windows Media Player 默认情况下无法识别的格式。 有两种方法可用来呈现包含此类数据流的 Windows Media 文件。 可以编写一个自定义播放应用程序。这种情况下,除了包含用于呈现您的特有内容的代码之外,还必须包含用于提供标准音频、视频和脚本流的代码以及用于显示用户界面的代码。 也可以为 Windows Media Player 编写一个呈现插件。这种情况下,仍须编写用于呈现您的特有内容的代码,但是可以利用 Player 的固有功能来呈现支持的流并提供最终用户已有所了解的用户界面。 Windows Media Player 插件向导可以创建用于呈现插件的示例实现。该示例可实现呈现插件所需的许多接口,还可以实现一个称为 DoRendering 的实用程序函数。只需使用用于呈现特有流的代码修改此实用程序,并利用向导提供的所有其他代码。 更多信息 有关创建 Player 应用程序、外观或插件的完整信息,可以从 Windows Media Technologies(英文)Web 页面下载 Windows Media Player SDK。 法律声明 本软件的部分内容基于 Independent JPEG Group 的工作。 GIF 解压缩代码 1990,David Koblas 版权所有。如果在所有副本中均包含上述版权声明,并在支持文档中同时包含该版权声明和本许可声明,则允许出于任何目的免费使用、复制、修改和发布本软件及其文档。本软件“依样”提供,不包含任何明示或暗示的担保 |
||||||||||
-- 使用CImage类处理图像显示与格式转换 Introduction CImage是MFC和ATL共享的新类,它能从外部磁盘中调入一个JPEG、GIF、BMP和PNG格式的图像文件加以显示,而且这些文件格式可以 相互转换。 Background 我们知道,Visual C++的CBitmap类和静态图片控件的功能是比较弱的,它只能显示出在资源总的图标、位图、光标以及图元文件的内容,而不像 VB中的Image控件可以显示出绝大多数的外部图像文件(BMP,GIF,JPEG等)。因此,要想在对话框或者其它窗口中显示外部图像文件则只能借助于第 三方提供的控件或者代码。现在,MFC和ATL共享的新类CImage为图象处理提供了许多相应的方法,这使得Visual C++在图像方面的缺憾一去不复 返了。 Using the code 使用CImage的一般方法是这样的过程: (1) 打开应用程序的stdafx.h文件添加CImage类的包含文件: #include (2)定义CImage类对象,然后调用CImage::Load方法装载一个外部图像文件。 (3)调用CImage::Draw方法绘制图像。 例如在单文档程序中显示图像文件的例子: void CEx_ImageView::OnFileOpen() { // TOD 在此添加命令处理程序代码 CString strFilter; CSimpleArray aguidFileTypes; HRESULT hResult; hResult = m_Image.GetExporterFilterString(strFilter,aguidFileTypes,_T("All Image Files")); if (FAILED(hResult)) { MessageBox("GetExporterFilter调用失败!"); return; } CFileDialog dlg(TRUE,NULL,NULL,OFN_FILEMUSTEXIST,strFilter); if (IDOK != dlg.DoModal()) { return; } m_Image.Destroy(); hResult = m_Image.Load(dlg.GetFileName()); if (FAILED(hResult)) { MessageBox("调用图像文件失败!"); return; } CString str; str.LoadString(AFX_IDS_APP_TITLE); AfxGetMainWnd()->SetWindowText(str+"-"+dlg.GetFileName()); Invalidate(); } 将图片用其他格式保存: void CEx_ImageView::OnFileSaveAs() { if(m_Image.IsNull()){ MessageBox("您还没有打开一个要保存的图像文件!"); return; } CString strFilter; strFilter = "位图文件|*.bmp|JPEG图像文件|*.jpg|GIF图像文件|*.gif|PNG图像文件|*.png||"; CFileDialog dlg(FALSE,NULL,NULL,NULL,strFilter); if (IDOK!=dlg.DoModal()) { return; } // 如果用户没有制定文件扩展民,则为其添加一个 CString strFileName; CString strExtension; strFileName = dlg.m_ofn.lpstrFile; if (dlg.m_ofn.nFileExtension == 0) { tch(dlg.m_ofn.nFilterIndex) { case 1: strExtension = "bmp"; break; case 2: strExtension = "jpg"; break; case 3: strExtension = "gif"; break; case 4: strExtension = "png"; break; default: break; } strFileName = strFileName + ’.’+strExtension; } // 保存图像 HRESULT hResult = m_Image.Save(strFileName); if (FAILED(hResult)) { MessageBox("保存图像文件失败!"); } } History 2003.10.1第一版. |
||||||||||
-- VC调用ACM音频压缩编程接口的方法 音频和视频数据是大多数多媒体应用程序向用户提供信息的主要方式,这些数据一般具有较高的采样速率,如果不经过压缩的话,保存它们需要消耗大量的存贮空间,在网络上进行传输的效率也很低,因此音频视频数字压缩编码在多媒体技术中占有很重要的地位。就音频数据而言,目前常用的压缩方法有很多种,不同的方法具有不同的压缩比和还原音质,编码的格式和算法也各不相同,其中某些压缩算法相当复杂,普通程序不可能去实现其编解码算法。所幸的是,与Windows3.x相比,Windows95/NT4.0为多媒体应用程序提供了更强的支持,引入了ACM(Audio Compression Manager,音频压缩管理器)和VCM(Video Compressio nManager,视频压缩管理器),它们负责管理系统中所有音频和视频编解码器(Coder-Decoder,简称CODEC,是实现音频视频数据编解码的驱动程序),应用程序可以通过ACM或VCM提供的编程接口调用这些系统中现成的编解码器来实现音频或视频数据的压缩和解压缩。95/NT4.0系统自带的音频CODECs支持一些早期的音频数据压缩标准,如ADPCM等,InternetExplorer4.0等应用程序包含的音频CODECs支持一些比较新的压缩标准,如MPEGLayer3等。在控制面板的多媒体组件中选择“高级”,打开“音频压缩的编码解码器”,就可列出系统中安装的所有音频CODECs。本文所要介绍的就是ACM音频压缩接口的编程方法,所用编程工具为VC++5.0。 获取CODECs的信息 ----ACM的API函数定义在头文件msacm.h中,除此之外,对ACM编程还必须包含头文件mmsystem.h,mmreg.h,这两个头文件定义了多媒体编程中最基本的常量和数据结构。为了避免有些高版本ACM才提供的函数和功能在较低版本的ACM中上不可用,程序中应调用acmGetVersion函数查询用户机器中ACM的版本信息。 ----前面提到,在控制面板中可以查看系统中CODECs的信息,而在应用程序中也常常需要知道某种音频CODECs是否存在,并获取其编解码参数等信息,这一点可以通过调用下面两个函数来实现。 ---- MMRESULT mmr=acmMetrics(NULL, ACM_METRIC_COUNT_CODECS, &dwCodecs); ---- mmr = acmDriverEnum(CodecsEnumProc, 0, 0); ----acmMetrics()函数可以获取许多ACM对象的有用信息,例如向其中传递ACM_METRIC_COUNT_CODECS可以查询系统中安装的音频CODECs总数。函数acmDriverEnum()的功能是枚举所有的音频CODECs,在acmDriverEnum()的参数中指定回调函数CodecsEnumProc()可以进一步查询每个CODEC的信息。Windows编程中经常要用到回调函数,下面是枚举音频CODECs的一个回调函数的示例。 BOOL CALLBACK CodecsEnumProc(HACMDRIVERID hadid, DWORD dwInstance, DWORD fdwSupport) { DWORD dwSize = 0; if (fdwSupport & ACMDRIVERDETAILS_SUPPORTF_CODEC) printf("多格式转换//n"); ACMDRIVERDETAILS add; acmdd.cbStruct = sizeof(acmdd); MMRESULT mmr = acmDriverDetails(hadid, &acmdd, 0); if (mmr) error_msg(mmr); else { printf(" 全称: %s//n", acmdd.szLongName); printf(" 描述: %s//n", acmdd.szFeatures); } HACMDRIVER had = NULL; mmr = acmDriverOpen(&had, hadid, 0); //打开驱动程序 if (mmr) error_msg(mmr); else { mmr = acmMetrics(had, ACM_METRIC_ MAX_SIZE_FORMAT, &dwSize); WAVEFORMATEX* pwf = (WAVEFORMATEX*) malloc(dwSize); memset(pwf, 0, dwSize); pwf->cbSize = LOWORD(dwSize) - sizeof(WAVEFORMATEX); pwf->wFormatTag = WAVE_FORMAT_UNKNOWN; ACMFORMATDETAILS fd; memset(&fd, 0, sizeof(fd)); fd.cbStruct = sizeof(fd); fd.pwfx = pwf; fd.cbwfx = dwSize; fd.dwFormatTag = WAVE_FORMAT_UNKNOWN; mmr = acmFormatEnum(had, &fd, FormatEnumProc, 0, 0); if (mmr) error_msg(mmr); free(pwf); acmDriverClose(had, 0); } return TRUE; } ----CodecsEnumProc()共有三个参数。第一个参数是驱动程序的ID值;第二个参数是实例数据,本文例子中未使用;第三个参数描述该驱动程序所支持的功能,它由一组标识进行或运算构成,例如,如果设置了标识ACMDRIVERDETAILS_SUPPORTF_CODEC,则说明该驱动程序可以将一种编码格式的音频信号转换成另一种编码格式。通过acmDriverDetails()函数可以获得对该驱动程序进一步的信息,如CODEC的名称、简单描述等。以上信息实际上是由ACM收集,并保存在ACM内部,所以查询以上信息时并未真正将驱动程序加载至内存。而要获得每一种驱动程序支持的音频格式信息,则必须将驱动程序加载至内存,这是通过acmDriverOpen()完成的,在退出CodecsEnumProc()前,还要用acmDriverClose()来关闭已打开的驱动程序。在使用音频格式枚举函数前,需要先分配一块缓冲区存置格式信息,缓冲区的大小可通过调用acmMetrics()查询ACM_METRIC_MAX_SIZE_FORMAT获得,格式信息中的音频格式标识设为WAVE_FORMAT_UNKNOWN。在音频格式枚举中同样使用了回调函数,此回调函数只是列出了该音频格式的名称和标识值。 BOOL CALLBACK FormatEnumProc (HACMDRIVERID hadid, LPACMFORMATDETAILS pafd, DWORD dwInstance, DWORD fdwSupport) { printf("%4.4lXH, %s//n", pafd- >dwFormatTag, pafd- >szFormat); return TRUE; } ----上面介绍了浏览系统中所有音频CODECs及每种CODEC所支持的音频格式的方法,某些典型的应用程序可能需要列出系统中所有可以选用的CODECs,并由用户来选择使用哪一种CODEC进行压缩,此时就需要利用上面的编程方法来获取CODECs的信息。 音频数据的压缩 ----下面说明使用某一CODEC实现音频压缩的过程,读者朋友只需稍加改动就可编写出相应的解压程序。假设源信号为8K采样、16bitsPCM编码、单声道、长度为1秒的音频信号。驱动程序采用Windows95自带的TrueSpeech音频CODEC,它能实现大约10:1的压缩。在此例中,TrueSpeechCODEC支持从源音频格式到目标格式的转换,而在实际应用中,可能某种CODEC不支持直接将源音频格式转换成目标格式,这时可以采取两步转换法,即先将源格式转换成一种中间格式,再将此中间格式转换成目标格式,因为线性PCM编码最为简单,且为绝大多数CODEC所支持,所以一般中间格式都选为线性PCM格式的一种。 ----在进行压缩之前首先需要确定TrueSpeech驱动程序的ID值。为此需要用到acmDriverEnum()函数,对枚举到的每一个驱动程序,由acmDriverEnum()指定的回调函数将检查其支持的所有音频格式,若其中包括wFormatTag值为WAVE_FORMAT_DSPGROUP_TRUESPEECH的音频格式,则此驱动程序就是要寻找的TrueSpeechCODEC,它所支持的第一种WAVE_FORMAT_DSPGROUP_TRUESPEECH音频格式即为目标音频压缩格式。查询所需的CODEC及其支持的音频格式的方法见前一小节的介绍。 ----根据查询的结果,设hadID为TrueSpeechCODEC的ID值,pwfDrv为指向目标WAVEFORMATEX结构的指针,接下来利用获得的ID值打开相应的驱动程序。 HACMDRIVER had = NULL; mmr = acmDriverOpen(&had, hadID, 0); if(mmr) { printf(" 打开驱动程序失败//n"); exit(1); } ----压缩和解压缩一样,都是将音频信号从一种音频格式转换成另一种格式,要完成这一过程,首先要打开转换流。在用acmStreamOpen打开转换流时,我们指定了ACM_STREAMOPENF_NONREALTIME标志,它表示转换无需实时进行。因为很多压缩算法的计算量是相当大的,实时完成几乎是不可能的,例如在本例中,如果不指定此标志,TrueSpeechCODEC就会返回“无法完成”的错误。 HACMSTREAM hstr = NULL; DWORD dwSrcBytes = dwSrcSamples * wfSrc.wBitsPerSample / 8; mmr = acmStreamOpen(&hstr,had, //驱动程序句柄 pwfSrc, //指向源音频格式的指针 pwfDrv, //指向目标音频格式的指针 NULL, //无过滤器 NULL, //无回调函数 0,ACM_STREAMOPENF_NONREALTIME); ----在真正进行转换之前,还必须准备转换流的信息头。下面一段代码中,先利用源数据的大小以及目标格式的平均数据率估算目标数据的缓存区大小,然后调用acmStreamPrepareHeader为转换准备信息头。 ---- DWORD dwDstBytes=pwfDrv->nAvgBytesPerSec*dwSrcSamples/wfSrc.nSamplesPerSec; ---- dwDstBytes = dwDstBytes*3/2; // 计 算 压 缩 后 音 频 数 据 大 小, 并 依 此 适 当 增 加 输 出 缓 冲 区 的 大 小。 BYTE* pDstData = new BYTE [dwDstBytes]; ACMSTREAMHEADER shdr; memset(&strhdr, 0, sizeof(shdr)); shdr.cbStruct = sizeof(shdr); shdr.pbSrc = pSrcData; //源音频数据区 shdr.cbSrcLength = dwSrcBytes; shdr.pbDst = pDstData; //压缩后音频数据缓冲区 shdr.cbDstLength = dwDstBytes; mmr = acmStreamPrepareHeader(hstr, &shdr, 0); ----语音数据真正的压缩过程是由函数acmStreamConvert()完成的。在调用acmStreamConvert()时可以指定回调函数,以便在转换过程中显示进度信息等。在本例中,未指定回调函数,只是简单地等待压缩的结束。 ----mmr=acmStreamConvert(hstr,&shdr,0); ----数据压缩完毕后,应用程序就可以把缓冲区中的数据写入目标文件中。 ----最后,必须关闭转换流和驱动程序。 mmr=acmStreamClose(hstr,0); mmr=acmDriverClose(had,0); ----本文介绍了利用ACM获取音频CODEC的信息以及实现音频压缩的一般方法和过程,对ACM编程感兴趣的读者可以进一步参考VC++5的联机帮助中关于ACM的信息 |
||||||||||
-- 一个支持所有媒体类型和循环播放的类
#include class CMidi public: #endif
#include "stdafx.h" CMidi::CMidi() } CMidi::~CMidi() } //------------播放--------------------- m_count=mciopenparms.wDeviceID; return true; //---------------获取歌曲信息(长度,已播放长度)------ //-------------------停止------------------- //-------------------下一首---------------- //----------上一首------------------ } //----------保存曲目列表----------------- } //----------读取曲目路径列表----------------- //----------打开对话框,获取曲目列表----------------- //-------------------获得播放列表文件的路径------------ //----------------------循环播放------------------ |
||||||||||
-- 视频音频通讯程序 该程序为可以进行视频,音频和文字通讯 |
||||||||||
-- 作者:admin -- 发布时间:2006-8-11 16:37:00 -- 碎片图像无缝拼合技术的VC++实现 一、 引言 在测绘、文博等行业经常会遇到这样一种情况:观测对象比较大,为保证分辨率又不能将其全部照下,只能进行局部照相,事后再将这些局部照相的重合部分去掉,拼合成一幅完整的图像。以前多采用手工拼合,误差较大,往往不能很好的实现无缝拼合,即使有少量的专业设备,成本也普遍较高。其实只需将照片通过扫描仪将其录入到计算机中,通过程序处理,完全能很好的实现多幅图像的无缝拼合,满足实际需要,而且对于文博行业中常会遇到的破碎的、不规则对象如古旧字画残片等也能很好的进行无缝拼合。本文就对针对该程序的实现原理及过程做了简要的介绍。 二、 程序设计原理 首先我们从实际出发,我们是通过进行局部照相的手段来保存整体的全部信息,而要保证这些局部照片所含的信息之和能包括整体的全部信息就必然的使每两幅邻近的图片有一部分交叠的部分,这样才能保证在将整体对象划分为若干局部照片而后再拼合成整体图像的过程中不遗漏任何信息,即该划分、拼合的整个过程是无损的。既然如此,我们只需能保证让两相邻图片的重叠部分能完全重合,那么我们也就能够肯定在此状态下的这两幅图像实现了无缝拼合。所以,问题就转换为使相邻图片的重叠部分能完全重合,而判断两相同的图像片段是否完全重叠可以用光栅掩码来进行直观的判断,比如我们可以采用"异或"的掩码,当相同位置上的两幅图片的像素相同时就为0即黑色,所以可以对两图片进行移动,只要重叠部分全黑,则表明此时两图像的重叠部分已准确的重合了,而此时也实现了图像的无缝拼合。此后只需再采用"或"的光栅掩码将合并后的图像显示出来,再通过拷屏等手段将其存盘即可。在实现拼合的全过程中主要涉及到图像的拖放、图像文件的读取及显示、光栅掩码、拷屏以及内存位图的保存等多种技术。接下来就对这些技术的具体应用进行介绍。 三、 程序的具体实现 在进行拼合之前,首先要将从扫描仪录入的图像从文件读取到内存中,并显示出来。由于在拼合时采取的光栅操作掩码是"异或",所以为保持图像的原始面貌,可以在消息WM_ERASEBKGND 的响应函数中用PatBlt函数将整个客户区的初始背景设定为黑色: …… pDC->PatBlt(0,0,rect.Width(),rect.Height(), BLACKNESS); return TRUE; 读取位图文件可以用LoadImage函数来实现,m_sPath1指定了文件的路径,LR_LOADFROMFILE属性指定从文件中读取位图,返回值为该位图的句柄: …… HBITMAP hbitmap; hbitmap=(HBITMAP)LoadImage(AfxGetInstanceHandle(), m_sPath1, IMAGE_BITMAP,0,0, LR_LOADFROMFILE|LR_CREATEDIBSECTION); 之后我们就可以创建一个和当前设备环境兼容的内存设备环境hMemDC1,并将刚才读取到内存的位图放置到该设备环境中: hMemDC1=::CreateCompatibleDC(NULL); SelectObject(hMemDC1,hbitmap); ::DeleteObject(hbitmap); //释放掉用过的位图句柄 Invalidate(); 至于位图的显示,由于需要频繁的拖动和其他处理,将其放置于OnDraw函数中较为合理,需要更新显示时只需显式地用Invalidate()函数刷新即可。OnDraw()中的显示位图部分最好用BitBlt函数来完成,该函数负责把hMemDC1中的位图放置到pDC页面中以完成内存页面的置换,其处理速度还是比较快的: …… ::BitBlt(pDC->m_hDC,m_nX1,m_nY1, m_nWidth1,m_nHeight1,hMemDC1,0,0,m_dwRop); …… 函数中的m_dwRop变量对光栅操作码进行设置,初始为SRCINVERT即光栅异或操作,当拼合成功需要显示合并后的效果时再将其设定为SRCPAINT光栅或操作。 我们可以通过对鼠标消息响应函数的编程来实现在客户区内的位图拖放,按照Windows系统的习惯,首先在鼠标左键的响应函数中通过PtInRect()函数判断鼠标在左键按下时是否是落在位图上,如果是就可以在鼠标左键弹起之前将图片随鼠标拖动了,显然这部分应在WM_MOUSEMOVE消息的响应函数内编写代码: …… if(m_bCanMove1==true) //在移动之前鼠标左键是在图片上点击的 { int dx=m_nOldX1-m_nX1; //计算鼠标距离图片原点的距离 int dy=m_nOldY1-m_nY1; m_nX1=point.x-dx; //计算新的图片原点的坐标(客户区坐标) m_nY1=point.y-dy; Invalidate(); //更新视图 } m_nOldX1=point.x; //保存上一次的鼠标位置 m_nOldY1=point.y; …… 到此为止,可以运行程序对多幅碎片图像进行拼合了,用鼠标拖动一幅图像在另一幅图像边缘移动,由于采用了"异或"的光栅掩码,两幅图片交叠的地方颜色会发生改变,但只有完全重合时才会全黑,表明此时的拼合是无缝的,将掩码换为"或"即可将拼合后的图像显示出来。但此时只是保留在内存中,还要经过进一步的处理,才能将合并后的图像存盘保留。 首先要对合并后的图像所在的矩形框的位置、大小进行判断,可以用下面的类似代码来完成(本例同时最多能有4幅图像进行拼合): …… int temp1,temp2,x0,y0,x1,y1; temp1=m_nX1 if(m_sPath3!="")//如果有3幅图片参与拼合 { if(m_sPath4!="")//如果有4幅图片参与拼合 temp2=m_nX3 else temp2=m_nX3; x0=temp1 } else x0=temp1; …… temp1=m_nX1+m_nWidth1>m_nX2+m_nWidth2?m_nX1+m_nWidth1:m_nX2+m_nWidth2; if(m_sPath3!="") { if(m_sPath4!="") temp2=m_nX3+m_nWidth3>m_nX4+m_nWidth4?m_nX3+m_nWidth3:m_nX4+m_nWidth4; else temp2=m_nX3+m_nWidth3; x1=temp1>temp2?temp1:temp2; } else x1=temp1; 可以用类似的代码计算出y0和y1。在进行屏幕截图之前必须将由x0,y0,x1,y1构成的矩形由客户坐标转换成屏幕坐标,可以用ClientToScreen()函数来实现。下面是将屏幕指定区域以位图形式拷贝到内存中去的函数的主要实现代码: HBITMAP CImageView::CopyScreenToBitmap(LPRECT lpRect) { …… // 确保选定区域不为空矩形 if(IsRectEmpty(lpRect)) return NULL; //为屏幕创建设备描述表 hScrDC = CreateDC("DISPLAY", NULL, NULL, NULL); //为屏幕设备描述表创建兼容的内存设备描述表 hMemDC = CreateCompatibleDC(hScrDC); …… // 创建一个与屏幕设备描述表兼容的位图 hBitmap = CreateCompatibleBitmap(hScrDC, lpRect->Width(),lpRect->Height()); // 把新位图选到内存设备描述表中 hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap); // 把屏幕设备描述表拷贝到内存设备描述表中 BitBlt(hMemDC, 0, 0, lpRect->Width(),lpRect->Height, hScrDC, lpRect->left lpRect->top, SRCCOPY); //得到屏幕位图的句柄 hBitmap =(HBITMAP)SelectObject(hMemDC, hOldBitmap); //清除 DeleteDC(hScrDC); DeleteDC(hMemDC); …… // 返回位图句柄 return hBitmap; } 当把拼合后的区域拷贝到内存,并获取到该内存位图的句柄后可以将其通过剪贴板传送到其他图形处理软件中进行进一布的处理,也可以按照位图的格式直接将其保存成文件,为方便计,本例采用了后者。其实现过程主要是根据刚才获取到的内存位图句柄按格式填充BMP文件的信息头以及像素阵列,下面就结合实现的关键代码进行介绍: 首先获取设备描述表句柄,并用函数GetDeviceCaps()获取到当前显示分辨率下每个像素所占字节数,并据此计算出调色板的大小: …… hDC = CreateDC("DISPLAY",NULL,NULL,NULL); iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES); DeleteDC(hDC); if (iBits <= 1) wBitCount = 1; else if (iBits<= 4) wBitCount = 4; else if (iBits<= 8) wBitCount = 8; else if (iBits <= 24) wBitCount = 24; //计算调色板大小 …… 然后就可以设置位图信息头结构了,其中bi 是BITMAPINFOHEADER 结构的实例对象: …… if (wBitCount <= 8) dwPaletteSize = (1< GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&Bitmap); bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = Bitmap.bmWidth; bi.biHeight = Bitmap.bmHeight; bi.biPlanes = 1; bi.biBitCount = wBitCount; bi.biCompression = BI_RGB; bi.biSizeImage = 0; bi.biXPelsPerMeter = 0; bi.biYPelsPerMeter = 0; bi.biClrUsed = 0; bi.biClrImportant = 0; 用GlobalAlloc()函数根据计算的结果为位图内容分配内存,并返回分配得到的内存句柄hDib, 并用GetStockObject()来设置缺省状态下的调色板: …… dwBmBitsSize = ((Bitmap.bmWidth*wBitCount+31)/32)*4*Bitmap.bmHeight; hDib = GlobalAlloc(GHND,dwBmBitsSize+dwPaletteSize+sizeof(BITMAPINFOHEADER)); lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib); *lpbi = bi; // 处理调色板 hPal = GetStockObject(DEFAULT_PALETTE); if (hPal) { hDC = ::GetDC(NULL); hOldPal =SelectPalette(hDC, (HPALETTE)hPal, FALSE); RealizePalette(hDC); } // 获取该调色板下新的像素值 GetDIBits(hDC, hBitmap, 0, (UINT) Bitmap.bmHeight, (LPSTR)lpbi + sizeof(BITMAPINFOHEADER)+dwPaletteSize, (BITMAPINFO*)lpbi, DIB_RGB_COLORS); //恢复调色板 if (hOldPal) { SelectPalette(hDC,(HPALETTE)hOldPal, TRUE); RealizePalette(hDC); ::ReleaseDC(NULL,hDC); } …… 最后的工作就是创建位图文件了,需要把设置好的位图文件头和像素点阵信息依次保存到文件中,其中bmfHdr 是BITMAPFILEHEADER位图文件头结构的实例对象,需要按照BMP位图的存盘格式对其进行设置: …… fh = CreateFile(lpFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); // 设置位图文件头 bmfHdr.bfType = 0x4D42; // "BM" dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize; bmfHdr.bfSize = dwDIBSize; bmfHdr.bfReserved1 = 0; bmfHdr.bfReserved2 = 0; bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER)+ dwPaletteSize; //写入位图文件头 WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL); // 写入位图文件其余内容 WriteFile(fh, (LPSTR)lpbi, dwDIBSize,&dwWritten, NULL); …… 小结:本程序通过一个实例讲述了处理图片无缝拼合的一种实用方法,在测绘、勘察、文博等行业均有较大的应用潜力。在理解了程序的设计思路和编程思想的前提下,结合具体的实际需求,通过对本文具体代码的改动可以设计出更适合本单位实际情况的类似软件。另外,本文所讲述的截取并保存屏幕技术在类似程序的编制上也可以提供一定的参考。本程序在Windows 98下,由Microsoft Visual C++ 6.0编译通过 |