绪言: 这是一篇很肤浅的文章,严格意义上来讲不能算是一篇技术型的文章,只能说是自己在学习direct编程时的一点心得,不过里面还是包含了不少的win32编程和direct编程的基础知识,拿到这里来献丑也是为了希望为正在从事direct编程的朋友尽一点绵力,这篇文章主要是讲了我利用direct编程实现一个图像的裁减的过程,其效果图如下:
裁减前:

裁减后:

图中画白线的地方就是裁减后的区域^_^。 如果朋友们在里面发现了bug请告诉我,如果朋友们有什么更好的意见和指教也请告诉我^_^。
第一回:初窥门径
windows的编程是有一定流程的,其实也就是最开始我们必须创建一个主函数,而这个主函数就应该是一个大的消息循环,这个主函数的大致流程如下图:

首先我们必须做一个最基本的事情就是创建一个窗体,并注册,再基于这个窗体上我们才能干更多的事,就好比先有一张画布,然后我们可以在画布中绘图。
//************************************************************ //函数:WinMain( ) //功能:Windows程序入口函数。创建主窗口,处理消息循环 //************************************************************ int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MSG msg; //消息结构
InitWindow(hInstance,nCmdShow); //初始化窗体
while(1) //消息循环 { if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message==WM_QUIT) //退出消息循环 break; TranslateMessage(&msg); //得到消息,处理回调函数 DispatchMessage(&msg); } } return msg.wParam; }
在讲本程序的消息循环之前,我想先谈一下Dos与Windows驱动机制的区别:
DOS程序主要使用顺序的,过程驱动的程序设计方法。顺序的,过程驱动的程序有一个明显的开始,明显的过程及一个明显的结束,因此程序能直接控制程序事件或过程的顺序。虽然在顺序的过程驱动的程序中也有很多处理异常的方法,但这样的异常处理也仍然是顺序的,过程驱动的结构。
而Windows的驱动方式是事件驱动,就是不由事件的顺序来控制,而是由事件的发生来控制,所有的事件是无序的,所为一个程序员,在你编写程序时,你并不知道用户先按哪个按纽,也不知道程序先触发哪个消息。你的任务就是对正在开发的应用程序要发出或要接收的消息进行排序和管理。事件驱动程序设计是密切围绕消息的产生与处理而展开的,一条消息是关于发生的事件的消息。
WinMain( )函数的原型如下:
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
第一个参数hInstance是标识该应用程序的句柄。不过句柄又是什么呢?其实就是一个指向该程序所占据的内存区域的指针,它唯一地代表了该应用程序,Windows使用它管理内存中的各种对象。 第二个参数是hPrevInstance,应用程序的前一个实例句柄,别管它,对于Win32位而言,它一般是NULL. 第三个参数是lpCmdLine,是指向应用程序命令行参数字符串的指针。比如说我们运行"test hello",则此参数指向的字符串为"hello"。 最后一个参数是nCmdShow,是一个用来指定窗口显示方式的整数。它告诉应用程序如何初始化窗口,如最大化,最小化等状态。关于窗口显示方式的其他种类,将在下图说明。

然后我们要初始化窗体,并且注册它,然后我们才能使用它。 //************************************************************ //函数:InitWindow( ) //功能:创建窗口 //************************************************************ static BOOL InitWindow( HINSTANCE hInstance, int nCmdShow ) { WNDCLASS wc; wc.style = NULL; //窗口类风格 wc.lpfnWndProc = (WNDPROC)WinProc; //指向窗口过程函数的指针 wc.cbClsExtra = 0; //窗口类附加数据 wc.cbWndExtra = 0; //窗口类附加数据 wc.hInstance = hInstance; //拥有窗口类的实例句柄 wc.hIcon = NULL; //最小窗口图标 wc.hCursor = NULL; //窗口内使用的光标 wc.hbrBackground = NULL; //用来着色窗口背景的刷子 wc.lpszMenuName = NULL; //指向菜单资源名的指针 wc.lpszClassName = "menpao_RPG_DEMO";// 指向窗口类名的指针 RegisterClass(&wc); //注册窗口 hwnd = CreateWindow("menpao_RPG_DEMO","menpao_RPG_DEMO",WS_POPUP|WS_MAXIMIZE,0,0,GetSystemMetrics( SM_CXSCREEN ) ,GetSystemMetrics( SM_CYSCREEN ), NULL,NULL,hInstance,NULL); if( !hwnd ) return FALSE; ShowWindow(hwnd,nCmdShow); //显示窗口 UpdateWindow(hwnd); //刷新窗口 return TRUE; } (1)第一个参数:成员style控制窗口样式的某些重要特性,在WINDOWS.H中定义了一些前缀为CS的常量,在程序中可组合使用这些常量.其他还有以下的一些特性:

(2)第二个参数:lpfnWndProc,给它消息处理函数的函数名称即可,必要时应该进行强制类型转换,将其转换成WNDPROC型。 (3)第三,四个参数:cbWndExtra域指定用本窗口类建立的所有窗口结构分配的额外字节数。当有两个以上的窗口属于同一窗口类时,如果想将不同的数据和每个窗口分别相对应。则使用该域很有用。这般来讲,你只要把它们设为0就行了,不必过多考虑。 (4)第五个参数:hInstance成员,给它的值是窗口所对应的应用程序的句柄,表明该窗口与此应用s程序是相关联的。 (5)第六个参数:成员hIcon被设置成应用程序所使用图标的句柄,图标是将应用程序最小化时出现在任务栏里的的图标,用以表示程序仍驻留在内存中。Windows提供了一些默认图标,我们也可定义自己的图标,VC里面专有一个制作图标的工具。 (6)第七个参数: hCursor域定义该窗口产生的光标形状。LoadCursor可返回固有光标句柄或者应用程序定义的光标句柄。IDC_ARROW表示箭头光标. (7)第八个参数:hbrBackground成员用来定义窗口的背景色。这里设为NULL。 (8)第九个参数:lpszMenuName用来指定菜单名,本程序中没有定义菜单,所以为NULL。 (9)第十个参数:lpszClassName指定了本窗口的类名。本程序命名为“menpao_RPG_DEMO”。 当对WNDCLASS结构域一一赋值后,就可注册窗口类了,在创建窗口之前,是必须要注册窗口类的,注册窗口类用的API函数是RegisterClass,注册失败的话,就会出现一个对话框如程序所示,函数RegisterClass返回0值,也只能返回0值,因为注册不成功,程序已经不能再进行下去了。
注册完了以后,就是创建该窗体,一般我们一时调用API函数中的CreatWindows()函数完成的 以上面注册的这个窗体为例 hwnd = CreateWindow( "menpao_RPG_DEMO", //创建的这个窗体类的名称 "menpao_RPG_DEMO", //窗口标题 WS_POPUP|WS_MAXIMIZE, //窗口风格,全部风格见后表 0, //窗口位置x坐标 0, //窗口位置y坐标 GetSystemMetrics(SM_CXSCREEN ), //窗口高度,这里是全屏 GetSystemMetrics( SM_CYSCREEN ),//窗口高度,这里是全屏 NULL, //父窗口句柄 NULL, //菜单句柄 hInstance, //应用程序句柄 NULL); //最后一个参数是附加数据,一般都是0 参数1:登记的窗口类名,这个类名刚才咱们在注册窗口时已经定义过了。 参数2:用来表明窗口的标题。可以和第一个参数一样。 参数3: 用来表明窗口的风格,如有无最大化,最小化按纽啊什么的。 具体其他风格见下图所示

在DirectX编程中,我们一般使用的是WS_POPUP | WS_MAXIMIZE,用这个标志创建的窗口没有标题栏和系统菜单且窗口为最大化,可以充分满足DirectX编程的需要。
参数4,5: 用来表明程序运行后窗口在屏幕中的坐标值。 参数6,7: 用来表明窗口初始化时(即程序初运行时)窗口的大小,即长度与宽度。 参数8: 在创建窗口时可以指定其父窗口,这里没有父窗口则参数值为0。 参数9: 用以指明窗口的菜单,菜单以后会讲,这里暂时为0。 最后一个参数是附加数据,一般都是0。 如果窗口创建成功,CreateWindow( )返回新窗口的句柄,否则返回NULL。 不要以为创建和注册完了以后就大功告成,这样的话你是在屏幕上什么也看不见,我们必须要调用另外一个API函数才能看见窗体就是ShowWindow,他的原型是: ShowWindow (hwnd, iCmdShow) 其第一个参数是窗口句柄,告诉ShowWindow()显示哪一个窗口,而第二个参数则告诉它如何显示这个窗口,还有很多其他样式。

WinMain()调用完ShowWindow后,还需要调用函数UpdateWindow,最终把窗口显示了出来。调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即使窗口得到更新.
然后就是最重要的消息循环和回调函数了,这就是windows编程的灵魂了。 reader:“其实上面介绍的是最最基本的win32编程的基础啊,“地球人都知道啊!”,作者真是笨蛋,以为我们都是白痴啊,不要在这里班门弄斧。” writer:“◎#!¥%※…………” 该死的寝室要停电了。 预听后事如何,且待下回分解。
第二回:循序渐进
大四了,真的象学长们说得那样很忙,可是在忙了几天莫名其妙的事后,又不知道这几天是怎么样浑浑噩噩过来的!好像这段经历是一瞬间就从地球上消失了一样。吴宇森的《记忆裂痕》。
书归正传!可以毫不夸张的说,windows编程核心中的核心就是回调函数,只要看过李维大师的《vcl构架》和侯捷老师的《深入浅出 MFC》的人都应该明白,windows就是一个大的消息循环程序,当遇到外部有其他命令时,就会暂停这个大循环,而进行外部给的命令的事件,而外部命令的这个函数就是回调函数(winproc()),他是通过句柄(hwnd)来确定这个命令触发的窗体。同样我们先写回调函数的原型。在这个函数中,不同的消息将被switch语句分配到不同的处理程序中去。Windows的消息处理函数的原型是这样定义的:
LRESULT CALLBACK WindowProc( HWND hwnd, //接收消息窗口的句柄 UINT uMsg, //主消息值 WPARAM wParam, //副消息值1 vLPARAM lParam //副消息值2 );
消息处理函数必须按照上面的这个样式来定义,当然函数名称可以随便取。 就以我们这个裁减为例子,我们的回调函数是这样写的:
LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { switch( message ) { case WM_SETCURSOR: SetCursor(NULL); return 0; case WM_KEYDOWN: switch( wParam ) { case VK_ESCAPE: //按下键盘的ESC键触发的事件 MessageBox(hWnd,"quit!","Keyboard",MB_OK); PostQuitMessage( 0 ); break; case VK_SPACE: //按下键盘的空格键触发的事件
drawp.x1=x; drawp.y1=y; drawp.x2=x3; drawp.y2=y3; enable=1; return 0; } return 0; case WM_LBUTTONDOWN: //按下鼠标的左键触发的事件 x=(int)LOWORD(lParam); y=(int)HIWORD(lParam); break; case WM_LBUTTONUP: //放开鼠标的右键触发的事件 x3=(int)LOWORD(lParam); y3=(int)HIWORD(lParam); drawp.Rectanglep(x,y,x3,y3); break; case WM_MOUSEMOVE: //鼠标移动触发的事件 enable=2; break; case WM_DESTROY: PostQuitMessage( 0 ); return 0; }
return DefWindowProc(hWnd, message, wParam, lParam); }
看起来就这么简单,可是这里面确包含了很多智慧的结晶!致敬!!
第三回:博大精深
上面就是windows编程的基础的东西了,没那么复杂,如果还有什么不清楚的强烈建议看我上面推荐的那两本书(好像在给李维老师和候捷老师的书作宣传J),不过这才远远不够呢,微软还给了我们很多API函数,这些函数他们都把他封装好了,我们拿来用就可以了,direct编程也包含了大量的这样函数,如果要编写游戏或完成其他图形处理的东东,direct编程的知识是必不可少,先说说基础的东东。
首先我们必须加入两个direct的库文件,这是微软给我们的,就好像当初老师告诉我们在编写c语言的时候如果要做运算,就必须在开始加上#inculde<math.h>是一样的道理。如果你用的是.net的编程环境,可以按以下步筹加入这两个头文件:首先在"Solution Explorer"窗口中找到工程名,然后在上面按右键并选择"Properties",在出现的窗口中选择"Linker" --- "Input" --- "Additional Dependencies",最后填上要加入的LIB文件名(ddraw.lib和dxguid.lib)即可。然后在头文件里申明调用得库函数(#include <ddraw.h>).
开始的时候我们要进行初始化,就是建立direct对象,设置显示模式,建立指针,建立缓存,设置DirectDraw控制级,建立页面,设置页面,填充页面,设置透明色……,还是先说过例子吧,就那我们这个裁减举例好一点
LPDIRECTDRAW7 lpDD; // DirectDraw对象的指针 LPDIRECTDRAWSURFACE7 lpDDSPrimary; // DirectDraw主页面的指针 LPDIRECTDRAWSURFACE7 lpDDSBuffer; // DirectDraw后台缓存的指针 LPDIRECTDRAWSURFACE7 lpDDSMap; // DirectDraw放背景图的指针 LPDIRECTDRAWSURFACE7 lpDDSMouse; // DirectDraw放鼠标的指针
void init() { DDSURFACEDESC2 ddsd; // DirectDraw的页面描述 DirectDrawCreateEx (NULL, (void **)&lpDD,IID_IDirectDraw7, NULL); //创建DirectDraw对象 lpDD->SetCooperativeLevel(hwnd,DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN);//设置DirectDraw控制级 lpDD->SetDisplayMode( 640, 480, 32, 0, DDSDM_STANDARDVGAMODE ); //设置显示模式 memset(&ddsd, 0, sizeof(DDSURFACEDESC2));//开始创建主页面,先清空页面描述 ddsd.dwSize = sizeof( ddsd );//填充页面描述 ddsd.dwFlags = DDSD_CAPS|DDSD_BACKBUFFERCOUNT; //有后台缓存 ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE|DDSCAPS_FLIP|DDSCAPS_COMPLEX; ddsd.dwBackBufferCount = 1; //一个后台缓存 lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL ); //创建主页面
ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER; //这是后台缓存 lpDDSPrimary->GetAttachedSurface( &ddsd.ddsCaps, &lpDDSBuffer ); //创建后台缓存
ddsd.dwSize = sizeof( ddsd ); ddsd.dwFlags = DDSD_CAPS|DDSD_WIDTH|DDSD_HEIGHT; ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; //这是离屏页面 ddsd.dwHeight = 480; //高 ddsd.dwWidth = 640; //宽 lpDD->CreateSurface( &ddsd, &lpDDSMap, NULL ); //创建放背景图的页面 ddsd.dwHeight=26; ddsd.dwWidth=32; lpDD->CreateSurface( &ddsd, &lpDDSMouse, NULL);//创建放鼠标的页面 ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; //全屏显示
//清空各个页面 DDBLTFX ddBltFx; ddBltFx.dwSize=sizeof(DDBLTFX); ddBltFx.dwFillColor=0; lpDDSPrimary ->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx); lpDDSBuffer->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx); lpDDSMap->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx); lpDDSMouse->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx);
//贴图和设置透明色 DDReLoadBitmap(lpDDSMap,"inn.BMP"); DDReLoadBitmap(lpDDSMouse,"mouse.BMP"); MakeRect(0,0,640,480); lpDDSPrimary->BltFast(0,0,lpDDSMap,&r,NoKey); DDSetColorKey(lpDDSMap,RGB(0,255,0)); DDSetColorKey(lpDDSMouse,RGB(0,255,0)); }
老规矩,还是对每个函数都来一个具体的说明吧:
创建direct对象,他的原型是 HRESULT WINAPI DirectDrawCreateEx( GUID FAR *lpGUID, LPVOID *lplpDD, REFIID iid, IUnknown FAR *pUnkOuter );
第一个参数是lpGUID:指向DirectDraw接口的全局唯一标志符(Global Unique IDentify)的指针。在这里,我们给它NULL,表示我们将使用当前的DirectDraw接口。 第二个参数是lplpDD:这个参数是用来接受初始化的DirectDraw对象的地址。在这里,我们给它用强制类型转换为void**类型的&lpdd(传递指针的指针,这样这个函数才能改变指针的指向)。 第三个参数是iid:这里给它IID_IDirectDraw7吧,表示我们要创建IDirectDraw7对象。如果是d8,d9后面写成8,9即可。 第四个参数是pUnkOuter:有COM 集中特征考虑到将来的兼容性。目前必须是NULL。 所有的DirectDraw函数的返回值都是HRESULT类型,它是一个32位的值。 从上面的初始化我们可以知道,在对象创建完成了以后是设置d7对象的控制级。它的原型是: HRESULT SetCooperativeLevel (HWND hWnd, DWORD dwFlags ) 第一个参数是窗口句柄,我们给它hWnd。 第二个参数是控制级标志。他的参数见下表:

ok,然后就是设置显示模式的函数,它的原型是: HRESULT SetDisplayMode( DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DWORD dwFlags );
dwWidth and dwHeight用来设置显示模式的宽度和高度。 dwBPP用来设置显示模式的颜色位数。 dwRefreshRate设置屏幕的刷新率,0为使用默认值。 dwFlags现在唯一有效的值是DDSDM_STANDARDVGAMODE。
创建页面,先清空,申请空间(类似于c中的析构函数)
memset(&ddsd, 0, sizeof(DDSURFACEDESC2));//开始创建主页面,先清空页面描述 ddsd.dwSize = sizeof( ddsd );//填充页面描述 ddsd.dwFlags = DDSD_CAPS|DDSD_BACKBUFFERCOUNT; //有后台缓存 ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE|DDSCAPS_FLIP|DDSCAPS_COMPLEX; ddsd.dwBackBufferCount = 1; //一个后台缓存 lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL ); //创建主页面
创建主页面的函数原型是:
HRESULT CreateSurface( LPDDSURFACEDESC2 lpDDSurfaceDesc, LPDIRECTDRAWSURFACE FAR *lplpDDSurface, IUnknown FAR *pUnkOuter );
第一个参数是被填充了页面信息的DDSURFACEDESC2结构的地址,此处为&ddsd; 第二个参数是接收主页面指针的地址,此处为&lpDDSPrimary; 第三个参数现在必须为NULL,为该函数所保留。
后面创建放背景和鼠标的页面也是一个道理。 后面就是贴图,把要显示的图片先贴出来,给一个指针指定其位置,后面要使用的的时候直接操作指针即可。不过在这之前还是要清空页面,最后贴好图后要设置图片的透明色,这样图片我们才可见。
//清空各个页面 DDBLTFX ddBltFx; ddBltFx.dwSize=sizeof(DDBLTFX); ddBltFx.dwFillColor=0; lpDDSPrimary ->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx); lpDDSBuffer->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx); lpDDSMap->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx); lpDDSMouse->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx);
//贴图和设置透明色 DDReLoadBitmap(lpDDSMap,"inn.BMP"); DDReLoadBitmap(lpDDSMouse,"mouse.BMP"); MakeRect(0,0,640,480); lpDDSPrimary->BltFast(0,0,lpDDSMap,&r,NoKey); DDSetColorKey(lpDDSMap,RGB(0,255,0)); DDSetColorKey(lpDDSMouse,RGB(0,255,0));
写到这里,也该休息一下了。 reader:“这人怎么这样不负责任,总是写的没头没尾的!#$%*^*” weter:“先卖个关子,下一篇章中我们会进入到具体的功能的实现上,到底是怎么样完成图片的裁减的工作的?他的原理是怎么样的?” 预听后事如何,且待下回分解。
第四回:
今天是最后一话了,而且废话了这么久都没有切入主题,偶对不住大家啦,今天我们就正儿八经的的进入这次我们这个冗长的故事的核心的部分了,同时也是最后一个部分。
书归正传,在direct编程里面有两个比较重要的函数,也是使用频率相当高的两个函数,在进入主题前我们必须先介绍这两个函数,我们这个程序里面也是用了他们。
第一个很有用的函数是BltFast(),他的原型是 HRESULT BltFast( DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans );
下面将逐一介绍这几个参数: (1)dwX和dwY:图像将被传送到目标页面何处。 (2)lpDDSrcSurface:图像传送操作的源页面。目标页面就是调用此方法的页面。 (3)lpSrcRect:一个 RECT (Rectangle,即矩形)结构的地址,指明源页面上将被传送的区域。如果该参数是NULL,整个源页面将被使用。RECT结构在DirectDraw中非常常用,最好在程序中定义一个RECT类型的全局变量,如rect,再象这样写一个函数:void MakeRect ()(关于这个函数就是我说的第二个非常有用的函数,我们在后面在专门对他进行介绍和说明)。 (4)dwTrans:指定传送类型。有如下几种: DDBLTFAST_NOCOLORKEY 指定进行一次普通的复制,不带透明成分。 DDBLTFAST_SRCCOLORKEY 指定进行一次带透明色的图像传送,使用源页面的透明色。 DDBLTFAST_WAIT 如果图像传送器正忙,不断重试直到图像传送器准备好并传送好时才返回。一般都使用这个参数。由于第四个参数比较长,而且有时候是两个搭配起来用,所以大多数时候都是定义两个全局变量,后面使用起来就比较方便。 DWORD SrcKey = DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT DWORD NoKey = DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT 第二个很有用的函数就是我们前面提到的void MakeRect (),这一般是一个自定义函数,我们可以这样定义他:
void MakeRect (int left, int top, int right, int bottom) { rect.bottom = bottom; rect.left = left; rect.right = right; rect.top = top; }
主要是确定了这个r结构的四个边际。 所以要裁减一副图片到一个页面中就很简单了,是不是^_^,举个例子:我们要贴第一回的哪个背景到SPrimary这个主页面里,就可以这样 MakeRect(0,0,640,480); lpDDSBuffer->BltFast(0,0,lpDDSPrimary,&r,NoKey);
核心部分
因为哪个背景的大小是640*480,所以我们的右边际是640,下边际是480,最后在利用BltFast()函数贴出来就是了,而且没有透明,这样我们就可以贴图了。
可是怎么样裁减呢?其实也就没什么难度了,你想在MakeRect()这个函数里面我们改变要贴图的大小不就可以贴同一幅图里任意位置的图部分了吗,这就是裁减了塞,然后我们设成变量,用鼠标自定义贴的大小,就必须和windows的消息循环机制联合起来使用了塞,我们在mousedown的时候得到他的一个坐标,在mouseup的时候得到他的另一个坐标,将两个坐标之间的图片贴出来就可以了塞。其实这个裁减程序的主要的核心技术就在这里了,实现思想也就是这么回事,简单吧:),其他的都是一些旁支末节的东西了。不过我们还是要一一介绍完。
ok,到这里我们就可以单独写一个类出来,完成我们的图片裁减和贴图功能了,我们写一个类叫draw.cpp,这里我们还把他的头文件draw.h写出来。
draw.h: #if !defined (draw_h) #define draw_h
class draw { public: int x1; int y1; int x2; int y2; void Rectanglep(int x1, int y1, int width, int height);//做成一个鼠标在屏幕移动矩形的函数 void blt(int x1,int y1,int x2,int y2);//完成我们的图片裁减功能 };
#endif
draw.cpp: #include "main.h"
void draw::blt(int x1,int y1,int x2,int y2) { x1=drawp.x1; y2=drawp.y1; x2=drawp.x2; y2=drawp.y2; MakeRect(x1,y1,x2,y2); lpDDSBuffer->BltFast(0,0,lpDDSMap,&r,SrcKey); }
void draw::Rectanglep(int x1,int y1,int x2,int y2) { HDC hdc; lpDDSPrimary->GetDC(&hdc);//主页面得到句柄 HPEN Red_Pen=CreatePen(PS_SOLID,0,RGB(0,255,0));//填充画笔的颜色 SelectObject(hdc,Red_Pen);//设置画笔的类型 Line(x1,y1,x1,y2,hdc);//画线 Line(x1,y1,x2,y1,hdc); Line(x2,y1,x2,y2,hdc); Line(x1,y2,x2,y2,hdc); lpDDSPrimary->ReleaseDC(hdc);//用完后释放句柄 }
这里就是对这两自定义函数的具体化了。 第一个函数就是我们前面介绍的实现图形的裁减的部分,而且定义了四个变量作为鼠标取点的坐标。 第二个函数就是鼠标在屏幕上运动画出的轨迹,是由四条线组成的一个矩形,这里用到了API函数作图,也就是微软给我们的函数(微软什么都给我们封装好了,用起来还真是方便呀)。
然后就是公共使用的函数的一个类,这里面自定义的函数可以全部被自由的使用 我们定义为
publicfuction.cpp
//direct的初始化,前面介绍过,可以参看前面的章节 void init() { DDSURFACEDESC2 ddsd; DirectDrawCreateEx (NULL, (void **)&lpDD,IID_IDirectDraw7, NULL); lpDD->SetCooperativeLevel(hwnd,DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN); lpDD->SetDisplayMode( 640, 480, 32, 0, DDSDM_STANDARDVGAMODE ); memset(&ddsd, 0, sizeof(DDSURFACEDESC2)); ddsd.dwSize = sizeof( ddsd ); ddsd.dwFlags = DDSD_CAPS|DDSD_BACKBUFFERCOUNT; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE|DDSCAPS_FLIP|DDSCAPS_COMPLEX; ddsd.dwBackBufferCount = 1; lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER; lpDDSPrimary->GetAttachedSurface( &ddsd.ddsCaps, &lpDDSBuffer );
ddsd.dwSize = sizeof( ddsd ); ddsd.dwFlags = DDSD_CAPS|DDSD_WIDTH|DDSD_HEIGHT; ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; ddsd.dwHeight = 480; ddsd.dwWidth = 640; lpDD->CreateSurface( &ddsd, &lpDDSMap, NULL ); ddsd.dwHeight=26; ddsd.dwWidth=32; lpDD->CreateSurface( &ddsd, &lpDDSMouse, NULL); ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
DDBLTFX ddBltFx; ddBltFx.dwSize=sizeof(DDBLTFX); ddBltFx.dwFillColor=0; lpDDSPrimary ->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx); lpDDSBuffer->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx); lpDDSMap->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx); lpDDSMouse->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx); DDReLoadBitmap(lpDDSMap,"inn.BMP"); DDReLoadBitmap(lpDDSMouse,"mouse.BMP"); MakeRect(0,0,640,480); lpDDSPrimary->BltFast(0,0,lpDDSMap,&r,NoKey); DDSetColorKey(lpDDSMap,RGB(0,255,0)); DDSetColorKey(lpDDSMouse,RGB(0,255,0)); }
//取矩形举例的函数,前面也介绍过 void MakeRect(int left,int top,int right,int bottom) { r.bottom=bottom; r.left=left; r.right=right; r.top=top; }
//画直线的函数 void Line(int x1,int y1,int x2,int y2,HDC dc) { MoveToEx (dc, x1, y1, 0);//得到画直线的起始坐标 LineTo (dc, x2, y2);//得到画直线的终点坐标 }
//在屏幕上输出文字的函数 void print(char text[255],int x,int y) { HDC hdc; lpDDSBuffer->GetDC(&hdc);//后台页面得到句柄 SetBkMode(hdc, TRANSPARENT);//设置显示模式 SetTextColor(hdc, RGB(0,255,255));//输出文字颜色 TextOut(hdc, x, y , text, strlen(text));//输出文字 lpDDSBuffer->ReleaseDC(hdc);//释放句柄 }
//前台和后台页面不断交换的函数,这样才能交替显示两个页面 void Flip() { HRESULT ddrval; ddrval=lpDDSPrimary->Flip(NULL,DDFLIP_WAIT); if (ddrval==DDERR_SURFACELOST) Restore(); }
//指针释放的函数,指针用完以后得释放 HRESULT Restore(void) { HRESULT ddrval; ddrval = lpDDSPrimary->Restore(); ddrval = lpDDSBuffer->Restore(); ddrval = lpDDSMouse->Restore(); ddrval = lpDDSMap->Restore(); init(); return ddrval; }
在公共类里面还有一个最关键得函数就是refresh(), void refresh() { //将背景图贴到buffer页面了,这样刷新的时候一直都在显示一幅图 MakeRect(0,0,640,480); lpDDSBuffer->BltFast(0,0,lpDDSMap,&r,NoKey); //鼠标的移动,同样写在刷新里面是为了不会有鼠标移动过的痕迹留下 POINT curpos; GetCursorPos(&curpos); for (int i=0;i<2;i++) { CursorX[i]=CursorX[i+1]; CursorY[i]=CursorY[i+1]; CursorX[2]=curpos.x; CursorY[2]=curpos.y; lpDDSBuffer->BltFast(CursorX[1],CursorY[1],lpDDSMouse,NULL,SrcKey); }
//ok,调用了裁减的方法,有一个标志,在这个标志为1的时候才调用 if(enable==1) drawp.blt(drawp.x1,drawp.y1,drawp.x2,drawp.y2);
//调用了鼠标移动画矩形的方法,也有一个标志,当标志为2的时候才调用 if(enable==2) drawp.Rectanglep(x,y,curpos.x,curpos.y);
//为字符串赋值,以便调用这些字符串 char *text="LEFT MOUSEDOWN:点击鼠标左键可以确定裁剪的一个顶点坐标"; char *text1="LEFT MOUSEMOVE:拖动鼠标可以选取裁剪的区域"; char *text2="LEFT MOUSEUP:放掉鼠标左键可以取定另外一个顶点坐标"; char *text3="SPACE:按空格键可以贴出裁剪区域"; char *text4="世间本无事,庸人自扰自!";
//调用显示字的自定义函数,在屏幕上显示文字,放在刷新页面里是因为可以一直都显示 print(text,10,10); print(text1,10,30); print(text2,10,50); print(text3,10,70); print(text4,400,450);
//翻页,主页和buffer页交替显示 Flip(); }
就这样,这就是全部功能的实现和原理了,再回到消息循环机制里面看看。
main.h: #if !defined(Course_Design)
#define Course_Design
#include <windows.h> #include <math.h> #include <ddraw.h> #include "ddutil.h" #include "draw.h"
extern HDC hdc; extern RECT r; extern HWND hwnd; extern DWORD SrcKey; extern DWORD NoKey; extern POINT curpos; extern int enable; extern int x; extern int y; extern int x3; extern int y3;
extern LPDIRECTDRAW7 lpDD; extern LPDIRECTDRAWSURFACE7 lpDDSPrimary; extern LPDIRECTDRAWSURFACE7 lpDDSBuffer; extern LPDIRECTDRAWSURFACE7 lpDDSMap; extern LPDIRECTDRAWSURFACE7 lpDDSMouse; extern draw drawp;
void init(); void MakeRect(int left,int top,int right,int bottom); void refresh(); void Flip(); void print(char text[255],int x,int y); void Line(int x1,int y1,int x2,int y2,HDC dc); HRESULT Restore(void);
#endif
main.cpp
#include "main.h"
main.cpp BOOL InitWindow( HINSTANCE hInstance, int nCmdShow ); LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );
HWND hwnd; RECT r; HDC hdc; POINT curpos; int enable; int x; int y; int x3; int y3;
LPDIRECTDRAW7 lpDD; LPDIRECTDRAWSURFACE7 lpDDSPrimary; LPDIRECTDRAWSURFACE7 lpDDSBuffer; LPDIRECTDRAWSURFACE7 lpDDSMap; LPDIRECTDRAWSURFACE7 lpDDSMouse;
DWORD SrcKey = DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT; DWORD NoKey = DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT; draw drawp;
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MSG msg;
InitWindow(hInstance,nCmdShow);
init();//初始化 while(1) { if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message==WM_QUIT) break; TranslateMessage(&msg); DispatchMessage(&msg); } else { refresh();//消息循环,完成刷新功能,具体功能在前面介绍这个函数时说过了 } } return msg.wParam; }
static BOOL InitWindow( HINSTANCE hInstance, int nCmdShow ) { WNDCLASS wc; wc.style = NULL; wc.lpfnWndProc = (WNDPROC)WinProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = NULL; wc.hCursor = NULL; wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = "Course Design"; RegisterClass(&wc); hwnd = CreateWindow("Course Design","My Course Design",WS_POPUP|WS_MAXIMIZE,0,0,GetSystemMetrics( SM_CXSCREEN ),GetSystemMetrics( SM_CYSCREEN ), NULL,NULL,hInstance,NULL); if( !hwnd ) return FALSE; ShowWindow(hwnd,nCmdShow); UpdateWindow(hwnd); return TRUE; }
LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { switch( message ) { case WM_SETCURSOR: SetCursor(NULL); return 0; case WM_KEYDOWN: switch( wParam ) { case VK_ESCAPE://按ESC键退出程序 MessageBox(hWnd,"quit!","Keyboard",MB_OK); PostQuitMessage( 0 ); break; case VK_SPACE://按空格键得到两个坐标,并将标志设为1 drawp.x1=x; drawp.y1=y; drawp.x2=x3; drawp.y2=y3; enable=1; return 0; } return 0; case WM_LBUTTONDOWN://按下鼠标左键得到一个点的坐标 x=(int)LOWORD(lParam); y=(int)HIWORD(lParam); break; case WM_LBUTTONUP://放开鼠标左键得到一个点的坐标。并画出这个矩形 x3=(int)LOWORD(lParam); y3=(int)HIWORD(lParam); drawp.Rectanglep(x,y,x3,y3); break; case WM_MOUSEMOVE://鼠标移动时设置标志为2 enable=2; break; case WM_DESTROY: PostQuitMessage( 0 ); return 0; } return DefWindowProc(hWnd, message, wParam, lParam); }
这样全部代码我就放出来,我想只要比照着注释看就能明白个大概了。
总结之,洋洋洒洒的写了这么多东西,自己都感觉很凌乱,用一句口头禅:“清不到方向”,所以有必要总结一下,总体来说,我们做了一个图片裁减的小程序,然后这是一个很简单的程序,主要用到了Windows消息循环和Direct编程这两个方面的知识,而要实现程序的核心就是裁减也就只用到了Direct编程的贴图(BltFast())函数,也就是贴图的大小和范围的变化不同就实现了裁减了。(程序-->裁减-->direct-->BltFast),这就是核心完成的任务了,其他的无非是使用起来方便或者其他的。希望我把自己要说的说清楚了的哈。希望大家能看懂我说的是什么,也就不枉费我莫名其妙的说了一大串了哈。
说实话这是我第一次写这么多字的技术性文章,没有什么经验,自己都觉得写的比较凌乱而且似乎没有重点,我只能做到尽量做到把关键的原理和知识说清楚,把源代码尽量多的做上注释,让大家看明白,如果有什么看不懂和希望问的就给我留言到邮箱,有时间的话,我会尽量给大家回复清楚的。其实Dierct编程是很有意思的一个部分,这里面包含了很多的东西,甚至还可以做游戏,不过我也只是一知半解,还有很多东西至今没有琢磨透,同时也非常希望在这方面编程有自己的心得和体会的朋友多和我交流,共同进步啦。
总算大功告成,辛苦啊!!一直以为写东西是件多么简单的事,到今天自己亲自尝试了后才知道,原本世界上根本没有什么简单的东西哈,这样一来更加佩服侯捷老师和李维老师的智慧了,写出来的东西丝丝入扣,不但能够简单明了的说清楚事物的本质,而且语言幽默,看了以后真是过目不忘哈。还应该多多学习哈。
#incldue<参考资料> { 李维大师的《vcl构架》; 侯捷老师的《深入浅出 MFC》; 彭博朋友的《电脑游戏自己编》; microsoft的《SDK7开发指南》; SDK路报《SDK讲座》; }
|