您的位置:首页 > 其它

运用Direct技术进行图像裁减的实现

2007-05-24 18:39 309 查看
绪言:
  这是一篇很肤浅的文章,严格意义上来讲不能算是一篇技术型的文章,只能说是自己在学习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,最终把窗口显示了出来。调用函数UpdateWin

 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|D
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: